@outputai/cli 0.1.12-next.ecd4dda.0 → 0.1.13-dev.01b8dea.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.
- package/bin/run.js +2 -0
- package/dist/api/generated/api.d.ts +4 -0
- package/dist/assets/config/costs.yml +6 -0
- package/dist/assets/docker/docker-compose-dev.yml +2 -8
- package/dist/commands/fix.d.ts +10 -0
- package/dist/commands/fix.js +58 -0
- package/dist/commands/fix.spec.d.ts +1 -0
- package/dist/commands/fix.spec.js +77 -0
- package/dist/commands/update.js +1 -1
- package/dist/commands/update.spec.js +2 -2
- package/dist/commands/workflow/generate.js +3 -1
- package/dist/commands/workflow/generate.spec.js +12 -0
- package/dist/commands/workflow/list.d.ts +1 -0
- package/dist/commands/workflow/list.js +12 -6
- package/dist/commands/workflow/list.spec.js +21 -0
- package/dist/commands/workflow/plan.js +5 -1
- package/dist/commands/workflow/plan.spec.js +3 -2
- package/dist/commands/workflow/run.js +2 -1
- package/dist/commands/workflow/run.spec.js +1 -0
- package/dist/commands/workflow/start.js +2 -1
- package/dist/commands/workflow/start.spec.js +1 -0
- package/dist/components/command_footer.d.ts +8 -0
- package/dist/components/command_footer.js +4 -0
- package/dist/components/status_icon.d.ts +11 -0
- package/dist/components/status_icon.js +25 -0
- package/dist/components/workflow_summary.d.ts +10 -0
- package/dist/components/workflow_summary.js +4 -0
- package/dist/generated/framework_version.json +1 -1
- package/dist/hooks/init.js +4 -0
- package/dist/services/claude_client.js +4 -1
- package/dist/services/coding_agents.js +1 -1
- package/dist/services/coding_agents.spec.js +6 -6
- package/dist/services/credentials_configurator.js +1 -1
- package/dist/services/docker.d.ts +1 -3
- package/dist/services/docker.js +38 -13
- package/dist/services/env_configurator.js +1 -1
- package/dist/services/env_configurator.spec.js +12 -12
- package/dist/services/fix_package.d.ts +34 -0
- package/dist/services/fix_package.js +105 -0
- package/dist/services/fix_package.spec.d.ts +1 -0
- package/dist/services/fix_package.spec.js +63 -0
- package/dist/services/messages.d.ts +1 -1
- package/dist/services/messages.js +2 -2
- package/dist/services/project_scaffold.js +2 -2
- package/dist/services/project_scaffold.spec.js +6 -6
- package/dist/services/workflow_builder.js +5 -1
- package/dist/services/workflow_builder.spec.js +3 -2
- package/dist/templates/agent_instructions/CLAUDE.md.template +36 -2
- package/dist/templates/agent_instructions/dotclaude/settings.json.template +2 -3
- package/dist/templates/project/README.md.template +2 -2
- package/dist/templates/project/package.json.template +8 -7
- package/dist/utils/date_formatter.d.ts +11 -1
- package/dist/utils/date_formatter.js +26 -1
- package/dist/utils/interactive.d.ts +2 -0
- package/dist/utils/interactive.js +5 -0
- package/dist/utils/interactive.spec.d.ts +1 -0
- package/dist/utils/interactive.spec.js +40 -0
- package/dist/utils/open_url.d.ts +1 -0
- package/dist/utils/open_url.js +12 -0
- package/dist/utils/prompt.d.ts +17 -0
- package/dist/utils/prompt.js +20 -0
- package/dist/utils/prompt.spec.d.ts +1 -0
- package/dist/utils/prompt.spec.js +74 -0
- package/dist/utils/proxy.d.ts +1 -0
- package/dist/utils/proxy.js +9 -0
- package/dist/utils/proxy.spec.d.ts +1 -0
- package/dist/utils/proxy.spec.js +39 -0
- package/dist/utils/workflow_dir_parser.d.ts +5 -0
- package/dist/utils/workflow_dir_parser.js +39 -0
- package/dist/utils/workflow_dir_parser.spec.d.ts +1 -0
- package/dist/utils/workflow_dir_parser.spec.js +74 -0
- package/dist/views/dev.js +70 -27
- package/dist/views/workflow/list.d.ts +6 -0
- package/dist/views/workflow/list.js +127 -0
- package/package.json +12 -11
package/dist/views/dev.js
CHANGED
|
@@ -1,31 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useState, useEffect, useRef } from 'react';
|
|
3
3
|
import { Box, Text, Static, useApp, useInput } from 'ink';
|
|
4
4
|
import Spinner from 'ink-spinner';
|
|
5
|
-
import { getServiceStatus, isServiceHealthy, isServiceFailed, SERVICE_HEALTH
|
|
5
|
+
import { getServiceStatus, isServiceHealthy, isServiceFailed, SERVICE_HEALTH } from '#services/docker.js';
|
|
6
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';
|
|
7
13
|
const POLL_INTERVAL_MS = 2000;
|
|
8
14
|
const HEALTH_TIMEOUT_MS = 120_000;
|
|
9
|
-
const
|
|
10
|
-
[SERVICE_HEALTH.HEALTHY]: '●',
|
|
11
|
-
[SERVICE_HEALTH.UNHEALTHY]: '○',
|
|
12
|
-
[SERVICE_HEALTH.STARTING]: '◐',
|
|
13
|
-
[SERVICE_HEALTH.NONE]: '●',
|
|
14
|
-
[SERVICE_STATE.RUNNING]: '●',
|
|
15
|
-
[SERVICE_STATE.EXITED]: '✗'
|
|
16
|
-
};
|
|
17
|
-
const STATUS_COLORS = {
|
|
18
|
-
[SERVICE_HEALTH.HEALTHY]: 'green',
|
|
19
|
-
[SERVICE_HEALTH.UNHEALTHY]: 'red',
|
|
20
|
-
[SERVICE_HEALTH.STARTING]: 'yellow',
|
|
21
|
-
[SERVICE_HEALTH.NONE]: 'blue',
|
|
22
|
-
[SERVICE_STATE.RUNNING]: 'blue',
|
|
23
|
-
[SERVICE_STATE.EXITED]: 'red'
|
|
24
|
-
};
|
|
25
|
-
const resolveServiceDisplay = (service) => {
|
|
26
|
-
const key = service.health === SERVICE_HEALTH.NONE ? service.state : service.health;
|
|
27
|
-
return { icon: STATUS_ICONS[key] ?? '?', color: STATUS_COLORS[key] ?? 'white', status: key };
|
|
28
|
-
};
|
|
15
|
+
const resolveServiceStatus = (service) => service.health === SERVICE_HEALTH.NONE ? service.state : service.health;
|
|
29
16
|
const fetchServices = async (dockerComposePath) => {
|
|
30
17
|
try {
|
|
31
18
|
return await getServiceStatus(dockerComposePath);
|
|
@@ -79,6 +66,10 @@ const useHealthPolling = (dockerComposePath, enabled, callbacks) => {
|
|
|
79
66
|
callbacksRef.current.onAllHealthy(svcs);
|
|
80
67
|
return 'done';
|
|
81
68
|
}
|
|
69
|
+
if (svcs.length > 0 && svcs.find(isServiceFailed)) {
|
|
70
|
+
callbacksRef.current.onFailure(svcs);
|
|
71
|
+
return 'done';
|
|
72
|
+
}
|
|
82
73
|
return 'continue';
|
|
83
74
|
});
|
|
84
75
|
};
|
|
@@ -93,6 +84,32 @@ const useStatusRefresh = (dockerComposePath, enabled, onServices) => {
|
|
|
93
84
|
return 'continue';
|
|
94
85
|
});
|
|
95
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
|
+
};
|
|
96
113
|
const useCtrlC = (onCleanup) => {
|
|
97
114
|
const { exit } = useApp();
|
|
98
115
|
const isExitingRef = useRef(false);
|
|
@@ -104,9 +121,9 @@ const useCtrlC = (onCleanup) => {
|
|
|
104
121
|
});
|
|
105
122
|
};
|
|
106
123
|
const ServiceRow = ({ service }) => {
|
|
107
|
-
const
|
|
124
|
+
const status = resolveServiceStatus(service);
|
|
108
125
|
const ports = service.ports.length ? service.ports.join(', ') : '-';
|
|
109
|
-
return (_jsxs(Box, { children: [
|
|
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 })] }));
|
|
110
127
|
};
|
|
111
128
|
const FailureWarning = ({ services }) => {
|
|
112
129
|
const failed = services.filter(isServiceFailed);
|
|
@@ -115,7 +132,7 @@ const FailureWarning = ({ services }) => {
|
|
|
115
132
|
}
|
|
116
133
|
const failedNames = failed.map(s => s.name).join(', ');
|
|
117
134
|
const hasWorker = failed.some(s => s.name.toLowerCase().includes('worker'));
|
|
118
|
-
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, { marginTop: 1, children:
|
|
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>" })] }) }))] }));
|
|
119
136
|
};
|
|
120
137
|
const DevSuccessMessage = ({ services }) => {
|
|
121
138
|
const divider = '─'.repeat(80);
|
|
@@ -124,21 +141,47 @@ const DevSuccessMessage = ({ services }) => {
|
|
|
124
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: ' }), _jsx(Text, { color: "yellow", children: "localhost:7233" })] }), _jsxs(Box, { children: [_jsx(Text, { color: "white", children: 'Temporal UI: ' }), _jsx(Text, { color: "cyan", children: "http://localhost:8080" })] }), _jsxs(Box, { children: [_jsx(Text, { color: "white", children: 'API Server: ' }), _jsx(Text, { color: "yellow", children: "localhost:3001" })] }), _jsxs(Box, { children: [_jsx(Text, { color: "white", children: 'Redis: ' }), _jsx(Text, { color: "yellow", children: "localhost:6379" })] })] }), _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: ' }), _jsx(Text, { color: "cyan", children: "open http://localhost:8080" })] }), _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" })] }));
|
|
125
142
|
};
|
|
126
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)) }))] }));
|
|
127
|
-
const RunningView = ({ services }) => (_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 }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: "cyan", children: '🌐 Temporal UI: ' }), _jsx(Text, { bold: true, children: "http://localhost:8080" })] }), _jsx(
|
|
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: "http://localhost:8080" })] }), _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
|
+
};
|
|
128
155
|
export const DevApp = ({ dockerComposePath, onCleanup }) => {
|
|
129
156
|
const { exit } = useApp();
|
|
130
157
|
const [phase, setPhase] = useState('waiting');
|
|
131
158
|
const [services, setServices] = useState([]);
|
|
132
159
|
const [successItems, setSuccessItems] = useState([]);
|
|
160
|
+
const [activeView, setActiveView] = useState('main');
|
|
161
|
+
const [workflowRuns, setWorkflowRuns] = useState([]);
|
|
133
162
|
useHealthPolling(dockerComposePath, phase === 'waiting', {
|
|
134
163
|
onServices: setServices,
|
|
135
164
|
onAllHealthy: svcs => {
|
|
136
165
|
setSuccessItems([{ id: 'success', services: svcs }]);
|
|
137
166
|
setPhase('running');
|
|
138
167
|
},
|
|
168
|
+
onFailure: () => {
|
|
169
|
+
setPhase('failed');
|
|
170
|
+
},
|
|
139
171
|
onTimeout: () => exit(new Error('Timeout waiting for services to become healthy'))
|
|
140
172
|
});
|
|
141
173
|
useStatusRefresh(dockerComposePath, phase === 'running', setServices);
|
|
174
|
+
useWorkflowPolling(phase === 'running' || phase === 'failed', setWorkflowRuns);
|
|
175
|
+
useMainViewInput(activeView === 'main' && phase !== 'waiting', {
|
|
176
|
+
onOpenTemporal: () => openUrl('http://localhost:8080'),
|
|
177
|
+
onOpenWorkflows: () => setActiveView('workflows')
|
|
178
|
+
});
|
|
142
179
|
useCtrlC(onCleanup);
|
|
143
|
-
|
|
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') }))] })] }));
|
|
144
187
|
};
|
|
@@ -0,0 +1,127 @@
|
|
|
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 { getWorkflowIdResult } 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
|
+
const TEMPORAL_UI_BASE = 'http://localhost:8080';
|
|
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
|
+
useEffect(() => {
|
|
63
|
+
if (clampedIndex !== selectedIndex) {
|
|
64
|
+
setSelectedIndex(clampedIndex);
|
|
65
|
+
}
|
|
66
|
+
}, [clampedIndex, selectedIndex]);
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
if (!selectedWorkflowId) {
|
|
69
|
+
setDetail(null);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const cached = cacheRef.current.get(selectedWorkflowId);
|
|
73
|
+
if (cached) {
|
|
74
|
+
setDetail(cached);
|
|
75
|
+
setDetailLoading(false);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const currentFetchId = ++fetchIdRef.current;
|
|
79
|
+
setDetailLoading(true);
|
|
80
|
+
getWorkflowIdResult(selectedWorkflowId)
|
|
81
|
+
.then(response => {
|
|
82
|
+
if (fetchIdRef.current !== currentFetchId) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const data = response.data;
|
|
86
|
+
cacheRef.current.set(selectedWorkflowId, data);
|
|
87
|
+
setDetail(data);
|
|
88
|
+
setDetailLoading(false);
|
|
89
|
+
})
|
|
90
|
+
.catch(() => {
|
|
91
|
+
if (fetchIdRef.current !== currentFetchId) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
setDetail(null);
|
|
95
|
+
setDetailLoading(false);
|
|
96
|
+
});
|
|
97
|
+
}, [selectedWorkflowId]);
|
|
98
|
+
useInput((input, key) => {
|
|
99
|
+
if (key.upArrow) {
|
|
100
|
+
setSelectedIndex(i => Math.max(0, i - 1));
|
|
101
|
+
}
|
|
102
|
+
else if (key.downArrow) {
|
|
103
|
+
setSelectedIndex(i => Math.min(sortedRuns.length - 1, i + 1));
|
|
104
|
+
}
|
|
105
|
+
else if (key.escape || input === 'q') {
|
|
106
|
+
onBack();
|
|
107
|
+
}
|
|
108
|
+
else if (input === 'o' && selectedWorkflowId) {
|
|
109
|
+
openUrl(`${TEMPORAL_UI_BASE}/namespaces/default/workflows/${selectedWorkflowId}`);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
const windowStart = useMemo(() => {
|
|
113
|
+
const half = Math.floor(VISIBLE_ROWS / 2);
|
|
114
|
+
const start = Math.max(0, clampedIndex - half);
|
|
115
|
+
const maxStart = Math.max(0, sortedRuns.length - VISIBLE_ROWS);
|
|
116
|
+
return Math.min(start, maxStart);
|
|
117
|
+
}, [clampedIndex, sortedRuns.length]);
|
|
118
|
+
const visibleRuns = sortedRuns.slice(windowStart, windowStart + VISIBLE_ROWS);
|
|
119
|
+
if (sortedRuns.length === 0) {
|
|
120
|
+
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' }] })] }));
|
|
121
|
+
}
|
|
122
|
+
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: [
|
|
123
|
+
{ key: '↑/↓', label: 'navigate' },
|
|
124
|
+
{ key: 'o', label: 'open in temporal' },
|
|
125
|
+
{ key: 'q', label: 'back' }
|
|
126
|
+
] })] }));
|
|
127
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@outputai/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.13-dev.01b8dea.0",
|
|
4
4
|
"description": "CLI for Output.ai workflow generation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -14,30 +14,31 @@
|
|
|
14
14
|
"output": "./bin/run.js"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@anthropic-ai/claude-agent-sdk": "0.2.
|
|
18
|
-
"@aws-sdk/client-s3": "3.
|
|
17
|
+
"@anthropic-ai/claude-agent-sdk": "0.2.92",
|
|
18
|
+
"@aws-sdk/client-s3": "3.1024.0",
|
|
19
19
|
"@hackylabs/deep-redact": "3.0.5",
|
|
20
20
|
"@inquirer/prompts": "8.3.2",
|
|
21
|
-
"@oclif/core": "4.10.
|
|
22
|
-
"@oclif/plugin-help": "6.2.
|
|
21
|
+
"@oclif/core": "4.10.5",
|
|
22
|
+
"@oclif/plugin-help": "6.2.42",
|
|
23
23
|
"change-case": "5.4.4",
|
|
24
24
|
"cli-progress": "3.12.0",
|
|
25
25
|
"cli-table3": "0.6.5",
|
|
26
26
|
"date-fns": "4.1.0",
|
|
27
27
|
"debug": "4.4.3",
|
|
28
|
-
"dotenv": "17.
|
|
28
|
+
"dotenv": "17.4.0",
|
|
29
29
|
"handlebars": "4.7.9",
|
|
30
|
-
"js-yaml": "4.1.1",
|
|
31
30
|
"ink": "6.8.0",
|
|
32
31
|
"ink-spinner": "5.0.0",
|
|
33
|
-
"
|
|
32
|
+
"js-yaml": "4.1.1",
|
|
33
|
+
"json-schema-library": "11.1.0",
|
|
34
34
|
"ky": "1.14.3",
|
|
35
35
|
"react": "19.2.4",
|
|
36
36
|
"semver": "7.7.4",
|
|
37
|
+
"undici": "8.0.2",
|
|
37
38
|
"yaml": "^2.8.3",
|
|
38
|
-
"@outputai/
|
|
39
|
-
"@outputai/
|
|
40
|
-
"@outputai/llm": "0.1.
|
|
39
|
+
"@outputai/credentials": "0.1.13-dev.01b8dea.0",
|
|
40
|
+
"@outputai/evals": "0.1.13-dev.01b8dea.0",
|
|
41
|
+
"@outputai/llm": "0.1.13-dev.01b8dea.0"
|
|
41
42
|
},
|
|
42
43
|
"devDependencies": {
|
|
43
44
|
"@types/cli-progress": "3.11.6",
|