@outputai/cli 0.1.12 → 0.1.13-dev.2f0a972.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 +322 -84
- package/dist/api/generated/api.js +67 -9
- package/dist/assets/docker/docker-compose-dev.yml +1 -1
- 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/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/commands/workflow/test_eval.js +2 -2
- 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/services/claude_client.js +4 -1
- package/dist/services/datasets.d.ts +1 -1
- package/dist/services/datasets.js +41 -37
- package/dist/services/datasets.test.d.ts +1 -0
- package/dist/services/datasets.test.js +202 -0
- package/dist/services/docker.d.ts +1 -3
- package/dist/services/docker.js +38 -13
- package/dist/services/messages.d.ts +1 -1
- package/dist/services/messages.js +2 -2
- package/dist/templates/agent_instructions/CLAUDE.md.template +36 -2
- package/dist/templates/agent_instructions/dotclaude/settings.json.template +2 -2
- package/dist/utils/date_formatter.d.ts +11 -1
- package/dist/utils/date_formatter.js +26 -1
- package/dist/utils/format_workflow_result.d.ts +3 -3
- package/dist/utils/open_url.d.ts +1 -0
- package/dist/utils/open_url.js +12 -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 +62 -26
- package/dist/views/workflow/list.d.ts +6 -0
- package/dist/views/workflow/list.js +127 -0
- package/package.json +14 -14
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Generated by orval v8.
|
|
2
|
+
* Generated by orval v8.8.0 🍺
|
|
3
3
|
* Do not edit manually.
|
|
4
4
|
* Output.ai API
|
|
5
5
|
* API for managing and executing Output.ai workflows
|
|
@@ -24,11 +24,7 @@ export const WorkflowRunInfoStatus = {
|
|
|
24
24
|
timed_out: 'timed_out',
|
|
25
25
|
continued: 'continued',
|
|
26
26
|
};
|
|
27
|
-
export const
|
|
28
|
-
completed: 'completed',
|
|
29
|
-
failed: 'failed',
|
|
30
|
-
};
|
|
31
|
-
export const GetWorkflowIdStatus200Status = {
|
|
27
|
+
export const WorkflowStatusResponseStatus = {
|
|
32
28
|
canceled: 'canceled',
|
|
33
29
|
completed: 'completed',
|
|
34
30
|
continued_as_new: 'continued_as_new',
|
|
@@ -38,7 +34,7 @@ export const GetWorkflowIdStatus200Status = {
|
|
|
38
34
|
timed_out: 'timed_out',
|
|
39
35
|
unspecified: 'unspecified',
|
|
40
36
|
};
|
|
41
|
-
export const
|
|
37
|
+
export const WorkflowResultResponseStatus = {
|
|
42
38
|
completed: 'completed',
|
|
43
39
|
failed: 'failed',
|
|
44
40
|
canceled: 'canceled',
|
|
@@ -46,6 +42,10 @@ export const GetWorkflowIdResult200Status = {
|
|
|
46
42
|
timed_out: 'timed_out',
|
|
47
43
|
continued: 'continued',
|
|
48
44
|
};
|
|
45
|
+
export const PostWorkflowRun200Status = {
|
|
46
|
+
completed: 'completed',
|
|
47
|
+
failed: 'failed',
|
|
48
|
+
};
|
|
49
49
|
;
|
|
50
50
|
export const getGetHealthUrl = () => {
|
|
51
51
|
return `/health`;
|
|
@@ -87,6 +87,24 @@ export const getWorkflowIdStatus = async (id, options) => {
|
|
|
87
87
|
method: 'GET'
|
|
88
88
|
});
|
|
89
89
|
};
|
|
90
|
+
export const getGetWorkflowIdRunsRidStatusUrl = (id, rid) => {
|
|
91
|
+
return `/workflow/${id}/runs/${rid}/status`;
|
|
92
|
+
};
|
|
93
|
+
export const getWorkflowIdRunsRidStatus = async (id, rid, options) => {
|
|
94
|
+
return customFetchInstance(getGetWorkflowIdRunsRidStatusUrl(id, rid), {
|
|
95
|
+
...options,
|
|
96
|
+
method: 'GET'
|
|
97
|
+
});
|
|
98
|
+
};
|
|
99
|
+
export const getPatchWorkflowIdRunsRidStopUrl = (id, rid) => {
|
|
100
|
+
return `/workflow/${id}/runs/${rid}/stop`;
|
|
101
|
+
};
|
|
102
|
+
export const patchWorkflowIdRunsRidStop = async (id, rid, options) => {
|
|
103
|
+
return customFetchInstance(getPatchWorkflowIdRunsRidStopUrl(id, rid), {
|
|
104
|
+
...options,
|
|
105
|
+
method: 'PATCH'
|
|
106
|
+
});
|
|
107
|
+
};
|
|
90
108
|
export const getPatchWorkflowIdStopUrl = (id) => {
|
|
91
109
|
return `/workflow/${id}/stop`;
|
|
92
110
|
};
|
|
@@ -96,6 +114,17 @@ export const patchWorkflowIdStop = async (id, options) => {
|
|
|
96
114
|
method: 'PATCH'
|
|
97
115
|
});
|
|
98
116
|
};
|
|
117
|
+
export const getPostWorkflowIdRunsRidTerminateUrl = (id, rid) => {
|
|
118
|
+
return `/workflow/${id}/runs/${rid}/terminate`;
|
|
119
|
+
};
|
|
120
|
+
export const postWorkflowIdRunsRidTerminate = async (id, rid, postWorkflowIdRunsRidTerminateBody, options) => {
|
|
121
|
+
return customFetchInstance(getPostWorkflowIdRunsRidTerminateUrl(id, rid), {
|
|
122
|
+
...options,
|
|
123
|
+
method: 'POST',
|
|
124
|
+
headers: { 'Content-Type': 'application/json', ...options?.headers },
|
|
125
|
+
body: JSON.stringify(postWorkflowIdRunsRidTerminateBody)
|
|
126
|
+
});
|
|
127
|
+
};
|
|
99
128
|
export const getPostWorkflowIdTerminateUrl = (id) => {
|
|
100
129
|
return `/workflow/${id}/terminate`;
|
|
101
130
|
};
|
|
@@ -107,15 +136,26 @@ export const postWorkflowIdTerminate = async (id, postWorkflowIdTerminateBody, o
|
|
|
107
136
|
body: JSON.stringify(postWorkflowIdTerminateBody)
|
|
108
137
|
});
|
|
109
138
|
};
|
|
139
|
+
export const getPostWorkflowIdRunsRidResetUrl = (id, rid) => {
|
|
140
|
+
return `/workflow/${id}/runs/${rid}/reset`;
|
|
141
|
+
};
|
|
142
|
+
export const postWorkflowIdRunsRidReset = async (id, rid, resetWorkflowRequest, options) => {
|
|
143
|
+
return customFetchInstance(getPostWorkflowIdRunsRidResetUrl(id, rid), {
|
|
144
|
+
...options,
|
|
145
|
+
method: 'POST',
|
|
146
|
+
headers: { 'Content-Type': 'application/json', ...options?.headers },
|
|
147
|
+
body: JSON.stringify(resetWorkflowRequest)
|
|
148
|
+
});
|
|
149
|
+
};
|
|
110
150
|
export const getPostWorkflowIdResetUrl = (id) => {
|
|
111
151
|
return `/workflow/${id}/reset`;
|
|
112
152
|
};
|
|
113
|
-
export const postWorkflowIdReset = async (id,
|
|
153
|
+
export const postWorkflowIdReset = async (id, resetWorkflowRequest, options) => {
|
|
114
154
|
return customFetchInstance(getPostWorkflowIdResetUrl(id), {
|
|
115
155
|
...options,
|
|
116
156
|
method: 'POST',
|
|
117
157
|
headers: { 'Content-Type': 'application/json', ...options?.headers },
|
|
118
|
-
body: JSON.stringify(
|
|
158
|
+
body: JSON.stringify(resetWorkflowRequest)
|
|
119
159
|
});
|
|
120
160
|
};
|
|
121
161
|
export const getGetWorkflowIdResultUrl = (id) => {
|
|
@@ -127,6 +167,15 @@ export const getWorkflowIdResult = async (id, options) => {
|
|
|
127
167
|
method: 'GET'
|
|
128
168
|
});
|
|
129
169
|
};
|
|
170
|
+
export const getGetWorkflowIdRunsRidResultUrl = (id, rid) => {
|
|
171
|
+
return `/workflow/${id}/runs/${rid}/result`;
|
|
172
|
+
};
|
|
173
|
+
export const getWorkflowIdRunsRidResult = async (id, rid, options) => {
|
|
174
|
+
return customFetchInstance(getGetWorkflowIdRunsRidResultUrl(id, rid), {
|
|
175
|
+
...options,
|
|
176
|
+
method: 'GET'
|
|
177
|
+
});
|
|
178
|
+
};
|
|
130
179
|
export const getGetWorkflowIdTraceLogUrl = (id) => {
|
|
131
180
|
return `/workflow/${id}/trace-log`;
|
|
132
181
|
};
|
|
@@ -136,6 +185,15 @@ export const getWorkflowIdTraceLog = async (id, options) => {
|
|
|
136
185
|
method: 'GET'
|
|
137
186
|
});
|
|
138
187
|
};
|
|
188
|
+
export const getGetWorkflowIdRunsRidTraceLogUrl = (id, rid) => {
|
|
189
|
+
return `/workflow/${id}/runs/${rid}/trace-log`;
|
|
190
|
+
};
|
|
191
|
+
export const getWorkflowIdRunsRidTraceLog = async (id, rid, options) => {
|
|
192
|
+
return customFetchInstance(getGetWorkflowIdRunsRidTraceLogUrl(id, rid), {
|
|
193
|
+
...options,
|
|
194
|
+
method: 'GET'
|
|
195
|
+
});
|
|
196
|
+
};
|
|
139
197
|
export const getGetWorkflowCatalogIdUrl = (id) => {
|
|
140
198
|
return `/workflow/catalog/${id}`;
|
|
141
199
|
};
|
|
@@ -4,6 +4,7 @@ import { buildWorkflow, buildWorkflowInteractiveLoop } from '#services/workflow_
|
|
|
4
4
|
import { ensureOutputAISystem } from '#services/coding_agents.js';
|
|
5
5
|
import { getWorkflowGenerateSuccessMessage } from '#services/messages.js';
|
|
6
6
|
import { DEFAULT_OUTPUT_DIRS } from '#utils/paths.js';
|
|
7
|
+
import { parseWorkflowDir } from '#utils/workflow_dir_parser.js';
|
|
7
8
|
import path from 'node:path';
|
|
8
9
|
import * as fsSync from 'node:fs';
|
|
9
10
|
import { getErrorMessage } from '#utils/error_utils.js';
|
|
@@ -83,7 +84,8 @@ export default class Generate extends Command {
|
|
|
83
84
|
this.displaySuccess(result);
|
|
84
85
|
}
|
|
85
86
|
displaySuccess(result) {
|
|
86
|
-
const
|
|
87
|
+
const dirInfo = parseWorkflowDir(result.targetDir);
|
|
88
|
+
const message = getWorkflowGenerateSuccessMessage(result.workflowName, dirInfo.workflowId ?? result.workflowName, dirInfo.scenarioNames[0], result.targetDir, result.filesCreated);
|
|
87
89
|
this.log(message);
|
|
88
90
|
}
|
|
89
91
|
}
|
|
@@ -2,10 +2,13 @@
|
|
|
2
2
|
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
3
3
|
import Generate from './generate.js';
|
|
4
4
|
import { generateWorkflow } from '#services/workflow_generator.js';
|
|
5
|
+
import { parseWorkflowDir } from '#utils/workflow_dir_parser.js';
|
|
5
6
|
import { InvalidNameError, WorkflowExistsError } from '#types/errors.js';
|
|
6
7
|
vi.mock('../../services/workflow_generator.js');
|
|
8
|
+
vi.mock('../../utils/workflow_dir_parser.js');
|
|
7
9
|
describe('Generate Command', () => {
|
|
8
10
|
let mockGenerateWorkflow;
|
|
11
|
+
let mockParseWorkflowDir;
|
|
9
12
|
let logSpy;
|
|
10
13
|
const createCommand = () => {
|
|
11
14
|
const cmd = new Generate([], {});
|
|
@@ -20,6 +23,7 @@ describe('Generate Command', () => {
|
|
|
20
23
|
beforeEach(() => {
|
|
21
24
|
vi.clearAllMocks();
|
|
22
25
|
mockGenerateWorkflow = vi.mocked(generateWorkflow);
|
|
26
|
+
mockParseWorkflowDir = vi.mocked(parseWorkflowDir);
|
|
23
27
|
});
|
|
24
28
|
describe('successful workflow generation', () => {
|
|
25
29
|
it('should generate workflow with skeleton flag', async () => {
|
|
@@ -38,6 +42,10 @@ describe('Generate Command', () => {
|
|
|
38
42
|
targetDir: '/tmp/test-workflow',
|
|
39
43
|
filesCreated: ['index.ts', 'steps.ts', 'types.ts']
|
|
40
44
|
});
|
|
45
|
+
mockParseWorkflowDir.mockReturnValue({
|
|
46
|
+
workflowId: 'testWorkflow',
|
|
47
|
+
scenarioNames: ['test_input']
|
|
48
|
+
});
|
|
41
49
|
await cmd.run();
|
|
42
50
|
expect(mockGenerateWorkflow).toHaveBeenCalledWith({
|
|
43
51
|
name: 'test-workflow',
|
|
@@ -105,6 +113,10 @@ describe('Generate Command', () => {
|
|
|
105
113
|
targetDir: '/custom/path/my-workflow',
|
|
106
114
|
filesCreated: ['index.ts', 'steps.ts', 'types.ts']
|
|
107
115
|
});
|
|
116
|
+
mockParseWorkflowDir.mockReturnValue({
|
|
117
|
+
workflowId: 'myWorkflow',
|
|
118
|
+
scenarioNames: ['test_input']
|
|
119
|
+
});
|
|
108
120
|
await cmd.run();
|
|
109
121
|
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('SUCCESS!'));
|
|
110
122
|
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('my-workflow'));
|
|
@@ -17,7 +17,8 @@ export function parseWorkflowForDisplay(workflow) {
|
|
|
17
17
|
description: parsed.description || 'No description',
|
|
18
18
|
inputs: formatParameters(parsed.inputs),
|
|
19
19
|
outputs: formatParameters(parsed.outputs),
|
|
20
|
-
scenarios: scenarioNames.length > 0 ? scenarioNames.join(', ') : 'none'
|
|
20
|
+
scenarios: scenarioNames.length > 0 ? scenarioNames.join(', ') : 'none',
|
|
21
|
+
aliases: workflow.aliases?.length ? workflow.aliases.join(', ') : 'none'
|
|
21
22
|
};
|
|
22
23
|
}
|
|
23
24
|
function caseInsensitiveIncludes(str, filter) {
|
|
@@ -26,7 +27,10 @@ function caseInsensitiveIncludes(str, filter) {
|
|
|
26
27
|
function matchName(filterString) {
|
|
27
28
|
return workflow => {
|
|
28
29
|
const name = workflow.name || '';
|
|
29
|
-
|
|
30
|
+
if (caseInsensitiveIncludes(name, filterString)) {
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
return (workflow.aliases ?? []).some(alias => caseInsensitiveIncludes(alias, filterString));
|
|
30
34
|
};
|
|
31
35
|
}
|
|
32
36
|
function sortWorkflowsByName(workflows) {
|
|
@@ -38,8 +42,8 @@ function sortWorkflowsByName(workflows) {
|
|
|
38
42
|
}
|
|
39
43
|
function createWorkflowTable(workflows, detailed) {
|
|
40
44
|
const table = new Table({
|
|
41
|
-
head: ['Name', 'Description', 'Inputs', 'Outputs', 'Scenarios'],
|
|
42
|
-
colWidths: detailed ? [
|
|
45
|
+
head: ['Name', 'Description', 'Aliases', 'Inputs', 'Outputs', 'Scenarios'],
|
|
46
|
+
colWidths: detailed ? [28, 36, 36, 36, 36, 48] : [22, 26, 26, 22, 22, 36],
|
|
43
47
|
wordWrap: true,
|
|
44
48
|
style: {
|
|
45
49
|
head: ['cyan']
|
|
@@ -49,13 +53,14 @@ function createWorkflowTable(workflows, detailed) {
|
|
|
49
53
|
sortedWorkflows.forEach(workflow => {
|
|
50
54
|
const display = parseWorkflowForDisplay(workflow);
|
|
51
55
|
if (detailed) {
|
|
56
|
+
const aliases = workflow.aliases?.length ? workflow.aliases.join('\n') : 'none';
|
|
52
57
|
const inputs = display.inputs.split(', ').join('\n');
|
|
53
58
|
const outputs = display.outputs.split(', ').join('\n');
|
|
54
59
|
const scenarios = display.scenarios.split(', ').join('\n');
|
|
55
|
-
table.push([display.name, display.description, inputs, outputs, scenarios]);
|
|
60
|
+
table.push([display.name, display.description, aliases, inputs, outputs, scenarios]);
|
|
56
61
|
}
|
|
57
62
|
else {
|
|
58
|
-
table.push([display.name, display.description, display.inputs, display.outputs, display.scenarios]);
|
|
63
|
+
table.push([display.name, display.description, display.aliases, display.inputs, display.outputs, display.scenarios]);
|
|
59
64
|
}
|
|
60
65
|
});
|
|
61
66
|
return table.toString();
|
|
@@ -72,6 +77,7 @@ function formatWorkflowsAsJson(workflows) {
|
|
|
72
77
|
return {
|
|
73
78
|
name: display.name,
|
|
74
79
|
description: display.description,
|
|
80
|
+
aliases: w.aliases ?? [],
|
|
75
81
|
inputs: display.inputs.split(', '),
|
|
76
82
|
outputs: display.outputs.split(', '),
|
|
77
83
|
scenarios: display.scenarios === 'none' ? [] : display.scenarios.split(', '),
|
|
@@ -64,6 +64,27 @@ describe('workflow list parsing', () => {
|
|
|
64
64
|
expect(parsed.inputs).toBe('none');
|
|
65
65
|
expect(parsed.outputs).toBe('none');
|
|
66
66
|
expect(parsed.scenarios).toBe('none');
|
|
67
|
+
expect(parsed.aliases).toBe('none');
|
|
68
|
+
});
|
|
69
|
+
it('should include aliases when present', async () => {
|
|
70
|
+
const { parseWorkflowForDisplay } = await import('./list.js');
|
|
71
|
+
const mockWorkflow = {
|
|
72
|
+
name: 'aliased-workflow',
|
|
73
|
+
description: 'Has aliases',
|
|
74
|
+
aliases: ['old_name', 'legacy_name']
|
|
75
|
+
};
|
|
76
|
+
const parsed = parseWorkflowForDisplay(mockWorkflow);
|
|
77
|
+
expect(parsed.aliases).toBe('old_name, legacy_name');
|
|
78
|
+
});
|
|
79
|
+
it('should show none when aliases array is empty', async () => {
|
|
80
|
+
const { parseWorkflowForDisplay } = await import('./list.js');
|
|
81
|
+
const mockWorkflow = {
|
|
82
|
+
name: 'no-aliases',
|
|
83
|
+
description: 'Empty aliases',
|
|
84
|
+
aliases: []
|
|
85
|
+
};
|
|
86
|
+
const parsed = parseWorkflowForDisplay(mockWorkflow);
|
|
87
|
+
expect(parsed.aliases).toBe('none');
|
|
67
88
|
});
|
|
68
89
|
it('should include scenario names when scenarios exist', async () => {
|
|
69
90
|
mockListScenarios.mockReturnValueOnce(['basic', 'advanced', 'stress_test']);
|
|
@@ -55,7 +55,8 @@ export default class WorkflowRun extends Command {
|
|
|
55
55
|
}),
|
|
56
56
|
'task-queue': Flags.string({
|
|
57
57
|
char: 'q',
|
|
58
|
-
description: 'Task queue name for workflow execution'
|
|
58
|
+
description: 'Task queue name for workflow execution (defaults to OUTPUT_CATALOG_ID)',
|
|
59
|
+
env: 'OUTPUT_CATALOG_ID'
|
|
59
60
|
}),
|
|
60
61
|
format: Flags.string({
|
|
61
62
|
char: 'f',
|
|
@@ -13,6 +13,7 @@ vi.mock('#utils/sleep.js', () => ({
|
|
|
13
13
|
describe('workflow run command', () => {
|
|
14
14
|
beforeEach(async () => {
|
|
15
15
|
vi.clearAllMocks();
|
|
16
|
+
delete process.env.OUTPUT_CATALOG_ID;
|
|
16
17
|
const { resolveInput } = await import('#utils/resolve_input.js');
|
|
17
18
|
const { sleep } = await import('#utils/sleep.js');
|
|
18
19
|
vi.mocked(resolveInput).mockResolvedValue({});
|
|
@@ -28,7 +28,8 @@ export default class WorkflowStart extends Command {
|
|
|
28
28
|
}),
|
|
29
29
|
'task-queue': Flags.string({
|
|
30
30
|
char: 'q',
|
|
31
|
-
description: 'Task queue name for workflow execution'
|
|
31
|
+
description: 'Task queue name for workflow execution (defaults to OUTPUT_CATALOG_ID)',
|
|
32
|
+
env: 'OUTPUT_CATALOG_ID'
|
|
32
33
|
})
|
|
33
34
|
};
|
|
34
35
|
async run() {
|
|
@@ -5,6 +5,7 @@ vi.mock('../../api/generated/api.js', () => ({
|
|
|
5
5
|
describe('workflow start command', () => {
|
|
6
6
|
beforeEach(() => {
|
|
7
7
|
vi.clearAllMocks();
|
|
8
|
+
delete process.env.OUTPUT_CATALOG_ID;
|
|
8
9
|
});
|
|
9
10
|
describe('command definition', () => {
|
|
10
11
|
it('should export a valid OCLIF command', async () => {
|
|
@@ -114,7 +114,7 @@ export default class WorkflowTest extends Command {
|
|
|
114
114
|
}
|
|
115
115
|
};
|
|
116
116
|
if (save) {
|
|
117
|
-
const filePath = join(dir, `${dataset.name}.yml`);
|
|
117
|
+
const filePath = dataset._source ?? join(dir, `${dataset.name}.yml`);
|
|
118
118
|
await writeDataset(updated, filePath);
|
|
119
119
|
this.log(` Saved output to ${filePath}`);
|
|
120
120
|
}
|
|
@@ -137,7 +137,7 @@ export default class WorkflowTest extends Command {
|
|
|
137
137
|
date: now
|
|
138
138
|
}
|
|
139
139
|
};
|
|
140
|
-
const filePath = join(dir, `${dataset.name}.yml`);
|
|
140
|
+
const filePath = dataset._source ?? join(dir, `${dataset.name}.yml`);
|
|
141
141
|
await writeDataset(updated, filePath);
|
|
142
142
|
this.log(` Saved eval result to ${filePath}`);
|
|
143
143
|
}
|
|
@@ -0,0 +1,4 @@
|
|
|
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))) }));
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
interface StatusDisplay {
|
|
3
|
+
icon: string;
|
|
4
|
+
color: string;
|
|
5
|
+
}
|
|
6
|
+
export declare const resolveStatus: (status: string) => StatusDisplay;
|
|
7
|
+
export declare const statusColor: (status: string) => string;
|
|
8
|
+
export declare const StatusIcon: React.FC<{
|
|
9
|
+
status: string;
|
|
10
|
+
}>;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Text } from 'ink';
|
|
3
|
+
const STATUS_MAP = {
|
|
4
|
+
// Docker service health
|
|
5
|
+
healthy: { icon: '●', color: 'green' },
|
|
6
|
+
unhealthy: { icon: '○', color: 'red' },
|
|
7
|
+
starting: { icon: '◐', color: 'yellow' },
|
|
8
|
+
none: { icon: '●', color: 'blue' },
|
|
9
|
+
exited: { icon: '✗', color: 'red' },
|
|
10
|
+
// Workflow run status
|
|
11
|
+
running: { icon: '●', color: 'blue' },
|
|
12
|
+
completed: { icon: '●', color: 'green' },
|
|
13
|
+
failed: { icon: '✗', color: 'red' },
|
|
14
|
+
canceled: { icon: '○', color: 'gray' },
|
|
15
|
+
terminated: { icon: '✗', color: 'red' },
|
|
16
|
+
timed_out: { icon: '✗', color: 'red' },
|
|
17
|
+
continued: { icon: '↻', color: 'blue' }
|
|
18
|
+
};
|
|
19
|
+
const DEFAULT_DISPLAY = { icon: '?', color: 'white' };
|
|
20
|
+
export const resolveStatus = (status) => STATUS_MAP[status] ?? DEFAULT_DISPLAY;
|
|
21
|
+
export const statusColor = (status) => resolveStatus(status).color;
|
|
22
|
+
export const StatusIcon = ({ status }) => {
|
|
23
|
+
const { icon, color } = resolveStatus(status);
|
|
24
|
+
return _jsx(Text, { color: color, children: icon });
|
|
25
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
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"] })] })] }));
|
|
@@ -105,7 +105,10 @@ function getTodoWriteMessage(message) {
|
|
|
105
105
|
if (message.type !== 'assistant') {
|
|
106
106
|
return null;
|
|
107
107
|
}
|
|
108
|
-
const todoWriteMessage = message.message.content.find((c) =>
|
|
108
|
+
const todoWriteMessage = message.message.content.find((c) => {
|
|
109
|
+
const block = c;
|
|
110
|
+
return block.type === 'tool_use' && block.name === 'TodoWrite';
|
|
111
|
+
});
|
|
109
112
|
return todoWriteMessage ?? null;
|
|
110
113
|
}
|
|
111
114
|
function applyInstructions(message, instructions) {
|
|
@@ -8,7 +8,7 @@ export interface DatasetInfo {
|
|
|
8
8
|
}
|
|
9
9
|
export declare function resolveDatasetsDir(workflowName: string, basePath?: string): string | null;
|
|
10
10
|
export declare function resolveDefaultDatasetsDir(workflowName: string, basePath?: string): string;
|
|
11
|
-
export declare function
|
|
11
|
+
export declare function readDatasetFile(filePath: string): Promise<Dataset[]>;
|
|
12
12
|
export declare function readAllDatasets(workflowName: string, filterNames?: string[], basePath?: string): Promise<{
|
|
13
13
|
datasets: Dataset[];
|
|
14
14
|
dir: string;
|
|
@@ -24,10 +24,19 @@ export function resolveDefaultDatasetsDir(workflowName, basePath = process.cwd()
|
|
|
24
24
|
// Default to first workflows path
|
|
25
25
|
return resolve(basePath, WORKFLOWS_PATHS[0], workflowName, DATASETS_DIR);
|
|
26
26
|
}
|
|
27
|
-
export async function
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
export async function readDatasetFile(filePath) {
|
|
28
|
+
const raw = yaml.load(await readFile(filePath, 'utf-8'));
|
|
29
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
30
|
+
throw new Error(`Invalid dataset file: ${filePath}`);
|
|
31
|
+
}
|
|
32
|
+
return Object.entries(raw).map(([name, body]) => {
|
|
33
|
+
if (!body || typeof body !== 'object' || !('input' in body)) {
|
|
34
|
+
throw new Error(`Dataset case "${name}" in ${filePath} is missing required "input" field`);
|
|
35
|
+
}
|
|
36
|
+
const dataset = DatasetSchema.parse({ name, ...body });
|
|
37
|
+
dataset._source = filePath;
|
|
38
|
+
return dataset;
|
|
39
|
+
});
|
|
31
40
|
}
|
|
32
41
|
export async function readAllDatasets(workflowName, filterNames, basePath) {
|
|
33
42
|
const dir = resolveDatasetsDir(workflowName, basePath);
|
|
@@ -36,42 +45,35 @@ export async function readAllDatasets(workflowName, filterNames, basePath) {
|
|
|
36
45
|
}
|
|
37
46
|
const files = await readdir(dir);
|
|
38
47
|
const ymlFiles = files.filter(f => f.endsWith('.yml') || f.endsWith('.yaml'));
|
|
48
|
+
const seen = new Set();
|
|
39
49
|
const datasets = [];
|
|
40
50
|
for (const file of ymlFiles) {
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
51
|
+
const cases = await readDatasetFile(join(dir, file));
|
|
52
|
+
for (const dataset of cases) {
|
|
53
|
+
if (seen.has(dataset.name)) {
|
|
54
|
+
throw new Error(`Duplicate dataset case name "${dataset.name}" found in ${file}`);
|
|
55
|
+
}
|
|
56
|
+
seen.add(dataset.name);
|
|
57
|
+
if (filterNames && !filterNames.includes(dataset.name)) {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
datasets.push(dataset);
|
|
44
61
|
}
|
|
45
|
-
datasets.push(dataset);
|
|
46
62
|
}
|
|
47
63
|
return { datasets, dir };
|
|
48
64
|
}
|
|
49
|
-
async function mergeWithExisting(dataset, filePath) {
|
|
50
|
-
if (!existsSync(filePath)) {
|
|
51
|
-
return dataset;
|
|
52
|
-
}
|
|
53
|
-
try {
|
|
54
|
-
const existing = await readDataset(filePath);
|
|
55
|
-
return {
|
|
56
|
-
...existing,
|
|
57
|
-
...dataset,
|
|
58
|
-
ground_truth: dataset.ground_truth ?? existing.ground_truth,
|
|
59
|
-
last_output: dataset.last_output ?? existing.last_output,
|
|
60
|
-
last_eval: dataset.last_eval ?? existing.last_eval
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
catch {
|
|
64
|
-
return dataset;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
65
|
export async function writeDataset(dataset, filePath) {
|
|
68
|
-
const merged = await mergeWithExisting(dataset, filePath);
|
|
69
66
|
const dir = resolve(filePath, '..');
|
|
70
67
|
if (!existsSync(dir)) {
|
|
71
68
|
await mkdir(dir, { recursive: true });
|
|
72
69
|
}
|
|
73
|
-
const
|
|
74
|
-
|
|
70
|
+
const loaded = existsSync(filePath) ? yaml.load(await readFile(filePath, 'utf-8')) : null;
|
|
71
|
+
const fileObj = (loaded && typeof loaded === 'object' && !Array.isArray(loaded)) ?
|
|
72
|
+
loaded :
|
|
73
|
+
{};
|
|
74
|
+
const { name, _source, ...caseBody } = dataset;
|
|
75
|
+
fileObj[name] = { ...fileObj[name], ...caseBody };
|
|
76
|
+
await writeFile(filePath, yaml.dump(fileObj, { lineWidth: 120, noRefs: true, sortKeys: false }), 'utf-8');
|
|
75
77
|
}
|
|
76
78
|
export async function listDatasets(workflowName, basePath) {
|
|
77
79
|
const dir = resolveDatasetsDir(workflowName, basePath);
|
|
@@ -84,14 +86,16 @@ export async function listDatasets(workflowName, basePath) {
|
|
|
84
86
|
for (const file of ymlFiles) {
|
|
85
87
|
const filePath = join(dir, file);
|
|
86
88
|
try {
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
89
|
+
const cases = await readDatasetFile(filePath);
|
|
90
|
+
for (const dataset of cases) {
|
|
91
|
+
results.push({
|
|
92
|
+
name: dataset.name,
|
|
93
|
+
path: filePath,
|
|
94
|
+
hasLastOutput: dataset.last_output?.output !== undefined,
|
|
95
|
+
lastOutputDate: dataset.last_output?.date,
|
|
96
|
+
lastEvalDate: dataset.last_eval?.date
|
|
97
|
+
});
|
|
98
|
+
}
|
|
95
99
|
}
|
|
96
100
|
catch {
|
|
97
101
|
results.push({
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|