@output.ai/cli 0.0.4 → 0.0.6

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 (50) hide show
  1. package/dist/api/http_client.js +1 -1
  2. package/dist/commands/agents/init.d.ts +2 -9
  3. package/dist/commands/agents/init.js +8 -145
  4. package/dist/commands/agents/init.spec.js +31 -26
  5. package/dist/commands/workflow/generate.d.ts +5 -5
  6. package/dist/commands/workflow/generate.js +2 -2
  7. package/dist/commands/workflow/generate.spec.js +2 -2
  8. package/dist/commands/workflow/list.d.ts +4 -4
  9. package/dist/commands/workflow/list.js +3 -3
  10. package/dist/commands/workflow/output.d.ts +2 -2
  11. package/dist/commands/workflow/output.js +4 -4
  12. package/dist/commands/workflow/plan.d.ts +12 -0
  13. package/dist/commands/workflow/plan.js +65 -0
  14. package/dist/commands/workflow/plan.spec.d.ts +1 -0
  15. package/dist/commands/workflow/plan.spec.js +339 -0
  16. package/dist/commands/workflow/run.d.ts +4 -4
  17. package/dist/commands/workflow/run.js +5 -5
  18. package/dist/commands/workflow/start.d.ts +3 -3
  19. package/dist/commands/workflow/start.js +3 -3
  20. package/dist/commands/workflow/status.d.ts +2 -2
  21. package/dist/commands/workflow/status.js +4 -4
  22. package/dist/commands/workflow/stop.d.ts +1 -1
  23. package/dist/commands/workflow/stop.js +2 -2
  24. package/dist/config.d.ts +4 -0
  25. package/dist/config.js +4 -0
  26. package/dist/services/claude_client.d.ts +13 -0
  27. package/dist/services/claude_client.integration.test.d.ts +1 -0
  28. package/dist/services/claude_client.integration.test.js +43 -0
  29. package/dist/services/claude_client.js +155 -0
  30. package/dist/services/claude_client.spec.d.ts +1 -0
  31. package/dist/services/claude_client.spec.js +141 -0
  32. package/dist/services/coding_agents.d.ts +43 -0
  33. package/dist/services/coding_agents.js +230 -0
  34. package/dist/services/coding_agents.spec.d.ts +1 -0
  35. package/dist/services/coding_agents.spec.js +254 -0
  36. package/dist/services/generate_plan_name@v1.prompt +24 -0
  37. package/dist/services/template_processor.d.ts +1 -1
  38. package/dist/services/template_processor.js +1 -1
  39. package/dist/services/workflow_generator.d.ts +1 -1
  40. package/dist/services/workflow_generator.js +4 -4
  41. package/dist/services/workflow_planner.d.ts +20 -0
  42. package/dist/services/workflow_planner.js +83 -0
  43. package/dist/services/workflow_planner.spec.d.ts +1 -0
  44. package/dist/services/workflow_planner.spec.js +208 -0
  45. package/dist/templates/agent_instructions/commands/plan_workflow.md.template +180 -386
  46. package/dist/test_helpers/mocks.d.ts +37 -0
  47. package/dist/test_helpers/mocks.js +67 -0
  48. package/dist/utils/error_handler.js +1 -1
  49. package/dist/utils/validation.js +1 -1
  50. package/package.json +13 -3
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Claude Agent SDK client for workflow planning
3
+ */
4
+ import { query } from '@anthropic-ai/claude-agent-sdk';
5
+ import { ux } from '@oclif/core';
6
+ import * as cliProgress from 'cli-progress';
7
+ const ADDITIONAL_INSTRUCTIONS = `
8
+ ! IMPORTANT !
9
+ 1. Use TodoWrite to track your progress through plan creation.
10
+
11
+ 2. Please response with only the final version of the plan.
12
+
13
+ 3. Response in a markdown format with these metadata headers:
14
+
15
+ ---
16
+ title: <plan-title>
17
+ description: <plan-description>
18
+ date: <plan-date>
19
+ ---
20
+
21
+ <plan-content>
22
+
23
+ 4. After you mark all todos as complete, you must respond with the final version of the plan.
24
+ `;
25
+ const PLAN_COMMAND = 'plan_workflow';
26
+ const GLOBAL_CLAUDE_OPTIONS = {
27
+ settingSources: ['user', 'project', 'local'],
28
+ allowedTools: [
29
+ 'Read',
30
+ 'Grep',
31
+ 'WebSearch',
32
+ 'WebFetch',
33
+ 'TodoWrite'
34
+ ]
35
+ };
36
+ export class ClaudeInvocationError extends Error {
37
+ cause;
38
+ constructor(message, cause) {
39
+ super(message);
40
+ this.cause = cause;
41
+ this.name = 'ClaudeInvocationError';
42
+ }
43
+ }
44
+ function validateEnvironment() {
45
+ if (!process.env.ANTHROPIC_API_KEY) {
46
+ throw new Error('ANTHROPIC_API_KEY environment variable is required');
47
+ }
48
+ }
49
+ function validateSystem(systemMessage) {
50
+ const requiredCommands = [PLAN_COMMAND];
51
+ const availableCommands = systemMessage.slash_commands;
52
+ 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.');
59
+ }
60
+ }
61
+ function applyDefaultOptions(options) {
62
+ return {
63
+ ...GLOBAL_CLAUDE_OPTIONS,
64
+ ...options
65
+ };
66
+ }
67
+ function getTodoWriteMessage(message) {
68
+ if (message.type !== 'assistant') {
69
+ return null;
70
+ }
71
+ const todoWriteMessage = message.message.content.find((c) => c?.type === 'tool_use' && c.name === 'TodoWrite');
72
+ return todoWriteMessage ?? null;
73
+ }
74
+ function applyInstructions(initialMessage) {
75
+ return `${initialMessage}\n\n${ADDITIONAL_INSTRUCTIONS}`;
76
+ }
77
+ function createProgressBar() {
78
+ return new cliProgress.SingleBar({
79
+ format: '{bar} | {message} ({percentage}%)',
80
+ barCompleteChar: '█',
81
+ barIncompleteChar: '░',
82
+ hideCursor: true,
83
+ barsize: 40,
84
+ fps: 10,
85
+ stopOnComplete: false
86
+ });
87
+ }
88
+ function calculateProgress(completedCount, totalCount) {
89
+ if (totalCount === 0) {
90
+ return 0;
91
+ }
92
+ const percentage = ((completedCount + 1) / (totalCount + 1)) * 100;
93
+ return Math.round(percentage * 10) / 10;
94
+ }
95
+ function getProgressUpdate(message) {
96
+ const todoWriteMessage = getTodoWriteMessage(message);
97
+ if (!todoWriteMessage) {
98
+ return null;
99
+ }
100
+ const allTodos = todoWriteMessage.input.todos;
101
+ const inProgressTodo = allTodos.find(t => t.status === 'in_progress');
102
+ if (!inProgressTodo?.content) {
103
+ return null;
104
+ }
105
+ const completedTodos = allTodos.filter(t => t.status === 'completed');
106
+ return {
107
+ message: `${inProgressTodo.content}...`,
108
+ completed: completedTodos.length,
109
+ total: allTodos.length
110
+ };
111
+ }
112
+ async function singleQuery(prompt, options = {}) {
113
+ validateEnvironment();
114
+ const progressBar = createProgressBar();
115
+ progressBar.start(100, 0, { message: 'Thinking...' });
116
+ try {
117
+ for await (const message of query({
118
+ prompt,
119
+ options: applyDefaultOptions(options)
120
+ })) {
121
+ if (message.type === 'system' && message.subtype === 'init') {
122
+ validateSystem(message);
123
+ progressBar.update(1, { message: 'Diving in...' });
124
+ }
125
+ const progressUpdate = getProgressUpdate(message);
126
+ if (progressUpdate) {
127
+ const percentage = calculateProgress(progressUpdate.completed, progressUpdate.total);
128
+ progressBar.update(percentage, { message: progressUpdate.message });
129
+ }
130
+ if (message.type === 'result' && message.subtype === 'success') {
131
+ progressBar.update(100, { message: 'Complete!' });
132
+ progressBar.stop();
133
+ return message.result;
134
+ }
135
+ }
136
+ throw new Error('No output received from claude-code');
137
+ }
138
+ catch (error) {
139
+ progressBar.stop();
140
+ throw new ClaudeInvocationError(`Failed to invoke claude-code: ${error.message}`, error);
141
+ }
142
+ }
143
+ export async function replyToClaude(message) {
144
+ return singleQuery(applyInstructions(message), { continue: true });
145
+ }
146
+ /**
147
+ * Invoke claude-code with /plan_workflow slash command
148
+ * The SDK loads custom commands from .claude/commands/ when settingSources includes 'project'.
149
+ * ensureOutputAIStructure() scaffolds the command files to that location.
150
+ * @param description - Workflow description
151
+ * @returns Plan output from claude-code
152
+ */
153
+ export async function invokePlanWorkflow(description) {
154
+ return singleQuery(applyInstructions(`/${PLAN_COMMAND} ${description}`));
155
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,141 @@
1
+ import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
2
+ import { invokePlanWorkflow, ClaudeInvocationError } from './claude_client.js';
3
+ // Mock Claude SDK
4
+ vi.mock('@anthropic-ai/claude-agent-sdk', () => ({
5
+ query: vi.fn()
6
+ }));
7
+ describe('invokePlanWorkflow', () => {
8
+ beforeEach(() => {
9
+ vi.clearAllMocks();
10
+ });
11
+ afterEach(() => {
12
+ // Clean up environment variables
13
+ delete process.env.ANTHROPIC_API_KEY;
14
+ });
15
+ it('should invoke /plan_workflow slash command with settingSources', async () => {
16
+ const { query } = await import('@anthropic-ai/claude-agent-sdk');
17
+ process.env.ANTHROPIC_API_KEY = 'test-key';
18
+ async function* mockIterator() {
19
+ yield { type: 'result', subtype: 'success', result: '# Test Plan\n\nTest plan content' };
20
+ }
21
+ vi.mocked(query).mockReturnValue(mockIterator());
22
+ await invokePlanWorkflow('Test workflow');
23
+ const calls = vi.mocked(query).mock.calls;
24
+ expect(calls[0]?.[0]?.prompt).toContain('/plan_workflow Test workflow');
25
+ expect(calls[0]?.[0]?.options?.settingSources).toEqual(['user', 'project', 'local']);
26
+ expect(calls[0]?.[0]?.options?.allowedTools).toEqual(['Read', 'Grep', 'WebSearch', 'WebFetch', 'TodoWrite']);
27
+ });
28
+ it('should pass workflow description to slash command', async () => {
29
+ const { query } = await import('@anthropic-ai/claude-agent-sdk');
30
+ process.env.ANTHROPIC_API_KEY = 'test-key';
31
+ async function* mockIterator() {
32
+ yield { type: 'result', subtype: 'success', result: '# Plan\n\nContent' };
33
+ }
34
+ vi.mocked(query).mockReturnValue(mockIterator());
35
+ const description = 'Build a user authentication system';
36
+ await invokePlanWorkflow(description);
37
+ const calls = vi.mocked(query).mock.calls;
38
+ expect(calls[0]?.[0]?.prompt).toContain(`/plan_workflow ${description}`);
39
+ expect(calls[0]?.[0]?.options?.settingSources).toEqual(['user', 'project', 'local']);
40
+ });
41
+ it('should return plan output from claude-code', async () => {
42
+ const { query } = await import('@anthropic-ai/claude-agent-sdk');
43
+ process.env.ANTHROPIC_API_KEY = 'test-key';
44
+ const expectedOutput = '# Workflow Plan\n\nDetailed plan content here';
45
+ async function* mockIterator() {
46
+ yield { type: 'result', subtype: 'success', result: expectedOutput };
47
+ }
48
+ vi.mocked(query).mockReturnValue(mockIterator());
49
+ const result = await invokePlanWorkflow('Test');
50
+ expect(result).toBe(expectedOutput);
51
+ });
52
+ it('should throw error if API key is missing', async () => {
53
+ delete process.env.ANTHROPIC_API_KEY;
54
+ await expect(invokePlanWorkflow('Test'))
55
+ .rejects.toThrow('ANTHROPIC_API_KEY');
56
+ });
57
+ describe('error handling', () => {
58
+ it('should throw ClaudeInvocationError on API failures', async () => {
59
+ const { query } = await import('@anthropic-ai/claude-agent-sdk');
60
+ process.env.ANTHROPIC_API_KEY = 'test-key';
61
+ const apiError = new Error('API connection failed');
62
+ vi.mocked(query).mockRejectedValue(apiError);
63
+ await expect(invokePlanWorkflow('Test'))
64
+ .rejects.toThrow(ClaudeInvocationError);
65
+ });
66
+ it('should preserve original error in cause property', async () => {
67
+ const { query } = await import('@anthropic-ai/claude-agent-sdk');
68
+ process.env.ANTHROPIC_API_KEY = 'test-key';
69
+ const originalError = new Error('Network timeout');
70
+ // Mock async iterator that throws
71
+ async function* mockIterator() {
72
+ throw originalError;
73
+ }
74
+ vi.mocked(query).mockReturnValue(mockIterator());
75
+ try {
76
+ await invokePlanWorkflow('Test');
77
+ // If we get here, the test should fail
78
+ expect.fail('Should have thrown an error');
79
+ }
80
+ catch (error) {
81
+ expect(error).toBeInstanceOf(ClaudeInvocationError);
82
+ expect(error.cause).toBe(originalError);
83
+ }
84
+ });
85
+ it('should handle rate limit errors', async () => {
86
+ const { query } = await import('@anthropic-ai/claude-agent-sdk');
87
+ process.env.ANTHROPIC_API_KEY = 'test-key';
88
+ const rateLimitError = new Error('Rate limit exceeded');
89
+ rateLimitError.status = 429;
90
+ vi.mocked(query).mockRejectedValue(rateLimitError);
91
+ await expect(invokePlanWorkflow('Test'))
92
+ .rejects.toThrow(ClaudeInvocationError);
93
+ });
94
+ it('should handle authentication errors', async () => {
95
+ const { query } = await import('@anthropic-ai/claude-agent-sdk');
96
+ process.env.ANTHROPIC_API_KEY = 'invalid-key';
97
+ const authError = new Error('Invalid API key');
98
+ authError.status = 401;
99
+ vi.mocked(query).mockRejectedValue(authError);
100
+ await expect(invokePlanWorkflow('Test'))
101
+ .rejects.toThrow(ClaudeInvocationError);
102
+ });
103
+ it('should provide user-friendly error messages', async () => {
104
+ const { query } = await import('@anthropic-ai/claude-agent-sdk');
105
+ process.env.ANTHROPIC_API_KEY = 'test-key';
106
+ vi.mocked(query).mockRejectedValue(new Error('API error'));
107
+ try {
108
+ await invokePlanWorkflow('Test');
109
+ }
110
+ catch (error) {
111
+ expect(error.message).toMatch(/Failed to invoke/i);
112
+ }
113
+ });
114
+ it('should throw error when no result received', async () => {
115
+ const { query } = await import('@anthropic-ai/claude-agent-sdk');
116
+ process.env.ANTHROPIC_API_KEY = 'test-key';
117
+ // Mock iterator that yields no result messages
118
+ async function* mockIterator() {
119
+ yield { type: 'assistant', message: { content: [] } };
120
+ }
121
+ vi.mocked(query).mockReturnValue(mockIterator());
122
+ await expect(invokePlanWorkflow('Test'))
123
+ .rejects.toThrow(ClaudeInvocationError);
124
+ });
125
+ });
126
+ });
127
+ describe('ClaudeInvocationError', () => {
128
+ it('should be instance of Error', () => {
129
+ const error = new ClaudeInvocationError('test message');
130
+ expect(error).toBeInstanceOf(Error);
131
+ });
132
+ it('should have correct name property', () => {
133
+ const error = new ClaudeInvocationError('test message');
134
+ expect(error.name).toBe('ClaudeInvocationError');
135
+ });
136
+ it('should store cause error', () => {
137
+ const causeError = new Error('original error');
138
+ const error = new ClaudeInvocationError('test message', causeError);
139
+ expect(error.cause).toBe(causeError);
140
+ });
141
+ });
@@ -0,0 +1,43 @@
1
+ export interface FileMapping {
2
+ from: string;
3
+ to: string;
4
+ type: 'template' | 'symlink' | 'copy';
5
+ }
6
+ export interface AgentSystemConfig {
7
+ id: string;
8
+ name: string;
9
+ mappings: FileMapping[];
10
+ }
11
+ export interface StructureCheckResult {
12
+ dirExists: boolean;
13
+ missingFiles: string[];
14
+ isComplete: boolean;
15
+ }
16
+ export interface InitOptions {
17
+ projectRoot: string;
18
+ force: boolean;
19
+ agentProvider: string;
20
+ }
21
+ /**
22
+ * Agent configuration mappings for different providers
23
+ */
24
+ export declare const AGENT_CONFIGS: Record<string, AgentSystemConfig>;
25
+ export declare function getRequiredFiles(): string[];
26
+ /**
27
+ * Get the full path to the agent configuration directory
28
+ */
29
+ export declare function getAgentConfigDir(projectRoot: string): string;
30
+ /**
31
+ * Check if .outputai directory exists
32
+ */
33
+ export declare function checkAgentConfigDirExists(projectRoot: string): Promise<boolean>;
34
+ export declare function checkAgentStructure(projectRoot: string): Promise<StructureCheckResult>;
35
+ /**
36
+ * Prepare template variables for file generation
37
+ */
38
+ export declare function prepareTemplateVariables(): Record<string, string>;
39
+ /**
40
+ * Initialize agent configuration files
41
+ * Main entry point for agent initialization logic
42
+ */
43
+ export declare function initializeAgentConfig(options: InitOptions): Promise<void>;
@@ -0,0 +1,230 @@
1
+ /**
2
+ * Coding agent configuration service
3
+ * Handles initialization and validation of agent configuration files
4
+ */
5
+ import fs from 'node:fs/promises';
6
+ import { access } from 'node:fs/promises';
7
+ import path from 'node:path';
8
+ import { join } from 'node:path';
9
+ import { ux } from '@oclif/core';
10
+ import { AGENT_CONFIG_DIR } from '#config.js';
11
+ import { getTemplateDir } from '#utils/paths.js';
12
+ import { processTemplate } from '#utils/template.js';
13
+ /**
14
+ * Agent configuration mappings for different providers
15
+ */
16
+ export const AGENT_CONFIGS = {
17
+ outputai: {
18
+ id: 'outputai',
19
+ name: 'OutputAI Core Files',
20
+ mappings: [
21
+ {
22
+ type: 'template',
23
+ from: 'AGENTS.md.template',
24
+ to: `${AGENT_CONFIG_DIR}/AGENTS.md`
25
+ },
26
+ {
27
+ type: 'template',
28
+ from: 'agents/workflow_planner.md.template',
29
+ to: `${AGENT_CONFIG_DIR}/agents/workflow_planner.md`
30
+ },
31
+ {
32
+ type: 'template',
33
+ from: 'commands/plan_workflow.md.template',
34
+ to: `${AGENT_CONFIG_DIR}/commands/plan_workflow.md`
35
+ },
36
+ {
37
+ type: 'template',
38
+ from: 'meta/pre_flight.md.template',
39
+ to: '.outputai/meta/pre_flight.md'
40
+ },
41
+ {
42
+ type: 'template',
43
+ from: 'meta/post_flight.md.template',
44
+ to: '.outputai/meta/post_flight.md'
45
+ }
46
+ ]
47
+ },
48
+ 'claude-code': {
49
+ id: 'claude-code',
50
+ name: 'Claude Code',
51
+ mappings: [
52
+ {
53
+ type: 'symlink',
54
+ from: `${AGENT_CONFIG_DIR}/AGENTS.md`,
55
+ to: 'CLAUDE.md'
56
+ },
57
+ {
58
+ type: 'symlink',
59
+ from: `${AGENT_CONFIG_DIR}/agents/workflow_planner.md`,
60
+ to: '.claude/agents/workflow_planner.md'
61
+ },
62
+ {
63
+ type: 'symlink',
64
+ from: `${AGENT_CONFIG_DIR}/commands/plan_workflow.md`,
65
+ to: '.claude/commands/plan_workflow.md'
66
+ }
67
+ ]
68
+ }
69
+ };
70
+ export function getRequiredFiles() {
71
+ const outputaiFiles = AGENT_CONFIGS.outputai.mappings.map(m => m.to);
72
+ const claudeCodeFiles = AGENT_CONFIGS['claude-code'].mappings.map(m => m.to);
73
+ return [...outputaiFiles, ...claudeCodeFiles];
74
+ }
75
+ /**
76
+ * Get the full path to the agent configuration directory
77
+ */
78
+ export function getAgentConfigDir(projectRoot) {
79
+ return join(projectRoot, AGENT_CONFIG_DIR);
80
+ }
81
+ /**
82
+ * Check if .outputai directory exists
83
+ */
84
+ export async function checkAgentConfigDirExists(projectRoot) {
85
+ const agentConfigDir = getAgentConfigDir(projectRoot);
86
+ try {
87
+ await access(agentConfigDir);
88
+ return true;
89
+ }
90
+ catch {
91
+ return false;
92
+ }
93
+ }
94
+ async function fileExists(filePath) {
95
+ try {
96
+ await access(filePath);
97
+ return true;
98
+ }
99
+ catch {
100
+ return false;
101
+ }
102
+ }
103
+ export async function checkAgentStructure(projectRoot) {
104
+ const requiredFiles = getRequiredFiles();
105
+ const dirExists = await checkAgentConfigDirExists(projectRoot);
106
+ if (!dirExists) {
107
+ return {
108
+ dirExists: false,
109
+ missingFiles: requiredFiles,
110
+ isComplete: false
111
+ };
112
+ }
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);
120
+ return {
121
+ dirExists: true,
122
+ missingFiles,
123
+ isComplete: missingFiles.length === 0
124
+ };
125
+ }
126
+ /**
127
+ * Prepare template variables for file generation
128
+ */
129
+ export function prepareTemplateVariables() {
130
+ return {
131
+ date: new Date().toLocaleDateString('en-US', {
132
+ year: 'numeric',
133
+ month: 'long',
134
+ day: 'numeric'
135
+ })
136
+ };
137
+ }
138
+ /**
139
+ * Ensure a directory exists, creating it if necessary
140
+ */
141
+ async function ensureDirectoryExists(dir) {
142
+ try {
143
+ await fs.mkdir(dir, { recursive: true });
144
+ ux.stdout(ux.colorize('gray', `Created directory: ${dir}`));
145
+ }
146
+ catch (error) {
147
+ if (error.code !== 'EEXIST') {
148
+ throw error;
149
+ }
150
+ }
151
+ }
152
+ /**
153
+ * Create a file from a template
154
+ */
155
+ async function createFromTemplate(template, output, variables) {
156
+ const templateDir = getTemplateDir('agent_instructions');
157
+ const templatePath = path.join(templateDir, template);
158
+ const content = await fs.readFile(templatePath, 'utf-8');
159
+ const processed = processTemplate(content, variables);
160
+ await fs.writeFile(output, processed, 'utf-8');
161
+ ux.stdout(ux.colorize('gray', `Created from template: ${output}`));
162
+ }
163
+ /**
164
+ * Create a symlink, falling back to copying if symlinks are not supported
165
+ */
166
+ async function createSymlink(source, target) {
167
+ try {
168
+ if (await fileExists(target)) {
169
+ await fs.unlink(target);
170
+ }
171
+ const relativePath = path.relative(path.dirname(target), source);
172
+ await fs.symlink(relativePath, target);
173
+ ux.stdout(ux.colorize('gray', `Created symlink: ${target} -> ${source}`));
174
+ }
175
+ catch (error) {
176
+ const code = error.code;
177
+ if (code === 'ENOTSUP' || code === 'EPERM') {
178
+ ux.stdout(ux.colorize('gray', `Symlinks not supported, creating copy: ${target}`));
179
+ const content = await fs.readFile(source, 'utf-8');
180
+ await fs.writeFile(target, content, 'utf-8');
181
+ return;
182
+ }
183
+ throw error;
184
+ }
185
+ }
186
+ /**
187
+ * Copy a file from source to target
188
+ */
189
+ async function copyFile(source, target) {
190
+ const content = await fs.readFile(source, 'utf-8');
191
+ await fs.writeFile(target, content, 'utf-8');
192
+ ux.stdout(ux.colorize('gray', `Copied file: ${source} -> ${target}`));
193
+ }
194
+ /**
195
+ * Process file mappings for a specific agent configuration
196
+ */
197
+ async function processMappings(config, variables, force, projectRoot) {
198
+ for (const mapping of config.mappings) {
199
+ const fullPath = path.isAbsolute(mapping.to) ?
200
+ mapping.to :
201
+ path.join(projectRoot, mapping.to);
202
+ const dir = path.dirname(fullPath);
203
+ await ensureDirectoryExists(dir);
204
+ if (!force && await fileExists(fullPath)) {
205
+ ux.warn(`File already exists: ${mapping.to} (use --force to overwrite)`);
206
+ continue;
207
+ }
208
+ switch (mapping.type) {
209
+ case 'template':
210
+ await createFromTemplate(mapping.from, fullPath, variables);
211
+ break;
212
+ case 'symlink':
213
+ await createSymlink(mapping.from, fullPath);
214
+ break;
215
+ case 'copy':
216
+ await copyFile(mapping.from, fullPath);
217
+ break;
218
+ }
219
+ }
220
+ }
221
+ /**
222
+ * Initialize agent configuration files
223
+ * Main entry point for agent initialization logic
224
+ */
225
+ export async function initializeAgentConfig(options) {
226
+ const { projectRoot, force, agentProvider } = options;
227
+ const variables = prepareTemplateVariables();
228
+ await processMappings(AGENT_CONFIGS.outputai, variables, force, projectRoot);
229
+ await processMappings(AGENT_CONFIGS[agentProvider], variables, force, projectRoot);
230
+ }
@@ -0,0 +1 @@
1
+ export {};