@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 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: `output-workflows/src`)
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
- ├── index.ts # Main workflow definition
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.0 🍺
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
- if (!flags.skeleton) {
42
- this.error('Full workflow generation not implemented yet. Please use --skeleton flag');
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 { ensureOutputAIStructure, generatePlanName, updateAgentTemplates, writePlanFile } from '#services/workflow_planner.js';
4
- import { invokePlanWorkflow, replyToClaude } from '#services/claude_client.js';
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 { ensureOutputAIStructure, generatePlanName, writePlanFile, updateAgentTemplates } from '#services/workflow_planner.js';
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('../../services/workflow_planner.js');
7
- vi.mock('../../services/claude_client.js');
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
- allowedTools: [
29
- 'Read',
30
- 'Grep',
31
- 'WebSearch',
32
- 'WebFetch',
33
- 'TodoWrite'
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
- for (const command of missingCommands) {
54
- ux.warn(`Missing required claude-code slash command: /${command}`);
55
- }
56
- if (missingCommands.length > 0) {
57
- ux.warn('Your claude-code agent is missing key configurations, it may not behave as expected.');
58
- ux.warn('Please run "output-cli agents init" to fix this.');
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
- return `${initialMessage}\n\n${ADDITIONAL_INSTRUCTIONS}`;
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 missingChecks = await Promise.all(requiredFiles.map(async (file) => ({
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
+ }