dotdotdot-cli 1.0.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/lib/context.js ADDED
@@ -0,0 +1,265 @@
1
+ 'use strict';
2
+
3
+ // ─────────────────────────────────────────────────────────────────────────────
4
+ // context.js — Rich environment context gathering for LLM prompts
5
+ // ─────────────────────────────────────────────────────────────────────────────
6
+
7
+ const os = require('os');
8
+ const path = require('path');
9
+ const fs = require('fs');
10
+ const { execSync, exec } = require('child_process');
11
+ const { promisify } = require('util');
12
+ const { CACHE_DIR } = require('./config');
13
+
14
+ const execAsync = promisify(exec);
15
+ const CACHE_FILE = path.join(CACHE_DIR, 'context-cache.json');
16
+ const CACHE_TTL = 3600000; // 1 hour
17
+
18
+ // ─── Safe exec wrappers ─────────────────────────────────────────────────────
19
+
20
+ function safeExec(cmd, fallback = '') {
21
+ try {
22
+ const suppress = process.platform === 'win32' ? ' 2>nul' : ' 2>/dev/null';
23
+ return execSync(cmd + suppress, { timeout: 2000, encoding: 'utf8' }).trim();
24
+ } catch {
25
+ return fallback;
26
+ }
27
+ }
28
+
29
+ async function safeExecAsync(cmd, fallback = '') {
30
+ try {
31
+ const suppress = process.platform === 'win32' ? ' 2>nul' : ' 2>/dev/null';
32
+ const { stdout } = await execAsync(cmd + suppress, { timeout: 2000 });
33
+ return stdout.trim();
34
+ } catch {
35
+ return fallback;
36
+ }
37
+ }
38
+
39
+ // ─── Cache management ───────────────────────────────────────────────────────
40
+
41
+ function loadCache() {
42
+ try {
43
+ const raw = fs.readFileSync(CACHE_FILE, 'utf8');
44
+ const data = JSON.parse(raw);
45
+ if (Date.now() - data._ts < CACHE_TTL) return data;
46
+ } catch { /* miss */ }
47
+ return null;
48
+ }
49
+
50
+ function saveCache(data) {
51
+ try {
52
+ fs.writeFileSync(CACHE_FILE, JSON.stringify({ ...data, _ts: Date.now() }));
53
+ } catch { /* ignore */ }
54
+ }
55
+
56
+ // ─── Shell detection ────────────────────────────────────────────────────────
57
+
58
+ function detectShell(cache) {
59
+ // NEVER use cached shell — user may switch between PowerShell, Git Bash, CMD.
60
+ // Shell detection is fast (<50ms), so always detect fresh.
61
+
62
+ const platform = process.platform;
63
+ let name, shellPath, version, isPowerShell = false, isCmd = false, isBash = false;
64
+
65
+ if (platform === 'win32') {
66
+ // Check for Git Bash / MINGW / MSYS FIRST.
67
+ // Key: $SHELL is set to a bash path (e.g. '/usr/bin/bash') only inside Git Bash.
68
+ // MSYSTEM alone is unreliable — it leaks into PowerShell on systems with Git installed.
69
+ const envShell = process.env.SHELL; // '/usr/bin/bash' in Git Bash, undefined in PS
70
+ const isMingwBash = envShell && (envShell.includes('bash') || envShell.includes('/sh'));
71
+
72
+ if (isMingwBash) {
73
+ name = 'bash'; shellPath = envShell; isBash = true;
74
+ version = safeExec('bash --version');
75
+ if (version.length > 80) version = version.split('\n')[0].slice(0, 80);
76
+ } else {
77
+ // Check for PowerShell 7+ first
78
+ const pwshVer = safeExec('pwsh -NoProfile -Command "$PSVersionTable.PSVersion.ToString()"');
79
+ if (pwshVer) {
80
+ name = 'pwsh'; shellPath = 'pwsh'; version = pwshVer; isPowerShell = true;
81
+ } else {
82
+ const psVer = safeExec('powershell -NoProfile -Command "$PSVersionTable.PSVersion.ToString()"');
83
+ if (psVer) {
84
+ name = 'powershell'; shellPath = 'powershell'; version = psVer; isPowerShell = true;
85
+ } else {
86
+ name = 'cmd'; shellPath = process.env.ComSpec || 'cmd.exe'; isCmd = true;
87
+ version = safeExec('ver');
88
+ }
89
+ }
90
+ }
91
+ } else {
92
+ shellPath = process.env.SHELL || '/bin/sh';
93
+ name = path.basename(shellPath);
94
+ version = safeExec(`${shellPath} --version`);
95
+ if (version.length > 80) version = version.slice(0, 80);
96
+ }
97
+
98
+ return { name, path: shellPath, version, isPowerShell, isCmd, isBash, platform };
99
+ }
100
+
101
+ // ─── Directory listing ──────────────────────────────────────────────────────
102
+
103
+ function getDirectoryListing(dir) {
104
+ try {
105
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
106
+ const items = [];
107
+ let count = 0;
108
+ const MAX = 30; // reduced from 50 — saves tokens
109
+
110
+ for (const entry of entries) {
111
+ if (count >= MAX) {
112
+ items.push(`+${entries.length - MAX} more`);
113
+ break;
114
+ }
115
+ // Skip hidden files except useful ones
116
+ if (entry.name.startsWith('.') && !['.env', '.gitignore', '.dockerignore', '.nvmrc'].includes(entry.name)) continue;
117
+ if (entry.name === 'node_modules') { items.push('node_modules/'); count++; continue; }
118
+
119
+ // Just name + trailing slash for dirs. No file sizes — saves tokens.
120
+ items.push(entry.isDirectory() ? entry.name + '/' : entry.name);
121
+ count++;
122
+ }
123
+ return items.join(',');
124
+ } catch {
125
+ return '';
126
+ }
127
+ }
128
+
129
+ // ─── Git info ───────────────────────────────────────────────────────────────
130
+
131
+ async function getGitInfo() {
132
+ const branch = await safeExecAsync('git branch --show-current');
133
+ if (!branch) return null;
134
+ const status = await safeExecAsync('git status --short');
135
+ return {
136
+ branch,
137
+ isDirty: status.length > 0,
138
+ // Limit to 5 lines max — just enough for LLM context
139
+ status: status ? status.split('\n').slice(0, 5).join('\n') : '',
140
+ };
141
+ }
142
+
143
+ // ─── Installed tools ────────────────────────────────────────────────────────
144
+
145
+ async function getInstalledTools(cache) {
146
+ if (cache?.tools) return cache.tools;
147
+
148
+ const checks = [
149
+ { name: 'node', cmd: 'node --version' },
150
+ { name: 'npm', cmd: 'npm --version' },
151
+ { name: 'python', cmd: process.platform === 'win32' ? 'python --version' : 'python3 --version' },
152
+ { name: 'git', cmd: 'git --version' },
153
+ { name: 'docker', cmd: 'docker --version' },
154
+ { name: 'go', cmd: 'go version' },
155
+ { name: 'cargo', cmd: 'cargo --version' },
156
+ { name: 'pip', cmd: process.platform === 'win32' ? 'pip --version' : 'pip3 --version' },
157
+ ];
158
+
159
+ const results = await Promise.all(
160
+ checks.map(async ({ name, cmd }) => {
161
+ const ver = await safeExecAsync(cmd);
162
+ // Just store the tool name — version detail wastes tokens, LLM just needs to know it's available
163
+ return ver ? { name } : null;
164
+ })
165
+ );
166
+
167
+ const tools = results.filter(Boolean);
168
+
169
+ // Detect package manager
170
+ const cwd = process.cwd();
171
+ if (fs.existsSync(path.join(cwd, 'pnpm-lock.yaml'))) tools.push({ name: 'pnpm', version: await safeExecAsync('pnpm --version') || 'installed' });
172
+ if (fs.existsSync(path.join(cwd, 'yarn.lock'))) tools.push({ name: 'yarn', version: await safeExecAsync('yarn --version') || 'installed' });
173
+ if (fs.existsSync(path.join(cwd, 'bun.lockb'))) tools.push({ name: 'bun', version: await safeExecAsync('bun --version') || 'installed' });
174
+
175
+ return tools;
176
+ }
177
+
178
+ // ─── Project info ───────────────────────────────────────────────────────────
179
+
180
+ function getProjectInfo() {
181
+ const cwd = process.cwd();
182
+ const info = { type: null, name: null, scripts: null, deps: null, files: [] };
183
+
184
+ // package.json
185
+ try {
186
+ const pkg = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8'));
187
+ info.type = 'node';
188
+ info.name = pkg.name;
189
+ info.scripts = pkg.scripts ? Object.keys(pkg.scripts).slice(0, 10) : [];
190
+ const deps = Object.keys(pkg.dependencies || {}).slice(0, 10);
191
+ const devDeps = Object.keys(pkg.devDependencies || {}).slice(0, 5);
192
+ info.deps = { deps, devDeps };
193
+ } catch { /* not a node project */ }
194
+
195
+ // Project indicator files
196
+ const indicators = [
197
+ 'Cargo.toml', 'pyproject.toml', 'setup.py', 'go.mod',
198
+ 'Makefile', 'CMakeLists.txt', 'Dockerfile', 'docker-compose.yml',
199
+ 'docker-compose.yaml', 'tsconfig.json', '.eslintrc.json',
200
+ 'vite.config.ts', 'vite.config.js', 'next.config.js', 'next.config.mjs',
201
+ 'webpack.config.js', 'tailwind.config.js', 'tailwind.config.ts',
202
+ '.env', '.env.local', 'Procfile', 'vercel.json', 'netlify.toml',
203
+ 'wrangler.toml', 'fly.toml',
204
+ ];
205
+
206
+ for (const f of indicators) {
207
+ if (fs.existsSync(path.join(cwd, f))) info.files.push(f);
208
+ }
209
+
210
+ return info;
211
+ }
212
+
213
+ // ─── Environment ────────────────────────────────────────────────────────────
214
+
215
+ function getEnvironment() {
216
+ return {
217
+ virtualEnv: process.env.VIRTUAL_ENV || process.env.CONDA_DEFAULT_ENV || null,
218
+ isSSH: !!process.env.SSH_CLIENT || !!process.env.SSH_TTY,
219
+ isDocker: fs.existsSync('/.dockerenv'),
220
+ isWSL: process.platform === 'linux' && safeExec('uname -r').toLowerCase().includes('microsoft'),
221
+ user: os.userInfo().username,
222
+ };
223
+ }
224
+
225
+ // ─── Main gather function ───────────────────────────────────────────────────
226
+
227
+ async function gatherContext() {
228
+ const cache = loadCache();
229
+
230
+ const shell = detectShell(cache);
231
+ const [gitInfo, tools] = await Promise.all([
232
+ getGitInfo(),
233
+ getInstalledTools(cache),
234
+ ]);
235
+
236
+ const context = {
237
+ system: {
238
+ platform: process.platform,
239
+ osName: `${os.type()} ${os.release()}`,
240
+ arch: os.arch(),
241
+ },
242
+ shell,
243
+ cwd: process.cwd(),
244
+ dirListing: getDirectoryListing(process.cwd()),
245
+ gitInfo,
246
+ tools,
247
+ projectInfo: getProjectInfo(),
248
+ environment: getEnvironment(),
249
+ };
250
+
251
+ // Save to cache (tools only — shell must never be cached, user switches terminals)
252
+ if (!cache) {
253
+ saveCache({ tools });
254
+ }
255
+
256
+ return context;
257
+ }
258
+
259
+ module.exports = {
260
+ gatherContext,
261
+ detectShell,
262
+ getDirectoryListing,
263
+ safeExec,
264
+ safeExecAsync,
265
+ };
@@ -0,0 +1,274 @@
1
+ 'use strict';
2
+
3
+ // ─────────────────────────────────────────────────────────────────────────────
4
+ // executor.js — Command execution, clipboard, interactive menu
5
+ // ─────────────────────────────────────────────────────────────────────────────
6
+
7
+ const { spawn, execSync } = require('child_process');
8
+ const { bold, dim, cyan, green, red, yellow, symbols } = require('./colors');
9
+ const { addEntry, getUserIntent } = require('./session');
10
+ const { commandBox, printError, printInfo, Spinner, subtle } = require('./renderer');
11
+ const { selectMenu } = require('./menu');
12
+ const { analyzeRisk } = require('./safety');
13
+ const { tokenLine, estimateCost } = require('./tokens');
14
+
15
+ // ─── Strip accidental shell wrappers from LLM output ────────────────────────
16
+ // LLMs sometimes wrap commands in "powershell -Command ..." when already in PS,
17
+ // or "bash -c ..." when already in bash. Only strip if it matches current shell.
18
+ function stripShellWrapper(cmd) {
19
+ const { shell } = detectBestShell();
20
+ const shellName = shell.toLowerCase().replace(/\.exe$/, '');
21
+ const baseName = require('path').basename(shellName);
22
+
23
+ let c = cmd;
24
+
25
+ // Only strip PowerShell wrappers if we're running IN PowerShell
26
+ if (baseName === 'pwsh' || baseName === 'powershell') {
27
+ c = c.replace(/^powershell(?:\.exe)?\s+(?:-NoProfile\s+)?-Command\s+["']?/i, '').replace(/["']?\s*$/, '');
28
+ c = c.replace(/^pwsh(?:\.exe)?\s+(?:-NoProfile\s+)?-Command\s+["']?/i, '').replace(/["']?\s*$/, '');
29
+ }
30
+
31
+ // Only strip bash wrappers if we're running IN bash
32
+ if (baseName === 'bash' || baseName === 'sh' || baseName === 'zsh') {
33
+ c = c.replace(/^bash\s+-c\s+["']/i, '').replace(/["']\s*$/, '');
34
+ c = c.replace(/^sh\s+-c\s+["']/i, '').replace(/["']\s*$/, '');
35
+ }
36
+
37
+ // Only strip cmd wrappers if we're running IN cmd
38
+ if (baseName === 'cmd') {
39
+ c = c.replace(/^cmd(?:\.exe)?\s+\/c\s+["']?/i, '').replace(/["']?\s*$/, '');
40
+ }
41
+
42
+ return c;
43
+ }
44
+
45
+ // ─── Detect shell ───────────────────────────────────────────────────────────
46
+
47
+ function detectBestShell(config) {
48
+ if (config?.preferredShell) return { shell: config.preferredShell, flag: '-c' };
49
+ if (process.platform !== 'win32') return { shell: process.env.SHELL || '/bin/sh', flag: '-c' };
50
+
51
+ // Git Bash / MINGW / MSYS — $SHELL is set to bash path only inside Git Bash
52
+ const envShell = process.env.SHELL;
53
+ if (envShell && (envShell.includes('bash') || envShell.includes('/sh'))) {
54
+ return { shell: envShell, flag: '-c' };
55
+ }
56
+
57
+ try { execSync('pwsh -NoProfile -Command "exit"', { timeout: 2000, stdio: 'ignore' }); return { shell: 'pwsh', flag: '-Command' }; } catch {}
58
+ try { execSync('powershell -NoProfile -Command "exit"', { timeout: 2000, stdio: 'ignore' }); return { shell: 'powershell', flag: '-Command' }; } catch {}
59
+ return { shell: process.env.ComSpec || 'cmd.exe', flag: '/c' };
60
+ }
61
+
62
+ // ─── Run command ────────────────────────────────────────────────────────────
63
+
64
+ function runCommand(command, config, opts = {}) {
65
+ const { shell, flag } = detectBestShell(config);
66
+ const { silent = false, captureOnly = false, tokenUsage = null } = opts;
67
+
68
+ return new Promise((resolve) => {
69
+ if (!silent && !captureOnly) {
70
+ console.log();
71
+ }
72
+
73
+ // For PowerShell, use -EncodedCommand to avoid $() string terminator issues.
74
+ // Base64-encode the command as UTF-16LE (PowerShell's expected encoding).
75
+ let spawnArgs;
76
+ const shellBase = require('path').basename(shell).toLowerCase().replace(/\.exe$/, '');
77
+ if (shellBase === 'powershell' || shellBase === 'pwsh') {
78
+ const encoded = Buffer.from(command, 'utf16le').toString('base64');
79
+ spawnArgs = ['-NoProfile', '-EncodedCommand', encoded];
80
+ } else {
81
+ spawnArgs = [flag, command];
82
+ }
83
+
84
+ const proc = spawn(shell, spawnArgs, {
85
+ stdio: captureOnly ? ['pipe', 'pipe', 'pipe'] : ['inherit', 'pipe', 'pipe'],
86
+ env: { ...process.env },
87
+ });
88
+
89
+ let output = '';
90
+ let stderrIsClixml = false;
91
+ proc.stdout?.on('data', (d) => { const t = d.toString(); output += t; if (!silent && !captureOnly) process.stdout.write(t); });
92
+ proc.stderr?.on('data', (d) => {
93
+ const t = d.toString();
94
+ // Filter PowerShell CLIXML noise (progress bars, module loading messages).
95
+ // Once we see the CLIXML header, all subsequent stderr is CLIXML until process ends.
96
+ if (t.includes('#< CLIXML')) stderrIsClixml = true;
97
+ if (stderrIsClixml) return;
98
+ output += t;
99
+ if (!silent && !captureOnly) process.stderr.write(t);
100
+ });
101
+
102
+ proc.on('close', (code) => {
103
+ addEntry(command, output, code, getUserIntent());
104
+ if (!silent && !captureOnly) {
105
+ if (tokenUsage) {
106
+ const tl = tokenLine(tokenUsage);
107
+ const cost = estimateCost(tokenUsage, config.provider, config.model);
108
+ const costStr = cost ? dim(` ~$${cost}`) : '';
109
+ console.log(`\n ${dim(tl)}${costStr}`);
110
+ }
111
+ const mark = code === 0 ? green(symbols.check) : red(symbols.cross);
112
+ console.log(` ${mark} ${dim(`exit ${code}`)}`);
113
+ }
114
+ resolve({ code, output });
115
+ });
116
+
117
+ proc.on('error', (err) => {
118
+ addEntry(command, err.message, 1, getUserIntent());
119
+ if (!silent && !captureOnly) console.error(` ${red(symbols.cross)} ${err.message}`);
120
+ resolve({ code: 1, output: err.message });
121
+ });
122
+ });
123
+ }
124
+
125
+ // ─── Clipboard ──────────────────────────────────────────────────────────────
126
+
127
+ function copyToClipboard(text) {
128
+ try {
129
+ const p = process.platform;
130
+ if (p === 'win32') execSync('clip', { input: text, timeout: 3000 });
131
+ else if (p === 'darwin') execSync('pbcopy', { input: text, timeout: 3000 });
132
+ else { try { execSync('xclip -selection clipboard', { input: text, timeout: 3000 }); } catch { execSync('xsel --clipboard --input', { input: text, timeout: 3000 }); } }
133
+ return true;
134
+ } catch { return false; }
135
+ }
136
+
137
+ // ─── Interactive mode ───────────────────────────────────────────────────────
138
+
139
+ async function interactiveMode(result, config, context) {
140
+ const { command: rawCommand, explanation, warning, _tokenUsage } = result;
141
+ const command = stripShellWrapper(rawCommand);
142
+ const risk = analyzeRisk(command);
143
+ const warn = warning || (risk.level === 'high' ? risk.reasons[0] : null);
144
+
145
+ console.log();
146
+ console.log(commandBox(command, explanation, warn));
147
+
148
+ const blocked = risk.level === 'high' && !config?.allowDangerous;
149
+ const choice = await selectMenu([
150
+ { label: 'Execute', key: 'e', disabled: blocked },
151
+ { label: 'Copy', key: 'c' },
152
+ { label: 'Insert', key: 'i' },
153
+ { label: 'Cancel', key: 'q' },
154
+ ]);
155
+
156
+ switch (choice) {
157
+ case 'e': {
158
+ const { code, output } = await runCommand(command, config, { tokenUsage: _tokenUsage });
159
+ if (code !== 0) {
160
+ // ─── Error recovery ──────────────────────────────────────────
161
+ const recovered = await handleFailedCommand(command, output, code, config, context);
162
+ if (recovered) return; // successfully handled, don't exit with error
163
+ }
164
+ cleanExit(code);
165
+ break;
166
+ }
167
+ case 'c': {
168
+ if (copyToClipboard(command)) console.log(` ${green(symbols.check)} ${dim('copied')}`);
169
+ else console.log(` ${dim(command)}`);
170
+ break;
171
+ }
172
+ case 'i': console.log(`\n${command}\n`); break;
173
+ default: console.log(` ${dim('cancelled')}`); break;
174
+ }
175
+ }
176
+
177
+ // ─── Clean exit helper ──────────────────────────────────────────────────────
178
+
179
+ function cleanExit(code) {
180
+ // Ensure raw mode is off and cursor is visible before exiting
181
+ try {
182
+ if (process.stdin.isTTY && process.stdin.isRaw) {
183
+ process.stdin.setRawMode(false);
184
+ }
185
+ process.stdin.pause();
186
+ process.stdout.write('\x1b[?25h'); // show cursor
187
+ } catch { /* ignore */ }
188
+ process.exit(code);
189
+ }
190
+
191
+ // ─── Error recovery after failed command ────────────────────────────────────
192
+ // Returns true if the error was handled (user chose fix/retry), false otherwise
193
+
194
+ async function handleFailedCommand(command, output, code, config, context) {
195
+ console.log();
196
+ const recovery = await selectMenu([
197
+ { label: 'Fix it', key: 'f' },
198
+ { label: 'Retry', key: 'r' },
199
+ { label: 'Copy error', key: 'c' },
200
+ { label: 'Exit', key: 'q' },
201
+ ]);
202
+
203
+ if (recovery === 'f') {
204
+ // Ask the LLM for a fix based on the error output
205
+ const { queryLLM } = require('./llm');
206
+
207
+ // Add the failure to session so LLM has context
208
+ addEntry(command, output, code, 'auto-fix');
209
+
210
+ const errorSnippet = output.length > 500 ? output.slice(-500) : output;
211
+ const fixPrompt = `The previous command failed. Fix it.\nCommand: ${command}\nError (exit ${code}):\n${errorSnippet}`;
212
+
213
+ const spin = new Spinner('fixing...').start();
214
+ try {
215
+ const fixResult = await queryLLM(fixPrompt, context, config, 'quick');
216
+ spin.succeed('done');
217
+
218
+ if (fixResult?.command) {
219
+ // Recurse into interactive mode with the new suggestion
220
+ await interactiveMode(fixResult, config, context);
221
+ return true;
222
+ } else {
223
+ printError('Could not generate a fix. Try rephrasing your request.');
224
+ return false;
225
+ }
226
+ } catch (err) {
227
+ spin.fail(err.message);
228
+ return false;
229
+ }
230
+ }
231
+
232
+ if (recovery === 'r') {
233
+ const { code: retryCode, output: retryOutput } = await runCommand(command, config);
234
+ if (retryCode !== 0) {
235
+ return await handleFailedCommand(command, retryOutput, retryCode, config, context);
236
+ }
237
+ cleanExit(retryCode);
238
+ return true;
239
+ }
240
+
241
+ if (recovery === 'c') {
242
+ const errorText = `$ ${command}\n${output}`;
243
+ if (copyToClipboard(errorText)) {
244
+ console.log(` ${green(symbols.check)} ${dim('error copied')}`);
245
+ } else {
246
+ console.log(` ${dim(output.slice(-300))}`);
247
+ }
248
+ return false;
249
+ }
250
+
251
+ // 'q' or null — just exit
252
+ return false;
253
+ }
254
+
255
+ // ─── Auto-execute ───────────────────────────────────────────────────────────
256
+
257
+ async function executeMode(result, config) {
258
+ const { command: rawCommand, explanation, warning, _tokenUsage } = result;
259
+ const command = stripShellWrapper(rawCommand);
260
+ const risk = analyzeRisk(command);
261
+
262
+ console.log();
263
+ console.log(commandBox(command, explanation, warning));
264
+
265
+ if (risk.level === 'high' || warning) {
266
+ console.log(` ${red(symbols.warning)} ${bold('Blocked.')} Use interactive mode.`);
267
+ cleanExit(1);
268
+ }
269
+
270
+ const { code } = await runCommand(command, config, { tokenUsage: _tokenUsage });
271
+ cleanExit(code);
272
+ }
273
+
274
+ module.exports = { runCommand, copyToClipboard, interactiveMode, executeMode, handleFailedCommand, cleanExit, detectBestShell, stripShellWrapper };
package/lib/index.js ADDED
@@ -0,0 +1,16 @@
1
+ 'use strict';
2
+
3
+ module.exports = {
4
+ ...require('./config'),
5
+ ...require('./context'),
6
+ ...require('./session'),
7
+ ...require('./llm'),
8
+ ...require('./executor'),
9
+ ...require('./planner'),
10
+ ...require('./safety'),
11
+ ...require('./menu'),
12
+ ...require('./renderer'),
13
+ ...require('./colors'),
14
+ ...require('./ui'),
15
+ ...require('./tokens'),
16
+ };