summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorTimor <[email protected]>2025-12-10 05:09:45 +0200
committerGitHub <[email protected]>2025-12-09 21:09:45 -0600
commitb2057791aa3f1d44213056a1738844e82e38d101 (patch)
tree808077fac78d112bca0e9f631a727995507eec92
parentc1ee6d6c41dff75fcb3b092b2a3b488efa0d8b0b (diff)
downloadopencode-b2057791aa3f1d44213056a1738844e82e38d101.tar.gz
opencode-b2057791aa3f1d44213056a1738844e82e38d101.zip
feat: add CLI arguments to agent create command for scripting (#5157)
-rw-r--r--packages/opencode/src/agent/agent.ts2
-rw-r--r--packages/opencode/src/cli/cmd/agent.ts256
2 files changed, 171 insertions, 87 deletions
diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts
index de0d2e504..94127e51c 100644
--- a/packages/opencode/src/agent/agent.ts
+++ b/packages/opencode/src/agent/agent.ts
@@ -252,7 +252,7 @@ export namespace Agent {
},
},
temperature: 0.3,
- prompt: [
+ messages: [
...system.map(
(item): ModelMessage => ({
role: "system",
diff --git a/packages/opencode/src/cli/cmd/agent.ts b/packages/opencode/src/cli/cmd/agent.ts
index a774c6d02..812e97423 100644
--- a/packages/opencode/src/cli/cmd/agent.ts
+++ b/packages/opencode/src/cli/cmd/agent.ts
@@ -4,131 +4,215 @@ import { UI } from "../ui"
import { Global } from "../../global"
import { Agent } from "../../agent/agent"
import path from "path"
+import fs from "fs/promises"
import matter from "gray-matter"
import { Instance } from "../../project/instance"
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({
command: "create",
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({
directory: process.cwd(),
async fn() {
- UI.empty()
- prompts.intro("Create agent")
+ 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()
+ prompts.intro("Create agent")
+ }
+
const project = Instance.project
- let scope: "global" | "project" = "global"
- if (project.vcs === "git") {
- const scopeResult = await prompts.select({
- message: "Location",
- options: [
- {
- label: "Current project",
- value: "project" as const,
- hint: Instance.worktree,
- },
- {
- label: "Global",
- value: "global" as const,
- hint: Global.Path.config,
- },
- ],
- })
- if (prompts.isCancel(scopeResult)) throw new UI.CancelledError()
- scope = scopeResult
+ // Determine scope/path
+ let targetPath: string
+ if (cliPath) {
+ targetPath = path.join(cliPath, "agent")
+ } else {
+ let scope: "global" | "project" = "global"
+ if (project.vcs === "git") {
+ const scopeResult = await prompts.select({
+ message: "Location",
+ options: [
+ {
+ label: "Current project",
+ value: "project" as const,
+ hint: Instance.worktree,
+ },
+ {
+ label: "Global",
+ value: "global" as const,
+ hint: Global.Path.config,
+ },
+ ],
+ })
+ if (prompts.isCancel(scopeResult)) throw new UI.CancelledError()
+ scope = scopeResult
+ }
+ targetPath = path.join(
+ scope === "global" ? Global.Path.config : path.join(Instance.worktree, ".opencode"),
+ "agent",
+ )
}
- const query = await prompts.text({
- message: "Description",
- placeholder: "What should this agent do?",
- validate: (x) => (x && x.length > 0 ? undefined : "Required"),
- })
- if (prompts.isCancel(query)) throw new UI.CancelledError()
+ // Get description
+ let description: string
+ if (cliDescription) {
+ description = cliDescription
+ } else {
+ const query = await prompts.text({
+ message: "Description",
+ placeholder: "What should this agent do?",
+ validate: (x) => (x && x.length > 0 ? undefined : "Required"),
+ })
+ if (prompts.isCancel(query)) throw new UI.CancelledError()
+ description = query
+ }
+ // Generate agent
const spinner = prompts.spinner()
-
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)
+ if (isFullyNonInteractive) process.exit(1)
throw new UI.CancelledError()
})
spinner.stop(`Agent ${generated.identifier} generated`)
- const availableTools = [
- "bash",
- "read",
- "write",
- "edit",
- "list",
- "glob",
- "grep",
- "webfetch",
- "task",
- "todowrite",
- "todoread",
- ]
-
- const selectedTools = await prompts.multiselect({
- message: "Select tools to enable",
- options: availableTools.map((tool) => ({
- label: tool,
- value: tool,
- })),
- initialValues: availableTools,
- })
- if (prompts.isCancel(selectedTools)) throw new UI.CancelledError()
-
- const modeResult = await prompts.select({
- message: "Agent mode",
- options: [
- {
- label: "All",
- value: "all" as const,
- hint: "Can function in both primary and subagent roles",
- },
- {
- label: "Primary",
- value: "primary" as const,
- hint: "Acts as a primary/main agent",
- },
- {
- label: "Subagent",
- value: "subagent" as const,
- hint: "Can be used as a subagent by other agents",
- },
- ],
- initialValue: "all",
- })
- if (prompts.isCancel(modeResult)) throw new UI.CancelledError()
+ // Select tools
+ let selectedTools: string[]
+ if (cliTools !== undefined) {
+ selectedTools = cliTools ? cliTools.split(",").map((t) => t.trim()) : AVAILABLE_TOOLS
+ } else {
+ const result = await prompts.multiselect({
+ message: "Select tools to enable",
+ options: AVAILABLE_TOOLS.map((tool) => ({
+ label: tool,
+ value: tool,
+ })),
+ initialValues: AVAILABLE_TOOLS,
+ })
+ 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({
+ message: "Agent mode",
+ options: [
+ {
+ label: "All",
+ value: "all" as const,
+ hint: "Can function in both primary and subagent roles",
+ },
+ {
+ label: "Primary",
+ value: "primary" as const,
+ hint: "Acts as a primary/main agent",
+ },
+ {
+ label: "Subagent",
+ value: "subagent" as const,
+ hint: "Can be used as a subagent by other agents",
+ },
+ ],
+ initialValue: "all" as const,
+ })
+ if (prompts.isCancel(modeResult)) throw new UI.CancelledError()
+ mode = modeResult
+ }
+
+ // Build tools config
const tools: Record<string, boolean> = {}
- for (const tool of availableTools) {
+ for (const tool of AVAILABLE_TOOLS) {
if (!selectedTools.includes(tool)) {
tools[tool] = false
}
}
- const frontmatter: any = {
+ // Build frontmatter
+ const frontmatter: {
+ description: string
+ mode: AgentMode
+ tools?: Record<string, boolean>
+ } = {
description: generated.whenToUse,
- mode: modeResult,
+ mode,
}
if (Object.keys(tools).length > 0) {
frontmatter.tools = tools
}
+ // Write file
const content = matter.stringify(generated.systemPrompt, frontmatter)
- const filePath = path.join(
- scope === "global" ? Global.Path.config : path.join(Instance.worktree, ".opencode"),
- `agent`,
- `${generated.identifier}.md`,
- )
+ const filePath = path.join(targetPath, `${generated.identifier}.md`)
+
+ await fs.mkdir(targetPath, { recursive: true })
+
+ 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)
- prompts.log.success(`Agent created: ${filePath}`)
- prompts.outro("Done")
+ if (isFullyNonInteractive) {
+ console.log(filePath)
+ } else {
+ prompts.log.success(`Agent created: ${filePath}`)
+ prompts.outro("Done")
+ }
},
})
},