feat: add CLI arguments to agent create command for scripting (#5157)

This commit is contained in:
Timor
2025-12-10 05:09:45 +02:00
committed by GitHub
parent c1ee6d6c41
commit b2057791aa
2 changed files with 171 additions and 87 deletions

View File

@@ -252,7 +252,7 @@ export namespace Agent {
}, },
}, },
temperature: 0.3, temperature: 0.3,
prompt: [ messages: [
...system.map( ...system.map(
(item): ModelMessage => ({ (item): ModelMessage => ({
role: "system", role: "system",

View File

@@ -4,21 +4,73 @@ import { UI } from "../ui"
import { Global } from "../../global" import { Global } from "../../global"
import { Agent } from "../../agent/agent" import { Agent } from "../../agent/agent"
import path from "path" import path from "path"
import fs from "fs/promises"
import matter from "gray-matter" import matter from "gray-matter"
import { Instance } from "../../project/instance" import { Instance } from "../../project/instance"
import { EOL } from "os" import { EOL } from "os"
import type { Argv } from "yargs"
type AgentMode = "all" | "primary" | "subagent"
const AVAILABLE_TOOLS = [
"bash",
"read",
"write",
"edit",
"list",
"glob",
"grep",
"webfetch",
"task",
"todowrite",
"todoread",
]
const AgentCreateCommand = cmd({ const AgentCreateCommand = cmd({
command: "create", command: "create",
describe: "create a new agent", describe: "create a new agent",
async handler() { builder: (yargs: Argv) =>
yargs
.option("path", {
type: "string",
describe: "directory path to generate the agent file",
})
.option("description", {
type: "string",
describe: "what the agent should do",
})
.option("mode", {
type: "string",
describe: "agent mode",
choices: ["all", "primary", "subagent"] as const,
})
.option("tools", {
type: "string",
describe: `comma-separated list of tools to enable (default: all). Available: "${AVAILABLE_TOOLS.join(", ")}"`,
}),
async handler(args) {
await Instance.provide({ await Instance.provide({
directory: process.cwd(), directory: process.cwd(),
async fn() { async fn() {
const cliPath = args.path
const cliDescription = args.description
const cliMode = args.mode as AgentMode | undefined
const cliTools = args.tools
const isFullyNonInteractive = cliPath && cliDescription && cliMode && cliTools !== undefined
if (!isFullyNonInteractive) {
UI.empty() UI.empty()
prompts.intro("Create agent") prompts.intro("Create agent")
}
const project = Instance.project const project = Instance.project
// Determine scope/path
let targetPath: string
if (cliPath) {
targetPath = path.join(cliPath, "agent")
} else {
let scope: "global" | "project" = "global" let scope: "global" | "project" = "global"
if (project.vcs === "git") { if (project.vcs === "git") {
const scopeResult = await prompts.select({ const scopeResult = await prompts.select({
@@ -39,47 +91,58 @@ const AgentCreateCommand = cmd({
if (prompts.isCancel(scopeResult)) throw new UI.CancelledError() if (prompts.isCancel(scopeResult)) throw new UI.CancelledError()
scope = scopeResult scope = scopeResult
} }
targetPath = path.join(
scope === "global" ? Global.Path.config : path.join(Instance.worktree, ".opencode"),
"agent",
)
}
// Get description
let description: string
if (cliDescription) {
description = cliDescription
} else {
const query = await prompts.text({ const query = await prompts.text({
message: "Description", message: "Description",
placeholder: "What should this agent do?", placeholder: "What should this agent do?",
validate: (x) => (x && x.length > 0 ? undefined : "Required"), validate: (x) => (x && x.length > 0 ? undefined : "Required"),
}) })
if (prompts.isCancel(query)) throw new UI.CancelledError() if (prompts.isCancel(query)) throw new UI.CancelledError()
description = query
}
// Generate agent
const spinner = prompts.spinner() const spinner = prompts.spinner()
spinner.start("Generating agent configuration...") spinner.start("Generating agent configuration...")
const generated = await Agent.generate({ description: query }).catch((error) => { const generated = await Agent.generate({ description }).catch((error) => {
spinner.stop(`LLM failed to generate agent: ${error.message}`, 1) spinner.stop(`LLM failed to generate agent: ${error.message}`, 1)
if (isFullyNonInteractive) process.exit(1)
throw new UI.CancelledError() throw new UI.CancelledError()
}) })
spinner.stop(`Agent ${generated.identifier} generated`) spinner.stop(`Agent ${generated.identifier} generated`)
const availableTools = [ // Select tools
"bash", let selectedTools: string[]
"read", if (cliTools !== undefined) {
"write", selectedTools = cliTools ? cliTools.split(",").map((t) => t.trim()) : AVAILABLE_TOOLS
"edit", } else {
"list", const result = await prompts.multiselect({
"glob",
"grep",
"webfetch",
"task",
"todowrite",
"todoread",
]
const selectedTools = await prompts.multiselect({
message: "Select tools to enable", message: "Select tools to enable",
options: availableTools.map((tool) => ({ options: AVAILABLE_TOOLS.map((tool) => ({
label: tool, label: tool,
value: tool, value: tool,
})), })),
initialValues: availableTools, initialValues: AVAILABLE_TOOLS,
}) })
if (prompts.isCancel(selectedTools)) throw new UI.CancelledError() if (prompts.isCancel(result)) throw new UI.CancelledError()
selectedTools = result
}
// Get mode
let mode: AgentMode
if (cliMode) {
mode = cliMode
} else {
const modeResult = await prompts.select({ const modeResult = await prompts.select({
message: "Agent mode", message: "Agent mode",
options: [ options: [
@@ -99,36 +162,57 @@ const AgentCreateCommand = cmd({
hint: "Can be used as a subagent by other agents", hint: "Can be used as a subagent by other agents",
}, },
], ],
initialValue: "all", initialValue: "all" as const,
}) })
if (prompts.isCancel(modeResult)) throw new UI.CancelledError() if (prompts.isCancel(modeResult)) throw new UI.CancelledError()
mode = modeResult
}
// Build tools config
const tools: Record<string, boolean> = {} const tools: Record<string, boolean> = {}
for (const tool of availableTools) { for (const tool of AVAILABLE_TOOLS) {
if (!selectedTools.includes(tool)) { if (!selectedTools.includes(tool)) {
tools[tool] = false tools[tool] = false
} }
} }
const frontmatter: any = { // Build frontmatter
const frontmatter: {
description: string
mode: AgentMode
tools?: Record<string, boolean>
} = {
description: generated.whenToUse, description: generated.whenToUse,
mode: modeResult, mode,
} }
if (Object.keys(tools).length > 0) { if (Object.keys(tools).length > 0) {
frontmatter.tools = tools frontmatter.tools = tools
} }
// Write file
const content = matter.stringify(generated.systemPrompt, frontmatter) const content = matter.stringify(generated.systemPrompt, frontmatter)
const filePath = path.join( const filePath = path.join(targetPath, `${generated.identifier}.md`)
scope === "global" ? Global.Path.config : path.join(Instance.worktree, ".opencode"),
`agent`, await fs.mkdir(targetPath, { recursive: true })
`${generated.identifier}.md`,
) const file = Bun.file(filePath)
if (await file.exists()) {
if (isFullyNonInteractive) {
console.error(`Error: Agent file already exists: ${filePath}`)
process.exit(1)
}
prompts.log.error(`Agent file already exists: ${filePath}`)
throw new UI.CancelledError()
}
await Bun.write(filePath, content) await Bun.write(filePath, content)
if (isFullyNonInteractive) {
console.log(filePath)
} else {
prompts.log.success(`Agent created: ${filePath}`) prompts.log.success(`Agent created: ${filePath}`)
prompts.outro("Done") prompts.outro("Done")
}
}, },
}) })
}, },