Skip to content

Commit d7830af

Browse files
committed
codebuff save-agent command
1 parent 95260dd commit d7830af

File tree

3 files changed

+125
-2
lines changed

3 files changed

+125
-2
lines changed
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import * as fs from 'fs'
2+
import * as path from 'path'
3+
4+
import { codebuffConfigFile } from '@codebuff/common/json-config/constants'
5+
import { green, red, yellow, cyan, gray } from 'picocolors'
6+
7+
import { getProjectRoot } from '../project-files'
8+
import { loadRawCodebuffConfig } from '../json-config/parser'
9+
10+
/**
11+
* Add one or more agent IDs to the addedSpawnableAgents field in codebuff.json
12+
* @param agentIds Array of agent IDs to add
13+
*/
14+
export async function handleSaveAgent(agentIds: string[]): Promise<void> {
15+
if (!agentIds || agentIds.length === 0) {
16+
console.log(
17+
red(
18+
'Agent ID is required. Usage: codebuff save-agent <agent-id> [agent-id2] ...',
19+
),
20+
)
21+
console.log(
22+
gray(
23+
'Example: codebuff save-agent codebuff/file-picker@1.0.0 codebuff/reviewer@1.0.0',
24+
),
25+
)
26+
return
27+
}
28+
29+
const projectRoot = getProjectRoot()
30+
const configPath = path.join(projectRoot, codebuffConfigFile)
31+
32+
try {
33+
// Use the safe configuration loader that handles JSONC
34+
const config = loadRawCodebuffConfig()
35+
36+
// Initialize addedSpawnableAgents if it doesn't exist
37+
if (!config.addedSpawnableAgents) {
38+
config.addedSpawnableAgents = []
39+
}
40+
41+
// Track which agents were added vs already existed
42+
const newAgents: string[] = []
43+
const existingAgents: string[] = []
44+
45+
for (const agentId of agentIds) {
46+
if (config.addedSpawnableAgents.includes(agentId)) {
47+
existingAgents.push(agentId)
48+
} else {
49+
config.addedSpawnableAgents.push(agentId)
50+
newAgents.push(agentId)
51+
}
52+
}
53+
54+
// Write the updated configuration back to file
55+
const configJson = JSON.stringify(config, null, 2)
56+
fs.writeFileSync(configPath, configJson + '\n')
57+
58+
// Provide feedback to the user
59+
if (newAgents.length > 0) {
60+
console.log(
61+
green(
62+
`✅ Successfully added ${newAgents.length} agent(s) to codebuff.json:`,
63+
),
64+
)
65+
newAgents.forEach((agentId) => {
66+
console.log(cyan(` + ${agentId}`))
67+
})
68+
}
69+
70+
if (existingAgents.length > 0) {
71+
console.log(
72+
yellow(
73+
`⚠️ ${existingAgents.length} agent(s) were already in the list:`,
74+
),
75+
)
76+
existingAgents.forEach((agentId) => {
77+
console.log(gray(` • ${agentId}`))
78+
})
79+
}
80+
81+
if (newAgents.length > 0) {
82+
console.log()
83+
console.log(
84+
gray('These agents are now available for spawning by the base agent.'),
85+
)
86+
}
87+
} catch (error) {
88+
console.log(
89+
red(
90+
`Error updating codebuff.json: ${error instanceof Error ? error.message : String(error)}`,
91+
),
92+
)
93+
}
94+
}

npm-app/src/index.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { CLI } from './cli'
1010
import { cliArguments, cliOptions } from './cli-definitions'
1111
import { handlePublish } from './cli-handlers/publish'
1212
import { handleInitAgents } from './cli-handlers/init-agents'
13+
import { handleSaveAgent } from './cli-handlers/save-agent'
1314
import { npmAppVersion, backendUrl } from './config'
1415
import { createTemplateProject } from './create-template-project'
1516
import { printModeLog, setPrintMode } from './display/print-mode'
@@ -122,6 +123,7 @@ Examples:
122123
$ codebuff --trace # Enable subagent trace logging to .agents/traces/*.log
123124
$ codebuff --create nextjs my-app # Create and scaffold a new Next.js project
124125
$ codebuff init-agents # Create example agent files in .agents directory
126+
$ codebuff save-agent my-agent-id # Add agent ID to spawnable agents list
125127
$ codebuff publish my-agent # Publish agent template to store
126128
$ codebuff --agent file-picker "find relevant files for authentication"
127129
$ codebuff --agent reviewer --params '{"focus": "security"}' "review this code"
@@ -159,6 +161,13 @@ For all commands and options, run 'codebuff' and then type 'help'.
159161
process.exit(0)
160162
}
161163

164+
// Handle save-agent command
165+
if (args[0] === 'save-agent') {
166+
const agentIds = args.slice(1)
167+
await handleSaveAgent(agentIds)
168+
process.exit(0)
169+
}
170+
162171
// Handle deprecated --pro flag
163172
if (options.pro) {
164173
console.error(
@@ -225,8 +234,10 @@ For all commands and options, run 'codebuff' and then type 'help'.
225234
// Remove the first argument if it's the compiled binary path which bun weirdly injects (starts with /$bunfs)
226235
const filteredArgs = args[0]?.startsWith('/$bunfs') ? args.slice(1) : args
227236

228-
// If first arg is a command like 'publish', don't treat it as initial input
229-
const isCommand = filteredArgs[0] === 'publish'
237+
// If first arg is a command like 'publish' or 'save-agent', don't treat it as initial input
238+
const isCommand = ['publish', 'init-agents', 'save-agent'].includes(
239+
filteredArgs[0],
240+
)
230241
const initialInput = isCommand ? '' : filteredArgs.join(' ')
231242

232243
codebuff({

npm-app/src/json-config/parser.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,3 +123,21 @@ export function loadCodebuffConfig(): CodebuffConfig {
123123
return getDefaultConfig()
124124
}
125125
}
126+
127+
export function loadRawCodebuffConfig(): Partial<CodebuffConfig> {
128+
const projectPath = getProjectRoot()
129+
const configPathPrimary = path.join(projectPath, codebuffConfigFile)
130+
const configPathBackup = path.join(projectPath, codebuffConfigFileBackup)
131+
const configPath = existsSync(configPathBackup)
132+
? configPathBackup
133+
: existsSync(configPathPrimary)
134+
? configPathPrimary
135+
: null
136+
137+
if (configPath === null) {
138+
return {}
139+
}
140+
141+
const jsoncContent = readFileSync(configPath, 'utf-8')
142+
return parseJsonc(jsoncContent)
143+
}

0 commit comments

Comments
 (0)