uda-cli 0.2.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 UDA Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,85 @@
1
+ # UDA (Universal Dev AI)
2
+
3
+ AI-agnostic context engineering + RAG CLI tool for game development.
4
+
5
+ ## Features
6
+
7
+ - **Local RAG**: LanceDB + MiniLM embeddings - no API keys needed
8
+ - **Multi-AI Support**: Claude, Cursor, Windsurf, AGENTS.md, Raw export
9
+ - **Plugin System**: Git-based engine plugins (Unity, Godot, Unreal, etc.)
10
+ - **Workflow Engine**: YAML-defined AI-assisted workflows
11
+ - **Project Context**: Automatic knowledge base management
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ npm install -g uda
17
+ ```
18
+
19
+ ## Quick Start
20
+
21
+ ```bash
22
+ # Initialize UDA in your project
23
+ uda init
24
+
25
+ # Scan and index knowledge
26
+ uda scan
27
+
28
+ # Search knowledge base
29
+ uda search "MonoBehaviour lifecycle"
30
+
31
+ # Export to your AI tool
32
+ uda export --format claude
33
+ ```
34
+
35
+ ## Commands
36
+
37
+ - `uda init` - Initialize UDA in current project
38
+ - `uda sync` - Generate AI tool files from knowledge base
39
+ - `uda search <query>` - Search knowledge base
40
+ - `uda learn <source>` - Teach knowledge to RAG
41
+ - `uda scan` - Scan project and index into RAG
42
+ - `uda plugin <action>` - Manage engine plugins
43
+ - `uda export --format <type>` - Export knowledge to specific format
44
+ - `uda status` - Show UDA system status
45
+ - `uda config [key] [value]` - Manage UDA settings
46
+
47
+ ## Supported Formats
48
+
49
+ - **claude**: Generates CLAUDE.md and .claude/ skills
50
+ - **cursor**: Generates .cursorrules
51
+ - **agents-md**: Generates AGENTS.md
52
+ - **raw**: Generates full-context.md
53
+
54
+ ## Plugin System
55
+
56
+ Install engine-specific knowledge:
57
+
58
+ ```bash
59
+ uda plugin add https://github.com/user/uda-plugin-unity.git
60
+ uda plugin update unity
61
+ uda plugin update-all
62
+ ```
63
+
64
+ ## Project Structure
65
+
66
+ ```
67
+ .uda/
68
+ ├── config.json # UDA configuration
69
+ ├── knowledge/
70
+ │ ├── engine/ # Engine plugins (Unity, Godot, etc.)
71
+ │ ├── project/ # Project-specific knowledge
72
+ │ └── community/ # Community contributions
73
+ ├── workflows/ # AI-assisted workflows
74
+ ├── agents/ # Specialized AI agents
75
+ ├── state/
76
+ │ ├── current.md # Active work state
77
+ │ ├── features/ # Feature specifications
78
+ │ └── history/ # Completed work
79
+ └── rag/
80
+ └── lancedb/ # Vector database
81
+ ```
82
+
83
+ ## License
84
+
85
+ MIT
package/bin/uda.js ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { createCli } from '../src/cli.js';
3
+ const program = createCli();
4
+ program.parse(process.argv);
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "uda-cli",
3
+ "version": "0.2.0",
4
+ "description": "Universal Dev AI — AI-agnostic context engineering + RAG for game development",
5
+ "type": "module",
6
+ "main": "./src/cli.js",
7
+ "bin": {
8
+ "uda-cli": "./bin/uda.js",
9
+ "uda": "./bin/uda.js"
10
+ },
11
+ "files": [
12
+ "bin/",
13
+ "src/",
14
+ "!src/**/*.test.js",
15
+ "README.md",
16
+ "LICENSE"
17
+ ],
18
+ "scripts": {
19
+ "test": "node --test src/**/*.test.js",
20
+ "test:coverage": "node --test --experimental-test-coverage src/**/*.test.js",
21
+ "dev": "node bin/uda.js"
22
+ },
23
+ "keywords": [
24
+ "ai",
25
+ "context-engineering",
26
+ "rag",
27
+ "game-dev",
28
+ "unity",
29
+ "godot",
30
+ "unreal",
31
+ "cli",
32
+ "vector-database",
33
+ "embeddings"
34
+ ],
35
+ "author": "UDA Contributors",
36
+ "license": "MIT",
37
+ "engines": {
38
+ "node": ">=18.0.0"
39
+ },
40
+ "dependencies": {
41
+ "@lancedb/lancedb": "^0.26.2",
42
+ "@xenova/transformers": "^2.17.2",
43
+ "commander": "^14.0.3",
44
+ "simple-git": "^3.32.3",
45
+ "yaml": "^2.8.2"
46
+ }
47
+ }
@@ -0,0 +1,22 @@
1
+ export class AgentsMdAdapter {
2
+ get name() { return 'agents-md'; }
3
+ detect() { return true; }
4
+
5
+ generate(knowledge, workflows, agents) {
6
+ const lines = ['# AGENTS.md', '', '> Generated by UDA (Universal Dev AI)', ''];
7
+
8
+ if (knowledge.conventions?.length > 0) {
9
+ lines.push('## Coding Conventions');
10
+ knowledge.conventions.forEach(c => lines.push(`- ${c}`));
11
+ lines.push('');
12
+ }
13
+
14
+ if (knowledge.decisions?.length > 0) {
15
+ lines.push('## Architecture');
16
+ knowledge.decisions.forEach(d => lines.push(`- ${d}`));
17
+ lines.push('');
18
+ }
19
+
20
+ return { 'AGENTS.md': lines.join('\n') };
21
+ }
22
+ }
@@ -0,0 +1,14 @@
1
+ // src/adapters/base.js
2
+ export class BaseAdapter {
3
+ constructor() {
4
+ if (new.target === BaseAdapter) {
5
+ throw new Error('BaseAdapter is abstract');
6
+ }
7
+ }
8
+
9
+ get name() { throw new Error('Not implemented'); }
10
+
11
+ detect(projectRoot) { throw new Error('Not implemented'); }
12
+
13
+ generate(knowledge, workflows, agents, projectRoot) { throw new Error('Not implemented'); }
14
+ }
@@ -0,0 +1,138 @@
1
+ // src/adapters/claude.js
2
+ import { existsSync } from 'fs';
3
+ import { join } from 'path';
4
+
5
+ export class ClaudeAdapter {
6
+ get name() { return 'claude'; }
7
+
8
+ detect(projectRoot) {
9
+ return (
10
+ existsSync(join(projectRoot, 'CLAUDE.md')) ||
11
+ existsSync(join(projectRoot, '.claude'))
12
+ );
13
+ }
14
+
15
+ generate(knowledge, workflows, agents, projectRoot, capabilities = {}) {
16
+ const files = {};
17
+ files['CLAUDE.md'] = this._generateClaudeMd(knowledge, workflows, capabilities);
18
+
19
+ for (const wf of workflows) {
20
+ const skillPath = `.claude/commands/uda/${wf.name}.md`;
21
+ files[skillPath] = this._generateSkill(wf);
22
+ }
23
+
24
+ for (const agent of agents) {
25
+ const agentPath = `.claude/agents/uda-${agent.name}.md`;
26
+ files[agentPath] = this._generateAgent(agent);
27
+ }
28
+
29
+ return files;
30
+ }
31
+
32
+ _generateClaudeMd(knowledge, workflows, capabilities) {
33
+ const lines = ['# CLAUDE.md', ''];
34
+
35
+ // Project info
36
+ if (knowledge.project) {
37
+ lines.push('## Project Info');
38
+ if (knowledge.project.name) lines.push(`- **Project**: ${knowledge.project.name}`);
39
+ if (knowledge.project.engine) lines.push(`- **Engine**: ${knowledge.project.engine}`);
40
+ if (knowledge.project.version) lines.push(`- **Version**: ${knowledge.project.version}`);
41
+ lines.push('');
42
+ }
43
+
44
+ if (knowledge.conventions?.length > 0) {
45
+ lines.push('## Conventions');
46
+ for (const conv of knowledge.conventions) lines.push(`- ${conv}`);
47
+ lines.push('');
48
+ }
49
+
50
+ if (knowledge.decisions?.length > 0) {
51
+ lines.push('## Architectural Decisions');
52
+ for (const dec of knowledge.decisions) lines.push(`- ${dec}`);
53
+ lines.push('');
54
+ }
55
+
56
+ // UDA AI-native instructions
57
+ lines.push('## UDA (Universal Dev AI)');
58
+ lines.push('');
59
+ lines.push('This project uses UDA for AI-assisted development. Follow these rules:');
60
+ lines.push('');
61
+
62
+ // Knowledge base instructions
63
+ lines.push('### Knowledge Base');
64
+ lines.push('- Engine knowledge is in `.uda/knowledge/`');
65
+ lines.push('- For engine questions, check these files FIRST');
66
+ lines.push('- For broader searches: `npx uda-cli search "query"`');
67
+ lines.push('');
68
+
69
+ // Log instructions (only if capability exists)
70
+ if (capabilities.logs) {
71
+ lines.push('### Console Logs');
72
+ lines.push(`- Log file: \`${capabilities.logs.source || '.uda/logs/console.jsonl'}\``);
73
+ lines.push('- Read logs: `npx uda-cli logs --errors --last 50`');
74
+ lines.push('- When user mentions logs, errors, or console issues, run this command');
75
+ lines.push('');
76
+ }
77
+
78
+ // Available commands
79
+ lines.push('### Available Commands');
80
+ lines.push('- `npx uda-cli search "query"` — search knowledge base');
81
+ if (capabilities.logs) {
82
+ lines.push('- `npx uda-cli logs [--errors|--warnings] [--last N]` — read engine logs');
83
+ }
84
+ lines.push('- `npx uda-cli plugin add <repo>` — add new plugin');
85
+ lines.push('- `npx uda-cli sync` — regenerate AI tool files');
86
+ lines.push('');
87
+
88
+ // Workflow skills
89
+ if (workflows.length > 0) {
90
+ lines.push('### UDA Skills');
91
+ for (const wf of workflows) {
92
+ lines.push(`- \`/uda:${wf.name}\` — ${wf.description}`);
93
+ }
94
+ lines.push('');
95
+ }
96
+
97
+ return lines.join('\n');
98
+ }
99
+
100
+ _generateSkill(workflow) {
101
+ const lines = [
102
+ '---',
103
+ `description: ${workflow.description}`,
104
+ '---',
105
+ '',
106
+ `# ${workflow.name}`,
107
+ '',
108
+ ];
109
+
110
+ if (workflow.steps) {
111
+ for (const step of workflow.steps) {
112
+ lines.push(`## ${step.name || step.id}`);
113
+ if (step.questions) {
114
+ for (const q of step.questions) {
115
+ lines.push(`- "${q}"`);
116
+ }
117
+ }
118
+ lines.push('');
119
+ }
120
+ }
121
+
122
+ return lines.join('\n');
123
+ }
124
+
125
+ _generateAgent(agent) {
126
+ return [
127
+ '---',
128
+ `name: uda-${agent.name}`,
129
+ `description: ${agent.description}`,
130
+ `tools: ${agent.tools || 'Read, Grep, Glob'}`,
131
+ `model: ${agent.model || 'sonnet'}`,
132
+ '---',
133
+ '',
134
+ agent.prompt || `You are a ${agent.description}.`,
135
+ '',
136
+ ].join('\n');
137
+ }
138
+ }
@@ -0,0 +1,46 @@
1
+ import { existsSync } from 'fs';
2
+ import { join } from 'path';
3
+
4
+ export class CursorAdapter {
5
+ get name() { return 'cursor'; }
6
+
7
+ detect(projectRoot) {
8
+ return (
9
+ existsSync(join(projectRoot, '.cursorrules')) ||
10
+ existsSync(join(projectRoot, '.cursor'))
11
+ );
12
+ }
13
+
14
+ generate(knowledge, workflows, agents, projectRoot) {
15
+ const files = {};
16
+ files['.cursorrules'] = this._generateRules(knowledge);
17
+ return files;
18
+ }
19
+
20
+ _generateRules(knowledge) {
21
+ const lines = [];
22
+
23
+ if (knowledge.project) {
24
+ lines.push(`Project: ${knowledge.project.name || 'Unknown'}`);
25
+ lines.push(`Engine: ${knowledge.project.engine || 'Unknown'}`);
26
+ lines.push('');
27
+ }
28
+
29
+ if (knowledge.conventions?.length > 0) {
30
+ lines.push('## Conventions');
31
+ for (const conv of knowledge.conventions) {
32
+ lines.push(`- ${conv}`);
33
+ }
34
+ lines.push('');
35
+ }
36
+
37
+ if (knowledge.decisions?.length > 0) {
38
+ lines.push('## Architecture');
39
+ for (const dec of knowledge.decisions) {
40
+ lines.push(`- ${dec}`);
41
+ }
42
+ }
43
+
44
+ return lines.join('\n');
45
+ }
46
+ }
@@ -0,0 +1,34 @@
1
+ export class RawAdapter {
2
+ get name() { return 'raw'; }
3
+ detect() { return true; } // always available
4
+
5
+ generate(knowledge, workflows, agents) {
6
+ const lines = ['# Project Context (UDA Generated)', ''];
7
+
8
+ if (knowledge.project) {
9
+ lines.push(`## Project: ${knowledge.project.name || 'Unknown'}`);
10
+ lines.push(`Engine: ${knowledge.project.engine || 'N/A'}`);
11
+ lines.push('');
12
+ }
13
+
14
+ if (knowledge.conventions?.length > 0) {
15
+ lines.push('## Conventions');
16
+ knowledge.conventions.forEach(c => lines.push(`- ${c}`));
17
+ lines.push('');
18
+ }
19
+
20
+ if (knowledge.decisions?.length > 0) {
21
+ lines.push('## Decisions');
22
+ knowledge.decisions.forEach(d => lines.push(`- ${d}`));
23
+ lines.push('');
24
+ }
25
+
26
+ if (workflows.length > 0) {
27
+ lines.push('## Available Workflows');
28
+ workflows.forEach(w => lines.push(`- **${w.name}**: ${w.description}`));
29
+ lines.push('');
30
+ }
31
+
32
+ return { '.uda/.generated/full-context.md': lines.join('\n') };
33
+ }
34
+ }
@@ -0,0 +1,18 @@
1
+ // src/adapters/registry.js
2
+ const adapters = [];
3
+
4
+ export function registerAdapter(adapter) {
5
+ adapters.push(adapter);
6
+ }
7
+
8
+ export function getAdapter(name) {
9
+ return adapters.find(a => a.name === name);
10
+ }
11
+
12
+ export function detectAdapters(projectRoot) {
13
+ return adapters.filter(a => a.detect(projectRoot));
14
+ }
15
+
16
+ export function getAllAdapters() {
17
+ return [...adapters];
18
+ }
package/src/cli.js ADDED
@@ -0,0 +1,106 @@
1
+ import { Command } from 'commander';
2
+ import { handleLearn } from './commands/learn.js';
3
+ import { handleSearch } from './commands/search.js';
4
+ import { handleScan } from './commands/scan.js';
5
+ import { handleSync } from './commands/sync.js';
6
+ import { handleInit } from './commands/init.js';
7
+ import { handlePluginAdd, handlePluginList, handlePluginRemove } from './commands/plugin.js';
8
+ import { handleStatus } from './commands/status.js';
9
+ import { handleConfig } from './commands/config.js';
10
+ import { handlePluginUpdate } from './commands/plugin.js';
11
+ import { handleExport } from './commands/export.js';
12
+ import { handleLogs } from './commands/logs.js';
13
+
14
+ export function createCli() {
15
+ const program = new Command();
16
+
17
+ program
18
+ .command('init')
19
+ .description('Initialize UDA in current project')
20
+ .option('-e, --engine <name>', 'Engine plugin to install (e.g. unity)')
21
+ .action(handleInit);
22
+
23
+ program
24
+ .command('sync')
25
+ .description('Generate AI tool files from knowledge base')
26
+ .action(handleSync);
27
+
28
+ program
29
+ .command('search <query>')
30
+ .description('Search knowledge base')
31
+ .option('-t, --top <number>', 'Number of results', '5')
32
+ .option('-f, --format <format>', 'Output format (terminal, md, clipboard)', 'terminal')
33
+ .action(handleSearch);
34
+
35
+ program
36
+ .command('learn <source>')
37
+ .description('Teach knowledge to RAG')
38
+ .option('--type <type>', 'Knowledge type (bug-fix, feature, pattern, knowledge)', 'knowledge')
39
+ .option('--tags <tags>', 'Comma-separated tags')
40
+ .action(handleLearn);
41
+
42
+ program
43
+ .command('scan')
44
+ .description('Scan project and index into RAG')
45
+ .action(handleScan);
46
+
47
+ const pluginCmd = program
48
+ .command('plugin')
49
+ .description('Manage engine plugins');
50
+
51
+ pluginCmd
52
+ .command('add <repo>')
53
+ .description('Install plugin from git repo')
54
+ .action(handlePluginAdd);
55
+
56
+ pluginCmd
57
+ .command('list')
58
+ .description('List installed plugins')
59
+ .action(handlePluginList);
60
+
61
+ pluginCmd
62
+ .command('remove <name>')
63
+ .description('Remove a plugin')
64
+ .action(handlePluginRemove);
65
+
66
+ pluginCmd
67
+ .command('update [name]')
68
+ .description('Update plugin(s)')
69
+ .action(handlePluginUpdate);
70
+
71
+ pluginCmd
72
+ .command('create <name>')
73
+ .description('Scaffold a new plugin')
74
+ .action(async (name) => {
75
+ console.log('uda plugin create — not yet implemented');
76
+ });
77
+
78
+ program
79
+ .command('export')
80
+ .description('Export knowledge to specific format')
81
+ .requiredOption('-f, --format <format>', 'Output format (claude, cursor, agents-md, raw)')
82
+ .option('-o, --output <path>', 'Output directory')
83
+ .action(handleExport);
84
+
85
+ program
86
+ .command('status')
87
+ .description('Show UDA system status')
88
+ .action(handleStatus);
89
+
90
+ program
91
+ .command('config')
92
+ .description('Manage UDA settings')
93
+ .argument('[key]', 'Config key to get/set')
94
+ .argument('[value]', 'Value to set')
95
+ .action(handleConfig);
96
+
97
+ program
98
+ .command('logs')
99
+ .description('Read engine console logs')
100
+ .option('-e, --errors', 'Show only errors')
101
+ .option('-w, --warnings', 'Show only warnings')
102
+ .option('-l, --last <count>', 'Show last N entries')
103
+ .action(handleLogs);
104
+
105
+ return program;
106
+ }
@@ -0,0 +1,55 @@
1
+ // src/commands/config.js
2
+ import { loadConfig, saveConfig, getConfigValue, setConfigValue } from '../core/config.js';
3
+ import { validateConfigKey } from '../core/validators.js';
4
+
5
+ export async function handleConfig(key, value) {
6
+ const root = process.cwd();
7
+
8
+ // Validate config key format if provided
9
+ if (key) {
10
+ const v = validateConfigKey(key);
11
+ if (!v.valid) {
12
+ console.error(`✘ ${v.error}`);
13
+ process.exitCode = 1;
14
+ return;
15
+ }
16
+ }
17
+
18
+ let config;
19
+ try {
20
+ config = await loadConfig(root);
21
+ } catch (err) {
22
+ console.error(`✘ Failed to load config: ${err.message}`);
23
+ console.error(' Run `uda init` to initialize the project.');
24
+ process.exitCode = 1;
25
+ return;
26
+ }
27
+
28
+ // No key — list all config
29
+ if (!key) {
30
+ console.log(JSON.stringify(config, null, 2));
31
+ return;
32
+ }
33
+
34
+ // Key only — get value
35
+ if (value === undefined) {
36
+ const result = getConfigValue(config, key);
37
+ if (result === undefined) {
38
+ console.error(`✘ Key "${key}" not found`);
39
+ process.exitCode = 1;
40
+ return;
41
+ }
42
+ console.log(typeof result === 'object' ? JSON.stringify(result, null, 2) : result);
43
+ return;
44
+ }
45
+
46
+ // Key + value — set value
47
+ try {
48
+ setConfigValue(config, key, value);
49
+ await saveConfig(root, config);
50
+ console.log(`✔ ${key} = ${JSON.stringify(getConfigValue(config, key))}`);
51
+ } catch (err) {
52
+ console.error(`✘ Failed to save config: ${err.message}`);
53
+ process.exitCode = 1;
54
+ }
55
+ }
@@ -0,0 +1,77 @@
1
+ // src/commands/export.js
2
+ import { writeFile, mkdir } from 'fs/promises';
3
+ import { join, dirname } from 'path';
4
+ import { udaPaths } from '../core/constants.js';
5
+ import { loadConfig } from '../core/config.js';
6
+ import { loadKnowledge, loadWorkflows, loadAgents } from '../core/knowledge-loader.js';
7
+ import { ClaudeAdapter } from '../adapters/claude.js';
8
+ import { CursorAdapter } from '../adapters/cursor.js';
9
+ import { RawAdapter } from '../adapters/raw.js';
10
+ import { AgentsMdAdapter } from '../adapters/agents-md.js';
11
+ import { validateExportFormat } from '../core/validators.js';
12
+
13
+ const ADAPTER_MAP = {
14
+ claude: () => new ClaudeAdapter(),
15
+ cursor: () => new CursorAdapter(),
16
+ 'agents-md': () => new AgentsMdAdapter(),
17
+ raw: () => new RawAdapter(),
18
+ };
19
+
20
+ export async function handleExport(options) {
21
+ const root = process.cwd();
22
+ const paths = udaPaths(root);
23
+ const format = options.format;
24
+
25
+ // Validate format
26
+ const fv = validateExportFormat(format);
27
+ if (!fv.valid) {
28
+ console.error(`✘ ${fv.error}`);
29
+ process.exitCode = 1;
30
+ return;
31
+ }
32
+
33
+ const createAdapter = ADAPTER_MAP[format];
34
+ if (!createAdapter) {
35
+ console.error(`✘ Unknown format "${format}". Available: ${Object.keys(ADAPTER_MAP).join(', ')}`);
36
+ process.exitCode = 1;
37
+ return;
38
+ }
39
+
40
+ let adapter, knowledge, workflows, agents;
41
+ try {
42
+ adapter = createAdapter();
43
+ knowledge = await loadKnowledge(paths);
44
+ workflows = await loadWorkflows(paths);
45
+ agents = await loadAgents(paths);
46
+ } catch (err) {
47
+ console.error(`✘ Failed to load knowledge base: ${err.message}`);
48
+ process.exitCode = 1;
49
+ return;
50
+ }
51
+
52
+ let files;
53
+ try {
54
+ files = adapter.generate(knowledge, workflows, agents, root);
55
+ } catch (err) {
56
+ console.error(`✘ Failed to generate ${format} output: ${err.message}`);
57
+ process.exitCode = 1;
58
+ return;
59
+ }
60
+
61
+ // Determine output base directory
62
+ const outputBase = options.output || join(paths.generated, format);
63
+
64
+ let totalFiles = 0;
65
+ for (const [filePath, content] of Object.entries(files)) {
66
+ const fullPath = join(outputBase, filePath);
67
+ try {
68
+ await mkdir(dirname(fullPath), { recursive: true });
69
+ await writeFile(fullPath, content);
70
+ totalFiles++;
71
+ } catch (err) {
72
+ console.error(` ✘ Failed to write ${filePath}: ${err.message}`);
73
+ }
74
+ }
75
+
76
+ console.log(`✔ Exported ${totalFiles} files (${format}) → ${outputBase}`);
77
+ }