@output.ai/cli 0.0.6 → 0.0.8
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/README.md +2 -3
- package/dist/api/generated/api.d.ts +0 -20
- package/dist/api/generated/api.js +1 -10
- package/dist/commands/workflow/generate.d.ts +1 -0
- package/dist/commands/workflow/generate.js +23 -4
- package/dist/commands/workflow/plan.js +4 -3
- package/dist/commands/workflow/plan.spec.js +5 -3
- package/dist/services/claude_client.d.ts +18 -1
- package/dist/services/claude_client.js +67 -21
- package/dist/services/coding_agents.d.ts +7 -0
- package/dist/services/coding_agents.js +64 -7
- package/dist/services/coding_agents.spec.js +155 -14
- package/dist/services/workflow_builder.d.ts +16 -0
- package/dist/services/workflow_builder.js +85 -0
- package/dist/services/workflow_builder.spec.d.ts +1 -0
- package/dist/services/workflow_builder.spec.js +165 -0
- package/dist/services/workflow_planner.d.ts +0 -5
- package/dist/services/workflow_planner.js +7 -44
- package/dist/services/workflow_planner.spec.js +8 -99
- package/dist/templates/agent_instructions/commands/build_workflow.md.template +247 -0
- package/dist/templates/agent_instructions/commands/plan_workflow.md.template +8 -7
- package/dist/templates/workflow/README.md.template +3 -5
- package/dist/templates/workflow/steps.ts.template +26 -54
- package/dist/templates/workflow/workflow.ts.template +23 -49
- package/dist/types/domain.d.ts +20 -0
- package/dist/types/domain.js +4 -0
- package/dist/utils/paths.d.ts +1 -1
- package/dist/utils/paths.js +1 -1
- package/package.json +18 -23
package/README.md
CHANGED
|
@@ -67,7 +67,7 @@ output workflow generate my-workflow --force
|
|
|
67
67
|
|
|
68
68
|
- `--description, -d` - Description of the workflow
|
|
69
69
|
- `--skeleton, -s` - Generate minimal skeleton without example steps
|
|
70
|
-
- `--output-dir, -o` - Output directory (default: `
|
|
70
|
+
- `--output-dir, -o` - Output directory (default: `src/`)
|
|
71
71
|
- `--force, -f` - Overwrite existing directory
|
|
72
72
|
|
|
73
73
|
#### Generated Structure
|
|
@@ -76,9 +76,8 @@ The CLI creates a complete workflow structure:
|
|
|
76
76
|
|
|
77
77
|
```
|
|
78
78
|
my-workflow/
|
|
79
|
-
├──
|
|
79
|
+
├── workflow.ts # Main workflow definition
|
|
80
80
|
├── steps.ts # Activity/step implementations
|
|
81
|
-
├── types.ts # TypeScript interfaces
|
|
82
81
|
├── prompt@v1.prompt # LLM prompt template (if not skeleton)
|
|
83
82
|
└── README.md # Workflow documentation
|
|
84
83
|
```
|
|
@@ -198,26 +198,6 @@ export type getWorkflowIdOutputResponseError = (getWorkflowIdOutputResponse404)
|
|
|
198
198
|
export type getWorkflowIdOutputResponse = (getWorkflowIdOutputResponseSuccess | getWorkflowIdOutputResponseError);
|
|
199
199
|
export declare const getGetWorkflowIdOutputUrl: (id: string) => string;
|
|
200
200
|
export declare const getWorkflowIdOutput: (id: string, options?: RequestInit) => Promise<getWorkflowIdOutputResponse>;
|
|
201
|
-
/**
|
|
202
|
-
* @summary Return the trace of a workflow execution
|
|
203
|
-
*/
|
|
204
|
-
export type getWorkflowIdTraceResponse200 = {
|
|
205
|
-
data: string[];
|
|
206
|
-
status: 200;
|
|
207
|
-
};
|
|
208
|
-
export type getWorkflowIdTraceResponse404 = {
|
|
209
|
-
data: void;
|
|
210
|
-
status: 404;
|
|
211
|
-
};
|
|
212
|
-
export type getWorkflowIdTraceResponseSuccess = (getWorkflowIdTraceResponse200) & {
|
|
213
|
-
headers: Headers;
|
|
214
|
-
};
|
|
215
|
-
export type getWorkflowIdTraceResponseError = (getWorkflowIdTraceResponse404) & {
|
|
216
|
-
headers: Headers;
|
|
217
|
-
};
|
|
218
|
-
export type getWorkflowIdTraceResponse = (getWorkflowIdTraceResponseSuccess | getWorkflowIdTraceResponseError);
|
|
219
|
-
export declare const getGetWorkflowIdTraceUrl: (id: string) => string;
|
|
220
|
-
export declare const getWorkflowIdTrace: (id: string, options?: RequestInit) => Promise<getWorkflowIdTraceResponse>;
|
|
221
201
|
/**
|
|
222
202
|
* @summary Get a specific workflow catalog by ID
|
|
223
203
|
*/
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Generated by orval v7.13.
|
|
2
|
+
* Generated by orval v7.13.2 🍺
|
|
3
3
|
* Do not edit manually.
|
|
4
4
|
* Output.ai SDK API
|
|
5
5
|
* API for managing and executing Temporal workflows through Output SDK
|
|
@@ -78,15 +78,6 @@ export const getWorkflowIdOutput = async (id, options) => {
|
|
|
78
78
|
method: 'GET'
|
|
79
79
|
});
|
|
80
80
|
};
|
|
81
|
-
export const getGetWorkflowIdTraceUrl = (id) => {
|
|
82
|
-
return `/workflow/${id}/trace`;
|
|
83
|
-
};
|
|
84
|
-
export const getWorkflowIdTrace = async (id, options) => {
|
|
85
|
-
return customFetchInstance(getGetWorkflowIdTraceUrl(id), {
|
|
86
|
-
...options,
|
|
87
|
-
method: 'GET'
|
|
88
|
-
});
|
|
89
|
-
};
|
|
90
81
|
;
|
|
91
82
|
export const getGetWorkflowCatalogIdUrl = (id) => {
|
|
92
83
|
return `/workflow/catalog/${id}`;
|
|
@@ -7,6 +7,7 @@ export default class Generate extends Command {
|
|
|
7
7
|
description: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
8
8
|
'output-dir': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
9
9
|
force: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
'plan-file': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
10
11
|
};
|
|
11
12
|
static args: {
|
|
12
13
|
name: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
import { Args, Command, Flags } from '@oclif/core';
|
|
1
|
+
import { Args, Command, Flags, ux } from '@oclif/core';
|
|
2
2
|
import { generateWorkflow } from '#services/workflow_generator.js';
|
|
3
|
+
import { buildWorkflow, buildWorkflowInteractiveLoop } from '#services/workflow_builder.js';
|
|
4
|
+
import { ensureOutputAIStructure } from '#services/coding_agents.js';
|
|
3
5
|
import { DEFAULT_OUTPUT_DIRS } from '#utils/paths.js';
|
|
6
|
+
import path from 'node:path';
|
|
4
7
|
export default class Generate extends Command {
|
|
5
8
|
static description = 'Generate a new Output SDK workflow';
|
|
6
9
|
static examples = [
|
|
7
10
|
'<%= config.bin %> <%= command.id %> my-workflow',
|
|
8
11
|
'<%= config.bin %> <%= command.id %> my-workflow --skeleton',
|
|
9
|
-
'<%= config.bin %> <%= command.id %> data-processing --description "Process and transform data"'
|
|
12
|
+
'<%= config.bin %> <%= command.id %> data-processing --description "Process and transform data"',
|
|
13
|
+
'<%= config.bin %> <%= command.id %> my-workflow --plan-file .outputai/plans/2025_10_09_my_workflow/PLAN.md'
|
|
10
14
|
];
|
|
11
15
|
static flags = {
|
|
12
16
|
skeleton: Flags.boolean({
|
|
@@ -28,6 +32,11 @@ export default class Generate extends Command {
|
|
|
28
32
|
char: 'f',
|
|
29
33
|
description: 'Overwrite existing directory',
|
|
30
34
|
default: false
|
|
35
|
+
}),
|
|
36
|
+
'plan-file': Flags.string({
|
|
37
|
+
char: 'p',
|
|
38
|
+
description: 'Path to plan file for AI-assisted workflow implementation',
|
|
39
|
+
required: false
|
|
31
40
|
})
|
|
32
41
|
};
|
|
33
42
|
static args = {
|
|
@@ -38,8 +47,9 @@ export default class Generate extends Command {
|
|
|
38
47
|
};
|
|
39
48
|
async run() {
|
|
40
49
|
const { args, flags } = await this.parse(Generate);
|
|
41
|
-
|
|
42
|
-
|
|
50
|
+
const planFile = flags['plan-file'];
|
|
51
|
+
if (!flags.skeleton && !planFile) {
|
|
52
|
+
this.error('Full workflow generation not implemented yet. Please use --skeleton flag or --plan-file');
|
|
43
53
|
}
|
|
44
54
|
try {
|
|
45
55
|
const result = await generateWorkflow({
|
|
@@ -49,6 +59,15 @@ export default class Generate extends Command {
|
|
|
49
59
|
skeleton: flags.skeleton,
|
|
50
60
|
force: flags.force
|
|
51
61
|
});
|
|
62
|
+
if (planFile) {
|
|
63
|
+
this.log('\nStarting AI-assisted workflow implementation...\n');
|
|
64
|
+
const projectRoot = process.cwd();
|
|
65
|
+
await ensureOutputAIStructure(projectRoot);
|
|
66
|
+
const absolutePlanPath = path.resolve(projectRoot, planFile);
|
|
67
|
+
const buildOutput = await buildWorkflow(absolutePlanPath, result.targetDir, args.name);
|
|
68
|
+
await buildWorkflowInteractiveLoop(buildOutput);
|
|
69
|
+
this.log(ux.colorize('green', '\nWorkflow implementation complete!\n'));
|
|
70
|
+
}
|
|
52
71
|
this.displaySuccess(result);
|
|
53
72
|
}
|
|
54
73
|
catch (error) {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Command, Flags, ux } from '@oclif/core';
|
|
2
2
|
import { input } from '@inquirer/prompts';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { generatePlanName, updateAgentTemplates, writePlanFile } from '#services/workflow_planner.js';
|
|
4
|
+
import { ensureOutputAIStructure } from '#services/coding_agents.js';
|
|
5
|
+
import { invokePlanWorkflow, PLAN_COMMAND_OPTIONS, replyToClaude } from '#services/claude_client.js';
|
|
5
6
|
export default class WorkflowPlan extends Command {
|
|
6
7
|
static description = 'Generate a workflow plan from a description';
|
|
7
8
|
static examples = [
|
|
@@ -51,7 +52,7 @@ export default class WorkflowPlan extends Command {
|
|
|
51
52
|
if (modifications === acceptKey) {
|
|
52
53
|
return originalPlanContent;
|
|
53
54
|
}
|
|
54
|
-
const modifiedPlanContent = await replyToClaude(modifications);
|
|
55
|
+
const modifiedPlanContent = await replyToClaude(modifications, PLAN_COMMAND_OPTIONS);
|
|
55
56
|
return this.planModificationLoop(modifiedPlanContent);
|
|
56
57
|
}
|
|
57
58
|
async planGenerationLoop(promptDescription, planName, projectRoot) {
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
|
|
2
2
|
import WorkflowPlan from './plan.js';
|
|
3
|
-
import {
|
|
3
|
+
import { generatePlanName, writePlanFile, updateAgentTemplates } from '#services/workflow_planner.js';
|
|
4
|
+
import { ensureOutputAIStructure } from '#services/coding_agents.js';
|
|
4
5
|
import { invokePlanWorkflow, replyToClaude, ClaudeInvocationError } from '#services/claude_client.js';
|
|
5
6
|
import { input } from '@inquirer/prompts';
|
|
6
|
-
vi.mock('
|
|
7
|
-
vi.mock('
|
|
7
|
+
vi.mock('#services/workflow_planner.js');
|
|
8
|
+
vi.mock('#services/coding_agents.js');
|
|
9
|
+
vi.mock('#services/claude_client.js');
|
|
8
10
|
vi.mock('@inquirer/prompts');
|
|
9
11
|
describe('WorkflowPlan Command', () => {
|
|
10
12
|
const createCommand = () => {
|
|
@@ -1,8 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Agent SDK client for workflow planning
|
|
3
|
+
*/
|
|
4
|
+
import { Options } from '@anthropic-ai/claude-agent-sdk';
|
|
5
|
+
export declare const PLAN_COMMAND_OPTIONS: Options;
|
|
6
|
+
export declare const BUILD_COMMAND_OPTIONS: Options;
|
|
1
7
|
export declare class ClaudeInvocationError extends Error {
|
|
2
8
|
cause?: Error | undefined;
|
|
3
9
|
constructor(message: string, cause?: Error | undefined);
|
|
4
10
|
}
|
|
5
|
-
export declare function replyToClaude(message: string): Promise<string>;
|
|
11
|
+
export declare function replyToClaude(message: string, options?: Options): Promise<string>;
|
|
6
12
|
/**
|
|
7
13
|
* Invoke claude-code with /plan_workflow slash command
|
|
8
14
|
* The SDK loads custom commands from .claude/commands/ when settingSources includes 'project'.
|
|
@@ -11,3 +17,14 @@ export declare function replyToClaude(message: string): Promise<string>;
|
|
|
11
17
|
* @returns Plan output from claude-code
|
|
12
18
|
*/
|
|
13
19
|
export declare function invokePlanWorkflow(description: string): Promise<string>;
|
|
20
|
+
/**
|
|
21
|
+
* Invoke claude-code with /build_workflow slash command
|
|
22
|
+
* The SDK loads custom commands from .claude/commands/ when settingSources includes 'project'.
|
|
23
|
+
* ensureOutputAIStructure() scaffolds the command files to that location.
|
|
24
|
+
* @param planFilePath - Absolute path to the plan file
|
|
25
|
+
* @param workflowDir - Absolute path to the workflow directory
|
|
26
|
+
* @param workflowName - Name of the workflow
|
|
27
|
+
* @param additionalInstructions - Optional additional instructions
|
|
28
|
+
* @returns Implementation output from claude-code
|
|
29
|
+
*/
|
|
30
|
+
export declare function invokeBuildWorkflow(planFilePath: string, workflowDir: string, workflowName: string, additionalInstructions?: string): Promise<string>;
|
|
@@ -22,16 +22,26 @@ date: <plan-date>
|
|
|
22
22
|
|
|
23
23
|
4. After you mark all todos as complete, you must respond with the final version of the plan.
|
|
24
24
|
`;
|
|
25
|
+
const ADDITIONAL_INSTRUCTIONS_BUILD = `
|
|
26
|
+
! IMPORTANT !
|
|
27
|
+
1. Use TodoWrite to track your progress through workflow implementation.
|
|
28
|
+
|
|
29
|
+
2. Follow the implementation plan exactly as specified in the plan file.
|
|
30
|
+
|
|
31
|
+
3. Implement all workflow files following Output SDK patterns and best practices.
|
|
32
|
+
|
|
33
|
+
4. After you mark all todos as complete, provide a summary of what was implemented.
|
|
34
|
+
`;
|
|
25
35
|
const PLAN_COMMAND = 'plan_workflow';
|
|
36
|
+
const BUILD_COMMAND = 'build_workflow';
|
|
26
37
|
const GLOBAL_CLAUDE_OPTIONS = {
|
|
27
|
-
settingSources: ['user', 'project', 'local']
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
]
|
|
38
|
+
settingSources: ['user', 'project', 'local']
|
|
39
|
+
};
|
|
40
|
+
export const PLAN_COMMAND_OPTIONS = {
|
|
41
|
+
allowedTools: ['Read', 'Grep', 'WebSearch', 'WebFetch', 'TodoWrite']
|
|
42
|
+
};
|
|
43
|
+
export const BUILD_COMMAND_OPTIONS = {
|
|
44
|
+
permissionMode: 'bypassPermissions'
|
|
35
45
|
};
|
|
36
46
|
export class ClaudeInvocationError extends Error {
|
|
37
47
|
cause;
|
|
@@ -47,16 +57,23 @@ function validateEnvironment() {
|
|
|
47
57
|
}
|
|
48
58
|
}
|
|
49
59
|
function validateSystem(systemMessage) {
|
|
50
|
-
const requiredCommands = [PLAN_COMMAND];
|
|
60
|
+
const requiredCommands = [PLAN_COMMAND, BUILD_COMMAND];
|
|
51
61
|
const availableCommands = systemMessage.slash_commands;
|
|
52
62
|
const missingCommands = requiredCommands.filter(command => !availableCommands.includes(command));
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
63
|
+
return {
|
|
64
|
+
missingCommands,
|
|
65
|
+
hasIssues: missingCommands.length > 0
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function displaySystemValidationWarnings(validation) {
|
|
69
|
+
if (!validation.hasIssues) {
|
|
70
|
+
return;
|
|
59
71
|
}
|
|
72
|
+
validation.missingCommands.forEach(command => {
|
|
73
|
+
ux.warn(`Missing required claude-code slash command: /${command}`);
|
|
74
|
+
});
|
|
75
|
+
ux.warn('Your claude-code agent is missing key configurations, it may not behave as expected.');
|
|
76
|
+
ux.warn('Please run "output-cli agents init" to fix this.');
|
|
60
77
|
}
|
|
61
78
|
function applyDefaultOptions(options) {
|
|
62
79
|
return {
|
|
@@ -71,8 +88,11 @@ function getTodoWriteMessage(message) {
|
|
|
71
88
|
const todoWriteMessage = message.message.content.find((c) => c?.type === 'tool_use' && c.name === 'TodoWrite');
|
|
72
89
|
return todoWriteMessage ?? null;
|
|
73
90
|
}
|
|
74
|
-
function applyInstructions(initialMessage) {
|
|
75
|
-
|
|
91
|
+
function applyInstructions(initialMessage, instructionsType = 'plan') {
|
|
92
|
+
const instructions = instructionsType === 'build' ?
|
|
93
|
+
ADDITIONAL_INSTRUCTIONS_BUILD :
|
|
94
|
+
ADDITIONAL_INSTRUCTIONS;
|
|
95
|
+
return `${initialMessage}\n\n${instructions}`;
|
|
76
96
|
}
|
|
77
97
|
function createProgressBar() {
|
|
78
98
|
return new cliProgress.SingleBar({
|
|
@@ -109,6 +129,13 @@ function getProgressUpdate(message) {
|
|
|
109
129
|
total: allTodos.length
|
|
110
130
|
};
|
|
111
131
|
}
|
|
132
|
+
function debugMessage(message) {
|
|
133
|
+
if (process.env.DEBUG !== 'true') {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
ux.stdout(ux.colorize('teal', `[Message]: ${message.type}`));
|
|
137
|
+
ux.stdout(ux.colorize('teal', `[JSON]: ${JSON.stringify(message, null, 2)}`));
|
|
138
|
+
}
|
|
112
139
|
async function singleQuery(prompt, options = {}) {
|
|
113
140
|
validateEnvironment();
|
|
114
141
|
const progressBar = createProgressBar();
|
|
@@ -118,8 +145,10 @@ async function singleQuery(prompt, options = {}) {
|
|
|
118
145
|
prompt,
|
|
119
146
|
options: applyDefaultOptions(options)
|
|
120
147
|
})) {
|
|
148
|
+
debugMessage(message);
|
|
121
149
|
if (message.type === 'system' && message.subtype === 'init') {
|
|
122
|
-
validateSystem(message);
|
|
150
|
+
const validation = validateSystem(message);
|
|
151
|
+
displaySystemValidationWarnings(validation);
|
|
123
152
|
progressBar.update(1, { message: 'Diving in...' });
|
|
124
153
|
}
|
|
125
154
|
const progressUpdate = getProgressUpdate(message);
|
|
@@ -140,8 +169,8 @@ async function singleQuery(prompt, options = {}) {
|
|
|
140
169
|
throw new ClaudeInvocationError(`Failed to invoke claude-code: ${error.message}`, error);
|
|
141
170
|
}
|
|
142
171
|
}
|
|
143
|
-
export async function replyToClaude(message) {
|
|
144
|
-
return singleQuery(applyInstructions(message), { continue: true });
|
|
172
|
+
export async function replyToClaude(message, options = {}) {
|
|
173
|
+
return singleQuery(applyInstructions(message), { continue: true, ...options });
|
|
145
174
|
}
|
|
146
175
|
/**
|
|
147
176
|
* Invoke claude-code with /plan_workflow slash command
|
|
@@ -151,5 +180,22 @@ export async function replyToClaude(message) {
|
|
|
151
180
|
* @returns Plan output from claude-code
|
|
152
181
|
*/
|
|
153
182
|
export async function invokePlanWorkflow(description) {
|
|
154
|
-
return singleQuery(applyInstructions(`/${PLAN_COMMAND} ${description}`));
|
|
183
|
+
return singleQuery(applyInstructions(`/${PLAN_COMMAND} ${description}`), PLAN_COMMAND_OPTIONS);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Invoke claude-code with /build_workflow slash command
|
|
187
|
+
* The SDK loads custom commands from .claude/commands/ when settingSources includes 'project'.
|
|
188
|
+
* ensureOutputAIStructure() scaffolds the command files to that location.
|
|
189
|
+
* @param planFilePath - Absolute path to the plan file
|
|
190
|
+
* @param workflowDir - Absolute path to the workflow directory
|
|
191
|
+
* @param workflowName - Name of the workflow
|
|
192
|
+
* @param additionalInstructions - Optional additional instructions
|
|
193
|
+
* @returns Implementation output from claude-code
|
|
194
|
+
*/
|
|
195
|
+
export async function invokeBuildWorkflow(planFilePath, workflowDir, workflowName, additionalInstructions) {
|
|
196
|
+
const commandArgs = `${planFilePath} ${workflowName} ${workflowDir}`;
|
|
197
|
+
const fullCommand = additionalInstructions ?
|
|
198
|
+
`/${BUILD_COMMAND} ${commandArgs} ${additionalInstructions}` :
|
|
199
|
+
`/${BUILD_COMMAND} ${commandArgs}`;
|
|
200
|
+
return singleQuery(applyInstructions(fullCommand, 'build'), BUILD_COMMAND_OPTIONS);
|
|
155
201
|
}
|
|
@@ -41,3 +41,10 @@ export declare function prepareTemplateVariables(): Record<string, string>;
|
|
|
41
41
|
* Main entry point for agent initialization logic
|
|
42
42
|
*/
|
|
43
43
|
export declare function initializeAgentConfig(options: InitOptions): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* Ensure .outputai directory structure exists by invoking agents init if needed
|
|
46
|
+
* Displays warnings for missing files and prompts for reinitialization
|
|
47
|
+
* @param projectRoot - Root directory of the project
|
|
48
|
+
* @throws Error if user declines to initialize or if initialization fails
|
|
49
|
+
*/
|
|
50
|
+
export declare function ensureOutputAIStructure(projectRoot: string): Promise<void>;
|
|
@@ -7,6 +7,7 @@ import { access } from 'node:fs/promises';
|
|
|
7
7
|
import path from 'node:path';
|
|
8
8
|
import { join } from 'node:path';
|
|
9
9
|
import { ux } from '@oclif/core';
|
|
10
|
+
import { confirm } from '@inquirer/prompts';
|
|
10
11
|
import { AGENT_CONFIG_DIR } from '#config.js';
|
|
11
12
|
import { getTemplateDir } from '#utils/paths.js';
|
|
12
13
|
import { processTemplate } from '#utils/template.js';
|
|
@@ -33,6 +34,11 @@ export const AGENT_CONFIGS = {
|
|
|
33
34
|
from: 'commands/plan_workflow.md.template',
|
|
34
35
|
to: `${AGENT_CONFIG_DIR}/commands/plan_workflow.md`
|
|
35
36
|
},
|
|
37
|
+
{
|
|
38
|
+
type: 'template',
|
|
39
|
+
from: 'commands/build_workflow.md.template',
|
|
40
|
+
to: `${AGENT_CONFIG_DIR}/commands/build_workflow.md`
|
|
41
|
+
},
|
|
36
42
|
{
|
|
37
43
|
type: 'template',
|
|
38
44
|
from: 'meta/pre_flight.md.template',
|
|
@@ -63,6 +69,11 @@ export const AGENT_CONFIGS = {
|
|
|
63
69
|
type: 'symlink',
|
|
64
70
|
from: `${AGENT_CONFIG_DIR}/commands/plan_workflow.md`,
|
|
65
71
|
to: '.claude/commands/plan_workflow.md'
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
type: 'symlink',
|
|
75
|
+
from: `${AGENT_CONFIG_DIR}/commands/build_workflow.md`,
|
|
76
|
+
to: '.claude/commands/build_workflow.md'
|
|
66
77
|
}
|
|
67
78
|
]
|
|
68
79
|
}
|
|
@@ -100,6 +111,15 @@ async function fileExists(filePath) {
|
|
|
100
111
|
return false;
|
|
101
112
|
}
|
|
102
113
|
}
|
|
114
|
+
async function findMissingFiles(files, projectRoot) {
|
|
115
|
+
const checks = await Promise.all(files.map(async (file) => ({
|
|
116
|
+
file,
|
|
117
|
+
missing: !await fileExists(join(projectRoot, file))
|
|
118
|
+
})));
|
|
119
|
+
return checks
|
|
120
|
+
.filter(check => check.missing)
|
|
121
|
+
.map(check => check.file);
|
|
122
|
+
}
|
|
103
123
|
export async function checkAgentStructure(projectRoot) {
|
|
104
124
|
const requiredFiles = getRequiredFiles();
|
|
105
125
|
const dirExists = await checkAgentConfigDirExists(projectRoot);
|
|
@@ -110,13 +130,7 @@ export async function checkAgentStructure(projectRoot) {
|
|
|
110
130
|
isComplete: false
|
|
111
131
|
};
|
|
112
132
|
}
|
|
113
|
-
const
|
|
114
|
-
file,
|
|
115
|
-
exists: await fileExists(join(projectRoot, file))
|
|
116
|
-
})));
|
|
117
|
-
const missingFiles = missingChecks
|
|
118
|
-
.filter(check => !check.exists)
|
|
119
|
-
.map(check => check.file);
|
|
133
|
+
const missingFiles = await findMissingFiles(requiredFiles, projectRoot);
|
|
120
134
|
return {
|
|
121
135
|
dirExists: true,
|
|
122
136
|
missingFiles,
|
|
@@ -228,3 +242,46 @@ export async function initializeAgentConfig(options) {
|
|
|
228
242
|
await processMappings(AGENT_CONFIGS.outputai, variables, force, projectRoot);
|
|
229
243
|
await processMappings(AGENT_CONFIGS[agentProvider], variables, force, projectRoot);
|
|
230
244
|
}
|
|
245
|
+
function formatMissingFilesList(files) {
|
|
246
|
+
return files.map(f => ` • ${f}`).join('\n');
|
|
247
|
+
}
|
|
248
|
+
async function promptForReinitialize() {
|
|
249
|
+
return confirm({
|
|
250
|
+
message: 'Would you like to run "agents init --force" to recreate missing files?',
|
|
251
|
+
default: true
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
async function initializeAgentStructure(projectRoot, force) {
|
|
255
|
+
await initializeAgentConfig({
|
|
256
|
+
projectRoot,
|
|
257
|
+
force,
|
|
258
|
+
agentProvider: 'claude-code'
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
function createIncompleteConfigError(missingFiles) {
|
|
262
|
+
return new Error(`Agent configuration incomplete. Missing files:\n${missingFiles.join('\n')}\n\n` +
|
|
263
|
+
'Run "output-cli agents init --force" to recreate them.');
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Ensure .outputai directory structure exists by invoking agents init if needed
|
|
267
|
+
* Displays warnings for missing files and prompts for reinitialization
|
|
268
|
+
* @param projectRoot - Root directory of the project
|
|
269
|
+
* @throws Error if user declines to initialize or if initialization fails
|
|
270
|
+
*/
|
|
271
|
+
export async function ensureOutputAIStructure(projectRoot) {
|
|
272
|
+
const structureCheck = await checkAgentStructure(projectRoot);
|
|
273
|
+
if (structureCheck.isComplete) {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
if (!structureCheck.dirExists) {
|
|
277
|
+
await initializeAgentStructure(projectRoot, false);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
const missingList = formatMissingFilesList(structureCheck.missingFiles);
|
|
281
|
+
ux.warn(`\n⚠️ Agent configuration is incomplete. Missing files:\n${missingList}\n`);
|
|
282
|
+
const shouldReinit = await promptForReinitialize();
|
|
283
|
+
if (!shouldReinit) {
|
|
284
|
+
throw createIncompleteConfigError(structureCheck.missingFiles);
|
|
285
|
+
}
|
|
286
|
+
await initializeAgentStructure(projectRoot, true);
|
|
287
|
+
}
|