@outputai/cli 0.2.1-next.af8a069.0 → 0.2.1-next.eadab44.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 +141 -2
- package/dist/api/generated/api.js +32 -0
- package/dist/api/http_client.js +24 -19
- package/dist/assets/docker/docker-compose-dev.yml +1 -1
- package/dist/commands/fix.js +1 -1
- package/dist/commands/fix.spec.js +2 -2
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +5 -1
- package/dist/commands/init.spec.js +10 -5
- package/dist/commands/update.js +1 -1
- package/dist/commands/update.spec.js +2 -2
- package/dist/commands/workflow/plan.js +5 -1
- package/dist/commands/workflow/plan.spec.js +3 -2
- package/dist/config.d.ts +6 -38
- package/dist/config.js +22 -42
- package/dist/config.spec.d.ts +1 -0
- package/dist/config.spec.js +75 -0
- package/dist/generated/framework_version.json +1 -1
- package/dist/hooks/init.d.ts +4 -0
- package/dist/hooks/init.js +17 -1
- package/dist/hooks/init.spec.js +79 -5
- package/dist/services/coding_agents.js +5 -1
- package/dist/services/coding_agents.spec.js +19 -6
- package/dist/services/credentials_configurator.js +1 -1
- package/dist/services/env_configurator.js +1 -1
- package/dist/services/env_configurator.spec.js +12 -12
- package/dist/services/project_scaffold.d.ts +1 -1
- package/dist/services/project_scaffold.js +17 -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/utils/env_loader.js +1 -2
- package/dist/utils/error_handler.js +10 -8
- 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/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 +70 -0
- package/dist/utils/proxy.d.ts +9 -0
- package/dist/utils/proxy.js +24 -0
- package/dist/utils/proxy.spec.d.ts +1 -0
- package/dist/utils/proxy.spec.js +48 -0
- package/dist/views/workflow/list.js +8 -6
- package/package.json +5 -4
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { confirm as inquirerConfirm, input as inquirerInput, password as inquirerPassword } from '@inquirer/prompts';
|
|
3
|
+
vi.mock('@inquirer/prompts', () => ({
|
|
4
|
+
confirm: vi.fn(),
|
|
5
|
+
input: vi.fn(),
|
|
6
|
+
password: vi.fn()
|
|
7
|
+
}));
|
|
8
|
+
vi.mock('./interactive.js', () => ({
|
|
9
|
+
isInteractive: vi.fn()
|
|
10
|
+
}));
|
|
11
|
+
describe('prompt wrapper', () => {
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
vi.clearAllMocks();
|
|
14
|
+
});
|
|
15
|
+
describe('when interactive', () => {
|
|
16
|
+
beforeEach(async () => {
|
|
17
|
+
const { isInteractive } = await import('./interactive.js');
|
|
18
|
+
vi.mocked(isInteractive).mockReturnValue(true);
|
|
19
|
+
});
|
|
20
|
+
it('confirm delegates to inquirer', async () => {
|
|
21
|
+
vi.mocked(inquirerConfirm).mockResolvedValue(false);
|
|
22
|
+
const { confirm } = await import('./prompt.js');
|
|
23
|
+
const result = await confirm({ message: 'Continue?', default: true });
|
|
24
|
+
expect(inquirerConfirm).toHaveBeenCalledWith({ message: 'Continue?', default: true });
|
|
25
|
+
expect(result).toBe(false);
|
|
26
|
+
});
|
|
27
|
+
it('input delegates to inquirer', async () => {
|
|
28
|
+
vi.mocked(inquirerInput).mockResolvedValue('user input');
|
|
29
|
+
const { input } = await import('./prompt.js');
|
|
30
|
+
const result = await input({ message: 'Name?', default: 'default' });
|
|
31
|
+
expect(inquirerInput).toHaveBeenCalledWith({ message: 'Name?', default: 'default' });
|
|
32
|
+
expect(result).toBe('user input');
|
|
33
|
+
});
|
|
34
|
+
it('password delegates to inquirer', async () => {
|
|
35
|
+
vi.mocked(inquirerPassword).mockResolvedValue('secret');
|
|
36
|
+
const { password } = await import('./prompt.js');
|
|
37
|
+
const result = await password({ message: 'Token?' });
|
|
38
|
+
expect(inquirerPassword).toHaveBeenCalledWith({ message: 'Token?' });
|
|
39
|
+
expect(result).toBe('secret');
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
describe('when non-interactive', () => {
|
|
43
|
+
beforeEach(async () => {
|
|
44
|
+
const { isInteractive } = await import('./interactive.js');
|
|
45
|
+
vi.mocked(isInteractive).mockReturnValue(false);
|
|
46
|
+
});
|
|
47
|
+
it('confirm always returns true regardless of default', async () => {
|
|
48
|
+
const { confirm } = await import('./prompt.js');
|
|
49
|
+
expect(await confirm({ message: 'Continue?', default: false })).toBe(true);
|
|
50
|
+
expect(await confirm({ message: 'Continue?', default: true })).toBe(true);
|
|
51
|
+
expect(await confirm({ message: 'Continue?' })).toBe(true);
|
|
52
|
+
expect(inquirerConfirm).not.toHaveBeenCalled();
|
|
53
|
+
});
|
|
54
|
+
it('input returns default value', async () => {
|
|
55
|
+
const { input } = await import('./prompt.js');
|
|
56
|
+
expect(await input({ message: 'Name?', default: 'fallback' })).toBe('fallback');
|
|
57
|
+
expect(inquirerInput).not.toHaveBeenCalled();
|
|
58
|
+
});
|
|
59
|
+
it('input returns empty string when no default', async () => {
|
|
60
|
+
const { input } = await import('./prompt.js');
|
|
61
|
+
expect(await input({ message: 'Name?' })).toBe('');
|
|
62
|
+
expect(inquirerInput).not.toHaveBeenCalled();
|
|
63
|
+
});
|
|
64
|
+
it('password returns empty string', async () => {
|
|
65
|
+
const { password } = await import('./prompt.js');
|
|
66
|
+
expect(await password({ message: 'Token?' })).toBe('');
|
|
67
|
+
expect(inquirerPassword).not.toHaveBeenCalled();
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Routes all `fetch()` calls through an HTTP/HTTPS proxy when standard
|
|
3
|
+
* proxy env vars are set (`HTTPS_PROXY`, `https_proxy`, `HTTP_PROXY`,
|
|
4
|
+
* `http_proxy`). No-op when none are set. Invalid URLs are logged and
|
|
5
|
+
* skipped so the CLI keeps running.
|
|
6
|
+
*
|
|
7
|
+
* Call once at CLI startup, before any network activity.
|
|
8
|
+
*/
|
|
9
|
+
export declare const bootstrapProxy: () => void;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { EnvHttpProxyAgent, setGlobalDispatcher } from 'undici';
|
|
2
|
+
/**
|
|
3
|
+
* Routes all `fetch()` calls through an HTTP/HTTPS proxy when standard
|
|
4
|
+
* proxy env vars are set (`HTTPS_PROXY`, `https_proxy`, `HTTP_PROXY`,
|
|
5
|
+
* `http_proxy`). No-op when none are set. Invalid URLs are logged and
|
|
6
|
+
* skipped so the CLI keeps running.
|
|
7
|
+
*
|
|
8
|
+
* Call once at CLI startup, before any network activity.
|
|
9
|
+
*/
|
|
10
|
+
export const bootstrapProxy = () => {
|
|
11
|
+
const proxyUrl = process.env.HTTPS_PROXY || process.env.https_proxy ||
|
|
12
|
+
process.env.HTTP_PROXY || process.env.http_proxy;
|
|
13
|
+
if (!proxyUrl) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
new URL(proxyUrl);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
console.warn(`[proxy] Ignoring invalid proxy URL: ${proxyUrl}`);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
setGlobalDispatcher(new EnvHttpProxyAgent());
|
|
24
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
const mockSetGlobalDispatcher = vi.fn();
|
|
3
|
+
const MockEnvHttpProxyAgent = vi.fn();
|
|
4
|
+
vi.mock('undici', () => ({
|
|
5
|
+
EnvHttpProxyAgent: MockEnvHttpProxyAgent,
|
|
6
|
+
setGlobalDispatcher: mockSetGlobalDispatcher
|
|
7
|
+
}));
|
|
8
|
+
describe('proxy bootstrap', () => {
|
|
9
|
+
const originalEnv = { ...process.env };
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
vi.clearAllMocks();
|
|
12
|
+
delete process.env.HTTPS_PROXY;
|
|
13
|
+
delete process.env.https_proxy;
|
|
14
|
+
delete process.env.HTTP_PROXY;
|
|
15
|
+
delete process.env.http_proxy;
|
|
16
|
+
});
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
process.env = { ...originalEnv };
|
|
19
|
+
});
|
|
20
|
+
it('does nothing when no proxy env vars are set', async () => {
|
|
21
|
+
const { bootstrapProxy } = await import('./proxy.js');
|
|
22
|
+
bootstrapProxy();
|
|
23
|
+
expect(mockSetGlobalDispatcher).not.toHaveBeenCalled();
|
|
24
|
+
});
|
|
25
|
+
it('sets global dispatcher when HTTPS_PROXY is set', async () => {
|
|
26
|
+
process.env.HTTPS_PROXY = 'http://proxy:8080';
|
|
27
|
+
const { bootstrapProxy } = await import('./proxy.js');
|
|
28
|
+
bootstrapProxy();
|
|
29
|
+
expect(MockEnvHttpProxyAgent).toHaveBeenCalled();
|
|
30
|
+
expect(mockSetGlobalDispatcher).toHaveBeenCalledTimes(1);
|
|
31
|
+
});
|
|
32
|
+
it('sets global dispatcher when HTTP_PROXY is set', async () => {
|
|
33
|
+
process.env.HTTP_PROXY = 'http://proxy:8080';
|
|
34
|
+
const { bootstrapProxy } = await import('./proxy.js');
|
|
35
|
+
bootstrapProxy();
|
|
36
|
+
expect(MockEnvHttpProxyAgent).toHaveBeenCalled();
|
|
37
|
+
expect(mockSetGlobalDispatcher).toHaveBeenCalledTimes(1);
|
|
38
|
+
});
|
|
39
|
+
it('does not set global dispatcher when proxy URL is malformed', async () => {
|
|
40
|
+
process.env.HTTPS_PROXY = 'not a url';
|
|
41
|
+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => { });
|
|
42
|
+
const { bootstrapProxy } = await import('./proxy.js');
|
|
43
|
+
bootstrapProxy();
|
|
44
|
+
expect(mockSetGlobalDispatcher).not.toHaveBeenCalled();
|
|
45
|
+
expect(warnSpy).toHaveBeenCalled();
|
|
46
|
+
warnSpy.mockRestore();
|
|
47
|
+
});
|
|
48
|
+
});
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useState, useEffect, useRef, useMemo } from 'react';
|
|
3
3
|
import { Box, Text, useInput } from 'ink';
|
|
4
4
|
import Spinner from 'ink-spinner';
|
|
5
|
-
import {
|
|
5
|
+
import { getWorkflowIdRunsRidResult } from '#api/generated/api.js';
|
|
6
6
|
import { StatusIcon, statusColor } from '#components/status_icon.js';
|
|
7
7
|
import { elapsedMs, formatDurationCompact } from '#utils/date_formatter.js';
|
|
8
8
|
import { CommandFooter } from '#components/command_footer.js';
|
|
@@ -59,17 +59,19 @@ export const WorkflowListView = ({ runs, onBack }) => {
|
|
|
59
59
|
const clampedIndex = Math.min(selectedIndex, Math.max(0, sortedRuns.length - 1));
|
|
60
60
|
const selectedRun = sortedRuns[clampedIndex];
|
|
61
61
|
const selectedWorkflowId = selectedRun?.workflowId;
|
|
62
|
+
const selectedRunId = selectedRun?.runId;
|
|
62
63
|
useEffect(() => {
|
|
63
64
|
if (clampedIndex !== selectedIndex) {
|
|
64
65
|
setSelectedIndex(clampedIndex);
|
|
65
66
|
}
|
|
66
67
|
}, [clampedIndex, selectedIndex]);
|
|
67
68
|
useEffect(() => {
|
|
68
|
-
if (!selectedWorkflowId) {
|
|
69
|
+
if (!selectedWorkflowId || !selectedRunId) {
|
|
69
70
|
setDetail(null);
|
|
70
71
|
return;
|
|
71
72
|
}
|
|
72
|
-
const
|
|
73
|
+
const cacheKey = `${selectedWorkflowId}:${selectedRunId}`;
|
|
74
|
+
const cached = cacheRef.current.get(cacheKey);
|
|
73
75
|
if (cached) {
|
|
74
76
|
setDetail(cached);
|
|
75
77
|
setDetailLoading(false);
|
|
@@ -77,13 +79,13 @@ export const WorkflowListView = ({ runs, onBack }) => {
|
|
|
77
79
|
}
|
|
78
80
|
const currentFetchId = ++fetchIdRef.current;
|
|
79
81
|
setDetailLoading(true);
|
|
80
|
-
|
|
82
|
+
getWorkflowIdRunsRidResult(selectedWorkflowId, selectedRunId)
|
|
81
83
|
.then(response => {
|
|
82
84
|
if (fetchIdRef.current !== currentFetchId) {
|
|
83
85
|
return;
|
|
84
86
|
}
|
|
85
87
|
const data = response.data;
|
|
86
|
-
cacheRef.current.set(
|
|
88
|
+
cacheRef.current.set(cacheKey, data);
|
|
87
89
|
setDetail(data);
|
|
88
90
|
setDetailLoading(false);
|
|
89
91
|
})
|
|
@@ -94,7 +96,7 @@ export const WorkflowListView = ({ runs, onBack }) => {
|
|
|
94
96
|
setDetail(null);
|
|
95
97
|
setDetailLoading(false);
|
|
96
98
|
});
|
|
97
|
-
}, [selectedWorkflowId]);
|
|
99
|
+
}, [selectedWorkflowId, selectedRunId]);
|
|
98
100
|
useInput((input, key) => {
|
|
99
101
|
if (key.upArrow) {
|
|
100
102
|
setSelectedIndex(i => Math.max(0, i - 1));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@outputai/cli",
|
|
3
|
-
"version": "0.2.1-next.
|
|
3
|
+
"version": "0.2.1-next.eadab44.0",
|
|
4
4
|
"description": "CLI for Output.ai workflow generation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -34,10 +34,11 @@
|
|
|
34
34
|
"ky": "1.14.3",
|
|
35
35
|
"react": "19.2.5",
|
|
36
36
|
"semver": "7.7.4",
|
|
37
|
+
"undici": "8.0.2",
|
|
37
38
|
"yaml": "^2.8.3",
|
|
38
|
-
"@outputai/credentials": "0.2.1-next.
|
|
39
|
-
"@outputai/evals": "0.2.1-next.
|
|
40
|
-
"@outputai/llm": "0.2.1-next.
|
|
39
|
+
"@outputai/credentials": "0.2.1-next.eadab44.0",
|
|
40
|
+
"@outputai/evals": "0.2.1-next.eadab44.0",
|
|
41
|
+
"@outputai/llm": "0.2.1-next.eadab44.0"
|
|
41
42
|
},
|
|
42
43
|
"devDependencies": {
|
|
43
44
|
"@types/cli-progress": "3.11.6",
|