@outputai/cli 0.1.11-next.49171f5.0 → 0.1.11-next.7b8340c.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 CHANGED
@@ -2,8 +2,10 @@
2
2
 
3
3
  import { execute } from '@oclif/core';
4
4
  import { loadEnvironment } from '../dist/utils/env_loader.js';
5
+ import { resolveCredentialRefs } from '@outputai/credentials';
5
6
 
6
7
  // Load environment variables from .env files before executing CLI
7
8
  loadEnvironment();
9
+ resolveCredentialRefs();
8
10
 
9
11
  await execute( { dir: import.meta.url } );
@@ -81,7 +81,7 @@ services:
81
81
  condition: service_healthy
82
82
  worker:
83
83
  condition: service_healthy
84
- image: outputai/api:${OUTPUT_API_VERSION:-0.1.11-next.49171f5.0}
84
+ image: outputai/api:${OUTPUT_API_VERSION:-0.1.11-next.7b8340c.0}
85
85
  init: true
86
86
  networks:
87
87
  - main
@@ -1,6 +1,7 @@
1
1
  import { Args, Command, Flags } from '@oclif/core';
2
2
  import { UserCancelledError } from '#types/errors.js';
3
3
  import { runInit } from '#services/project_scaffold.js';
4
+ import { getErrorMessage } from '#utils/error_utils.js';
4
5
  export default class Init extends Command {
5
6
  static description = 'Initialize a new Output project by scaffolding the complete project structure';
6
7
  static examples = [
@@ -30,8 +31,7 @@ export default class Init extends Command {
30
31
  return;
31
32
  }
32
33
  // runInit handles cleanup internally and throws Error with message
33
- const errorMessage = error instanceof Error ? error.message : String(error);
34
- this.error(errorMessage);
34
+ this.error(getErrorMessage(error));
35
35
  }
36
36
  }
37
37
  }
@@ -5,6 +5,8 @@ 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
7
  import path from 'node:path';
8
+ import * as fsSync from 'node:fs';
9
+ import { getErrorMessage } from '#utils/error_utils.js';
8
10
  export default class Generate extends Command {
9
11
  static description = 'Generate a new Output workflow from a skeleton or plan file';
10
12
  static examples = [
@@ -52,31 +54,33 @@ export default class Generate extends Command {
52
54
  if (!flags.skeleton && !planFile) {
53
55
  this.error('Full workflow generation not implemented yet. Please use --skeleton flag or --plan-file');
54
56
  }
55
- try {
56
- const result = await generateWorkflow({
57
- name: args.name,
58
- description: flags.description,
59
- outputDir: flags['output-dir'],
60
- skeleton: flags.skeleton,
61
- force: flags.force
62
- });
63
- if (planFile) {
64
- this.log('\nStarting AI-assisted workflow implementation...\n');
65
- const projectRoot = process.cwd();
66
- await ensureOutputAISystem(projectRoot);
67
- const absolutePlanPath = path.resolve(projectRoot, planFile);
68
- const buildOutput = await buildWorkflow(absolutePlanPath, result.targetDir, args.name);
69
- await buildWorkflowInteractiveLoop(buildOutput);
70
- this.log(ux.colorize('green', '\nWorkflow implementation complete!\n'));
71
- }
72
- this.displaySuccess(result);
57
+ const projectRoot = process.cwd();
58
+ const absolutePlanPath = planFile ? path.resolve(projectRoot, planFile) : '';
59
+ if (planFile && !fsSync.existsSync(absolutePlanPath)) {
60
+ this.error(`Plan file not found: ${absolutePlanPath}`);
73
61
  }
74
- catch (error) {
75
- if (error instanceof Error) {
76
- this.error(error.message);
77
- }
78
- throw error;
62
+ const result = await generateWorkflow({
63
+ name: args.name,
64
+ description: flags.description,
65
+ outputDir: flags['output-dir'],
66
+ skeleton: flags.skeleton,
67
+ force: flags.force
68
+ }).catch((error) => {
69
+ this.error(getErrorMessage(error));
70
+ });
71
+ if (planFile) {
72
+ this.log('\nStarting AI-assisted workflow implementation...\n');
73
+ await ensureOutputAISystem(projectRoot);
74
+ const buildOutput = await buildWorkflow(absolutePlanPath, result.targetDir, args.name)
75
+ .catch((error) => {
76
+ fsSync.rmSync(result.targetDir, { recursive: true, force: true });
77
+ const message = getErrorMessage(error);
78
+ this.error(`Workflow implementation failed, created files have been rolled back: ${message}`);
79
+ });
80
+ await buildWorkflowInteractiveLoop(buildOutput);
81
+ this.log(ux.colorize('green', '\nWorkflow implementation complete!\n'));
79
82
  }
83
+ this.displaySuccess(result);
80
84
  }
81
85
  displaySuccess(result) {
82
86
  const message = getWorkflowGenerateSuccessMessage(result.workflowName, result.targetDir, result.filesCreated);
@@ -52,7 +52,7 @@ export default class WorkflowPlan extends Command {
52
52
  if (modifications === acceptKey) {
53
53
  return originalPlanContent;
54
54
  }
55
- const modifiedPlanContent = await replyToClaude(modifications, PLAN_COMMAND_OPTIONS);
55
+ const modifiedPlanContent = await replyToClaude(modifications, { anthropicOpts: PLAN_COMMAND_OPTIONS });
56
56
  return this.planModificationLoop(modifiedPlanContent);
57
57
  }
58
58
  async planGenerationLoop(promptDescription, planName, projectRoot) {
@@ -62,5 +62,7 @@ export default class WorkflowPlan extends Command {
62
62
  const modifiedPlanContent = await this.planModificationLoop(planContent);
63
63
  const modifiedSavedPath = await writePlanFile(planName, modifiedPlanContent, projectRoot);
64
64
  this.log(`✅ Plan saved to: ${modifiedSavedPath}\n`);
65
+ const generateCmd = ux.colorize('cyan', `npx output workflow generate <WORKFLOW_NAME> --plan-file=${modifiedSavedPath}`);
66
+ this.log(`⏭️ To execute this plan run: ${generateCmd}\n`);
65
67
  }
66
68
  }
@@ -1,3 +1,3 @@
1
1
  {
2
- "framework": "0.1.11-next.49171f5.0"
2
+ "framework": "0.1.11-next.7b8340c.0"
3
3
  }
@@ -2,13 +2,21 @@
2
2
  * Claude Agent SDK client for workflow planning
3
3
  */
4
4
  import { Options } from '@anthropic-ai/claude-agent-sdk';
5
+ export declare const ADDITIONAL_INSTRUCTIONS: {
6
+ readonly PLAN: "\n! IMPORTANT !\n1. Use TodoWrite to track your progress through plan creation.\n\n2. Please respond with only the final version of the plan content.\n\n3. Respond in a markdown format with these metadata headers:\n\n---\ntitle: <plan-title>\ndescription: <plan-description>\ndate: <plan-date>\n---\n\n<plan-content>\n\n4. After you mark all todos as complete, you must respond with the final version of the plan.\n\n5. DO NOT write the plan to disk — the CLI will handle saving the file to the plans directory.\n\n6. DO NOT suggest any next steps, follow-up commands, or instructions for the user — the CLI will inform the user of next steps after saving.\n";
7
+ readonly BUILD: "\n! IMPORTANT !\n1. Use TodoWrite to track your progress through workflow implementation.\n\n2. Follow the implementation plan exactly as specified in the plan file.\n\n3. Implement all workflow files following Output.ai patterns and best practices.\n\n4. After you mark all todos as complete, provide a summary of what was implemented.\n";
8
+ };
5
9
  export declare const PLAN_COMMAND_OPTIONS: Options;
10
+ interface ReplyToClaudeOptions {
11
+ anthropicOpts?: Options;
12
+ applyAdditionalInstructions?: string;
13
+ }
6
14
  export declare const BUILD_COMMAND_OPTIONS: Options;
7
15
  export declare class ClaudeInvocationError extends Error {
8
16
  cause?: Error | undefined;
9
17
  constructor(message: string, cause?: Error | undefined);
10
18
  }
11
- export declare function replyToClaude(message: string, options?: Options): Promise<string>;
19
+ export declare function replyToClaude(message: string, { anthropicOpts, applyAdditionalInstructions }?: ReplyToClaudeOptions): Promise<string>;
12
20
  /**
13
21
  * Invoke claude-code with /outputai:plan_workflow slash command
14
22
  * The SDK loads custom commands from .claude/commands/ when settingSources includes 'project'.
@@ -28,3 +36,4 @@ export declare function invokePlanWorkflow(description: string): Promise<string>
28
36
  * @returns Implementation output from claude-code
29
37
  */
30
38
  export declare function invokeBuildWorkflow(planFilePath: string, workflowDir: string, workflowName: string, additionalInstructions?: string): Promise<string>;
39
+ export {};
@@ -6,13 +6,14 @@ import { ux } from '@oclif/core';
6
6
  import * as cliProgress from 'cli-progress';
7
7
  import { getErrorMessage, toError } from '#utils/error_utils.js';
8
8
  import { config } from '#config.js';
9
- const ADDITIONAL_INSTRUCTIONS = `
9
+ export const ADDITIONAL_INSTRUCTIONS = {
10
+ PLAN: `
10
11
  ! IMPORTANT !
11
12
  1. Use TodoWrite to track your progress through plan creation.
12
13
 
13
- 2. Please response with only the final version of the plan.
14
+ 2. Please respond with only the final version of the plan content.
14
15
 
15
- 3. Response in a markdown format with these metadata headers:
16
+ 3. Respond in a markdown format with these metadata headers:
16
17
 
17
18
  ---
18
19
  title: <plan-title>
@@ -23,8 +24,12 @@ date: <plan-date>
23
24
  <plan-content>
24
25
 
25
26
  4. After you mark all todos as complete, you must respond with the final version of the plan.
26
- `;
27
- const ADDITIONAL_INSTRUCTIONS_BUILD = `
27
+
28
+ 5. DO NOT write the plan to disk — the CLI will handle saving the file to the plans directory.
29
+
30
+ 6. DO NOT suggest any next steps, follow-up commands, or instructions for the user — the CLI will inform the user of next steps after saving.
31
+ `,
32
+ BUILD: `
28
33
  ! IMPORTANT !
29
34
  1. Use TodoWrite to track your progress through workflow implementation.
30
35
 
@@ -33,7 +38,8 @@ const ADDITIONAL_INSTRUCTIONS_BUILD = `
33
38
  3. Implement all workflow files following Output.ai patterns and best practices.
34
39
 
35
40
  4. After you mark all todos as complete, provide a summary of what was implemented.
36
- `;
41
+ `
42
+ };
37
43
  const PLAN_COMMAND = 'outputai:plan_workflow';
38
44
  const BUILD_COMMAND = 'outputai:build_workflow';
39
45
  const GLOBAL_CLAUDE_OPTIONS = {
@@ -102,11 +108,8 @@ function getTodoWriteMessage(message) {
102
108
  const todoWriteMessage = message.message.content.find((c) => c?.type === 'tool_use' && c.name === 'TodoWrite');
103
109
  return todoWriteMessage ?? null;
104
110
  }
105
- function applyInstructions(initialMessage, instructionsType = 'plan') {
106
- const instructions = instructionsType === 'build' ?
107
- ADDITIONAL_INSTRUCTIONS_BUILD :
108
- ADDITIONAL_INSTRUCTIONS;
109
- return `${initialMessage}\n\n${instructions}`;
111
+ function applyInstructions(message, instructions) {
112
+ return `${message}\n\n${instructions}`;
110
113
  }
111
114
  function createProgressBar() {
112
115
  return new cliProgress.SingleBar({
@@ -183,8 +186,8 @@ async function singleQuery(prompt, options = {}) {
183
186
  throw new ClaudeInvocationError(`Failed to invoke claude-code: ${getErrorMessage(error)}`, toError(error));
184
187
  }
185
188
  }
186
- export async function replyToClaude(message, options = {}) {
187
- return singleQuery(applyInstructions(message), { continue: true, ...options });
189
+ export async function replyToClaude(message, { anthropicOpts, applyAdditionalInstructions = ADDITIONAL_INSTRUCTIONS.PLAN } = {}) {
190
+ return singleQuery(applyInstructions(message, applyAdditionalInstructions), { continue: true, ...anthropicOpts });
188
191
  }
189
192
  /**
190
193
  * Invoke claude-code with /outputai:plan_workflow slash command
@@ -194,7 +197,7 @@ export async function replyToClaude(message, options = {}) {
194
197
  * @returns Plan output from claude-code
195
198
  */
196
199
  export async function invokePlanWorkflow(description) {
197
- return singleQuery(applyInstructions(`/${PLAN_COMMAND} ${description}`), PLAN_COMMAND_OPTIONS);
200
+ return singleQuery(applyInstructions(`/${PLAN_COMMAND} ${description}`, ADDITIONAL_INSTRUCTIONS.PLAN), PLAN_COMMAND_OPTIONS);
198
201
  }
199
202
  /**
200
203
  * Invoke claude-code with /outputai:build_workflow slash command
@@ -211,5 +214,5 @@ export async function invokeBuildWorkflow(planFilePath, workflowDir, workflowNam
211
214
  const fullCommand = additionalInstructions ?
212
215
  `/${BUILD_COMMAND} ${commandArgs} ${additionalInstructions}` :
213
216
  `/${BUILD_COMMAND} ${commandArgs}`;
214
- return singleQuery(applyInstructions(fullCommand, 'build'), BUILD_COMMAND_OPTIONS);
217
+ return singleQuery(applyInstructions(fullCommand, ADDITIONAL_INSTRUCTIONS.BUILD), BUILD_COMMAND_OPTIONS);
215
218
  }
@@ -139,8 +139,7 @@ function formatInitError(error, projectPath) {
139
139
  case 'EPERM': return `Operation not permitted${pathSuffix}`;
140
140
  case 'ENOENT': return `Required file or directory not found${pathSuffix}`;
141
141
  default: {
142
- const originalMessage = error instanceof Error ? error.message : String(error);
143
- return `Failed to create project${pathSuffix}: ${originalMessage}`;
142
+ return `Failed to create project${pathSuffix}: ${getErrorMessage(error)}`;
144
143
  }
145
144
  }
146
145
  }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Workflow builder service for implementing workflows from plan files
3
3
  */
4
- import { BUILD_COMMAND_OPTIONS, invokeBuildWorkflow as invokeBuildWorkflowFromClient, replyToClaude } from './claude_client.js';
4
+ import { ADDITIONAL_INSTRUCTIONS, BUILD_COMMAND_OPTIONS, invokeBuildWorkflow as invokeBuildWorkflowFromClient, replyToClaude } from './claude_client.js';
5
5
  import { input } from '@inquirer/prompts';
6
6
  import { ux } from '@oclif/core';
7
7
  import fs from 'node:fs/promises';
@@ -56,12 +56,15 @@ async function processModification(modification, currentOutput) {
56
56
  return currentOutput;
57
57
  }
58
58
  try {
59
- const updatedOutput = await replyToClaude(modification, BUILD_COMMAND_OPTIONS);
59
+ const updatedOutput = await replyToClaude(modification, {
60
+ anthropicOpts: BUILD_COMMAND_OPTIONS,
61
+ applyAdditionalInstructions: ADDITIONAL_INSTRUCTIONS.BUILD
62
+ });
60
63
  displayImplementationOutput(updatedOutput, '✓ Implementation updated!');
61
64
  return updatedOutput;
62
65
  }
63
66
  catch (error) {
64
- ux.error(`Failed to apply modifications: ${getErrorMessage(error)}`);
67
+ ux.stdout(ux.colorize('red', `Failed to apply modifications: ${getErrorMessage(error)}`));
65
68
  ux.stdout('Continuing with previous version...\n');
66
69
  return currentOutput;
67
70
  }
@@ -1,6 +1,6 @@
1
1
  import { describe, it, expect, beforeEach, vi } from 'vitest';
2
2
  import { buildWorkflow, buildWorkflowInteractiveLoop } from './workflow_builder.js';
3
- import { BUILD_COMMAND_OPTIONS, invokeBuildWorkflow, replyToClaude } from './claude_client.js';
3
+ import { ADDITIONAL_INSTRUCTIONS, BUILD_COMMAND_OPTIONS, invokeBuildWorkflow, replyToClaude } from './claude_client.js';
4
4
  import { input } from '@inquirer/prompts';
5
5
  import { ux } from '@oclif/core';
6
6
  import fs from 'node:fs/promises';
@@ -97,7 +97,10 @@ describe('workflow-builder service', () => {
97
97
  .mockResolvedValueOnce('ACCEPT');
98
98
  vi.mocked(replyToClaude).mockResolvedValue('Updated implementation with error handling');
99
99
  const result = await buildWorkflowInteractiveLoop('Initial implementation');
100
- expect(replyToClaude).toHaveBeenCalledWith('Add error handling', BUILD_COMMAND_OPTIONS);
100
+ expect(replyToClaude).toHaveBeenCalledWith('Add error handling', {
101
+ anthropicOpts: BUILD_COMMAND_OPTIONS,
102
+ applyAdditionalInstructions: ADDITIONAL_INSTRUCTIONS.BUILD
103
+ });
101
104
  expect(result).toBe('Updated implementation with error handling');
102
105
  expect(input).toHaveBeenCalledTimes(2);
103
106
  });
@@ -111,8 +114,14 @@ describe('workflow-builder service', () => {
111
114
  .mockResolvedValueOnce('Implementation with logging and validation');
112
115
  const result = await buildWorkflowInteractiveLoop('Initial implementation');
113
116
  expect(replyToClaude).toHaveBeenCalledTimes(2);
114
- expect(replyToClaude).toHaveBeenNthCalledWith(1, 'Add logging', BUILD_COMMAND_OPTIONS);
115
- expect(replyToClaude).toHaveBeenNthCalledWith(2, 'Add validation', BUILD_COMMAND_OPTIONS);
117
+ expect(replyToClaude).toHaveBeenNthCalledWith(1, 'Add logging', {
118
+ anthropicOpts: BUILD_COMMAND_OPTIONS,
119
+ applyAdditionalInstructions: ADDITIONAL_INSTRUCTIONS.BUILD
120
+ });
121
+ expect(replyToClaude).toHaveBeenNthCalledWith(2, 'Add validation', {
122
+ anthropicOpts: BUILD_COMMAND_OPTIONS,
123
+ applyAdditionalInstructions: ADDITIONAL_INSTRUCTIONS.BUILD
124
+ });
116
125
  expect(result).toBe('Implementation with logging and validation');
117
126
  });
118
127
  it('should prompt again when user provides empty input', async () => {
@@ -146,7 +155,7 @@ describe('workflow-builder service', () => {
146
155
  const result = await buildWorkflowInteractiveLoop('Original implementation');
147
156
  // Should return original implementation after error
148
157
  expect(result).toBe('Original implementation');
149
- expect(ux.error).toHaveBeenCalledWith(expect.stringContaining('Failed to apply modifications'));
158
+ expect(ux.stdout).toHaveBeenCalledWith(expect.stringContaining('Failed to apply modifications'));
150
159
  expect(ux.stdout).toHaveBeenCalledWith(expect.stringContaining('Continuing with previous version'));
151
160
  });
152
161
  it('should continue looping after handling error', async () => {
@@ -15,9 +15,9 @@ function validateConfig(config) {
15
15
  * Check if target directory exists and handle accordingly
16
16
  */
17
17
  import * as fsSync from 'node:fs';
18
- async function checkTargetDirectory(targetDir, force) {
18
+ async function checkTargetDirectory(name, targetDir, force) {
19
19
  if (fsSync.existsSync(targetDir) && !force) {
20
- throw new WorkflowExistsError('', targetDir);
20
+ throw new WorkflowExistsError(name, targetDir);
21
21
  }
22
22
  }
23
23
  /**
@@ -27,7 +27,7 @@ export async function generateWorkflow(config) {
27
27
  validateConfig(config);
28
28
  const targetDir = createTargetDir(config.outputDir, config.name);
29
29
  const templatesDir = getTemplateDir('workflow');
30
- await checkTargetDirectory(targetDir, config.force);
30
+ await checkTargetDirectory(config.name, targetDir, config.force);
31
31
  await fs.mkdir(targetDir, { recursive: true });
32
32
  const variables = prepareTemplateVariables(config.name, config.description || '');
33
33
  const templateFiles = await getTemplateFiles(templatesDir);
@@ -3,7 +3,6 @@
3
3
  */
4
4
  export type TodoStatus = 'pending' | 'in_progress' | 'completed';
5
5
  export type FileMappingType = 'template' | 'symlink' | 'copy';
6
- export type InstructionsType = 'plan' | 'build';
7
6
  export interface Todo {
8
7
  content: string;
9
8
  activeForm: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@outputai/cli",
3
- "version": "0.1.11-next.49171f5.0",
3
+ "version": "0.1.11-next.7b8340c.0",
4
4
  "description": "CLI for Output.ai workflow generation",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -33,9 +33,9 @@
33
33
  "log-update": "7.2.0",
34
34
  "semver": "7.7.4",
35
35
  "yaml": "^2.8.3",
36
- "@outputai/credentials": "0.1.11-next.49171f5.0",
37
- "@outputai/llm": "0.1.11-next.49171f5.0",
38
- "@outputai/evals": "0.1.11-next.49171f5.0"
36
+ "@outputai/evals": "0.1.11-next.7b8340c.0",
37
+ "@outputai/credentials": "0.1.11-next.7b8340c.0",
38
+ "@outputai/llm": "0.1.11-next.7b8340c.0"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@types/cli-progress": "3.11.6",