Skip to content

Commit 81b5edd

Browse files
committed
Bundle all local agents for dev and binary
1 parent d60f29e commit 81b5edd

File tree

12 files changed

+383
-147
lines changed

12 files changed

+383
-147
lines changed

cli/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,6 @@ bin
44
*.log
55
.DS_Store
66
debug/
7+
8+
# Generated files
9+
src/agents/bundled-agents.generated.ts

cli/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
}
1616
},
1717
"scripts": {
18-
"dev": "bun run src/index.tsx",
18+
"dev": "bun run prebuild:agents && bun run src/index.tsx",
19+
"prebuild:agents": "bun run scripts/prebuild-agents.ts",
1920
"build:sdk": "cd ../sdk && bun run build",
2021
"build:binary": "bun ./scripts/build-binary.ts codebuff $npm_package_version",
2122
"release": "bun run scripts/release.ts",

cli/scripts/build-binary.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,10 @@ async function main() {
127127
mkdirSync(binDir, { recursive: true })
128128
}
129129

130+
// Generate bundled agents file before compiling
131+
log('Generating bundled agents...')
132+
runCommand('bun', ['run', 'scripts/prebuild-agents.ts'], { cwd: cliRoot, env: process.env })
133+
130134
// Ensure SDK assets exist before compiling the CLI
131135
log('Building SDK dependencies...')
132136
runCommand('bun', ['run', 'build:sdk'], { cwd: cliRoot, env: process.env })

cli/scripts/prebuild-agents.ts

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
#!/usr/bin/env bun
2+
/**
3+
* Prebuild script that scans the .agents/ directory and generates a TypeScript
4+
* module with all agent definitions embedded as static data.
5+
*
6+
* This allows agent definitions to be bundled into the CLI binary without
7+
* requiring runtime filesystem access to the .agents/ directory.
8+
*
9+
* Run this before building the binary:
10+
* bun run scripts/prebuild-agents.ts
11+
*/
12+
13+
import * as fs from 'fs'
14+
import * as path from 'path'
15+
16+
const AGENTS_DIR = path.join(import.meta.dir, '../../.agents')
17+
const OUTPUT_FILE = path.join(import.meta.dir, '../src/agents/bundled-agents.generated.ts')
18+
19+
interface AgentDefinition {
20+
id: string
21+
displayName?: string
22+
[key: string]: any
23+
}
24+
25+
/**
26+
* Recursively get all TypeScript files from a directory
27+
*/
28+
function getAllTsFiles(dir: string): string[] {
29+
const files: string[] = []
30+
31+
try {
32+
const entries = fs.readdirSync(dir, { withFileTypes: true })
33+
34+
for (const entry of entries) {
35+
const fullPath = path.join(dir, entry.name)
36+
37+
if (entry.isDirectory()) {
38+
// Skip __tests__ and node_modules directories
39+
if (entry.name === '__tests__' || entry.name === 'node_modules' || entry.name === 'types') {
40+
continue
41+
}
42+
files.push(...getAllTsFiles(fullPath))
43+
} else if (
44+
entry.isFile() &&
45+
entry.name.endsWith('.ts') &&
46+
!entry.name.endsWith('.d.ts') &&
47+
!entry.name.endsWith('.test.ts')
48+
) {
49+
files.push(fullPath)
50+
}
51+
}
52+
} catch (error) {
53+
console.error(`Error reading directory ${dir}:`, error)
54+
}
55+
56+
return files
57+
}
58+
59+
/**
60+
* Load and process an agent definition from a TypeScript file
61+
*/
62+
async function loadAgentDefinition(filePath: string): Promise<AgentDefinition | null> {
63+
try {
64+
// Use dynamic import to load the module
65+
const module = await import(filePath)
66+
const definition = module.default
67+
68+
if (!definition || !definition.id || !definition.model) {
69+
return null
70+
}
71+
72+
// Process the definition - convert handleSteps function to string
73+
const processed: AgentDefinition = { ...definition }
74+
75+
if (typeof processed.handleSteps === 'function') {
76+
processed.handleSteps = processed.handleSteps.toString()
77+
}
78+
79+
return processed
80+
} catch (error) {
81+
console.error(`Error loading agent from ${filePath}:`, error)
82+
return null
83+
}
84+
}
85+
86+
/**
87+
* Generate the bundled agents TypeScript file
88+
*/
89+
function generateBundledAgentsFile(agents: Record<string, AgentDefinition>): string {
90+
const agentCount = Object.keys(agents).length
91+
92+
return `/**
93+
* AUTO-GENERATED FILE - DO NOT EDIT MANUALLY
94+
*
95+
* This file is generated by scripts/prebuild-agents.ts
96+
* It contains all bundled agent definitions from the .agents/ directory.
97+
*
98+
* Generated at: ${new Date().toISOString()}
99+
* Agent count: ${agentCount}
100+
*/
101+
102+
import type { LocalAgentInfo } from '../utils/local-agent-registry'
103+
104+
/**
105+
* All bundled agent definitions keyed by their ID.
106+
* These are the default Codebuff agents that ship with the CLI binary.
107+
*/
108+
export const bundledAgents: Record<string, any> = ${JSON.stringify(agents, null, 2)};
109+
110+
/**
111+
* Get bundled agents as LocalAgentInfo format for the CLI
112+
*/
113+
export function getBundledAgentsAsLocalInfo(): LocalAgentInfo[] {
114+
return Object.values(bundledAgents).map((agent) => ({
115+
id: agent.id,
116+
displayName: agent.displayName || agent.id,
117+
filePath: '[bundled]',
118+
}));
119+
}
120+
121+
/**
122+
* Get all bundled agent IDs
123+
*/
124+
export function getBundledAgentIds(): string[] {
125+
return Object.keys(bundledAgents);
126+
}
127+
128+
/**
129+
* Check if an agent ID is a bundled agent
130+
*/
131+
export function isBundledAgent(agentId: string): boolean {
132+
return agentId in bundledAgents;
133+
}
134+
`
135+
}
136+
137+
async function main() {
138+
const DEBUG = false
139+
if (DEBUG) {
140+
console.log('🔍 DEBUG: Scanning .agents/ directory...')
141+
}
142+
143+
if (!fs.existsSync(AGENTS_DIR)) {
144+
console.error(`Error: .agents/ directory not found at ${AGENTS_DIR}`)
145+
process.exit(1)
146+
}
147+
148+
const tsFiles = getAllTsFiles(AGENTS_DIR)
149+
if (DEBUG) {
150+
console.log(`📁 DEBUG: Found ${tsFiles.length} TypeScript files`)
151+
}
152+
153+
const agents: Record<string, AgentDefinition> = {}
154+
let loadedCount = 0
155+
let skippedCount = 0
156+
157+
for (const filePath of tsFiles) {
158+
const relativePath = path.relative(AGENTS_DIR, filePath)
159+
const definition = await loadAgentDefinition(filePath)
160+
161+
if (definition) {
162+
agents[definition.id] = definition
163+
loadedCount++
164+
if (DEBUG) {
165+
console.log(` ✅ DEBUG: ${definition.id} (${relativePath})`)
166+
}
167+
} else {
168+
skippedCount++
169+
if (DEBUG) {
170+
console.log(` ⏭️ DEBUG: Skipped: ${relativePath} (no valid default export)`)
171+
}
172+
}
173+
}
174+
175+
if (DEBUG) {
176+
console.log(`\n📦 DEBUG: Loaded ${loadedCount} agents, skipped ${skippedCount} files`)
177+
}
178+
179+
// Generate the output file
180+
const output = generateBundledAgentsFile(agents)
181+
182+
// Ensure output directory exists
183+
const outputDir = path.dirname(OUTPUT_FILE)
184+
if (!fs.existsSync(outputDir)) {
185+
fs.mkdirSync(outputDir, { recursive: true })
186+
}
187+
188+
fs.writeFileSync(OUTPUT_FILE, output, 'utf-8')
189+
if (DEBUG) {
190+
console.log(`\n✨ DEBUG: Generated ${OUTPUT_FILE}`)
191+
console.log(` DEBUG: ${Object.keys(agents).length} agents bundled`)
192+
}
193+
}
194+
195+
main().catch((error) => {
196+
console.error('Fatal error:', error)
197+
process.exit(1)
198+
})

cli/src/__tests__/integration/local-agents.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { validateAgents } from '@codebuff/sdk'
66
import { describe, test, expect, beforeEach, afterEach, mock } from 'bun:test'
77

88
import { setProjectRoot, getProjectRoot } from '../../project-files'
9-
import { loadAgentDefinitions } from '../../utils/load-agent-definitions'
9+
import { loadAgentDefinitions } from '../../utils/local-agent-registry'
1010
import {
1111
findAgentsDirectory,
1212
__resetLocalAgentRegistryForTests,

cli/src/commands/publish.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ import { cyan, green, red, yellow } from 'picocolors'
33

44
import { getUserCredentials } from '../utils/auth'
55
import { getApiClient, setApiClientAuthToken } from '../utils/codebuff-api'
6-
import { loadAgentDefinitions } from '../utils/load-agent-definitions'
7-
import { getLoadedAgentsData } from '../utils/local-agent-registry'
6+
import { loadAgentDefinitions, getLoadedAgentsData } from '../utils/local-agent-registry'
87

98
import type {
109
PublishAgentsErrorResponse,

cli/src/hooks/use-agent-validation.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { validateAgents } from '@codebuff/sdk'
22
import { useCallback, useState } from 'react'
33

44

5-
import { loadAgentDefinitions } from '../utils/load-agent-definitions'
5+
import { loadAgentDefinitions } from '../utils/local-agent-registry'
66
import { logger } from '../utils/logger'
77

88
export type ValidationError = {

cli/src/hooks/use-send-message.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { getCodebuffClient } from '../utils/codebuff-client'
88
import { AGENT_MODE_TO_ID } from '../utils/constants'
99
import { createEventHandlerState } from '../utils/create-event-handler-state'
1010
import { createRunConfig } from '../utils/create-run-config'
11-
import { loadAgentDefinitions } from '../utils/load-agent-definitions'
11+
import { loadAgentDefinitions } from '../utils/local-agent-registry'
1212
import { logger } from '../utils/logger'
1313
import {
1414
loadMostRecentChatState,

cli/src/index.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@ import { initializeApp } from './init/init-app'
1919
import { getProjectRoot } from './project-files'
2020
import { initAnalytics } from './utils/analytics'
2121
import { getUserCredentials } from './utils/auth'
22-
import { loadAgentDefinitions } from './utils/load-agent-definitions'
23-
import { getLoadedAgentsData } from './utils/local-agent-registry'
22+
import { loadAgentDefinitions, getLoadedAgentsData } from './utils/local-agent-registry'
2423
import { clearLogFile, logger } from './utils/logger'
2524
import { detectTerminalTheme } from './utils/terminal-color-detection'
2625
import { setOscDetectedTheme } from './utils/theme-system'

cli/src/utils/codebuff-client.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { AskUserBridge } from '@codebuff/common/utils/ask-user-bridge'
33
import { CodebuffClient } from '@codebuff/sdk'
44

55
import { getAuthTokenDetails } from './auth'
6-
import { loadAgentDefinitions } from './load-agent-definitions'
6+
import { loadAgentDefinitions } from './local-agent-registry'
77
import { logger } from './logger'
88
import { getRgPath } from '../native/ripgrep'
99
import { getProjectRoot } from '../project-files'

0 commit comments

Comments
 (0)