@output.ai/cli 0.0.9 → 0.2.3

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 (65) hide show
  1. package/README.md +48 -0
  2. package/bin/copyassets.sh +8 -0
  3. package/bin/run.js +4 -0
  4. package/dist/api/generated/api.d.ts +31 -13
  5. package/dist/api/http_client.d.ts +8 -2
  6. package/dist/api/http_client.js +14 -6
  7. package/dist/api/orval_post_process.d.ts +2 -1
  8. package/dist/api/orval_post_process.js +18 -5
  9. package/dist/assets/docker/docker-compose-dev.yml +130 -0
  10. package/dist/commands/agents/init.js +4 -2
  11. package/dist/commands/dev/eject.d.ts +11 -0
  12. package/dist/commands/dev/eject.js +58 -0
  13. package/dist/commands/dev/eject.spec.d.ts +1 -0
  14. package/dist/commands/dev/eject.spec.js +109 -0
  15. package/dist/commands/dev/index.d.ts +11 -0
  16. package/dist/commands/dev/index.js +54 -0
  17. package/dist/commands/dev/index.spec.d.ts +1 -0
  18. package/dist/commands/dev/index.spec.js +150 -0
  19. package/dist/commands/init.d.ts +10 -0
  20. package/dist/commands/init.js +30 -0
  21. package/dist/commands/init.spec.d.ts +1 -0
  22. package/dist/commands/init.spec.js +109 -0
  23. package/dist/commands/workflow/run.js +5 -0
  24. package/dist/services/claude_client.js +15 -2
  25. package/dist/services/claude_client.spec.js +5 -1
  26. package/dist/services/coding_agents.js +13 -4
  27. package/dist/services/coding_agents.spec.js +2 -1
  28. package/dist/services/docker.d.ts +12 -0
  29. package/dist/services/docker.js +79 -0
  30. package/dist/services/env_configurator.d.ts +11 -0
  31. package/dist/services/env_configurator.js +158 -0
  32. package/dist/services/env_configurator.spec.d.ts +1 -0
  33. package/dist/services/env_configurator.spec.js +123 -0
  34. package/dist/services/messages.d.ts +5 -0
  35. package/dist/services/messages.js +233 -0
  36. package/dist/services/project_scaffold.d.ts +6 -0
  37. package/dist/services/project_scaffold.js +140 -0
  38. package/dist/services/project_scaffold.spec.d.ts +1 -0
  39. package/dist/services/project_scaffold.spec.js +43 -0
  40. package/dist/services/template_processor.d.ts +1 -1
  41. package/dist/services/template_processor.js +26 -11
  42. package/dist/services/workflow_builder.js +2 -1
  43. package/dist/templates/project/.env.template +9 -0
  44. package/dist/templates/project/.gitignore.template +33 -0
  45. package/dist/templates/project/README.md.template +60 -0
  46. package/dist/templates/project/package.json.template +26 -0
  47. package/dist/templates/project/src/simple/prompts/answer_question@v1.prompt.template +13 -0
  48. package/dist/templates/project/src/simple/steps.ts.template +16 -0
  49. package/dist/templates/project/src/simple/workflow.ts.template +22 -0
  50. package/dist/templates/project/tsconfig.json.template +20 -0
  51. package/dist/types/errors.d.ts +32 -0
  52. package/dist/types/errors.js +48 -0
  53. package/dist/utils/env_loader.d.ts +6 -0
  54. package/dist/utils/env_loader.js +43 -0
  55. package/dist/utils/error_utils.d.ts +24 -0
  56. package/dist/utils/error_utils.js +87 -0
  57. package/dist/utils/file_system.d.ts +3 -0
  58. package/dist/utils/file_system.js +33 -0
  59. package/dist/utils/process.d.ts +4 -0
  60. package/dist/utils/process.js +48 -0
  61. package/dist/utils/sdk_versions.d.ts +7 -0
  62. package/dist/utils/sdk_versions.js +28 -0
  63. package/dist/utils/sdk_versions.spec.d.ts +1 -0
  64. package/dist/utils/sdk_versions.spec.js +19 -0
  65. package/package.json +4 -2
@@ -0,0 +1,43 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { handleRunInitError } from './project_scaffold.js';
3
+ import { UserCancelledError } from '#types/errors.js';
4
+ // Mock the SDK versions utility
5
+ vi.mock('#utils/sdk_versions.js', () => ({
6
+ getSDKVersions: vi.fn().mockResolvedValue({
7
+ core: '0.1.5',
8
+ llm: '0.2.3',
9
+ http: '0.0.3',
10
+ cli: '0.1.1'
11
+ })
12
+ }));
13
+ // Mock other dependencies
14
+ vi.mock('@inquirer/prompts', () => ({
15
+ input: vi.fn()
16
+ }));
17
+ vi.mock('#utils/file_system.js');
18
+ vi.mock('#utils/process.js');
19
+ vi.mock('./env_configurator.js', () => ({
20
+ configureEnvironmentVariables: vi.fn().mockResolvedValue(false)
21
+ }));
22
+ vi.mock('./template_processor.js');
23
+ vi.mock('./coding_agents.js');
24
+ describe('project_scaffold', () => {
25
+ beforeEach(() => {
26
+ vi.clearAllMocks();
27
+ });
28
+ describe('handleRunInitError', () => {
29
+ it('should handle UserCancelledError', async () => {
30
+ const error = new UserCancelledError();
31
+ const warn = vi.fn();
32
+ const result = await handleRunInitError(error, warn);
33
+ expect(result.errorMessage).toBe(error.message);
34
+ expect(result.shouldCleanup).toBe(false);
35
+ });
36
+ it('should return error message for generic errors', async () => {
37
+ const error = new Error('Unknown error');
38
+ const warn = vi.fn();
39
+ const result = await handleRunInitError(error, warn);
40
+ expect(result.errorMessage).toBe('Failed to create project');
41
+ });
42
+ });
43
+ });
@@ -1,7 +1,7 @@
1
1
  import type { TemplateFile } from '#types/generator.js';
2
2
  /**
3
3
  * Get list of template files from a directory
4
- * Automatically discovers all .template files and derives output names
4
+ * Automatically discovers all .template files (including in subdirectories) and derives output names
5
5
  */
6
6
  export declare function getTemplateFiles(templatesDir: string): Promise<TemplateFile[]>;
7
7
  /**
@@ -2,25 +2,37 @@ import * as fs from 'node:fs/promises';
2
2
  import * as path from 'node:path';
3
3
  import { processTemplate } from '#utils/template.js';
4
4
  const TEMPLATE_EXTENSION = '.template';
5
- function isTemplateFile(file) {
6
- return file.endsWith(TEMPLATE_EXTENSION);
7
- }
8
- function fileToTemplateFile(file, templatesDir) {
5
+ const isTemplateFile = (file) => file.endsWith(TEMPLATE_EXTENSION);
6
+ const fileToTemplateFile = (file, templatesDir, relativePath = '') => {
7
+ const fullPath = relativePath ? path.join(relativePath, file) : file;
9
8
  return {
10
9
  name: file,
11
- path: path.join(templatesDir, file),
12
- outputName: file.replace(TEMPLATE_EXTENSION, '')
10
+ path: path.join(templatesDir, relativePath, file),
11
+ outputName: fullPath.replace(TEMPLATE_EXTENSION, '')
13
12
  };
13
+ };
14
+ const processEntry = async (entry, templatesDir, relativePath, getFilesRecursively) => {
15
+ if (entry.isDirectory()) {
16
+ const subDir = relativePath ? path.join(relativePath, entry.name) : entry.name;
17
+ return getFilesRecursively(templatesDir, subDir);
18
+ }
19
+ if (entry.isFile() && isTemplateFile(entry.name)) {
20
+ return [fileToTemplateFile(entry.name, templatesDir, relativePath)];
21
+ }
22
+ return [];
23
+ };
24
+ async function getTemplateFilesRecursive(templatesDir, relativePath = '') {
25
+ const fullPath = relativePath ? path.join(templatesDir, relativePath) : templatesDir;
26
+ const entries = await fs.readdir(fullPath, { withFileTypes: true });
27
+ const results = await Promise.all(entries.map(entry => processEntry(entry, templatesDir, relativePath, getTemplateFilesRecursive)));
28
+ return results.flatMap(x => x);
14
29
  }
15
30
  /**
16
31
  * Get list of template files from a directory
17
- * Automatically discovers all .template files and derives output names
32
+ * Automatically discovers all .template files (including in subdirectories) and derives output names
18
33
  */
19
34
  export async function getTemplateFiles(templatesDir) {
20
- const files = await fs.readdir(templatesDir);
21
- return files
22
- .filter(isTemplateFile)
23
- .map(file => fileToTemplateFile(file, templatesDir));
35
+ return getTemplateFilesRecursive(templatesDir);
24
36
  }
25
37
  /**
26
38
  * Process a single template file
@@ -29,6 +41,9 @@ export async function processTemplateFile(templateFile, targetDir, variables) {
29
41
  const templateContent = await fs.readFile(templateFile.path, 'utf-8');
30
42
  const processedContent = processTemplate(templateContent, variables);
31
43
  const outputPath = path.join(targetDir, templateFile.outputName);
44
+ // Create parent directories if they don't exist
45
+ const outputDir = path.dirname(outputPath);
46
+ await fs.mkdir(outputDir, { recursive: true });
32
47
  await fs.writeFile(outputPath, processedContent, 'utf-8');
33
48
  }
34
49
  /**
@@ -6,6 +6,7 @@ import { input } from '@inquirer/prompts';
6
6
  import { ux } from '@oclif/core';
7
7
  import fs from 'node:fs/promises';
8
8
  import path from 'node:path';
9
+ import { getErrorMessage } from '#utils/error_utils.js';
9
10
  const ACCEPT_KEY = 'ACCEPT';
10
11
  const SEPARATOR_LINE = '─'.repeat(80);
11
12
  function displayImplementationOutput(output, message) {
@@ -60,7 +61,7 @@ async function processModification(modification, currentOutput) {
60
61
  return updatedOutput;
61
62
  }
62
63
  catch (error) {
63
- ux.error(`Failed to apply modifications: ${error.message}`);
64
+ ux.error(`Failed to apply modifications: ${getErrorMessage(error)}`);
64
65
  ux.stdout('Continuing with previous version...\n');
65
66
  return currentOutput;
66
67
  }
@@ -0,0 +1,9 @@
1
+ # Docker Compose service name for development environment
2
+ DOCKER_SERVICE_NAME={{projectName}}
3
+
4
+ # Configure if you plan to use Anthropic Models in your LLM prompts
5
+ ANTHROPIC_API_KEY=<SECRET>
6
+
7
+ # Configure if you plan to use OpenAI in your LLM prompts
8
+ OPENAI_API_KEY=<SECRET>
9
+
@@ -0,0 +1,33 @@
1
+ # Dependencies
2
+ node_modules/
3
+ package-lock.json
4
+ yarn.lock
5
+ bun.lockb
6
+
7
+ # Build output
8
+ dist/
9
+ build/
10
+
11
+ # IDE & Editor
12
+ .vscode/
13
+ .idea/
14
+ *~
15
+ .env
16
+ .DS_Store
17
+ .env.local
18
+ .env.*.local
19
+
20
+ # TypeScript
21
+ *.tsbuildinfo
22
+ *.js.map
23
+ *.d.ts.map
24
+
25
+ # Logs
26
+ logs/
27
+ *.log
28
+ npm-debug.log*
29
+ yarn-debug.log*
30
+
31
+ # OS specific
32
+ Thumbs.db
33
+ .DS_Store
@@ -0,0 +1,60 @@
1
+ # {{projectName}}
2
+
3
+ {{description}}
4
+
5
+ ## Prerequisites
6
+
7
+ - Node.js >= 24.3
8
+ - Docker and Docker Compose (for local development)
9
+
10
+ ## Getting Started
11
+
12
+ ### 1. Install Dependencies
13
+
14
+ ```bash
15
+ npm install
16
+ ```
17
+
18
+ ### 2. Configure Environment
19
+
20
+ Copy `.env.example` to `.env` and add your API keys:
21
+
22
+ ```bash
23
+ cp .env.example .env
24
+ ```
25
+
26
+ Edit `.env` to add:
27
+ - `ANTHROPIC_API_KEY` - for Claude LLM integration
28
+ - `OPENAI_API_KEY` - for OpenAI LLM integration (optional)
29
+
30
+ ### 3. Start Output Services
31
+
32
+ ```bash
33
+ output dev
34
+ ```
35
+
36
+ This starts:
37
+ - Temporal server and UI (http://localhost:8080)
38
+ - PostgreSQL and Redis databases
39
+ - Output.ai API server (http://localhost:3001)
40
+ - Worker process for executing workflows
41
+
42
+ ### 4. Run a workflow
43
+
44
+ In a new terminal:
45
+
46
+ ```bash
47
+ output workflow run simple --input '{"question": "who really is ada lovelace?"}'
48
+ ```
49
+
50
+ ### 5. Stop Services
51
+
52
+ Press `Ctrl+C` in the terminal running `output dev` to stop all services gracefully.
53
+
54
+ ### 6. View Logs
55
+
56
+ Monitor workflow execution and system status in the Temporal UI:
57
+
58
+ ```bash
59
+ open http://localhost:8080
60
+ ```
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "{{projectName}}",
3
+ "version": "0.0.1",
4
+ "description": "{{description}}",
5
+ "type": "module",
6
+ "main": "dist/worker.js",
7
+ "scripts": {
8
+ "build": "rm -rf dist/* && tsc -p ./ && copyfiles -u 1 './src/**/*.prompt' dist",
9
+ "start-worker": "npm install && npm run build && output-worker",
10
+ "dev": "output dev"
11
+ },
12
+ "dependencies": {
13
+ "@output.ai/core": "^{{coreVersion}}",
14
+ "@output.ai/llm": "^{{llmVersion}}",
15
+ "@output.ai/http": "^{{httpVersion}}",
16
+ "copyfiles": "^{{cliVersion}}"
17
+ },
18
+ "devDependencies": {
19
+ "@output.ai/cli": "latest",
20
+ "@types/node": "^22.0.0",
21
+ "typescript": "^5.7.0"
22
+ },
23
+ "engines": {
24
+ "node": ">=24.3.0"
25
+ }
26
+ }
@@ -0,0 +1,13 @@
1
+ ---
2
+ provider: anthropic
3
+ model: claude-opus-4-1-20250805
4
+ temperature: 0.7
5
+ ---
6
+
7
+ <system>
8
+ You are a helpful assistant. Answer the user's question concisely and clearly.
9
+ </system>
10
+
11
+ <user>
12
+ Answer the following question: \{{ question }}
13
+ </user>
@@ -0,0 +1,16 @@
1
+ import { step, z } from '@output.ai/core';
2
+ import { generateText } from '@output.ai/llm';
3
+
4
+ export const answerQuestion = step( {
5
+ name: 'answerQuestion',
6
+ description: 'Answer a question using an LLM',
7
+ inputSchema: z.string(),
8
+ outputSchema: z.string(),
9
+ fn: async question => {
10
+ const response = await generateText( {
11
+ prompt: 'answer_question@v1',
12
+ variables: { question }
13
+ } );
14
+ return response;
15
+ }
16
+ } );
@@ -0,0 +1,22 @@
1
+ import { workflow, z } from '@output.ai/core';
2
+ import { answerQuestion } from './steps.js';
3
+
4
+ export default workflow( {
5
+ name: 'simple',
6
+ description: '{{description}}',
7
+ inputSchema: z.object( {
8
+ question: z.string().describe( 'A question to answer' )
9
+ } ),
10
+ outputSchema: z.object( {
11
+ answer: z.string().describe( 'The answer to the question' )
12
+ } ),
13
+ fn: async input => {
14
+ const answer = await answerQuestion( input.question );
15
+ return { answer };
16
+ },
17
+ options: {
18
+ retry: {
19
+ maximumAttempts: 3
20
+ }
21
+ }
22
+ } );
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ES2022",
5
+ "moduleResolution": "bundler",
6
+ "resolveJsonModule": true,
7
+ "allowJs": true,
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "declaration": true,
13
+ "declarationMap": true,
14
+ "sourceMap": true,
15
+ "outDir": "./dist",
16
+ "rootDir": "./src",
17
+ },
18
+ "include": ["./**/*.js", "./**/*.ts", "./**/*.prompt" ],
19
+ "exclude": ["node_modules", "dist"]
20
+ }
@@ -22,3 +22,35 @@ export declare class TemplateNotFoundError extends Error {
22
22
  export declare class InvalidOutputDirectoryError extends Error {
23
23
  constructor(outputDir: string, reason?: string);
24
24
  }
25
+ /**
26
+ * Error thrown when folder already exists during initialization
27
+ */
28
+ export declare class FolderAlreadyExistsError extends Error {
29
+ folderPath: string;
30
+ constructor(folderPath: string);
31
+ }
32
+ /**
33
+ * Error thrown when user cancels initialization
34
+ */
35
+ export declare class UserCancelledError extends Error {
36
+ constructor();
37
+ }
38
+ /**
39
+ * Type-safe error wrapper that preserves original error with context
40
+ */
41
+ export declare class ProjectInitError extends Error {
42
+ readonly originalError: Error;
43
+ readonly projectPath: string | null;
44
+ constructor(message: string, originalError: Error, projectPath?: string | null);
45
+ }
46
+ /**
47
+ * Error thrown when directory creation fails with project context
48
+ */
49
+ export declare class DirectoryCreationError extends Error {
50
+ readonly projectPath: string;
51
+ /**
52
+ * @param message - The error message
53
+ * @param projectPath - The path where directory creation failed
54
+ */
55
+ constructor(message: string, projectPath: string);
56
+ }
@@ -33,3 +33,51 @@ export class InvalidOutputDirectoryError extends Error {
33
33
  super(message);
34
34
  }
35
35
  }
36
+ /**
37
+ * Error thrown when folder already exists during initialization
38
+ */
39
+ export class FolderAlreadyExistsError extends Error {
40
+ folderPath;
41
+ constructor(folderPath) {
42
+ super(`Folder already exists: ${folderPath}`);
43
+ this.folderPath = folderPath;
44
+ }
45
+ }
46
+ /**
47
+ * Error thrown when user cancels initialization
48
+ */
49
+ export class UserCancelledError extends Error {
50
+ constructor() {
51
+ super('Init cancelled by user.');
52
+ }
53
+ }
54
+ /**
55
+ * Type-safe error wrapper that preserves original error with context
56
+ */
57
+ export class ProjectInitError extends Error {
58
+ originalError;
59
+ projectPath;
60
+ constructor(message, originalError, projectPath = null) {
61
+ super(message);
62
+ this.originalError = originalError;
63
+ this.projectPath = projectPath;
64
+ // Preserve original stack trace
65
+ if (originalError.stack) {
66
+ this.stack = originalError.stack;
67
+ }
68
+ }
69
+ }
70
+ /**
71
+ * Error thrown when directory creation fails with project context
72
+ */
73
+ export class DirectoryCreationError extends Error {
74
+ projectPath;
75
+ /**
76
+ * @param message - The error message
77
+ * @param projectPath - The path where directory creation failed
78
+ */
79
+ constructor(message, projectPath) {
80
+ super(message);
81
+ this.projectPath = projectPath;
82
+ }
83
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Load environment variables from .env files in the current working directory
3
+ * Loads in order: .env, then .env.local (if exists)
4
+ * .env.local overrides values from .env
5
+ */
6
+ export declare function loadEnvironment(): void;
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Environment loader utility
3
+ * Loads .env files from the current working directory
4
+ */
5
+ import * as fs from 'node:fs';
6
+ import * as path from 'node:path';
7
+ import * as dotenv from 'dotenv';
8
+ /**
9
+ * Load environment variables from .env files in the current working directory
10
+ * Loads in order: .env, then .env.local (if exists)
11
+ * .env.local overrides values from .env
12
+ */
13
+ export function loadEnvironment() {
14
+ const cwd = process.cwd();
15
+ // Load .env file
16
+ const envPath = path.join(cwd, '.env');
17
+ if (fs.existsSync(envPath)) {
18
+ try {
19
+ const result = dotenv.config({ path: envPath });
20
+ if (result.error) {
21
+ // Log warning but don't fail - malformed .env shouldn't break the CLI
22
+ console.warn(`Warning: Error parsing .env file: ${result.error.message}`);
23
+ }
24
+ }
25
+ catch (error) {
26
+ // Silent failure - .env loading is optional
27
+ console.warn(`Warning: Could not load .env file: ${error}`);
28
+ }
29
+ }
30
+ // Load .env.local file (overrides .env)
31
+ const envLocalPath = path.join(cwd, '.env.local');
32
+ if (fs.existsSync(envLocalPath)) {
33
+ try {
34
+ const result = dotenv.config({ path: envLocalPath });
35
+ if (result.error) {
36
+ console.warn(`Warning: Error parsing .env.local file: ${result.error.message}`);
37
+ }
38
+ }
39
+ catch (error) {
40
+ console.warn(`Warning: Could not load .env.local file: ${error}`);
41
+ }
42
+ }
43
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Type guard to check if an unknown value is an Error object
3
+ */
4
+ export declare function isError(error: unknown): error is Error;
5
+ /**
6
+ * Type guard to check if an error is a Node.js system error
7
+ */
8
+ export declare function isNodeError(error: unknown): error is NodeJS.ErrnoException;
9
+ /**
10
+ * Safely extract an error message from an unknown error type
11
+ */
12
+ export declare function getErrorMessage(error: unknown): string;
13
+ /**
14
+ * Safely extract an error code from a Node.js error
15
+ */
16
+ export declare function getErrorCode(error: unknown): string | undefined;
17
+ /**
18
+ * Convert an unknown error to a proper Error object
19
+ */
20
+ export declare function toError(error: unknown): Error;
21
+ /**
22
+ * Create a formatted error message with optional context
23
+ */
24
+ export declare function formatError(error: unknown, context?: string): string;
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Type guard to check if an unknown value is an Error object
3
+ */
4
+ export function isError(error) {
5
+ return (error instanceof Error ||
6
+ (typeof error === 'object' &&
7
+ error !== null &&
8
+ 'message' in error &&
9
+ typeof error.message === 'string'));
10
+ }
11
+ /**
12
+ * Type guard to check if an error is a Node.js system error
13
+ */
14
+ export function isNodeError(error) {
15
+ return isError(error) && 'code' in error;
16
+ }
17
+ /**
18
+ * Safely extract an error message from an unknown error type
19
+ */
20
+ export function getErrorMessage(error) {
21
+ if (isError(error)) {
22
+ return error.message;
23
+ }
24
+ if (typeof error === 'string') {
25
+ return error;
26
+ }
27
+ // Handle objects with a code property (like Node.js errors)
28
+ if (error && typeof error === 'object' && 'code' in error) {
29
+ const errorObj = error;
30
+ if (errorObj.message) {
31
+ return errorObj.message;
32
+ }
33
+ if (errorObj.code) {
34
+ return `Error: ${errorObj.code}`;
35
+ }
36
+ }
37
+ // Handle objects with a custom toString
38
+ if (error && typeof error === 'object' && 'toString' in error) {
39
+ const str = String(error);
40
+ // Avoid returning '[object Object]'
41
+ if (str !== '[object Object]') {
42
+ return str;
43
+ }
44
+ }
45
+ // Try to extract any useful information from the object
46
+ if (error && typeof error === 'object') {
47
+ try {
48
+ const json = JSON.stringify(error);
49
+ if (json && json !== '{}') {
50
+ return `Error: ${json}`;
51
+ }
52
+ }
53
+ catch {
54
+ // Ignore circular reference errors
55
+ }
56
+ }
57
+ return 'An unknown error occurred';
58
+ }
59
+ /**
60
+ * Safely extract an error code from a Node.js error
61
+ */
62
+ export function getErrorCode(error) {
63
+ // Check if it's a proper Node.js error with a code
64
+ if (isNodeError(error)) {
65
+ return error.code;
66
+ }
67
+ // Also handle plain objects with a code property (like mocked errors in tests)
68
+ return error && typeof error === 'object' && 'code' in error ?
69
+ error.code :
70
+ undefined;
71
+ }
72
+ /**
73
+ * Convert an unknown error to a proper Error object
74
+ */
75
+ export function toError(error) {
76
+ if (isError(error)) {
77
+ return error;
78
+ }
79
+ return new Error(getErrorMessage(error));
80
+ }
81
+ /**
82
+ * Create a formatted error message with optional context
83
+ */
84
+ export function formatError(error, context) {
85
+ const message = getErrorMessage(error);
86
+ return context ? `${context}: ${message}` : message;
87
+ }
@@ -0,0 +1,3 @@
1
+ export declare function directoryExists(dirPath: string): boolean;
2
+ export declare function createDirectory(dirPath: string): void;
3
+ export declare function removeDirectory(dirPath: string, onError?: (message: string) => void): void;
@@ -0,0 +1,33 @@
1
+ import fs from 'node:fs';
2
+ import { FolderAlreadyExistsError } from '#types/errors.js';
3
+ import { getErrorCode, getErrorMessage } from './error_utils.js';
4
+ export function directoryExists(dirPath) {
5
+ try {
6
+ fs.accessSync(dirPath);
7
+ return true;
8
+ }
9
+ catch (error) {
10
+ if (getErrorCode(error) === 'ENOENT') {
11
+ return false;
12
+ }
13
+ throw error;
14
+ }
15
+ }
16
+ export function createDirectory(dirPath) {
17
+ const exists = directoryExists(dirPath);
18
+ if (exists) {
19
+ throw new FolderAlreadyExistsError(dirPath);
20
+ }
21
+ fs.mkdirSync(dirPath, { recursive: true });
22
+ }
23
+ export function removeDirectory(dirPath, onError) {
24
+ try {
25
+ fs.rmSync(dirPath, { recursive: true, force: true });
26
+ }
27
+ catch (error) {
28
+ const message = getErrorMessage(error);
29
+ if (onError) {
30
+ onError(`Failed to cleanup folder: ${message}`);
31
+ }
32
+ }
33
+ }
@@ -0,0 +1,4 @@
1
+ export declare function executeCommand(command: string, args: string[], cwd: string): Promise<{
2
+ stderr: string[];
3
+ }>;
4
+ export declare function executeCommandWithMessages(command: () => Promise<void>, startMessage: string, successMessage: string): Promise<boolean>;