@output.ai/cli 0.5.6 → 0.6.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.
Files changed (52) hide show
  1. package/dist/commands/agents/init.d.ts +0 -1
  2. package/dist/commands/agents/init.js +9 -16
  3. package/dist/commands/agents/init.spec.js +49 -224
  4. package/dist/commands/workflow/generate.js +2 -2
  5. package/dist/commands/workflow/plan.js +3 -3
  6. package/dist/commands/workflow/plan.spec.js +13 -13
  7. package/dist/services/claude_client.d.ts +4 -4
  8. package/dist/services/claude_client.integration.test.js +2 -2
  9. package/dist/services/claude_client.js +7 -7
  10. package/dist/services/claude_client.spec.js +3 -3
  11. package/dist/services/coding_agents.d.ts +10 -24
  12. package/dist/services/coding_agents.js +112 -368
  13. package/dist/services/coding_agents.spec.js +101 -290
  14. package/dist/services/project_scaffold.js +3 -3
  15. package/dist/services/workflow_builder.d.ts +1 -1
  16. package/dist/services/workflow_builder.js +1 -1
  17. package/dist/services/workflow_planner.js +1 -2
  18. package/dist/services/workflow_planner.spec.js +4 -5
  19. package/dist/templates/agent_instructions/dotclaude/settings.json.template +29 -0
  20. package/dist/templates/agent_instructions/{AGENTS.md.template → dotoutputai/AGENTS.md.template} +12 -10
  21. package/dist/utils/claude.d.ts +5 -0
  22. package/dist/utils/claude.js +19 -0
  23. package/dist/utils/claude.spec.d.ts +1 -0
  24. package/dist/utils/claude.spec.js +119 -0
  25. package/dist/utils/paths.d.ts +0 -4
  26. package/dist/utils/paths.js +0 -6
  27. package/package.json +3 -3
  28. package/dist/templates/agent_instructions/agents/workflow_context_fetcher.md.template +0 -82
  29. package/dist/templates/agent_instructions/agents/workflow_debugger.md.template +0 -98
  30. package/dist/templates/agent_instructions/agents/workflow_planner.md.template +0 -113
  31. package/dist/templates/agent_instructions/agents/workflow_prompt_writer.md.template +0 -595
  32. package/dist/templates/agent_instructions/agents/workflow_quality.md.template +0 -244
  33. package/dist/templates/agent_instructions/commands/build_workflow.md.template +0 -290
  34. package/dist/templates/agent_instructions/commands/debug_workflow.md.template +0 -198
  35. package/dist/templates/agent_instructions/commands/plan_workflow.md.template +0 -261
  36. package/dist/templates/agent_instructions/meta/post_flight.md.template +0 -94
  37. package/dist/templates/agent_instructions/meta/pre_flight.md.template +0 -60
  38. package/dist/templates/agent_instructions/skills/output-error-direct-io/SKILL.md.template +0 -249
  39. package/dist/templates/agent_instructions/skills/output-error-http-client/SKILL.md.template +0 -298
  40. package/dist/templates/agent_instructions/skills/output-error-missing-schemas/SKILL.md.template +0 -265
  41. package/dist/templates/agent_instructions/skills/output-error-nondeterminism/SKILL.md.template +0 -252
  42. package/dist/templates/agent_instructions/skills/output-error-try-catch/SKILL.md.template +0 -226
  43. package/dist/templates/agent_instructions/skills/output-error-zod-import/SKILL.md.template +0 -209
  44. package/dist/templates/agent_instructions/skills/output-services-check/SKILL.md.template +0 -128
  45. package/dist/templates/agent_instructions/skills/output-workflow-list/SKILL.md.template +0 -117
  46. package/dist/templates/agent_instructions/skills/output-workflow-result/SKILL.md.template +0 -199
  47. package/dist/templates/agent_instructions/skills/output-workflow-run/SKILL.md.template +0 -228
  48. package/dist/templates/agent_instructions/skills/output-workflow-runs-list/SKILL.md.template +0 -141
  49. package/dist/templates/agent_instructions/skills/output-workflow-start/SKILL.md.template +0 -201
  50. package/dist/templates/agent_instructions/skills/output-workflow-status/SKILL.md.template +0 -151
  51. package/dist/templates/agent_instructions/skills/output-workflow-stop/SKILL.md.template +0 -164
  52. package/dist/templates/agent_instructions/skills/output-workflow-trace/SKILL.md.template +0 -134
@@ -4,7 +4,6 @@ export default class Init extends Command {
4
4
  static examples: string[];
5
5
  static args: {};
6
6
  static flags: {
7
- 'agent-provider': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
8
7
  force: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
9
8
  };
10
9
  run(): Promise<void>;
@@ -1,21 +1,14 @@
1
1
  import { Command, Flags } from '@oclif/core';
2
- import { AGENT_CONFIG_DIR } from '#config.js';
3
- import { initializeAgentConfig, AGENT_CONFIGS } from '#services/coding_agents.js';
2
+ import { initializeAgentConfig } from '#services/coding_agents.js';
4
3
  import { getErrorMessage, getErrorCode } from '#utils/error_utils.js';
5
4
  export default class Init extends Command {
6
- static description = 'Initialize agent configuration files for AI assistant integration';
5
+ static description = 'Initialize agent configuration files for Claude Code plugin integration';
7
6
  static examples = [
8
7
  '<%= config.bin %> <%= command.id %>',
9
- '<%= config.bin %> <%= command.id %> --agent-provider claude-code',
10
8
  '<%= config.bin %> <%= command.id %> --force'
11
9
  ];
12
10
  static args = {};
13
11
  static flags = {
14
- 'agent-provider': Flags.string({
15
- description: 'Specify the coding agent provider',
16
- default: AGENT_CONFIGS['claude-code'].id,
17
- options: [AGENT_CONFIGS['claude-code'].id]
18
- }),
19
12
  force: Flags.boolean({
20
13
  char: 'f',
21
14
  description: 'Overwrite existing files',
@@ -28,16 +21,16 @@ export default class Init extends Command {
28
21
  try {
29
22
  await initializeAgentConfig({
30
23
  projectRoot: process.cwd(),
31
- force: flags.force,
32
- agentProvider: flags['agent-provider']
24
+ force: flags.force
33
25
  });
34
- this.log('Agent configuration initialized successfully!');
26
+ this.log('Agent configuration initialized successfully!');
35
27
  this.log('');
36
- this.log('Created:');
37
- this.log(` ${AGENT_CONFIG_DIR}/ directory with agent and command configurations`);
38
- this.log(' .claude/ directory with symlinks for Claude Code integration');
28
+ this.log('Configured:');
29
+ this.log(' - Registered marketplace: growthxai/output-claude-plugins');
30
+ this.log(' - Updated marketplace: outputai');
31
+ this.log(' - Installed plugin: outputai@outputai');
39
32
  this.log('');
40
- this.log('Claude Code will automatically detect and use these configurations.');
33
+ this.log('Claude Code will automatically use the OutputAI plugin.');
41
34
  }
42
35
  catch (error) {
43
36
  if (getErrorCode(error) === 'EACCES') {
@@ -1,36 +1,15 @@
1
- /* eslint-disable no-restricted-syntax, @typescript-eslint/no-explicit-any, init-declarations */
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
2
  import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
3
- import fs from 'node:fs/promises';
4
- import path from 'node:path';
5
3
  import Init from './init.js';
6
- import { getTemplateDir } from '#utils/paths.js';
7
- import { processTemplate } from '#utils/template.js';
8
- vi.mock('node:fs/promises');
9
- vi.mock('node:path', async () => {
10
- const actual = await vi.importActual('node:path');
11
- return {
12
- ...actual,
13
- join: vi.fn((...args) => args.join('/')),
14
- dirname: vi.fn(p => p.split('/').slice(0, -1).join('/') || '.'),
15
- relative: vi.fn((from, to) => {
16
- if (to === 'CLAUDE.md' && from === '.') {
17
- return '.outputai/AGENTS.md';
18
- }
19
- if (to.includes('.outputai/agents')) {
20
- return `../../.outputai/agents/${path.basename(to)}`;
21
- }
22
- if (to.includes('.outputai/commands')) {
23
- return `../../.outputai/commands/${path.basename(to)}`;
24
- }
25
- return to;
26
- })
27
- };
28
- });
29
- vi.mock('../../utils/paths.js');
30
- vi.mock('../../utils/template.js');
4
+ import { initializeAgentConfig } from '#services/coding_agents.js';
5
+ vi.mock('#services/coding_agents.js', () => ({
6
+ initializeAgentConfig: vi.fn(),
7
+ AGENT_CONFIGS: { 'claude-code': { id: 'claude-code' } }
8
+ }));
9
+ vi.mock('#config.js', () => ({
10
+ AGENT_CONFIG_DIR: '.outputai'
11
+ }));
31
12
  describe('agents init', () => {
32
- let mockGetTemplateDir;
33
- let mockProcessTemplate;
34
13
  const createTestCommand = (args = []) => {
35
14
  const cmd = new Init(args, {});
36
15
  cmd.log = vi.fn();
@@ -42,19 +21,7 @@ describe('agents init', () => {
42
21
  };
43
22
  beforeEach(() => {
44
23
  vi.clearAllMocks();
45
- vi.mocked(fs.mkdir).mockResolvedValue(undefined);
46
- vi.mocked(fs.writeFile).mockResolvedValue(undefined);
47
- vi.mocked(fs.symlink).mockResolvedValue(undefined);
48
- vi.mocked(fs.stat).mockRejectedValue({ code: 'ENOENT' });
49
- vi.mocked(fs.access).mockRejectedValue({ code: 'ENOENT' }); // Files don't exist by default
50
- vi.mocked(fs.unlink).mockResolvedValue(undefined);
51
- vi.mocked(fs.readFile).mockResolvedValue('Template content with {{date}}');
52
- mockGetTemplateDir = vi.mocked(getTemplateDir);
53
- mockGetTemplateDir.mockReturnValue('/templates/agent_instructions');
54
- mockProcessTemplate = vi.mocked(processTemplate);
55
- mockProcessTemplate.mockImplementation((content, variables) => {
56
- return content.replace(/\{\{(\w+)\}\}/g, (_match, key) => variables[key] || _match);
57
- });
24
+ vi.mocked(initializeAgentConfig).mockResolvedValue(undefined);
58
25
  });
59
26
  afterEach(() => {
60
27
  vi.restoreAllMocks();
@@ -64,221 +31,79 @@ describe('agents init', () => {
64
31
  expect(Init.description).toBeDefined();
65
32
  expect(Init.description).toContain('agent configuration');
66
33
  });
67
- it('should have correct examples', () => {
34
+ it('should have correct examples without agent-provider flag', () => {
68
35
  expect(Init.examples).toBeDefined();
69
36
  expect(Array.isArray(Init.examples)).toBe(true);
37
+ const examplesStr = Init.examples.join(' ');
38
+ expect(examplesStr).not.toContain('agent-provider');
70
39
  });
71
40
  it('should have no required arguments', () => {
72
41
  expect(Init.args).toBeDefined();
73
42
  expect(Object.keys(Init.args)).toHaveLength(0);
74
43
  });
75
- it('should have appropriate flags', () => {
44
+ it('should only have force flag', () => {
76
45
  expect(Init.flags).toBeDefined();
77
46
  expect(Init.flags.force).toBeDefined();
47
+ expect(Object.keys(Init.flags)).toHaveLength(1);
78
48
  });
79
49
  });
80
- describe('directory creation', () => {
81
- it('should create .outputai directory structure', async () => {
82
- const mockMkdir = vi.mocked(fs.mkdir);
83
- mockMkdir.mockResolvedValue(undefined);
84
- const cmd = createTestCommand();
85
- cmd.parse.mockResolvedValue({ flags: { 'agent-provider': 'claude-code', force: false }, args: {} });
86
- await cmd.run();
87
- expect(mockMkdir).toHaveBeenCalledWith(expect.stringContaining('.outputai'), expect.objectContaining({ recursive: true }));
88
- expect(mockMkdir).toHaveBeenCalledWith(expect.stringContaining('.outputai/agents'), expect.objectContaining({ recursive: true }));
89
- expect(mockMkdir).toHaveBeenCalledWith(expect.stringContaining('.outputai/commands'), expect.objectContaining({ recursive: true }));
90
- expect(mockMkdir).toHaveBeenCalledWith(expect.stringContaining('.outputai/meta'), expect.objectContaining({ recursive: true }));
91
- });
92
- it('should create .claude directory structure', async () => {
93
- const mockMkdir = vi.mocked(fs.mkdir);
94
- mockMkdir.mockResolvedValue(undefined);
95
- const cmd = createTestCommand();
96
- cmd.parse.mockResolvedValue({ flags: { 'agent-provider': 'claude-code', force: false }, args: {} });
97
- await cmd.run();
98
- expect(mockMkdir).toHaveBeenCalledWith(expect.stringContaining('.claude'), expect.objectContaining({ recursive: true }));
99
- expect(mockMkdir).toHaveBeenCalledWith(expect.stringContaining('.claude/agents'), expect.objectContaining({ recursive: true }));
100
- expect(mockMkdir).toHaveBeenCalledWith(expect.stringContaining('.claude/commands'), expect.objectContaining({ recursive: true }));
101
- });
102
- });
103
- describe('file creation from templates', () => {
104
- it('should create AGENTS.md file from template', async () => {
105
- const mockWriteFile = vi.mocked(fs.writeFile);
106
- const mockReadFile = vi.mocked(fs.readFile);
107
- mockReadFile.mockResolvedValue('Agent instructions {{date}}');
50
+ describe('successful execution', () => {
51
+ it('should call initializeAgentConfig with correct options', async () => {
108
52
  const cmd = createTestCommand();
109
- cmd.parse.mockResolvedValue({ flags: { 'agent-provider': 'claude-code', force: false }, args: {} });
53
+ cmd.parse.mockResolvedValue({ flags: { force: false }, args: {} });
110
54
  await cmd.run();
111
- expect(mockReadFile).toHaveBeenCalledWith(expect.stringContaining('AGENTS.md.template'), 'utf-8');
112
- expect(mockWriteFile).toHaveBeenCalledWith(expect.stringContaining('.outputai/AGENTS.md'), expect.stringContaining('Agent instructions'), 'utf-8');
55
+ expect(initializeAgentConfig).toHaveBeenCalledWith({
56
+ projectRoot: expect.any(String),
57
+ force: false
58
+ });
113
59
  });
114
- it('should create all agent configuration files', async () => {
115
- const mockWriteFile = vi.mocked(fs.writeFile);
116
- const cmd = createTestCommand();
117
- cmd.parse.mockResolvedValue({ flags: { 'agent-provider': 'claude-code', force: false }, args: {} });
118
- await cmd.run();
119
- const agentFiles = [
120
- '.outputai/agents/workflow_planner.md'
121
- ];
122
- for (const file of agentFiles) {
123
- expect(mockWriteFile).toHaveBeenCalledWith(expect.stringContaining(file), expect.any(String), 'utf-8');
124
- }
125
- });
126
- it('should create all command configuration files', async () => {
127
- const mockWriteFile = vi.mocked(fs.writeFile);
128
- const cmd = createTestCommand();
129
- cmd.parse.mockResolvedValue({ flags: { 'agent-provider': 'claude-code', force: false }, args: {} });
130
- await cmd.run();
131
- const commandFiles = [
132
- '.outputai/commands/plan_workflow.md'
133
- ];
134
- for (const file of commandFiles) {
135
- expect(mockWriteFile).toHaveBeenCalledWith(expect.stringContaining(file), expect.any(String), 'utf-8');
136
- }
137
- });
138
- it('should create all meta configuration files', async () => {
139
- const mockWriteFile = vi.mocked(fs.writeFile);
140
- const cmd = createTestCommand();
141
- cmd.parse.mockResolvedValue({ flags: { 'agent-provider': 'claude-code', force: false }, args: {} });
142
- await cmd.run();
143
- const metaFiles = [
144
- '.outputai/meta/pre_flight.md',
145
- '.outputai/meta/post_flight.md'
146
- ];
147
- for (const file of metaFiles) {
148
- expect(mockWriteFile).toHaveBeenCalledWith(expect.stringContaining(file), expect.any(String), 'utf-8');
149
- }
150
- });
151
- });
152
- describe('symlink creation', () => {
153
- it('should create symlink from CLAUDE.md to .outputai/AGENTS.md', async () => {
154
- const mockSymlink = vi.mocked(fs.symlink);
155
- const cmd = createTestCommand();
156
- cmd.parse.mockResolvedValue({ flags: { 'agent-provider': 'claude-code', force: false }, args: {} });
157
- await cmd.run();
158
- // Symlink creates a relative link from target to source
159
- expect(mockSymlink).toHaveBeenCalledWith(expect.stringContaining('.outputai/AGENTS.md'), expect.stringContaining('CLAUDE.md'));
160
- });
161
- it('should create symlinks for all agent files', async () => {
162
- const mockSymlink = vi.mocked(fs.symlink);
163
- const cmd = createTestCommand();
164
- cmd.parse.mockResolvedValue({ flags: { 'agent-provider': 'claude-code', force: false }, args: {} });
60
+ it('should pass force flag to initializeAgentConfig', async () => {
61
+ const cmd = createTestCommand(['--force']);
62
+ cmd.parse.mockResolvedValue({ flags: { force: true }, args: {} });
165
63
  await cmd.run();
166
- expect(mockSymlink).toHaveBeenCalledWith(expect.stringContaining('.outputai/agents/workflow_planner.md'), expect.stringContaining('.claude/agents/workflow_planner.md'));
64
+ expect(initializeAgentConfig).toHaveBeenCalledWith({
65
+ projectRoot: expect.any(String),
66
+ force: true
67
+ });
167
68
  });
168
- it('should create symlinks for all command files', async () => {
169
- const mockSymlink = vi.mocked(fs.symlink);
69
+ it('should display success messages', async () => {
170
70
  const cmd = createTestCommand();
171
- cmd.parse.mockResolvedValue({ flags: { 'agent-provider': 'claude-code', force: false }, args: {} });
71
+ cmd.parse.mockResolvedValue({ flags: { force: false }, args: {} });
172
72
  await cmd.run();
173
- expect(mockSymlink).toHaveBeenCalledWith(expect.stringContaining('.outputai/commands/plan_workflow.md'), expect.stringContaining('.claude/commands/plan_workflow.md'));
73
+ expect(cmd.log).toHaveBeenCalledWith(expect.stringContaining('initialized successfully'));
74
+ expect(cmd.log).toHaveBeenCalledWith('Configured:');
75
+ expect(cmd.log).toHaveBeenCalledWith(expect.stringContaining('OutputAI plugin'));
174
76
  });
175
- it('should handle Windows by copying files when symlinks are not supported', async () => {
176
- const mockSymlink = vi.mocked(fs.symlink);
177
- const mockWriteFile = vi.mocked(fs.writeFile);
178
- const mockReadFile = vi.mocked(fs.readFile);
179
- mockSymlink.mockRejectedValue({ code: 'ENOTSUP' });
180
- // readFile is called for both templates AND fallback copy
181
- mockReadFile.mockResolvedValue('File content');
77
+ it('should display plugin configuration messages', async () => {
182
78
  const cmd = createTestCommand();
183
- cmd.parse.mockResolvedValue({ flags: { 'agent-provider': 'claude-code', force: false }, args: {} });
79
+ cmd.parse.mockResolvedValue({ flags: { force: false }, args: {} });
184
80
  await cmd.run();
185
- // When symlinks fail, it falls back to copying the source files
186
- // Source files are the .outputai files, targets are CLAUDE.md and .claude/ files
187
- const writeCalls = mockWriteFile.mock.calls;
188
- // Should have written template files + fallback copies
189
- expect(writeCalls.length).toBeGreaterThan(3); // At least 3 templates + 3 symlink fallbacks
190
- // Verify at least one fallback copy happened
191
- const claudeFileCalls = writeCalls.filter(call => typeof call[0] === 'string' && (call[0].includes('CLAUDE.md') ||
192
- call[0].includes('.claude/')));
193
- expect(claudeFileCalls.length).toBeGreaterThan(0);
81
+ expect(cmd.log).toHaveBeenCalledWith(expect.stringContaining('marketplace'));
82
+ expect(cmd.log).toHaveBeenCalledWith(expect.stringContaining('plugin'));
194
83
  });
195
84
  });
196
85
  describe('error handling', () => {
197
- it('should handle existing directory gracefully', async () => {
198
- const mockMkdir = vi.mocked(fs.mkdir);
199
- mockMkdir.mockRejectedValue({ code: 'EEXIST' });
200
- const cmd = createTestCommand();
201
- cmd.parse.mockResolvedValue({ flags: { 'agent-provider': 'claude-code', force: false }, args: {} });
202
- await cmd.run();
203
- expect(cmd.error).not.toHaveBeenCalled();
204
- });
205
86
  it('should handle permission errors', async () => {
206
- const mockMkdir = vi.mocked(fs.mkdir);
207
- mockMkdir.mockRejectedValue({ code: 'EACCES' });
87
+ vi.mocked(initializeAgentConfig).mockRejectedValue({ code: 'EACCES' });
208
88
  const cmd = createTestCommand();
209
- cmd.parse.mockResolvedValue({ flags: { 'agent-provider': 'claude-code', force: false }, args: {} });
89
+ cmd.parse.mockResolvedValue({ flags: { force: false }, args: {} });
210
90
  await cmd.run();
211
91
  expect(cmd.error).toHaveBeenCalledWith(expect.stringContaining('Permission denied'));
212
92
  });
213
- it('should be idempotent (safe to run multiple times)', async () => {
214
- const mockWriteFile = vi.mocked(fs.writeFile);
215
- mockWriteFile.mockResolvedValue(undefined);
93
+ it('should handle general errors with message', async () => {
94
+ vi.mocked(initializeAgentConfig).mockRejectedValue(new Error('Something went wrong'));
216
95
  const cmd = createTestCommand();
217
- cmd.parse.mockResolvedValue({ flags: { 'agent-provider': 'claude-code', force: false }, args: {} });
218
- await cmd.run();
96
+ cmd.parse.mockResolvedValue({ flags: { force: false }, args: {} });
219
97
  await cmd.run();
220
- expect(cmd.error).not.toHaveBeenCalled();
98
+ expect(cmd.error).toHaveBeenCalledWith(expect.stringContaining('Failed to initialize agent configuration'));
99
+ expect(cmd.error).toHaveBeenCalledWith(expect.stringContaining('Something went wrong'));
221
100
  });
222
- });
223
- describe('complete initialization', () => {
224
- it('should create all required files and directories', async () => {
225
- const mockMkdir = vi.mocked(fs.mkdir);
226
- const mockWriteFile = vi.mocked(fs.writeFile);
227
- const mockSymlink = vi.mocked(fs.symlink);
101
+ it('should handle Claude CLI not found error', async () => {
102
+ vi.mocked(initializeAgentConfig).mockRejectedValue(new Error('Claude CLI not found. Please install Claude Code CLI and ensure \'claude\' is in your PATH.'));
228
103
  const cmd = createTestCommand();
229
- cmd.parse.mockResolvedValue({ flags: { 'agent-provider': 'claude-code', force: false }, args: {} });
230
- await cmd.run();
231
- // Verify all expected directories are created
232
- const expectedDirs = [
233
- '.outputai',
234
- '.outputai/agents',
235
- '.outputai/commands',
236
- '.outputai/meta',
237
- '.claude',
238
- '.claude/agents',
239
- '.claude/commands'
240
- ];
241
- for (const dir of expectedDirs) {
242
- expect(mockMkdir).toHaveBeenCalledWith(expect.stringContaining(dir), expect.objectContaining({ recursive: true }));
243
- }
244
- // Verify all expected template files are created
245
- const expectedFiles = [
246
- '.outputai/AGENTS.md',
247
- '.outputai/agents/workflow_planner.md',
248
- '.outputai/commands/plan_workflow.md',
249
- '.outputai/meta/pre_flight.md',
250
- '.outputai/meta/post_flight.md'
251
- ];
252
- for (const file of expectedFiles) {
253
- expect(mockWriteFile).toHaveBeenCalledWith(expect.stringContaining(file), expect.any(String), 'utf-8');
254
- }
255
- // Verify symlinks are created
256
- expect(mockSymlink).toHaveBeenCalledWith(expect.stringContaining('.outputai/AGENTS.md'), expect.stringContaining('CLAUDE.md'));
257
- expect(mockSymlink).toHaveBeenCalledWith(expect.stringContaining('.outputai/agents/workflow_planner.md'), expect.stringContaining('.claude/agents/workflow_planner.md'));
258
- expect(mockSymlink).toHaveBeenCalledWith(expect.stringContaining('.outputai/commands/plan_workflow.md'), expect.stringContaining('.claude/commands/plan_workflow.md'));
259
- });
260
- });
261
- describe('force flag', () => {
262
- it('should overwrite existing files when force flag is set', async () => {
263
- const mockWriteFile = vi.mocked(fs.writeFile);
264
- const mockStat = vi.mocked(fs.stat);
265
- mockStat.mockResolvedValue({ isFile: () => true });
266
- mockWriteFile.mockResolvedValue(undefined);
267
- const cmd = createTestCommand(['--force']);
268
- cmd.parse.mockResolvedValue({ flags: { 'agent-provider': 'claude-code', force: true }, args: {} });
104
+ cmd.parse.mockResolvedValue({ flags: { force: false }, args: {} });
269
105
  await cmd.run();
270
- expect(mockWriteFile).toHaveBeenCalled();
271
- expect(cmd.warn).not.toHaveBeenCalled();
272
- });
273
- it('should skip existing files without force flag', async () => {
274
- const mockAccess = vi.mocked(fs.access);
275
- mockAccess.mockResolvedValue(undefined);
276
- const cmd = createTestCommand();
277
- cmd.parse.mockResolvedValue({ flags: { 'agent-provider': 'claude-code', force: false }, args: {} });
278
- await cmd.run();
279
- // Files exist, so they should be skipped
280
- // The command should complete without errors
281
- expect(cmd.log).toHaveBeenCalledWith(expect.stringContaining('initialized successfully'));
106
+ expect(cmd.error).toHaveBeenCalledWith(expect.stringContaining('Claude CLI not found'));
282
107
  });
283
108
  });
284
109
  });
@@ -1,7 +1,7 @@
1
1
  import { Args, Command, Flags, ux } from '@oclif/core';
2
2
  import { generateWorkflow } from '#services/workflow_generator.js';
3
3
  import { buildWorkflow, buildWorkflowInteractiveLoop } from '#services/workflow_builder.js';
4
- import { ensureOutputAIStructure } from '#services/coding_agents.js';
4
+ 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';
@@ -63,7 +63,7 @@ export default class Generate extends Command {
63
63
  if (planFile) {
64
64
  this.log('\nStarting AI-assisted workflow implementation...\n');
65
65
  const projectRoot = process.cwd();
66
- await ensureOutputAIStructure(projectRoot);
66
+ await ensureOutputAISystem(projectRoot);
67
67
  const absolutePlanPath = path.resolve(projectRoot, planFile);
68
68
  const buildOutput = await buildWorkflow(absolutePlanPath, result.targetDir, args.name);
69
69
  await buildWorkflowInteractiveLoop(buildOutput);
@@ -1,7 +1,7 @@
1
1
  import { Command, Flags, ux } from '@oclif/core';
2
2
  import { input } from '@inquirer/prompts';
3
3
  import { generatePlanName, updateAgentTemplates, writePlanFile } from '#services/workflow_planner.js';
4
- import { ensureOutputAIStructure } from '#services/coding_agents.js';
4
+ import { ensureOutputAISystem } from '#services/coding_agents.js';
5
5
  import { invokePlanWorkflow, PLAN_COMMAND_OPTIONS, replyToClaude } from '#services/claude_client.js';
6
6
  export default class WorkflowPlan extends Command {
7
7
  static description = 'Generate a workflow plan from a description';
@@ -25,7 +25,7 @@ export default class WorkflowPlan extends Command {
25
25
  const { flags } = await this.parse(WorkflowPlan);
26
26
  const projectRoot = process.cwd();
27
27
  this.log('Checking .outputai directory structure...');
28
- await ensureOutputAIStructure(projectRoot);
28
+ await ensureOutputAISystem(projectRoot);
29
29
  if (flags['force-agent-file-write']) {
30
30
  this.log('Updating agent templates...');
31
31
  await updateAgentTemplates(projectRoot);
@@ -56,7 +56,7 @@ export default class WorkflowPlan extends Command {
56
56
  return this.planModificationLoop(modifiedPlanContent);
57
57
  }
58
58
  async planGenerationLoop(promptDescription, planName, projectRoot) {
59
- this.log('\nInvoking the /plan_workflow command...');
59
+ this.log('\nInvoking the /outputai:plan_workflow command...');
60
60
  this.log('This may take a moment...\n');
61
61
  const planContent = await invokePlanWorkflow(promptDescription);
62
62
  const modifiedPlanContent = await this.planModificationLoop(planContent);
@@ -1,7 +1,7 @@
1
1
  import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
2
2
  import WorkflowPlan from './plan.js';
3
3
  import { generatePlanName, writePlanFile, updateAgentTemplates } from '#services/workflow_planner.js';
4
- import { ensureOutputAIStructure } from '#services/coding_agents.js';
4
+ import { ensureOutputAISystem } from '#services/coding_agents.js';
5
5
  import { invokePlanWorkflow, replyToClaude, ClaudeInvocationError } from '#services/claude_client.js';
6
6
  import { input } from '@inquirer/prompts';
7
7
  vi.mock('#services/workflow_planner.js');
@@ -28,7 +28,7 @@ describe('WorkflowPlan Command', () => {
28
28
  }
29
29
  return 'ACCEPT'; // Always accept on second call to stop recursion
30
30
  }));
31
- vi.mocked(ensureOutputAIStructure).mockResolvedValue();
31
+ vi.mocked(ensureOutputAISystem).mockResolvedValue();
32
32
  vi.mocked(generatePlanName).mockResolvedValue(planName);
33
33
  vi.mocked(invokePlanWorkflow).mockResolvedValue(planContent);
34
34
  vi.mocked(replyToClaude).mockResolvedValue(planContent);
@@ -69,7 +69,7 @@ describe('WorkflowPlan Command', () => {
69
69
  });
70
70
  setupSuccessfulMocks('Build a user authentication system', '2025_10_06_user_authentication', '# Workflow Plan\n\nPlan content here');
71
71
  await command.run();
72
- expect(ensureOutputAIStructure).toHaveBeenCalled();
72
+ expect(ensureOutputAISystem).toHaveBeenCalled();
73
73
  expect(generatePlanName).toHaveBeenCalledWith('Build a user authentication system');
74
74
  expect(invokePlanWorkflow).toHaveBeenCalledWith('Build a user authentication system');
75
75
  expect(writePlanFile).toHaveBeenCalledWith('2025_10_06_user_authentication', '# Workflow Plan\n\nPlan content here', expect.any(String));
@@ -106,7 +106,7 @@ describe('WorkflowPlan Command', () => {
106
106
  return 'ACCEPT'; // Second call from modification loop
107
107
  });
108
108
  vi.mocked(input).mockImplementation(inputMock);
109
- vi.mocked(ensureOutputAIStructure).mockResolvedValue();
109
+ vi.mocked(ensureOutputAISystem).mockResolvedValue();
110
110
  vi.mocked(generatePlanName).mockResolvedValue('2025_10_06_test');
111
111
  vi.mocked(invokePlanWorkflow).mockResolvedValue('# Plan');
112
112
  vi.mocked(replyToClaude).mockResolvedValue('# Plan');
@@ -122,7 +122,7 @@ describe('WorkflowPlan Command', () => {
122
122
  args: {},
123
123
  flags: { 'force-agent-file-write': false }
124
124
  });
125
- vi.mocked(ensureOutputAIStructure).mockResolvedValue();
125
+ vi.mocked(ensureOutputAISystem).mockResolvedValue();
126
126
  vi.mocked(input).mockResolvedValue('Test workflow');
127
127
  vi.mocked(generatePlanName).mockResolvedValue('2025_10_06_test');
128
128
  vi.mocked(invokePlanWorkflow).mockRejectedValue(new Error('ANTHROPIC_API_KEY environment variable is required'));
@@ -134,7 +134,7 @@ describe('WorkflowPlan Command', () => {
134
134
  args: {},
135
135
  flags: { 'force-agent-file-write': false }
136
136
  });
137
- vi.mocked(ensureOutputAIStructure).mockResolvedValue();
137
+ vi.mocked(ensureOutputAISystem).mockResolvedValue();
138
138
  vi.mocked(input).mockResolvedValue('Test workflow');
139
139
  vi.mocked(generatePlanName).mockResolvedValue('2025_10_06_test');
140
140
  const claudeError = new ClaudeInvocationError('Failed to invoke claude-code: API error');
@@ -158,7 +158,7 @@ describe('WorkflowPlan Command', () => {
158
158
  state.inputCallCount++;
159
159
  return state.inputCallCount === 1 ? 'Test workflow' : 'ACCEPT';
160
160
  }));
161
- vi.mocked(ensureOutputAIStructure).mockResolvedValue();
161
+ vi.mocked(ensureOutputAISystem).mockResolvedValue();
162
162
  vi.mocked(generatePlanName).mockResolvedValue('2025_10_06_test');
163
163
  vi.mocked(invokePlanWorkflow).mockResolvedValue('# Plan');
164
164
  vi.mocked(replyToClaude).mockResolvedValue('# Plan');
@@ -247,7 +247,7 @@ describe('WorkflowPlan Command', () => {
247
247
  args: {},
248
248
  flags: { 'force-agent-file-write': false }
249
249
  });
250
- vi.mocked(ensureOutputAIStructure).mockResolvedValue();
250
+ vi.mocked(ensureOutputAISystem).mockResolvedValue();
251
251
  vi.mocked(input).mockResolvedValue('Test workflow');
252
252
  vi.mocked(generatePlanName).mockResolvedValue('2025_10_06_test');
253
253
  const timeoutError = new Error('Request timeout');
@@ -261,7 +261,7 @@ describe('WorkflowPlan Command', () => {
261
261
  args: {},
262
262
  flags: { 'force-agent-file-write': false }
263
263
  });
264
- vi.mocked(ensureOutputAIStructure).mockResolvedValue();
264
+ vi.mocked(ensureOutputAISystem).mockResolvedValue();
265
265
  vi.mocked(input).mockResolvedValue('Test workflow');
266
266
  vi.mocked(generatePlanName).mockResolvedValue('2025_10_06_test');
267
267
  const rateLimitError = new ClaudeInvocationError('Rate limit exceeded');
@@ -279,7 +279,7 @@ describe('WorkflowPlan Command', () => {
279
279
  state.inputCallCount++;
280
280
  return state.inputCallCount === 1 ? 'Test workflow' : 'ACCEPT';
281
281
  }));
282
- vi.mocked(ensureOutputAIStructure).mockResolvedValue();
282
+ vi.mocked(ensureOutputAISystem).mockResolvedValue();
283
283
  vi.mocked(generatePlanName).mockResolvedValue('2025_10_06_test');
284
284
  vi.mocked(invokePlanWorkflow).mockResolvedValue('# Plan');
285
285
  vi.mocked(replyToClaude).mockResolvedValue('# Plan');
@@ -295,7 +295,7 @@ describe('WorkflowPlan Command', () => {
295
295
  flags: { 'force-agent-file-write': false }
296
296
  });
297
297
  // First call fails with ENOENT, second succeeds
298
- vi.mocked(ensureOutputAIStructure).mockRejectedValueOnce(Object.assign(new Error('Directory does not exist'), { code: 'ENOENT' }));
298
+ vi.mocked(ensureOutputAISystem).mockRejectedValueOnce(Object.assign(new Error('Directory does not exist'), { code: 'ENOENT' }));
299
299
  await expect(command.run()).rejects.toThrow();
300
300
  });
301
301
  it('should handle malformed API responses', async () => {
@@ -304,7 +304,7 @@ describe('WorkflowPlan Command', () => {
304
304
  args: {},
305
305
  flags: { 'force-agent-file-write': false }
306
306
  });
307
- vi.mocked(ensureOutputAIStructure).mockResolvedValue();
307
+ vi.mocked(ensureOutputAISystem).mockResolvedValue();
308
308
  vi.mocked(input).mockResolvedValue('Test workflow');
309
309
  vi.mocked(generatePlanName).mockResolvedValue('2025_10_06_test');
310
310
  const malformedError = new ClaudeInvocationError('Invalid JSON response');
@@ -323,7 +323,7 @@ describe('WorkflowPlan Command', () => {
323
323
  vi.mocked(updateAgentTemplates).mockResolvedValue();
324
324
  await command.run();
325
325
  // Verify templates are updated before plan generation
326
- const ensureCall = vi.mocked(ensureOutputAIStructure).mock.invocationCallOrder[0];
326
+ const ensureCall = vi.mocked(ensureOutputAISystem).mock.invocationCallOrder[0];
327
327
  const updateCall = vi.mocked(updateAgentTemplates).mock.invocationCallOrder[0];
328
328
  expect(updateCall).toBeGreaterThan(ensureCall);
329
329
  });
@@ -10,17 +10,17 @@ export declare class ClaudeInvocationError extends Error {
10
10
  }
11
11
  export declare function replyToClaude(message: string, options?: Options): Promise<string>;
12
12
  /**
13
- * Invoke claude-code with /plan_workflow slash command
13
+ * Invoke claude-code with /outputai:plan_workflow slash command
14
14
  * The SDK loads custom commands from .claude/commands/ when settingSources includes 'project'.
15
- * ensureOutputAIStructure() scaffolds the command files to that location.
15
+ * ensureOutputAISystem() scaffolds the command files to that location.
16
16
  * @param description - Workflow description
17
17
  * @returns Plan output from claude-code
18
18
  */
19
19
  export declare function invokePlanWorkflow(description: string): Promise<string>;
20
20
  /**
21
- * Invoke claude-code with /build_workflow slash command
21
+ * Invoke claude-code with /outputai:build_workflow slash command
22
22
  * The SDK loads custom commands from .claude/commands/ when settingSources includes 'project'.
23
- * ensureOutputAIStructure() scaffolds the command files to that location.
23
+ * ensureOutputAISystem() scaffolds the command files to that location.
24
24
  * @param planFilePath - Absolute path to the plan file
25
25
  * @param workflowDir - Absolute path to the workflow directory
26
26
  * @param workflowName - Name of the workflow
@@ -15,7 +15,7 @@ describe('invokePlanWorkflow - Integration Tests', () => {
15
15
  const messages = [];
16
16
  try {
17
17
  for await (const message of query({
18
- prompt: `/plan_workflow ${description}`,
18
+ prompt: `/outputai:plan_workflow ${description}`,
19
19
  options: { maxTurns: 1 }
20
20
  })) {
21
21
  console.log('\nReceived message:', JSON.stringify(message, null, 2));
@@ -31,7 +31,7 @@ describe('invokePlanWorkflow - Integration Tests', () => {
31
31
  // This test is just for debugging - we expect messages
32
32
  expect(messages.length).toBeGreaterThan(0);
33
33
  }, 60000); // 60 second timeout
34
- it('should successfully invoke /plan_workflow slash command and return content', async () => {
34
+ it('should successfully invoke /outputai:plan_workflow slash command and return content', async () => {
35
35
  const description = 'Simple workflow that takes a number and doubles it';
36
36
  const result = await invokePlanWorkflow(description);
37
37
  console.log('\n===== PLAN RESULT =====');
@@ -33,8 +33,8 @@ const ADDITIONAL_INSTRUCTIONS_BUILD = `
33
33
 
34
34
  4. After you mark all todos as complete, provide a summary of what was implemented.
35
35
  `;
36
- const PLAN_COMMAND = 'plan_workflow';
37
- const BUILD_COMMAND = 'build_workflow';
36
+ const PLAN_COMMAND = 'outputai:plan_workflow';
37
+ const BUILD_COMMAND = 'outputai:build_workflow';
38
38
  const GLOBAL_CLAUDE_OPTIONS = {
39
39
  settingSources: ['user', 'project', 'local']
40
40
  };
@@ -86,7 +86,7 @@ function displaySystemValidationWarnings(validation) {
86
86
  ux.warn(`Missing required claude-code slash command: /${command}`);
87
87
  });
88
88
  ux.warn('Your claude-code agent is missing key configurations, it may not behave as expected.');
89
- ux.warn('Please run "output-cli agents init" to fix this.');
89
+ ux.warn('Please run "npx output agents init" to fix this.');
90
90
  }
91
91
  function applyDefaultOptions(options) {
92
92
  return {
@@ -186,9 +186,9 @@ export async function replyToClaude(message, options = {}) {
186
186
  return singleQuery(applyInstructions(message), { continue: true, ...options });
187
187
  }
188
188
  /**
189
- * Invoke claude-code with /plan_workflow slash command
189
+ * Invoke claude-code with /outputai:plan_workflow slash command
190
190
  * The SDK loads custom commands from .claude/commands/ when settingSources includes 'project'.
191
- * ensureOutputAIStructure() scaffolds the command files to that location.
191
+ * ensureOutputAISystem() scaffolds the command files to that location.
192
192
  * @param description - Workflow description
193
193
  * @returns Plan output from claude-code
194
194
  */
@@ -196,9 +196,9 @@ export async function invokePlanWorkflow(description) {
196
196
  return singleQuery(applyInstructions(`/${PLAN_COMMAND} ${description}`), PLAN_COMMAND_OPTIONS);
197
197
  }
198
198
  /**
199
- * Invoke claude-code with /build_workflow slash command
199
+ * Invoke claude-code with /outputai:build_workflow slash command
200
200
  * The SDK loads custom commands from .claude/commands/ when settingSources includes 'project'.
201
- * ensureOutputAIStructure() scaffolds the command files to that location.
201
+ * ensureOutputAISystem() scaffolds the command files to that location.
202
202
  * @param planFilePath - Absolute path to the plan file
203
203
  * @param workflowDir - Absolute path to the workflow directory
204
204
  * @param workflowName - Name of the workflow