@outputai/cli 0.8.2-next.e658cc2.0 → 0.8.2-next.ec4c07d.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/dist/api/generated/api.d.ts +0 -79
- package/dist/api/generated/api.js +0 -32
- package/dist/assets/docker/docker-compose-dev.yml +1 -1
- package/dist/generated/framework_version.json +1 -1
- package/dist/views/dev/modals/run_modal.d.ts +0 -9
- package/dist/views/dev/modals/run_modal.js +56 -56
- package/dist/views/dev/panels/help_panel.js +1 -1
- package/dist/views/dev/utils/json_editor.d.ts +0 -2
- package/dist/views/dev/utils/json_editor.js +15 -28
- package/dist/views/dev/utils/json_editor.spec.js +1 -16
- package/oclif.manifest.json +1 -1
- package/package.json +4 -4
- package/dist/views/dev/modals/run_modal.spec.d.ts +0 -1
- package/dist/views/dev/modals/run_modal.spec.js +0 -34
|
@@ -452,26 +452,6 @@ export type GetWorkflowIdRunsRidHistory200 = {
|
|
|
452
452
|
/** @nullable */
|
|
453
453
|
nextPageToken?: string | null;
|
|
454
454
|
};
|
|
455
|
-
export type GetWorkflowIdHistoryStreamParams = {
|
|
456
|
-
/**
|
|
457
|
-
* Include decoded input/output payloads in events
|
|
458
|
-
*/
|
|
459
|
-
includePayloads?: boolean;
|
|
460
|
-
/**
|
|
461
|
-
* Resume from this event ID (alternative to Last-Event-ID header)
|
|
462
|
-
*/
|
|
463
|
-
lastEventId?: number;
|
|
464
|
-
};
|
|
465
|
-
export type GetWorkflowIdRunsRidHistoryStreamParams = {
|
|
466
|
-
/**
|
|
467
|
-
* Include decoded input/output payloads in events
|
|
468
|
-
*/
|
|
469
|
-
includePayloads?: boolean;
|
|
470
|
-
/**
|
|
471
|
-
* Resume from this event ID (alternative to Last-Event-ID header)
|
|
472
|
-
*/
|
|
473
|
-
lastEventId?: number;
|
|
474
|
-
};
|
|
475
455
|
export type GetWorkflowCatalogId200 = {
|
|
476
456
|
/** Each workflow available in this catalog */
|
|
477
457
|
workflows?: Workflow[];
|
|
@@ -1063,65 +1043,6 @@ export type getWorkflowIdRunsRidHistoryResponseError = (getWorkflowIdRunsRidHist
|
|
|
1063
1043
|
export type getWorkflowIdRunsRidHistoryResponse = (getWorkflowIdRunsRidHistoryResponseSuccess | getWorkflowIdRunsRidHistoryResponseError);
|
|
1064
1044
|
export declare const getGetWorkflowIdRunsRidHistoryUrl: (id: string, rid: string, params?: GetWorkflowIdRunsRidHistoryParams) => string;
|
|
1065
1045
|
export declare const getWorkflowIdRunsRidHistory: (id: string, rid: string, params?: GetWorkflowIdRunsRidHistoryParams, options?: ApiRequestOptions) => Promise<getWorkflowIdRunsRidHistoryResponse>;
|
|
1066
|
-
/**
|
|
1067
|
-
* Opens a persistent SSE connection that delivers Temporal workflow history events in real time. Emits named events: `workflow` (metadata, once), `history` (event batches), `done` (terminal state), `server_error` (post-flush errors). The `done` event carries `{ reason, newRunId? }` where `reason` is the terminal Temporal event type (`WORKFLOW_EXECUTION_COMPLETED`, `WORKFLOW_EXECUTION_FAILED`, `WORKFLOW_EXECUTION_TIMED_OUT`, `WORKFLOW_EXECUTION_CANCELED`, `WORKFLOW_EXECUTION_TERMINATED`, `WORKFLOW_EXECUTION_CONTINUED_AS_NEW`) and `newRunId` is present only when the terminal event chains a follow-on run. `server_error` carries `{ error, message, workflowId, runId }`. Errors before the stream opens are returned as JSON HTTP responses (400/404); once open, failures arrive as a `server_error` event. Supports reconnect via `Last-Event-ID` header or `lastEventId` query param.
|
|
1068
|
-
|
|
1069
|
-
* @summary Stream workflow history events via Server-Sent Events
|
|
1070
|
-
*/
|
|
1071
|
-
export type getWorkflowIdHistoryStreamResponse200 = {
|
|
1072
|
-
data: string;
|
|
1073
|
-
status: 200;
|
|
1074
|
-
};
|
|
1075
|
-
export type getWorkflowIdHistoryStreamResponse400 = {
|
|
1076
|
-
data: BadRequestResponse;
|
|
1077
|
-
status: 400;
|
|
1078
|
-
};
|
|
1079
|
-
export type getWorkflowIdHistoryStreamResponse404 = {
|
|
1080
|
-
data: NotFoundResponse;
|
|
1081
|
-
status: 404;
|
|
1082
|
-
};
|
|
1083
|
-
export type getWorkflowIdHistoryStreamResponse500 = {
|
|
1084
|
-
data: InternalServerErrorResponse;
|
|
1085
|
-
status: 500;
|
|
1086
|
-
};
|
|
1087
|
-
export type getWorkflowIdHistoryStreamResponseSuccess = (getWorkflowIdHistoryStreamResponse200) & {
|
|
1088
|
-
headers: Headers;
|
|
1089
|
-
};
|
|
1090
|
-
export type getWorkflowIdHistoryStreamResponseError = (getWorkflowIdHistoryStreamResponse400 | getWorkflowIdHistoryStreamResponse404 | getWorkflowIdHistoryStreamResponse500) & {
|
|
1091
|
-
headers: Headers;
|
|
1092
|
-
};
|
|
1093
|
-
export type getWorkflowIdHistoryStreamResponse = (getWorkflowIdHistoryStreamResponseSuccess | getWorkflowIdHistoryStreamResponseError);
|
|
1094
|
-
export declare const getGetWorkflowIdHistoryStreamUrl: (id: string, params?: GetWorkflowIdHistoryStreamParams) => string;
|
|
1095
|
-
export declare const getWorkflowIdHistoryStream: (id: string, params?: GetWorkflowIdHistoryStreamParams, options?: ApiRequestOptions) => Promise<getWorkflowIdHistoryStreamResponse>;
|
|
1096
|
-
/**
|
|
1097
|
-
* Same as /workflow/{id}/history/stream but targets a specific run ID.
|
|
1098
|
-
* @summary Stream pinned-run workflow history events via Server-Sent Events
|
|
1099
|
-
*/
|
|
1100
|
-
export type getWorkflowIdRunsRidHistoryStreamResponse200 = {
|
|
1101
|
-
data: string;
|
|
1102
|
-
status: 200;
|
|
1103
|
-
};
|
|
1104
|
-
export type getWorkflowIdRunsRidHistoryStreamResponse400 = {
|
|
1105
|
-
data: BadRequestResponse;
|
|
1106
|
-
status: 400;
|
|
1107
|
-
};
|
|
1108
|
-
export type getWorkflowIdRunsRidHistoryStreamResponse404 = {
|
|
1109
|
-
data: NotFoundResponse;
|
|
1110
|
-
status: 404;
|
|
1111
|
-
};
|
|
1112
|
-
export type getWorkflowIdRunsRidHistoryStreamResponse500 = {
|
|
1113
|
-
data: InternalServerErrorResponse;
|
|
1114
|
-
status: 500;
|
|
1115
|
-
};
|
|
1116
|
-
export type getWorkflowIdRunsRidHistoryStreamResponseSuccess = (getWorkflowIdRunsRidHistoryStreamResponse200) & {
|
|
1117
|
-
headers: Headers;
|
|
1118
|
-
};
|
|
1119
|
-
export type getWorkflowIdRunsRidHistoryStreamResponseError = (getWorkflowIdRunsRidHistoryStreamResponse400 | getWorkflowIdRunsRidHistoryStreamResponse404 | getWorkflowIdRunsRidHistoryStreamResponse500) & {
|
|
1120
|
-
headers: Headers;
|
|
1121
|
-
};
|
|
1122
|
-
export type getWorkflowIdRunsRidHistoryStreamResponse = (getWorkflowIdRunsRidHistoryStreamResponseSuccess | getWorkflowIdRunsRidHistoryStreamResponseError);
|
|
1123
|
-
export declare const getGetWorkflowIdRunsRidHistoryStreamUrl: (id: string, rid: string, params?: GetWorkflowIdRunsRidHistoryStreamParams) => string;
|
|
1124
|
-
export declare const getWorkflowIdRunsRidHistoryStream: (id: string, rid: string, params?: GetWorkflowIdRunsRidHistoryStreamParams, options?: ApiRequestOptions) => Promise<getWorkflowIdRunsRidHistoryStreamResponse>;
|
|
1125
1046
|
/**
|
|
1126
1047
|
* @summary Get a specific workflow catalog by ID
|
|
1127
1048
|
*/
|
|
@@ -231,38 +231,6 @@ export const getWorkflowIdRunsRidHistory = async (id, rid, params, options) => {
|
|
|
231
231
|
method: 'GET'
|
|
232
232
|
});
|
|
233
233
|
};
|
|
234
|
-
export const getGetWorkflowIdHistoryStreamUrl = (id, params) => {
|
|
235
|
-
const normalizedParams = new URLSearchParams();
|
|
236
|
-
Object.entries(params || {}).forEach(([key, value]) => {
|
|
237
|
-
if (value !== undefined) {
|
|
238
|
-
normalizedParams.append(key, value === null ? 'null' : value.toString());
|
|
239
|
-
}
|
|
240
|
-
});
|
|
241
|
-
const stringifiedParams = normalizedParams.toString();
|
|
242
|
-
return stringifiedParams.length > 0 ? `/workflow/${id}/history/stream?${stringifiedParams}` : `/workflow/${id}/history/stream`;
|
|
243
|
-
};
|
|
244
|
-
export const getWorkflowIdHistoryStream = async (id, params, options) => {
|
|
245
|
-
return customFetchInstance(getGetWorkflowIdHistoryStreamUrl(id, params), {
|
|
246
|
-
...options,
|
|
247
|
-
method: 'GET'
|
|
248
|
-
});
|
|
249
|
-
};
|
|
250
|
-
export const getGetWorkflowIdRunsRidHistoryStreamUrl = (id, rid, params) => {
|
|
251
|
-
const normalizedParams = new URLSearchParams();
|
|
252
|
-
Object.entries(params || {}).forEach(([key, value]) => {
|
|
253
|
-
if (value !== undefined) {
|
|
254
|
-
normalizedParams.append(key, value === null ? 'null' : value.toString());
|
|
255
|
-
}
|
|
256
|
-
});
|
|
257
|
-
const stringifiedParams = normalizedParams.toString();
|
|
258
|
-
return stringifiedParams.length > 0 ? `/workflow/${id}/runs/${rid}/history/stream?${stringifiedParams}` : `/workflow/${id}/runs/${rid}/history/stream`;
|
|
259
|
-
};
|
|
260
|
-
export const getWorkflowIdRunsRidHistoryStream = async (id, rid, params, options) => {
|
|
261
|
-
return customFetchInstance(getGetWorkflowIdRunsRidHistoryStreamUrl(id, rid, params), {
|
|
262
|
-
...options,
|
|
263
|
-
method: 'GET'
|
|
264
|
-
});
|
|
265
|
-
};
|
|
266
234
|
export const getGetWorkflowCatalogIdUrl = (id) => {
|
|
267
235
|
return `/workflow/catalog/${id}`;
|
|
268
236
|
};
|
|
@@ -1,14 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
type EntryKind = 'scenario' | 'custom';
|
|
3
|
-
interface Entry {
|
|
4
|
-
kind: EntryKind;
|
|
5
|
-
label: string;
|
|
6
|
-
scenarioName?: string;
|
|
7
|
-
}
|
|
8
|
-
export declare const buildEntries: (scenarios: string[]) => Entry[];
|
|
9
|
-
export declare const validateScenarioName: (raw: string, existing: string[]) => string | null;
|
|
10
2
|
export declare const RunModal: React.FC<{
|
|
11
3
|
workflowName: string;
|
|
12
4
|
workflowPath?: string;
|
|
13
5
|
}>;
|
|
14
|
-
export {};
|
|
@@ -9,40 +9,26 @@ import { startWorkflow } from '#views/dev/services/run_workflow.js';
|
|
|
9
9
|
import { readScenario, writeScenario } from '#views/dev/services/scenario_io.js';
|
|
10
10
|
import { JsonEditor } from '#views/dev/utils/json_editor.js';
|
|
11
11
|
import { ModalFrame } from '#views/dev/modals/modal_frame.js';
|
|
12
|
-
const SCENARIO_NAME_RE = /^[a-zA-Z0-9_-]+$/;
|
|
13
|
-
// Seed for a brand-new input — an empty "": "" pair reads friendlier than a bare {}.
|
|
14
12
|
const CUSTOM_SEED = { '': '' };
|
|
15
|
-
|
|
13
|
+
const SCENARIO_NAME_RE = /^[a-zA-Z0-9_-]+$/;
|
|
14
|
+
const buildEntries = (scenarios) => {
|
|
16
15
|
const list = scenarios.map(s => ({
|
|
17
16
|
kind: 'scenario',
|
|
18
17
|
label: s,
|
|
19
18
|
scenarioName: s
|
|
20
19
|
}));
|
|
21
|
-
list.push({ kind: 'custom', label: '[
|
|
20
|
+
list.push({ kind: 'custom', label: '[Create new scenario]' });
|
|
22
21
|
return list;
|
|
23
22
|
};
|
|
24
|
-
export const validateScenarioName = (raw, existing) => {
|
|
25
|
-
const name = raw.trim();
|
|
26
|
-
if (!name) {
|
|
27
|
-
return 'Scenario name cannot be empty.';
|
|
28
|
-
}
|
|
29
|
-
if (!SCENARIO_NAME_RE.test(name)) {
|
|
30
|
-
return 'Use letters, numbers, dashes, and underscores only.';
|
|
31
|
-
}
|
|
32
|
-
if (existing.includes(name)) {
|
|
33
|
-
return `A scenario named '${name}' already exists.`;
|
|
34
|
-
}
|
|
35
|
-
return null;
|
|
36
|
-
};
|
|
37
23
|
const SELECT_SHORTCUTS = [
|
|
38
24
|
['↑/↓', 'navigate'],
|
|
39
25
|
['enter', 'run'],
|
|
40
26
|
['d', 'duplicate'],
|
|
41
27
|
['esc', 'cancel']
|
|
42
28
|
];
|
|
43
|
-
const
|
|
44
|
-
['enter', '
|
|
45
|
-
['esc', 'back
|
|
29
|
+
const NAME_SHORTCUTS = [
|
|
30
|
+
['enter', 'next'],
|
|
31
|
+
['esc', 'back']
|
|
46
32
|
];
|
|
47
33
|
const ERROR_SHORTCUTS = [
|
|
48
34
|
{ key: 'enter', label: 'return' },
|
|
@@ -55,11 +41,9 @@ export const RunModal = ({ workflowName, workflowPath }) => {
|
|
|
55
41
|
const entries = useMemo(() => buildEntries(scenarios), [scenarios]);
|
|
56
42
|
const [mode, setMode] = useState('select');
|
|
57
43
|
const [index, setIndex] = useState(0);
|
|
58
|
-
const [editSeed, setEditSeed] = useState({});
|
|
59
|
-
const [editFrameTitle, setEditFrameTitle] = useState('');
|
|
60
|
-
const [defaultSaveName, setDefaultSaveName] = useState('');
|
|
61
44
|
const [editName, setEditName] = useState('');
|
|
62
|
-
const [
|
|
45
|
+
const [editSeed, setEditSeed] = useState(CUSTOM_SEED);
|
|
46
|
+
const [editFrameTitle, setEditFrameTitle] = useState('');
|
|
63
47
|
const [nameError, setNameError] = useState(null);
|
|
64
48
|
const [errorMessage, setErrorMessage] = useState(null);
|
|
65
49
|
const closeWith = (message, tone = 'info') => {
|
|
@@ -92,55 +76,63 @@ export const RunModal = ({ workflowName, workflowPath }) => {
|
|
|
92
76
|
setMode('error');
|
|
93
77
|
}
|
|
94
78
|
};
|
|
95
|
-
// Custom + duplicate both open the editor first; saving a scenario is opt-in (ctrl+s).
|
|
96
|
-
const startCustom = () => {
|
|
97
|
-
setEditSeed(CUSTOM_SEED);
|
|
98
|
-
setDefaultSaveName('');
|
|
99
|
-
setEditFrameTitle('Enter input');
|
|
100
|
-
setMode('edit_content');
|
|
101
|
-
};
|
|
102
79
|
const startDuplicate = async (scenarioName) => {
|
|
103
80
|
try {
|
|
104
81
|
const sourceContent = await readScenario(workflowName, scenarioName, workflowPath);
|
|
82
|
+
setEditName(`${scenarioName}_copy`);
|
|
105
83
|
setEditSeed(sourceContent);
|
|
106
|
-
setDefaultSaveName(`${scenarioName}_copy`);
|
|
107
84
|
setEditFrameTitle(`Duplicate '${scenarioName}'`);
|
|
108
|
-
|
|
85
|
+
setNameError(null);
|
|
86
|
+
setMode('edit_name');
|
|
109
87
|
}
|
|
110
88
|
catch (err) {
|
|
111
89
|
setErrorMessage(err instanceof Error ? err.message : String(err));
|
|
112
90
|
setMode('error');
|
|
113
91
|
}
|
|
114
92
|
};
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
// ctrl+s in the editor: keep the payload and ask for a name before saving + running.
|
|
120
|
-
const beginSave = (value) => {
|
|
121
|
-
setPendingValue(value);
|
|
122
|
-
setEditSeed(value);
|
|
123
|
-
setEditName(defaultSaveName);
|
|
93
|
+
const startCustom = () => {
|
|
94
|
+
setEditName('');
|
|
95
|
+
setEditSeed(CUSTOM_SEED);
|
|
96
|
+
setEditFrameTitle('New scenario');
|
|
124
97
|
setNameError(null);
|
|
125
|
-
setMode('
|
|
98
|
+
setMode('edit_name');
|
|
99
|
+
};
|
|
100
|
+
const validateName = (raw) => {
|
|
101
|
+
const name = raw.trim();
|
|
102
|
+
if (!name) {
|
|
103
|
+
return 'Scenario name cannot be empty.';
|
|
104
|
+
}
|
|
105
|
+
if (!SCENARIO_NAME_RE.test(name)) {
|
|
106
|
+
return 'Use letters, numbers, dashes, and underscores only.';
|
|
107
|
+
}
|
|
108
|
+
if (scenarios.includes(name)) {
|
|
109
|
+
return `A scenario named '${name}' already exists.`;
|
|
110
|
+
}
|
|
111
|
+
return null;
|
|
126
112
|
};
|
|
127
|
-
const
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
113
|
+
const handleEditorSubmit = async (value) => {
|
|
114
|
+
const name = editName.trim();
|
|
115
|
+
const writeError = validateName(editName);
|
|
116
|
+
if (writeError) {
|
|
117
|
+
setNameError(writeError);
|
|
118
|
+
setMode('edit_name');
|
|
131
119
|
return;
|
|
132
120
|
}
|
|
133
121
|
setMode('submitting');
|
|
134
122
|
try {
|
|
135
|
-
const writtenPath = await writeScenario(workflowName,
|
|
123
|
+
const writtenPath = await writeScenario(workflowName, name, value, workflowPath);
|
|
136
124
|
ui.pushToast(`Saved scenario at ${writtenPath}`, 'info');
|
|
137
|
-
await submit(
|
|
125
|
+
await submit(value, name);
|
|
138
126
|
}
|
|
139
127
|
catch (err) {
|
|
140
128
|
setErrorMessage(err instanceof Error ? err.message : String(err));
|
|
141
129
|
setMode('error');
|
|
142
130
|
}
|
|
143
131
|
};
|
|
132
|
+
const handleEditorCancel = () => {
|
|
133
|
+
// Bring the user back to the name step so they can adjust it or bail.
|
|
134
|
+
setMode('edit_name');
|
|
135
|
+
};
|
|
144
136
|
useInput((input, key) => {
|
|
145
137
|
if (mode === 'edit_content' || mode === 'submitting') {
|
|
146
138
|
return;
|
|
@@ -176,13 +168,19 @@ export const RunModal = ({ workflowName, workflowPath }) => {
|
|
|
176
168
|
}
|
|
177
169
|
return;
|
|
178
170
|
}
|
|
179
|
-
if (mode === '
|
|
171
|
+
if (mode === 'edit_name') {
|
|
180
172
|
if (key.escape) {
|
|
181
|
-
setMode('
|
|
173
|
+
setMode('select');
|
|
182
174
|
return;
|
|
183
175
|
}
|
|
184
176
|
if (key.return) {
|
|
185
|
-
|
|
177
|
+
const err = validateName(editName);
|
|
178
|
+
if (err) {
|
|
179
|
+
setNameError(err);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
setNameError(null);
|
|
183
|
+
setMode('edit_content');
|
|
186
184
|
return;
|
|
187
185
|
}
|
|
188
186
|
if (key.backspace || key.delete) {
|
|
@@ -208,10 +206,12 @@ export const RunModal = ({ workflowName, workflowPath }) => {
|
|
|
208
206
|
}
|
|
209
207
|
});
|
|
210
208
|
if (mode === 'edit_content') {
|
|
211
|
-
return (_jsx(ModalFrame, { title: editFrameTitle, children: _jsx(JsonEditor, { seed: editSeed, title:
|
|
209
|
+
return (_jsx(ModalFrame, { title: editFrameTitle, children: _jsx(JsonEditor, { seed: editSeed, title: `${editName}.json`, isActive: true, onSubmit: value => {
|
|
210
|
+
void handleEditorSubmit(value);
|
|
211
|
+
}, onCancel: handleEditorCancel }) }));
|
|
212
212
|
}
|
|
213
|
-
if (mode === '
|
|
214
|
-
return (_jsxs(ModalFrame, { title:
|
|
213
|
+
if (mode === 'edit_name') {
|
|
214
|
+
return (_jsxs(ModalFrame, { title: editFrameTitle, shortcuts: NAME_SHORTCUTS, children: [_jsx(TextPrompt, { label: "Scenario name:", value: editName }), nameError ? (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "red", children: nameError }) })) : null] }));
|
|
215
215
|
}
|
|
216
216
|
if (mode === 'submitting') {
|
|
217
217
|
return (_jsxs(ModalFrame, { title: `Run ${workflowName}`, children: [_jsx(Text, { color: "yellow", children: _jsx(Spinner, { type: "dots" }) }), _jsx(Text, { children: "\u00A0Starting workflow\u2026" })] }));
|
|
@@ -219,5 +219,5 @@ export const RunModal = ({ workflowName, workflowPath }) => {
|
|
|
219
219
|
if (mode === 'error') {
|
|
220
220
|
return (_jsx(ModalFrame, { title: `Run workflow "${workflowName}"`, shortcuts: ERROR_SHORTCUTS, children: _jsxs(Text, { color: "red", bold: true, children: ["\u2717 ", errorMessage ?? 'Something went wrong.'] }) }));
|
|
221
221
|
}
|
|
222
|
-
return (_jsx(ModalFrame, { title: `Run ${workflowName}`, shortcuts: SELECT_SHORTCUTS, children: _jsxs(Box, { flexDirection: "column", gap: 1, children: [_jsx(Text, { dimColor: true, children: scenarios.length === 0 ? 'No
|
|
222
|
+
return (_jsx(ModalFrame, { title: `Run ${workflowName}`, shortcuts: SELECT_SHORTCUTS, children: _jsxs(Box, { flexDirection: "column", gap: 1, children: [_jsx(Text, { dimColor: true, children: scenarios.length === 0 ? 'No scenarios found. Create a new one:' : 'Select scenarios:' }), _jsx(Box, { flexDirection: "column", children: entries.map((entry, i) => (_jsxs(Box, { children: [_jsx(SelectionIndicator, { selected: i === index }), _jsxs(Text, { bold: i === index, children: ["\u00A0", entry.label] })] }, `${entry.kind}-${entry.scenarioName ?? i}`))) })] }) }));
|
|
223
223
|
};
|
|
@@ -10,7 +10,7 @@ const DOCS_URL = 'https://docs.output.ai';
|
|
|
10
10
|
export const Section = ({ children, title, direction = 'v' }) => (_jsxs(Box, { flexDirection: "column", gap: 1, children: [_jsx(Text, { bold: true, children: title }), _jsx(Box, { flexDirection: direction === 'v' ? 'column' : 'row', gap: 1, flexWrap: 'wrap', children: children })] }));
|
|
11
11
|
export const SubSection = ({ children, title }) => (_jsxs(Box, { flexDirection: "column", gap: 1, borderStyle: "single", borderColor: "blackBright", paddingLeft: 1, paddingRight: 1, children: [_jsx(Text, { italic: true, dimColor: true, children: title }), _jsx(Box, { flexDirection: "column", children: children })] }));
|
|
12
12
|
const KV = ({ label, value }) => (_jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { width: 26, children: _jsx(Text, { children: label }) }), _jsx(Text, { bold: true, children: value })] }));
|
|
13
|
-
const RunFromCli = () => (_jsxs(Section, { title: "Running a workflow", children: [_jsxs(SubSection, { title: "From the CLI", children: [_jsx(InlineSnippet, { content: "npx output workflow run blog_evaluator paulgraham_hwh" }), _jsx(InlineSnippet, { content: 'npx output workflow run simple --input {"values":[1,2,3]}' }), _jsx(InlineSnippet, { content: "npx output workflow run simple --input scenario.json" })] }), _jsxs(SubSection, { title: "From the TUI", children: [_jsxs(Text, { children: ["Open Workflows tab, hover a workflow, press ", _jsx(Text, { bold: true, children: "r" }), "."] }), _jsx(Text, { children: "
|
|
13
|
+
const RunFromCli = () => (_jsxs(Section, { title: "Running a workflow", children: [_jsxs(SubSection, { title: "From the CLI", children: [_jsx(InlineSnippet, { content: "npx output workflow run blog_evaluator paulgraham_hwh" }), _jsx(InlineSnippet, { content: 'npx output workflow run simple --input {"values":[1,2,3]}' }), _jsx(InlineSnippet, { content: "npx output workflow run simple --input scenario.json" })] }), _jsxs(SubSection, { title: "From the TUI", children: [_jsxs(Text, { children: ["Open Workflows tab, hover a workflow, press ", _jsx(Text, { bold: true, children: "r" }), "."] }), _jsx(Text, { children: "Create a custom input from the TUI using the editor with live JSON validation or select an existing scenario." })] })] }));
|
|
14
14
|
const ServiceUrls = () => (_jsx(Section, { title: "Service URLs", children: _jsxs(SubSection, { title: "Where the services are available", children: [_jsx(KV, { label: "Temporal gRPC", value: "localhost:7233" }), _jsx(KV, { label: "Temporal UI", value: "http://localhost:8080" }), _jsx(KV, { label: "API server", value: "localhost:3001" }), _jsx(KV, { label: "Redis", value: "localhost:6379" })] }) }));
|
|
15
15
|
const UpdatingMigrating = () => (_jsxs(Section, { title: "Updating / Migrating", children: [_jsxs(SubSection, { title: "Update", children: [_jsx(Text, { wrap: "wrap", children: "Update the CLI to the latest published version:" }), _jsx(InlineSnippet, { content: "output update" })] }), _jsxs(SubSection, { title: "Migrate", children: [_jsx(Text, { wrap: "wrap", children: "Migrate a workflow project to the SDK version this CLI ships with:" }), _jsx(InlineSnippet, { content: "output migrate" }), _jsx(Text, { wrap: "wrap", children: "The migration walks `package.json` and project files, updates `@outputai/*` deps, and applies any code-mod steps the SDK ships with the new version." })] })] }));
|
|
16
16
|
const ClaudePlugins = () => (_jsx(Section, { title: "Claude Plugins", children: _jsxs(SubSection, { title: "Reinstall", children: [_jsx(Text, { wrap: "wrap", children: "Command `output init` already installs the Claude Code plugins (skills, commands, agents) into your project during scaffolding." }), _jsx(Text, { wrap: "wrap", children: "But if it is necessary to reinstall it, use the update command:" }), _jsx(InlineSnippet, { content: "output update --agents" }), _jsx(Text, { children: "This pulls the latest plugin bundle that ships with the installed CLI version." })] }) }));
|
|
@@ -11,13 +11,11 @@ export declare const tryParseJson: (text: string) => {
|
|
|
11
11
|
ok: false;
|
|
12
12
|
error: string;
|
|
13
13
|
};
|
|
14
|
-
export declare const initialCursor: (buffer: string) => number;
|
|
15
14
|
export declare const JsonEditor: React.FC<{
|
|
16
15
|
seed: unknown;
|
|
17
16
|
title: string;
|
|
18
17
|
isActive?: boolean;
|
|
19
18
|
onSubmit: (value: unknown) => void;
|
|
20
|
-
onSave?: (value: unknown) => void;
|
|
21
19
|
onCancel: () => void;
|
|
22
20
|
}>;
|
|
23
21
|
export {};
|
|
@@ -25,17 +25,11 @@ export const tryParseJson = (text) => {
|
|
|
25
25
|
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
26
26
|
}
|
|
27
27
|
};
|
|
28
|
-
// Start the cursor inside the first empty "" pair (e.g. the key of a fresh `{ "": "" }`
|
|
29
|
-
// seed) so the user can type immediately; otherwise sit at the end of the buffer.
|
|
30
|
-
export const initialCursor = (buffer) => {
|
|
31
|
-
const emptyPair = buffer.indexOf('""');
|
|
32
|
-
return emptyPair === -1 ? buffer.length : emptyPair + 1;
|
|
33
|
-
};
|
|
34
28
|
const VISIBLE_BUFFER = 6;
|
|
35
|
-
export const JsonEditor = ({ seed, title, isActive = true, onSubmit,
|
|
29
|
+
export const JsonEditor = ({ seed, title, isActive = true, onSubmit, onCancel }) => {
|
|
36
30
|
const initial = JSON.stringify(seed ?? {}, null, 2);
|
|
37
31
|
const [buffer, setBuffer] = useState(initial);
|
|
38
|
-
const [cursor, setCursor] = useState(
|
|
32
|
+
const [cursor, setCursor] = useState(initial.length);
|
|
39
33
|
const [status, setStatus] = useState(() => tryParseJson(initial));
|
|
40
34
|
const [submitMessage, setSubmitMessage] = useState(null);
|
|
41
35
|
useEffect(() => {
|
|
@@ -52,30 +46,23 @@ export const JsonEditor = ({ seed, title, isActive = true, onSubmit, onSave, onC
|
|
|
52
46
|
setBuffer(b => b.slice(0, cursor - 1) + b.slice(cursor));
|
|
53
47
|
setCursor(c => Math.max(0, c - 1));
|
|
54
48
|
};
|
|
55
|
-
const commit = (handler) => {
|
|
56
|
-
const parsed = tryParseJson(buffer);
|
|
57
|
-
if (!parsed.ok) {
|
|
58
|
-
setSubmitMessage(`Invalid JSON — ${parsed.error}`);
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
try {
|
|
62
|
-
handler(JSON.parse(buffer));
|
|
63
|
-
}
|
|
64
|
-
catch (err) {
|
|
65
|
-
setSubmitMessage(err instanceof Error ? err.message : String(err));
|
|
66
|
-
}
|
|
67
|
-
};
|
|
68
49
|
useInput((input, key) => {
|
|
69
50
|
if (key.escape) {
|
|
70
51
|
onCancel();
|
|
71
52
|
return;
|
|
72
53
|
}
|
|
73
|
-
if (key.ctrl && input === '
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
54
|
+
if (key.ctrl && input === 's') {
|
|
55
|
+
const parsed = tryParseJson(buffer);
|
|
56
|
+
if (!parsed.ok) {
|
|
57
|
+
setSubmitMessage(`Cannot submit — ${parsed.error}`);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
onSubmit(JSON.parse(buffer));
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
setSubmitMessage(err instanceof Error ? err.message : String(err));
|
|
65
|
+
}
|
|
79
66
|
return;
|
|
80
67
|
}
|
|
81
68
|
if (key.return) {
|
|
@@ -126,5 +113,5 @@ export const JsonEditor = ({ seed, title, isActive = true, onSubmit, onSave, onC
|
|
|
126
113
|
const at = line[cursorPos.col] ?? ' ';
|
|
127
114
|
const after = line.slice(cursorPos.col + 1);
|
|
128
115
|
return (_jsxs(Text, { bold: true, children: [_jsx(Text, { children: before }), _jsx(Text, { inverse: true, children: at }), _jsx(Text, { children: after })] }, lineIdx));
|
|
129
|
-
}) }), !status.ok && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "red", wrap: "truncate-end", children: status.error }) })), submitMessage && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "yellow", children: submitMessage }) })), _jsxs(Box, { marginTop: 1, columnGap: 2, children: [_jsxs(Box, { columnGap: 1, children: [_jsx(Text, { bold: true, children: "ctrl+
|
|
116
|
+
}) }), !status.ok && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "red", wrap: "truncate-end", children: status.error }) })), submitMessage && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "yellow", children: submitMessage }) })), _jsxs(Box, { marginTop: 1, columnGap: 2, children: [_jsxs(Box, { columnGap: 1, children: [_jsx(Text, { bold: true, children: "ctrl+s" }), _jsx(Text, { dimColor: true, children: "submit" })] }), _jsxs(Box, { columnGap: 1, children: [_jsx(Text, { bold: true, children: "esc" }), _jsx(Text, { dimColor: true, children: "cancel" })] }), _jsxs(Box, { columnGap: 1, children: [_jsx(Text, { bold: true, children: "\u2191\u2193\u2190\u2192" }), _jsx(Text, { dimColor: true, children: "move" })] }), _jsxs(Box, { columnGap: 1, children: [_jsx(Text, { bold: true, children: "tab" }), _jsx(Text, { dimColor: true, children: "indent" })] })] })] }));
|
|
130
117
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { cursorToPosition,
|
|
2
|
+
import { cursorToPosition, positionToCursor, tryParseJson } from './json_editor.js';
|
|
3
3
|
describe('cursorToPosition', () => {
|
|
4
4
|
it('returns line 0 col 0 for an empty buffer', () => {
|
|
5
5
|
expect(cursorToPosition('', 0)).toEqual({ line: 0, col: 0 });
|
|
@@ -55,18 +55,3 @@ describe('tryParseJson', () => {
|
|
|
55
55
|
}
|
|
56
56
|
});
|
|
57
57
|
});
|
|
58
|
-
describe('initialCursor', () => {
|
|
59
|
-
it('lands inside the first empty "" pair so typing starts there', () => {
|
|
60
|
-
const buffer = JSON.stringify({ '': '' }, null, 2); // {\n "": ""\n}
|
|
61
|
-
const cursor = initialCursor(buffer);
|
|
62
|
-
expect(cursor).toBe(buffer.indexOf('""') + 1);
|
|
63
|
-
expect(buffer[cursor - 1]).toBe('"');
|
|
64
|
-
});
|
|
65
|
-
it('falls back to the end when there is no empty pair', () => {
|
|
66
|
-
const buffer = JSON.stringify({ values: [1, 2, 3] }, null, 2);
|
|
67
|
-
expect(initialCursor(buffer)).toBe(buffer.length);
|
|
68
|
-
});
|
|
69
|
-
it('returns 0 for an empty buffer', () => {
|
|
70
|
-
expect(initialCursor('')).toBe(0);
|
|
71
|
-
});
|
|
72
|
-
});
|
package/oclif.manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@outputai/cli",
|
|
3
|
-
"version": "0.8.2-next.
|
|
3
|
+
"version": "0.8.2-next.ec4c07d.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.5.0",
|
|
38
38
|
"yaml": "^2.8.3",
|
|
39
|
-
"@outputai/
|
|
40
|
-
"@outputai/llm": "0.8.2-next.
|
|
41
|
-
"@outputai/
|
|
39
|
+
"@outputai/credentials": "0.8.2-next.ec4c07d.0",
|
|
40
|
+
"@outputai/llm": "0.8.2-next.ec4c07d.0",
|
|
41
|
+
"@outputai/evals": "0.8.2-next.ec4c07d.0"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@types/cli-progress": "3.11.6",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { buildEntries, validateScenarioName } from './run_modal.js';
|
|
3
|
-
describe('buildEntries', () => {
|
|
4
|
-
it('lists scenarios then the custom-JSON entry', () => {
|
|
5
|
-
const entries = buildEntries(['basic', 'edge']);
|
|
6
|
-
expect(entries.map(e => e.label)).toEqual(['basic', 'edge', '[Enter input]']);
|
|
7
|
-
expect(entries[0]).toMatchObject({ kind: 'scenario', scenarioName: 'basic' });
|
|
8
|
-
expect(entries.at(-1)).toMatchObject({ kind: 'custom' });
|
|
9
|
-
});
|
|
10
|
-
it('offers the custom entry even with no saved scenarios', () => {
|
|
11
|
-
const entries = buildEntries([]);
|
|
12
|
-
expect(entries).toHaveLength(1);
|
|
13
|
-
expect(entries[0].kind).toBe('custom');
|
|
14
|
-
});
|
|
15
|
-
});
|
|
16
|
-
describe('validateScenarioName', () => {
|
|
17
|
-
it('rejects empty or whitespace-only names', () => {
|
|
18
|
-
expect(validateScenarioName('', [])).toMatch(/cannot be empty/);
|
|
19
|
-
expect(validateScenarioName(' ', [])).toMatch(/cannot be empty/);
|
|
20
|
-
});
|
|
21
|
-
it('rejects names with unsupported characters', () => {
|
|
22
|
-
expect(validateScenarioName('has space', [])).toMatch(/letters, numbers/);
|
|
23
|
-
expect(validateScenarioName('bad/name', [])).toMatch(/letters, numbers/);
|
|
24
|
-
});
|
|
25
|
-
it('rejects names that already exist', () => {
|
|
26
|
-
expect(validateScenarioName('basic', ['basic'])).toMatch(/already exists/);
|
|
27
|
-
});
|
|
28
|
-
it('trims before checking for duplicates', () => {
|
|
29
|
-
expect(validateScenarioName(' basic ', ['basic'])).toMatch(/already exists/);
|
|
30
|
-
});
|
|
31
|
-
it('accepts a unique name with allowed characters', () => {
|
|
32
|
-
expect(validateScenarioName('edge_case-1', ['basic'])).toBeNull();
|
|
33
|
-
});
|
|
34
|
-
});
|