diff --git a/CLAUDE.md b/CLAUDE.md
index b34afddcd..6656f319a 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -101,6 +101,34 @@ class UserService { // Never do this
}
```
+### Import Patterns
+- **Always use top-level imports** - Import all dependencies at the top of the file
+- **No dynamic imports** - Avoid `await import()` in the middle of functions
+- **Rationale** - Prioritize code simplicity and consistency over premature optimization
+- **Exception** - Only use dynamic imports if you have clear evidence of measurable startup performance issues
+
+```typescript
+// Good: Top-level imports
+import { runHeadless } from '../services/headless-handler';
+import { render } from 'ink';
+
+export function handleCommand(options) {
+ if (options.prompt) {
+ return runHeadless(options);
+ }
+ return render();
+}
+
+// Bad: Dynamic imports without justification
+export async function handleCommand(options) {
+ if (options.prompt) {
+ const { runHeadless } = await import('../services/headless-handler');
+ return runHeadless(options);
+ }
+ return render();
+}
+```
+
## Cross-Cutting Concerns
### Environment Variables
diff --git a/apps/cli/src/buster/program.tsx b/apps/cli/src/buster/program.tsx
index f2e9677b7..b11cd880c 100644
--- a/apps/cli/src/buster/program.tsx
+++ b/apps/cli/src/buster/program.tsx
@@ -3,6 +3,7 @@ import { render } from 'ink';
import { Main } from '../commands/main/main';
import { getCurrentVersion } from '../commands/update/update-handler';
import { setupPreActionHook } from './hooks';
+import { runHeadless } from '../services/headless-handler';
interface RootOptions {
cwd?: string;
@@ -32,7 +33,6 @@ program.action(async (options: RootOptions) => {
// Check if running in headless mode
if (options.prompt) {
try {
- const { runHeadless } = await import('../services/headless-handler');
const chatId = await runHeadless({
prompt: options.prompt,
...(options.chatId && { chatId: options.chatId }),
diff --git a/apps/cli/src/commands/auth/index.tsx b/apps/cli/src/commands/auth/index.tsx
index 074aea164..21809f6a4 100644
--- a/apps/cli/src/commands/auth/index.tsx
+++ b/apps/cli/src/commands/auth/index.tsx
@@ -1,5 +1,7 @@
+import { createBusterSDK } from '@buster/sdk';
import { Command } from 'commander';
import { render } from 'ink';
+import { saveCredentials } from '../../utils/credentials';
import { Auth } from './auth';
/**
@@ -33,9 +35,6 @@ export function createAuthCommand(): Command {
// If we have an API key in CI, just validate and save it without interactive UI
if (isCIEnvironment && (options.apiKey || process.env.BUSTER_API_KEY)) {
- const { createBusterSDK } = await import('@buster/sdk');
- const { saveCredentials } = await import('../../utils/credentials');
-
const apiKey = options.apiKey || process.env.BUSTER_API_KEY;
const host =
options.host ||
diff --git a/apps/cli/src/commands/deploy/index.tsx b/apps/cli/src/commands/deploy/index.tsx
index 54df38438..af702eab4 100644
--- a/apps/cli/src/commands/deploy/index.tsx
+++ b/apps/cli/src/commands/deploy/index.tsx
@@ -1,7 +1,9 @@
import { Command } from 'commander';
import { render } from 'ink';
import { DeployCommand } from './deploy';
+import { deployHandler } from './deploy-handler.js';
import { DeployOptionsSchema } from './schemas';
+import { formatDeployError, getExitCode, isDeploymentValidationError } from './utils/errors.js';
/**
* Creates the deploy command for deploying semantic models
@@ -32,15 +34,9 @@ export function createDeployCommand(): Command {
render();
} else {
// Direct execution for cleaner CLI output
- const { deployHandler } = await import('./deploy-handler.js');
await deployHandler(parsedOptions);
}
} catch (error) {
- // Import the error formatter and type guard
- const { isDeploymentValidationError, formatDeployError, getExitCode } = await import(
- './utils/errors.js'
- );
-
// Check if it's a DeploymentValidationError to handle it specially
if (isDeploymentValidationError(error)) {
// The error message already contains the formatted output
diff --git a/apps/cli/src/commands/hello.test.tsx b/apps/cli/src/commands/hello.test.tsx
deleted file mode 100644
index affec0373..000000000
--- a/apps/cli/src/commands/hello.test.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import { render } from 'ink-testing-library';
-import { describe, expect, it } from 'vitest';
-import { HelloCommand } from './hello';
-
-describe('HelloCommand', () => {
- it('should render greeting with default name', () => {
- const { lastFrame } = render();
-
- expect(lastFrame()).toContain('Hello, World!');
- expect(lastFrame()).toContain('Buster CLI');
- });
-
- it('should render greeting in uppercase when flag is set', () => {
- const { lastFrame } = render();
-
- expect(lastFrame()).toContain('HELLO, CLAUDE!');
- });
-
- it('should render greeting in normal case when uppercase flag is false', () => {
- const { lastFrame } = render();
-
- expect(lastFrame()).toContain('Hello, Claude!');
- expect(lastFrame()).not.toContain('HELLO, CLAUDE!');
- });
-});
diff --git a/apps/cli/src/commands/hello.tsx b/apps/cli/src/commands/hello.tsx
deleted file mode 100644
index 95e0c9f3c..000000000
--- a/apps/cli/src/commands/hello.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-import chalk from 'chalk';
-import { Box, Text } from 'ink';
-import type React from 'react';
-import { useEffect } from 'react';
-
-interface HelloCommandProps {
- name: string;
- uppercase?: boolean;
-}
-
-export const HelloCommand: React.FC = ({ name, uppercase }) => {
- const greeting = `Hello, ${name}!`;
- const displayText = uppercase ? greeting.toUpperCase() : greeting;
-
- useEffect(() => {
- // Exit after rendering
- setTimeout(() => {
- process.exit(0);
- }, 100);
- }, []);
-
- return (
-
- {chalk.bold('🚀 Buster CLI')}
- {displayText}
-
- );
-};
diff --git a/apps/cli/src/commands/interactive.tsx b/apps/cli/src/commands/interactive.tsx
deleted file mode 100644
index f5dc98d5b..000000000
--- a/apps/cli/src/commands/interactive.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-import chalk from 'chalk';
-import { Box, Text, useApp, useInput } from 'ink';
-import type React from 'react';
-import { useState } from 'react';
-
-export const InteractiveCommand: React.FC = () => {
- const [selectedOption, setSelectedOption] = useState(0);
- const { exit } = useApp();
-
- const options = ['Create a new project', 'Deploy to production', 'Run tests', 'Exit'];
-
- useInput((input, key) => {
- if (key.upArrow) {
- setSelectedOption((prev) => Math.max(0, prev - 1));
- } else if (key.downArrow) {
- setSelectedOption((prev) => Math.min(options.length - 1, prev + 1));
- } else if (key.return) {
- if (selectedOption === options.length - 1) {
- exit();
- } else {
- // Handle selection
- console.info(`\nYou selected: ${options[selectedOption]}`);
- exit();
- }
- } else if (input === 'q' || key.escape) {
- exit();
- }
- });
-
- return (
-
-
-
- 🚀 Buster CLI - Interactive Mode
-
-
-
- Use arrow keys to navigate, Enter to select, Q to quit
-
- {options.map((option, index) => (
-
-
- {selectedOption === index ? chalk.bold('â–¶ ') : ' '}
- {option}
-
-
- ))}
-
-
- );
-};
diff --git a/apps/cli/src/commands/main/main.tsx b/apps/cli/src/commands/main/main.tsx
index c67b74478..44081ff23 100644
--- a/apps/cli/src/commands/main/main.tsx
+++ b/apps/cli/src/commands/main/main.tsx
@@ -14,6 +14,7 @@ import { AgentMessageComponent } from '../../components/message';
import { SettingsForm } from '../../components/settings-form';
import { ExpansionContext } from '../../hooks/use-expansion';
import type { CliAgentMessage } from '../../services/analytics-engineer-handler';
+import { runAnalyticsEngineerAgent } from '../../services/analytics-engineer-handler';
import type { Conversation } from '../../utils/conversation-history';
import { loadConversation, saveModelMessages } from '../../utils/conversation-history';
import { getCurrentChatId, initNewSession, setSessionChatId } from '../../utils/session';
@@ -137,9 +138,6 @@ export function Main() {
// Save to disk
await saveModelMessages(chatId, cwd, updatedModelMessages);
- // Import and run the analytics engineer agent
- const { runAnalyticsEngineerAgent } = await import('../../services/analytics-engineer-handler');
-
// Create AbortController for this agent execution
const abortController = new AbortController();
abortControllerRef.current = abortController;
diff --git a/apps/cli/src/commands/update/update-handler.ts b/apps/cli/src/commands/update/update-handler.ts
index eaa892016..5d4652880 100644
--- a/apps/cli/src/commands/update/update-handler.ts
+++ b/apps/cli/src/commands/update/update-handler.ts
@@ -1,7 +1,7 @@
import { spawn } from 'node:child_process';
import { createHash } from 'node:crypto';
-import { createWriteStream, existsSync } from 'node:fs';
-import { chmod, mkdir, rename, unlink } from 'node:fs/promises';
+import { createReadStream, createWriteStream, existsSync } from 'node:fs';
+import { chmod, mkdir, readFile, rename, rm, unlink } from 'node:fs/promises';
import { arch, platform, tmpdir } from 'node:os';
import { join } from 'node:path';
import { pipeline } from 'node:stream/promises';
@@ -102,7 +102,6 @@ async function downloadFile(url: string, destination: string): Promise {
* Verify file checksum
*/
async function verifyChecksum(filePath: string, expectedChecksum: string): Promise {
- const { createReadStream } = await import('node:fs');
const hash = createHash('sha256');
const stream = createReadStream(filePath);
@@ -315,7 +314,6 @@ export async function updateHandler(options: UpdateOptions): Promise {
- const timer = setTimeout(() => {
- exit();
- }, 5000);
-
- return () => clearTimeout(timer);
- }, [exit]);
-
- return (
-
-
-
-
-
- Welcome to Buster
-
- Type / to use slash commands
-
-
- Type @ to mention files
-
-
- Ctrl-C to exit
-
-
- /help for more
-
-
- "Run `buster` and fix all the errors"
-
-
-
- );
-}
diff --git a/apps/cli/src/utils/ai-proxy.ts b/apps/cli/src/utils/ai-proxy.ts
index fad40a6eb..825b0c15f 100644
--- a/apps/cli/src/utils/ai-proxy.ts
+++ b/apps/cli/src/utils/ai-proxy.ts
@@ -1,4 +1,5 @@
import { z } from 'zod';
+import { getCredentials } from './credentials';
const ProxyConfigSchema = z.object({
baseURL: z.string().url().describe('Base URL for the AI proxy endpoint'),
@@ -18,7 +19,6 @@ export type ProxyConfig = z.infer;
* API key comes from credentials (required)
*/
export async function getProxyConfig(): Promise {
- const { getCredentials } = await import('./credentials');
const creds = await getCredentials();
if (!creds?.apiKey) {
diff --git a/apps/cli/src/utils/conversation-history.ts b/apps/cli/src/utils/conversation-history.ts
index 64faf2d84..fafdc8795 100644
--- a/apps/cli/src/utils/conversation-history.ts
+++ b/apps/cli/src/utils/conversation-history.ts
@@ -1,4 +1,4 @@
-import { mkdir, readFile, readdir, writeFile } from 'node:fs/promises';
+import { mkdir, readFile, readdir, unlink, writeFile } from 'node:fs/promises';
import { homedir } from 'node:os';
import { join } from 'node:path';
import { z } from 'zod';
@@ -205,7 +205,6 @@ export async function getLatestConversation(
*/
export async function deleteConversation(chatId: string, workingDirectory: string): Promise {
const filePath = getConversationFilePath(chatId, workingDirectory);
- const { unlink } = await import('node:fs/promises');
await unlink(filePath);
}
diff --git a/apps/cli/src/utils/version/version-cache.ts b/apps/cli/src/utils/version/version-cache.ts
index 6d16177cd..bd09ecce3 100644
--- a/apps/cli/src/utils/version/version-cache.ts
+++ b/apps/cli/src/utils/version/version-cache.ts
@@ -1,4 +1,4 @@
-import { mkdir, readFile, writeFile } from 'node:fs/promises';
+import { mkdir, readFile, unlink, writeFile } from 'node:fs/promises';
import { homedir } from 'node:os';
import { join } from 'node:path';
import { type VersionCache, VersionCacheSchema } from './version-schemas';
@@ -72,7 +72,6 @@ export async function getCachedVersion(): Promise {
*/
export async function clearVersionCache(): Promise {
try {
- const { unlink } = await import('node:fs/promises');
await unlink(CACHE_FILE);
} catch {
// Cache file might not exist, that's fine