@output.ai/cli 0.7.2 → 0.7.4
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/services/project_scaffold.js +3 -6
- package/dist/services/project_scaffold.spec.js +8 -16
- package/dist/services/workflow_planner.js +1 -1
- package/dist/services/workflow_planner.spec.js +10 -4
- package/dist/templates/project/src/workflows/example_question/steps.ts.template +2 -2
- package/dist/templates/workflow/steps.ts.template +2 -2
- package/dist/test_helpers/mocks.js +12 -2
- package/package.json +1 -1
|
@@ -61,11 +61,8 @@ const promptForProjectName = async (defaultProjectName) => {
|
|
|
61
61
|
default: defaultProjectName
|
|
62
62
|
}) || defaultProjectName;
|
|
63
63
|
};
|
|
64
|
-
const
|
|
65
|
-
return
|
|
66
|
-
message: 'What is your project description? (optional)',
|
|
67
|
-
default: `An Output SDK workflow for ${kebabCase(projectName)}`
|
|
68
|
-
}) || `An Output SDK workflow for ${kebabCase(projectName)}`;
|
|
64
|
+
const generateProjectDescription = (projectName) => {
|
|
65
|
+
return `An Output SDK workflow for ${kebabCase(projectName)}`;
|
|
69
66
|
};
|
|
70
67
|
/**
|
|
71
68
|
* Get project configuration from user input
|
|
@@ -80,7 +77,7 @@ export const getProjectConfig = async (userFolderNameArg) => {
|
|
|
80
77
|
const folderName = userFolderNameArg ?
|
|
81
78
|
userFolderNameArg :
|
|
82
79
|
await promptForFolderName(projectName);
|
|
83
|
-
const description =
|
|
80
|
+
const description = generateProjectDescription(projectName);
|
|
84
81
|
return {
|
|
85
82
|
projectName,
|
|
86
83
|
folderName,
|
|
@@ -39,35 +39,27 @@ describe('project_scaffold', () => {
|
|
|
39
39
|
vi.clearAllMocks();
|
|
40
40
|
});
|
|
41
41
|
describe('getProjectConfig', () => {
|
|
42
|
-
it('should skip
|
|
42
|
+
it('should skip all prompts when folderName is provided', async () => {
|
|
43
43
|
const { input } = await import('@inquirer/prompts');
|
|
44
|
-
vi.mocked(input).mockResolvedValueOnce('Test description');
|
|
45
44
|
const config = await getProjectConfig('my-project');
|
|
46
45
|
expect(config.folderName).toBe('my-project');
|
|
47
46
|
expect(config.projectName).toBe('my-project');
|
|
48
|
-
|
|
49
|
-
expect(input).toHaveBeenCalledTimes(1);
|
|
47
|
+
expect(input).not.toHaveBeenCalled();
|
|
50
48
|
});
|
|
51
|
-
it('should
|
|
52
|
-
const { input } = await import('@inquirer/prompts');
|
|
53
|
-
vi.mocked(input).mockResolvedValueOnce('My custom description');
|
|
49
|
+
it('should auto-generate description when folderName provided', async () => {
|
|
54
50
|
const config = await getProjectConfig('test-folder');
|
|
55
|
-
expect(config.description).toBe('
|
|
56
|
-
expect(input).toHaveBeenCalledWith(expect.objectContaining({
|
|
57
|
-
message: expect.stringContaining('description')
|
|
58
|
-
}));
|
|
51
|
+
expect(config.description).toBe('An Output SDK workflow for test-folder');
|
|
59
52
|
});
|
|
60
|
-
it('should prompt for
|
|
53
|
+
it('should prompt for project name and folder name when not provided', async () => {
|
|
61
54
|
const { input } = await import('@inquirer/prompts');
|
|
62
55
|
vi.mocked(input)
|
|
63
56
|
.mockResolvedValueOnce('Test Project')
|
|
64
|
-
.mockResolvedValueOnce('test-project')
|
|
65
|
-
.mockResolvedValueOnce('A test project');
|
|
57
|
+
.mockResolvedValueOnce('test-project');
|
|
66
58
|
const config = await getProjectConfig();
|
|
67
59
|
expect(config.projectName).toBe('Test Project');
|
|
68
60
|
expect(config.folderName).toBe('test-project');
|
|
69
|
-
expect(config.description).toBe('
|
|
70
|
-
expect(input).toHaveBeenCalledTimes(
|
|
61
|
+
expect(config.description).toBe('An Output SDK workflow for test-project');
|
|
62
|
+
expect(input).toHaveBeenCalledTimes(2);
|
|
71
63
|
});
|
|
72
64
|
});
|
|
73
65
|
describe('checkDependencies', () => {
|
|
@@ -8,7 +8,7 @@ export async function generatePlanName(description, date = new Date()) {
|
|
|
8
8
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
9
9
|
const day = String(date.getDate()).padStart(2, '0');
|
|
10
10
|
const datePrefix = `${year}_${month}_${day}`;
|
|
11
|
-
const planNameSlug = await generateText({
|
|
11
|
+
const { text: planNameSlug } = await generateText({
|
|
12
12
|
prompt: 'generate_plan_name@v1',
|
|
13
13
|
variables: { description }
|
|
14
14
|
});
|
|
@@ -6,13 +6,19 @@ import fs from 'node:fs/promises';
|
|
|
6
6
|
vi.mock('./coding_agents.js');
|
|
7
7
|
vi.mock('@output.ai/llm');
|
|
8
8
|
vi.mock('node:fs/promises');
|
|
9
|
+
const mockGenerateTextResult = (text) => ({
|
|
10
|
+
text,
|
|
11
|
+
result: text,
|
|
12
|
+
usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 },
|
|
13
|
+
finishReason: 'stop'
|
|
14
|
+
});
|
|
9
15
|
describe('workflow-planner service', () => {
|
|
10
16
|
beforeEach(() => {
|
|
11
17
|
vi.clearAllMocks();
|
|
12
18
|
});
|
|
13
19
|
describe('generatePlanName', () => {
|
|
14
20
|
it('should generate plan name with date prefix using LLM', async () => {
|
|
15
|
-
vi.mocked(generateText).mockResolvedValue('customer_order_processing');
|
|
21
|
+
vi.mocked(generateText).mockResolvedValue(mockGenerateTextResult('customer_order_processing'));
|
|
16
22
|
const testDate = new Date(2025, 9, 6);
|
|
17
23
|
const planName = await generatePlanName('A workflow that processes customer orders', testDate);
|
|
18
24
|
expect(planName).toMatch(/^2025_10_06_/);
|
|
@@ -23,7 +29,7 @@ describe('workflow-planner service', () => {
|
|
|
23
29
|
});
|
|
24
30
|
});
|
|
25
31
|
it('should clean and validate LLM response', async () => {
|
|
26
|
-
vi.mocked(generateText).mockResolvedValue(' User-Auth & Security!@# ');
|
|
32
|
+
vi.mocked(generateText).mockResolvedValue(mockGenerateTextResult(' User-Auth & Security!@# '));
|
|
27
33
|
const testDate = new Date(2025, 9, 6);
|
|
28
34
|
const planName = await generatePlanName('User authentication workflow', testDate);
|
|
29
35
|
expect(planName).toBe('2025_10_06_user_auth_security');
|
|
@@ -34,14 +40,14 @@ describe('workflow-planner service', () => {
|
|
|
34
40
|
await expect(generatePlanName('Test workflow')).rejects.toThrow('API rate limit exceeded');
|
|
35
41
|
});
|
|
36
42
|
it('should limit plan name length to 50 characters', async () => {
|
|
37
|
-
vi.mocked(generateText).mockResolvedValue('this_is_an_extremely_long_plan_name_that_exceeds_the_maximum_allowed_length_for_file_names');
|
|
43
|
+
vi.mocked(generateText).mockResolvedValue(mockGenerateTextResult('this_is_an_extremely_long_plan_name_that_exceeds_the_maximum_allowed_length_for_file_names'));
|
|
38
44
|
const testDate = new Date(2025, 9, 6);
|
|
39
45
|
const planName = await generatePlanName('Long workflow description', testDate);
|
|
40
46
|
const namePart = planName.replace(/^2025_10_06_/, '');
|
|
41
47
|
expect(namePart.length).toBeLessThanOrEqual(50);
|
|
42
48
|
});
|
|
43
49
|
it('should handle multiple underscores correctly', async () => {
|
|
44
|
-
vi.mocked(generateText).mockResolvedValue('user___auth___workflow');
|
|
50
|
+
vi.mocked(generateText).mockResolvedValue(mockGenerateTextResult('user___auth___workflow'));
|
|
45
51
|
const testDate = new Date(2025, 9, 6);
|
|
46
52
|
const planName = await generatePlanName('Test', testDate);
|
|
47
53
|
expect(planName).toBe('2025_10_06_user_auth_workflow');
|
|
@@ -7,10 +7,10 @@ export const answerQuestion = step( {
|
|
|
7
7
|
inputSchema: z.string(),
|
|
8
8
|
outputSchema: z.string(),
|
|
9
9
|
fn: async question => {
|
|
10
|
-
const
|
|
10
|
+
const { result } = await generateText( {
|
|
11
11
|
prompt: 'answer_question@v1',
|
|
12
12
|
variables: { question }
|
|
13
13
|
} );
|
|
14
|
-
return
|
|
14
|
+
return result;
|
|
15
15
|
}
|
|
16
16
|
} );
|
|
@@ -10,11 +10,11 @@ export const exampleLLMStep = step( {
|
|
|
10
10
|
} ),
|
|
11
11
|
outputSchema: z.string(),
|
|
12
12
|
fn: async ( { userInput } ) => {
|
|
13
|
-
const
|
|
13
|
+
const { result } = await generateText( {
|
|
14
14
|
prompt: 'prompt@v1',
|
|
15
15
|
variables: { userInput }
|
|
16
16
|
} );
|
|
17
|
-
return
|
|
17
|
+
return result;
|
|
18
18
|
}
|
|
19
19
|
} );
|
|
20
20
|
|
|
@@ -16,7 +16,12 @@ export const mockClaudeAgentSDK = {
|
|
|
16
16
|
* Mock for @output.ai/llm
|
|
17
17
|
*/
|
|
18
18
|
export const mockLLM = {
|
|
19
|
-
generateText: vi.fn().mockResolvedValue(
|
|
19
|
+
generateText: vi.fn().mockResolvedValue({
|
|
20
|
+
text: 'workflow_plan_name',
|
|
21
|
+
sources: [],
|
|
22
|
+
usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 },
|
|
23
|
+
finishReason: 'stop'
|
|
24
|
+
})
|
|
20
25
|
};
|
|
21
26
|
/**
|
|
22
27
|
* Mock for child_process spawn (for agents init)
|
|
@@ -61,7 +66,12 @@ export function resetAllMocks() {
|
|
|
61
66
|
mockFS.writeFile.mockResolvedValue(undefined);
|
|
62
67
|
mockFS.readFile.mockResolvedValue('template content');
|
|
63
68
|
mockFS.stat.mockRejectedValue({ code: 'ENOENT' });
|
|
64
|
-
mockLLM.generateText.mockResolvedValue(
|
|
69
|
+
mockLLM.generateText.mockResolvedValue({
|
|
70
|
+
text: 'workflow_plan_name',
|
|
71
|
+
sources: [],
|
|
72
|
+
usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 },
|
|
73
|
+
finishReason: 'stop'
|
|
74
|
+
});
|
|
65
75
|
mockInquirer.input.mockResolvedValue('Mock workflow description');
|
|
66
76
|
mockInquirer.confirm.mockResolvedValue(true);
|
|
67
77
|
}
|