Compare commits

...

21 Commits

Author SHA1 Message Date
opencode ee4190aa41 release: v1.0.143 2025-12-10 13:53:21 +00:00
Dax Raad de8460cb99 docs: improve bash and grep tool documentation with clearer usage guidelines 2025-12-10 08:48:41 -05:00
opencode f7b2beaaf1 release: v1.0.142 2025-12-10 13:25:55 +00:00
GitHub Action 9b0933187e ignore: update download stats 2025-12-10 2025-12-10 12:04:47 +00:00
Adam 862141e8b2 fix: exit aliases 2025-12-10 02:49:54 -06:00
Aiden Cline 070ced0b3f fix: revert hook try/catch that surpressed errors 2025-12-10 00:14:24 -06:00
GitHub Action cc3b699823 chore: format code 2025-12-10 06:02:31 +00:00
spoj 301f1a191b fix: add Windows support for shell mode (! command) (#5311) 2025-12-10 00:01:56 -06:00
Adam d149c25aab fix: types 2025-12-09 21:44:34 -06:00
Adam 18d24b8f5f wip(desktop): progress 2025-12-09 21:39:13 -06:00
Adam cf34981e8f wip(desktop): progress 2025-12-09 21:39:13 -06:00
Adam e2ebe560ea feat: provider icon component 2025-12-09 21:39:12 -06:00
GitHub Action 6db822fd92 chore: format code 2025-12-10 03:32:26 +00:00
Brendan Allan 661122bab8 tauri: don't ask to restart separately in updater 2025-12-10 11:31:50 +08:00
Brendan Allan 4a96836d11 tauri: update macos icon 2025-12-10 11:28:40 +08:00
Brendan Allan e072f9605c tauri: comment out restart server dialog 2025-12-10 11:18:17 +08:00
Brendan Allan 9986031481 fix: use project references for desktop typecheck 2025-12-10 11:15:12 +08:00
Dax Raad 3d95848607 ci 2025-12-09 22:14:18 -05:00
GitHub Action 221c9028af chore: format code 2025-12-10 03:10:18 +00:00
Timor b2057791aa feat: add CLI arguments to agent create command for scripting (#5157) 2025-12-09 21:09:45 -06:00
Dax Raad c1ee6d6c41 ci 2025-12-10 02:57:39 +00:00
1202 changed files with 1834 additions and 196 deletions
+1 -1
View File
@@ -81,7 +81,7 @@ jobs:
NPM_CONFIG_PROVENANCE: false
publish-tauri:
if: inputs.bump || inputs.version
if: false # inputs.bump || inputs.version
continue-on-error: true
strategy:
fail-fast: false
+1
View File
@@ -165,3 +165,4 @@
| 2025-12-07 | 994,046 (+6,162) | 951,425 (+7,652) | 1,945,471 (+13,814) |
| 2025-12-08 | 1,000,898 (+6,852) | 957,149 (+5,724) | 1,958,047 (+12,576) |
| 2025-12-09 | 1,011,488 (+10,590) | 973,922 (+16,773) | 1,985,410 (+27,363) |
| 2025-12-10 | 1,025,891 (+14,403) | 991,708 (+17,786) | 2,017,599 (+32,189) |
+15 -15
View File
@@ -20,7 +20,7 @@
},
"packages/console/app": {
"name": "@opencode-ai/console-app",
"version": "1.0.141",
"version": "1.0.143",
"dependencies": {
"@cloudflare/vite-plugin": "1.15.2",
"@ibm/plex": "6.4.1",
@@ -48,7 +48,7 @@
},
"packages/console/core": {
"name": "@opencode-ai/console-core",
"version": "1.0.141",
"version": "1.0.143",
"dependencies": {
"@aws-sdk/client-sts": "3.782.0",
"@jsx-email/render": "1.1.1",
@@ -75,7 +75,7 @@
},
"packages/console/function": {
"name": "@opencode-ai/console-function",
"version": "1.0.141",
"version": "1.0.143",
"dependencies": {
"@ai-sdk/anthropic": "2.0.0",
"@ai-sdk/openai": "2.0.2",
@@ -99,7 +99,7 @@
},
"packages/console/mail": {
"name": "@opencode-ai/console-mail",
"version": "1.0.141",
"version": "1.0.143",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",
@@ -123,7 +123,7 @@
},
"packages/desktop": {
"name": "@opencode-ai/desktop",
"version": "1.0.141",
"version": "1.0.143",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/sdk": "workspace:*",
@@ -168,7 +168,7 @@
},
"packages/enterprise": {
"name": "@opencode-ai/enterprise",
"version": "1.0.141",
"version": "1.0.143",
"dependencies": {
"@opencode-ai/ui": "workspace:*",
"@opencode-ai/util": "workspace:*",
@@ -196,7 +196,7 @@
},
"packages/function": {
"name": "@opencode-ai/function",
"version": "1.0.141",
"version": "1.0.143",
"dependencies": {
"@octokit/auth-app": "8.0.1",
"@octokit/rest": "22.0.0",
@@ -212,7 +212,7 @@
},
"packages/opencode": {
"name": "opencode",
"version": "1.0.141",
"version": "1.0.143",
"bin": {
"opencode": "./bin/opencode",
},
@@ -304,7 +304,7 @@
},
"packages/plugin": {
"name": "@opencode-ai/plugin",
"version": "1.0.141",
"version": "1.0.143",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"zod": "catalog:",
@@ -324,7 +324,7 @@
},
"packages/sdk/js": {
"name": "@opencode-ai/sdk",
"version": "1.0.141",
"version": "1.0.143",
"devDependencies": {
"@hey-api/openapi-ts": "0.88.1",
"@tsconfig/node22": "catalog:",
@@ -335,7 +335,7 @@
},
"packages/slack": {
"name": "@opencode-ai/slack",
"version": "1.0.141",
"version": "1.0.143",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"@slack/bolt": "^3.17.1",
@@ -348,7 +348,7 @@
},
"packages/tauri": {
"name": "@opencode-ai/tauri",
"version": "1.0.141",
"version": "1.0.143",
"dependencies": {
"@opencode-ai/desktop": "workspace:*",
"@tauri-apps/api": "^2",
@@ -370,7 +370,7 @@
},
"packages/ui": {
"name": "@opencode-ai/ui",
"version": "1.0.141",
"version": "1.0.143",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/sdk": "workspace:*",
@@ -402,7 +402,7 @@
},
"packages/util": {
"name": "@opencode-ai/util",
"version": "1.0.141",
"version": "1.0.143",
"dependencies": {
"zod": "catalog:",
},
@@ -413,7 +413,7 @@
},
"packages/web": {
"name": "@opencode-ai/web",
"version": "1.0.141",
"version": "1.0.143",
"dependencies": {
"@astrojs/cloudflare": "12.6.3",
"@astrojs/markdown-remark": "6.3.1",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-app",
"version": "1.0.141",
"version": "1.0.143",
"type": "module",
"scripts": {
"typecheck": "tsgo --noEmit",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/console-core",
"version": "1.0.141",
"version": "1.0.143",
"private": true,
"type": "module",
"dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-function",
"version": "1.0.141",
"version": "1.0.143",
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-mail",
"version": "1.0.141",
"version": "1.0.143",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/desktop",
"version": "1.0.141",
"version": "1.0.143",
"description": "",
"type": "module",
"exports": {
@@ -8,7 +8,7 @@
"./vite": "./vite.js"
},
"scripts": {
"typecheck": "tsgo --noEmit",
"typecheck": "tsgo -b",
"start": "vite",
"dev": "vite",
"build": "vite build",
@@ -14,15 +14,43 @@ import { Button } from "@opencode-ai/ui/button"
import { Icon } from "@opencode-ai/ui/icon"
import { Tooltip } from "@opencode-ai/ui/tooltip"
import { IconButton } from "@opencode-ai/ui/icon-button"
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
import { Select } from "@opencode-ai/ui/select"
import { getDirectory, getFilename } from "@opencode-ai/util/path"
import { type IconName } from "@opencode-ai/ui/icons/provider"
interface PromptInputProps {
class?: string
ref?: (el: HTMLDivElement) => void
}
const PLACEHOLDERS = ["Fix a TODO in the codebase", "What is the tech stack of this project?", "Fix broken tests"]
const PLACEHOLDERS = [
"Fix a TODO in the codebase",
"What is the tech stack of this project?",
"Fix broken tests",
"Explain how authentication works",
"Find and fix security vulnerabilities",
"Add unit tests for the user service",
"Refactor this function to be more readable",
"What does this error mean?",
"Help me debug this issue",
"Generate API documentation",
"Optimize database queries",
"Add input validation",
"Create a new component for...",
"How do I deploy this project?",
"Review my code for best practices",
"Add error handling to this function",
"Explain this regex pattern",
"Convert this to TypeScript",
"Add logging throughout the codebase",
"What dependencies are outdated?",
"Help me write a migration script",
"Implement caching for this endpoint",
"Add pagination to this list",
"Create a CLI command for...",
"How do environment variables work here?",
]
export const PromptInput: Component<PromptInputProps> = (props) => {
const navigate = useNavigate()
@@ -43,7 +71,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
onMount(() => {
const interval = setInterval(() => {
setPlaceholder((prev) => (prev + 1) % PLACEHOLDERS.length)
}, 5000)
}, 6500)
onCleanup(() => clearInterval(interval))
})
@@ -460,7 +488,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
{(i) => (
<div class="w-full flex items-center justify-between gap-x-3">
<div class="flex items-center gap-x-2.5 text-text-muted grow min-w-0">
<img src={`https://models.dev/logos/${i.provider.id}.svg`} class="size-6 p-0.5 shrink-0" />
<ProviderIcon name={i.provider.id as IconName} class="size-6 p-0.5 shrink-0" />
<div class="flex gap-x-3 items-baseline flex-[1_0_0]">
<span class="text-14-medium text-text-strong overflow-hidden text-ellipsis">{i.name}</span>
<Show when={false}>
+15 -6
View File
@@ -31,13 +31,20 @@ const PASTEL_COLORS = [
"#C1E1C1", // pastel mint
]
function randomPastelColor() {
return PASTEL_COLORS[Math.floor(Math.random() * PASTEL_COLORS.length)]
function pickAvailableColor(usedColors: Set<string>) {
const available = PASTEL_COLORS.filter((c) => !usedColors.has(c))
if (available.length === 0) return PASTEL_COLORS[Math.floor(Math.random() * PASTEL_COLORS.length)]
return available[Math.floor(Math.random() * available.length)]
}
async function ensureProjectColor(project: Project, sdk: ReturnType<typeof useGlobalSDK>): Promise<Project> {
async function ensureProjectColor(
project: Project,
sdk: ReturnType<typeof useGlobalSDK>,
usedColors: Set<string>,
): Promise<Project> {
if (project.icon?.color) return project
const color = randomPastelColor()
const color = pickAvailableColor(usedColors)
usedColors.add(color)
const updated = { ...project, icon: { ...project.icon, color } }
sdk.client.project.update({ projectID: project.id, icon: { color } })
return updated
@@ -117,7 +124,8 @@ export const { use: useGlobalSync, provider: GlobalSyncProvider } = createSimple
if (directory === "global") {
switch (event.type) {
case "project.updated": {
ensureProjectColor(event.properties, sdk).then((project) => {
const usedColors = new Set(globalStore.projects.map((p) => p.icon?.color).filter(Boolean) as string[])
ensureProjectColor(event.properties, sdk, usedColors).then((project) => {
const result = Binary.search(globalStore.projects, project.id, (s) => s.id)
if (result.found) {
setGlobalStore("projects", result.index, reconcile(project))
@@ -209,7 +217,8 @@ export const { use: useGlobalSync, provider: GlobalSyncProvider } = createSimple
Promise.all([
sdk.client.project.list().then(async (x) => {
const filtered = x.data!.filter((p) => !p.worktree.includes("opencode-test") && p.vcs)
const projects = await Promise.all(filtered.map((p) => ensureProjectColor(p, sdk)))
const usedColors = new Set(filtered.map((p) => p.icon?.color).filter(Boolean) as string[])
const projects = await Promise.all(filtered.map((p) => ensureProjectColor(p, sdk, usedColors)))
setGlobalStore(
"projects",
projects.sort((a, b) => a.id.localeCompare(b.id)),
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/enterprise",
"version": "1.0.141",
"version": "1.0.143",
"private": true,
"type": "module",
"scripts": {
@@ -8,6 +8,7 @@ import { createEffect, createMemo, ErrorBoundary, For, Match, Show, Switch } fro
import { Share } from "~/core/share"
import { Logo, Mark } from "@opencode-ai/ui/logo"
import { IconButton } from "@opencode-ai/ui/icon-button"
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
import { createDefaultOptions } from "@opencode-ai/ui/pierre"
import { iife } from "@opencode-ai/util/iife"
import { Binary } from "@opencode-ai/util/binary"
@@ -21,6 +22,7 @@ import { Tabs } from "@opencode-ai/ui/tabs"
import { preloadMultiFileDiff, PreloadMultiFileDiffResult } from "@pierre/precision-diffs/ssr"
import { Diff as SSRDiff } from "@opencode-ai/ui/diff-ssr"
import { clientOnly } from "@solidjs/start"
import { type IconName } from "@opencode-ai/ui/icons/provider"
const ClientOnlyDiff = clientOnly(() => import("@opencode-ai/ui/diff").then((m) => ({ default: m.Diff })))
@@ -210,10 +212,7 @@ export default function () {
<div class="text-12-mono text-text-base">v{info().version}</div>
</div>
<div class="flex gap-2 items-center">
<img
src={`https://models.dev/logos/${provider()}.svg`}
class="size-3.5 shrink-0 dark:invert"
/>
<ProviderIcon name={provider() as IconName} class="size-3.5 shrink-0 text-icon-strong-base" />
<div class="text-12-regular text-text-base">{model()?.name ?? modelID()}</div>
</div>
<div class="text-12-regular text-text-weaker">
+6 -6
View File
@@ -1,7 +1,7 @@
id = "opencode"
name = "OpenCode"
description = "The open source coding agent."
version = "1.0.141"
version = "1.0.143"
schema_version = 1
authors = ["Anomaly"]
repository = "https://github.com/sst/opencode"
@@ -11,26 +11,26 @@ name = "OpenCode"
icon = "./icons/opencode.svg"
[agent_servers.opencode.targets.darwin-aarch64]
archive = "https://github.com/sst/opencode/releases/download/v1.0.141/opencode-darwin-arm64.zip"
archive = "https://github.com/sst/opencode/releases/download/v1.0.143/opencode-darwin-arm64.zip"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.darwin-x86_64]
archive = "https://github.com/sst/opencode/releases/download/v1.0.141/opencode-darwin-x64.zip"
archive = "https://github.com/sst/opencode/releases/download/v1.0.143/opencode-darwin-x64.zip"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.linux-aarch64]
archive = "https://github.com/sst/opencode/releases/download/v1.0.141/opencode-linux-arm64.tar.gz"
archive = "https://github.com/sst/opencode/releases/download/v1.0.143/opencode-linux-arm64.tar.gz"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.linux-x86_64]
archive = "https://github.com/sst/opencode/releases/download/v1.0.141/opencode-linux-x64.tar.gz"
archive = "https://github.com/sst/opencode/releases/download/v1.0.143/opencode-linux-x64.tar.gz"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.windows-x86_64]
archive = "https://github.com/sst/opencode/releases/download/v1.0.141/opencode-windows-x64.zip"
archive = "https://github.com/sst/opencode/releases/download/v1.0.143/opencode-windows-x64.zip"
cmd = "./opencode.exe"
args = ["acp"]
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/function",
"version": "1.0.141",
"version": "1.0.143",
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "1.0.141",
"version": "1.0.143",
"name": "opencode",
"type": "module",
"private": true,
-4
View File
@@ -37,10 +37,6 @@ await Bun.file(`./dist/${pkg.name}/package.json`).write(
)
const tags = [Script.channel]
if (!Script.preview) {
const major = Script.version.split(".")[0]
tags[0] = `latest-${major}`
}
const tasks = Object.entries(binaries).map(async ([name]) => {
if (process.platform !== "win32") {
+1 -1
View File
@@ -252,7 +252,7 @@ export namespace Agent {
},
},
temperature: 0.3,
prompt: [
messages: [
...system.map(
(item): ModelMessage => ({
role: "system",
+170 -86
View File
@@ -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 project = Instance.project
const cliPath = args.path
const cliDescription = args.description
const cliMode = args.mode as AgentMode | undefined
const cliTools = args.tools
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
const isFullyNonInteractive = cliPath && cliDescription && cliMode && cliTools !== undefined
if (!isFullyNonInteractive) {
UI.empty()
prompts.intro("Create 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()
const project = Instance.project
// 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",
)
}
// 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",
]
// 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
}
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()
// 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")
}
},
})
},
@@ -410,6 +410,11 @@ export function Prompt(props: PromptProps) {
if (props.disabled) return
if (autocomplete.visible) return
if (!store.prompt.input) return
const trimmed = store.prompt.input.trim()
if (trimmed === "exit" || trimmed === "quit" || trimmed === ":q") {
exit()
return
}
const selectedModel = local.model.current()
if (!selectedModel) {
promptModelWarning()
+2 -1
View File
@@ -184,7 +184,8 @@ export namespace Installation {
return reg.endsWith("/") ? reg.slice(0, -1) : reg
})
const [major] = VERSION.split(".").map((x) => Number(x))
const channel = CHANNEL === "latest" ? `latest-${major}` : CHANNEL
// const channel = CHANNEL === "latest" ? `latest-${major}` : CHANNEL
const channel = CHANNEL
return fetch(`${registry}/opencode-ai/${channel}`)
.then((res) => {
if (!res.ok) throw new Error(res.statusText)
+4 -8
View File
@@ -61,14 +61,10 @@ export namespace Plugin {
for (const hook of await state().then((x) => x.hooks)) {
const fn = hook[name]
if (!fn) continue
try {
// @ts-expect-error if you feel adventurous, please fix the typing, make sure to bump the try-counter if you
// give up.
// try-counter: 2
await fn(input, output)
} catch (e) {
log.error("failed to trigger hook", { name, error: e })
}
// @ts-expect-error if you feel adventurous, please fix the typing, make sure to bump the try-counter if you
// give up.
// try-counter: 2
await fn(input, output)
}
return output
}
+11 -3
View File
@@ -1231,8 +1231,8 @@ export namespace SessionPrompt {
},
}
await Session.updatePart(part)
const shell = process.env["SHELL"] ?? "bash"
const shellName = path.basename(shell)
const shell = process.env["SHELL"] ?? (process.platform === "win32" ? process.env["COMSPEC"] || "cmd.exe" : "bash")
const shellName = path.basename(shell).toLowerCase()
const invocations: Record<string, { args: string[] }> = {
nu: {
@@ -1262,6 +1262,14 @@ export namespace SessionPrompt {
`,
],
},
// Windows cmd.exe
"cmd.exe": {
args: ["/c", input.command],
},
// Windows PowerShell
"powershell.exe": {
args: ["-NoProfile", "-Command", input.command],
},
// Fallback: any shell that doesn't match those above
"": {
args: ["-c", "-l", `${input.command}`],
@@ -1273,7 +1281,7 @@ export namespace SessionPrompt {
const proc = spawn(shell, args, {
cwd: Instance.directory,
detached: true,
detached: process.platform !== "win32",
stdio: ["ignore", "pipe", "pipe"],
env: {
...process.env,
+42 -8
View File
@@ -17,14 +17,48 @@ Before executing the command, please follow these steps:
- Capture the output of the command.
Usage notes:
- The command argument is required.
- You can specify an optional timeout in milliseconds. If not specified, commands will timeout after 120000ms (2 minutes). Use the `timeout` parameter to control execution time.
- The `workdir` parameter specifies the working directory for the command. Defaults to the current working directory. Prefer setting `workdir` over using `cd` in your commands.
- It is very helpful if you write a clear, concise description of what this command does in 5-10 words.
- If the output exceeds 30000 characters, output will be truncated before being returned to you.
- VERY IMPORTANT: You MUST avoid using search commands like `find` and `grep`. Instead use Grep, Glob, or Task to search. You MUST avoid read tools like `cat`, `head`, `tail`, and `ls`, and use Read and List to read files.
- If you _still_ need to run `grep`, STOP. ALWAYS USE ripgrep at `rg` (or /usr/bin/rg) first, which all opencode users have pre-installed.
- When issuing multiple commands, use the ';' or '&&' operator to separate them. DO NOT use newlines (newlines are ok in quoted strings).
- The command argument is required.
- You can specify an optional timeout in milliseconds (up to 600000ms / 10 minutes).
If not specified, commands will timeout after 120000ms (2 minutes).
- It is very helpful if you write a clear, concise description of what this command
does in 5-10 words.
- If the output exceeds 30000 characters, output will be truncated before being
returned to you.
- You can use the `run_in_background` parameter to run the command in the background,
which allows you to continue working while the command runs. You can monitor the output
using the Bash tool as it becomes available. You do not need to use '&' at the end of
the command when using this parameter.
- Avoid using Bash with the `find`, `grep`, `cat`, `head`, `tail`, `sed`, `awk`, or
`echo` commands, unless explicitly instructed or when these commands are truly necessary
for the task. Instead, always prefer using the dedicated tools for these commands:
- File search: Use Glob (NOT find or ls)
- Content search: Use Grep (NOT grep or rg)
- Read files: Use Read (NOT cat/head/tail)
- Edit files: Use Edit (NOT sed/awk)
- Write files: Use Write (NOT echo >/cat <<EOF)
- Communication: Output text directly (NOT echo/printf)
- When issuing multiple commands:
- If the commands are independent and can run in parallel, make multiple Bash tool
calls in a single message. For example, if you need to run "git status" and "git diff",
send a single message with two Bash tool calls in parallel.
- If the commands depend on each other and must run sequentially, use a single Bash
call with '&&' to chain them together (e.g., `git add . && git commit -m "message" &&
git push`). For instance, if one operation must complete before another starts (like
mkdir before cp, Write before Bash for git operations, or git add before git commit),
run these operations sequentially instead.
- Use ';' only when you need to run commands sequentially but don't care if earlier
commands fail
- DO NOT use newlines to separate commands (newlines are ok in quoted strings)
- Try to maintain your current working directory throughout the session by using
absolute paths and avoiding usage of `cd`. You may use `cd` if the User explicitly
requests it.
<good-example>
pytest /foo/bar/tests
</good-example>
<bad-example>
cd /foo/bar && pytest tests
</bad-example>
# Working Directory
+1 -1
View File
@@ -2,7 +2,7 @@
- Searches file contents using regular expressions
- Supports full regex syntax (eg. "log.*Error", "function\s+\w+", etc.)
- Filter files by pattern with the include parameter (eg. "*.js", "*.{ts,tsx}")
- Returns file paths with at least one match sorted by modification time
- Returns file paths and line numbers with at least one match sorted by modification time
- Use this tool when you need to find files containing specific patterns
- If you need to identify/count the number of matches within files, use the Bash tool with `rg` (ripgrep) directly. Do NOT use `grep`.
- When you are doing an open ended search that may require multiple rounds of globbing and grepping, use the Task tool instead
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/plugin",
"version": "1.0.141",
"version": "1.0.143",
"type": "module",
"scripts": {
"typecheck": "tsgo --noEmit",
+1 -1
View File
@@ -29,7 +29,7 @@ const IS_PREVIEW = CHANNEL !== "latest"
const VERSION = await (async () => {
if (env.OPENCODE_VERSION) return env.OPENCODE_VERSION
if (IS_PREVIEW) return `0.0.0-${CHANNEL}-${new Date().toISOString().slice(0, 16).replace(/[-:T]/g, "")}`
const version = await fetch("https://registry.npmjs.org/opencode-ai/latest-1")
const version = await fetch("https://registry.npmjs.org/opencode-ai/latest")
.then((res) => {
if (!res.ok) throw new Error(res.statusText)
return res.json()
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/sdk",
"version": "1.0.141",
"version": "1.0.143",
"type": "module",
"scripts": {
"typecheck": "tsgo --noEmit",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/slack",
"version": "1.0.141",
"version": "1.0.143",
"type": "module",
"scripts": {
"dev": "bun run src/index.ts",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@opencode-ai/tauri",
"private": true,
"version": "1.0.141",
"version": "1.0.143",
"type": "module",
"scripts": {
"typecheck": "tsgo -b",
Binary file not shown.
+22 -21
View File
@@ -108,29 +108,30 @@ pub fn run() {
tauri::async_runtime::spawn(async move {
let port = get_sidecar_port();
let socket_connected = is_server_running(port).await;
let should_spawn_sidecar = if socket_connected {
let res = app
.dialog()
.message(
"OpenCode Server is already running, would you like to restart it?",
)
.buttons(MessageDialogButtons::YesNo)
.blocking_show_with_result();
let should_spawn_sidecar = !is_server_running(port).await;
match res {
MessageDialogResult::Yes => {
if let Err(e) = find_and_kill_process_on_port(port) {
eprintln!("Failed to kill process on port {}: {}", port, e);
}
true
}
_ => false,
}
} else {
true
};
// if server_running {
// let res = app
// .dialog()
// .message(
// "OpenCode Server is already running, would you like to restart it?",
// )
// .buttons(MessageDialogButtons::YesNo)
// .blocking_show_with_result();
// match res {
// MessageDialogResult::Yes => {
// if let Err(e) = find_and_kill_process_on_port(port) {
// eprintln!("Failed to kill process on port {}: {}", port, e);
// }
// true
// }
// _ => false,
// }
// } else {
// true
// };
let child = if should_spawn_sidecar {
let child = spawn_sidecar(&app, port);
+4 -3
View File
@@ -20,7 +20,9 @@ export async function runUpdater(onDownloadEvent?: (progress: DownloadEvent) =>
return false
}
const shouldUpdate = await ask(`Version ${update.version} of OpenCode is available, would you like to install it?`)
const shouldUpdate = await ask(
`Version ${update.version} of OpenCode has been downloaded, would you like to install it and relaunch?`,
)
if (!shouldUpdate) return
try {
@@ -30,8 +32,7 @@ export async function runUpdater(onDownloadEvent?: (progress: DownloadEvent) =>
return false
}
const shouldRestart = await ask("Update installed successfully, would you like to restart OpenCode?")
if (shouldRestart) await relaunch()
await relaunch()
return true
}
+3 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/ui",
"version": "1.0.141",
"version": "1.0.143",
"type": "module",
"exports": {
"./*": "./src/components/*.tsx",
@@ -10,6 +10,8 @@
"./context/*": "./src/context/*.tsx",
"./styles": "./src/styles/index.css",
"./styles/tailwind": "./src/styles/tailwind/index.css",
"./icons/provider": "./src/components/provider-icons/types.ts",
"./icons/file-type": "./src/components/file-icons/types.ts",
"./fonts/*": "./src/assets/fonts/*"
},
"scripts": {

Before

Width:  |  Height:  |  Size: 385 B

After

Width:  |  Height:  |  Size: 385 B

Before

Width:  |  Height:  |  Size: 110 B

After

Width:  |  Height:  |  Size: 110 B

Before

Width:  |  Height:  |  Size: 746 B

After

Width:  |  Height:  |  Size: 746 B

Before

Width:  |  Height:  |  Size: 758 B

After

Width:  |  Height:  |  Size: 758 B

Before

Width:  |  Height:  |  Size: 348 B

After

Width:  |  Height:  |  Size: 348 B

Before

Width:  |  Height:  |  Size: 511 B

After

Width:  |  Height:  |  Size: 511 B

Before

Width:  |  Height:  |  Size: 511 B

After

Width:  |  Height:  |  Size: 511 B

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Before

Width:  |  Height:  |  Size: 297 B

After

Width:  |  Height:  |  Size: 297 B

Before

Width:  |  Height:  |  Size: 262 B

After

Width:  |  Height:  |  Size: 262 B

Before

Width:  |  Height:  |  Size: 775 B

After

Width:  |  Height:  |  Size: 775 B

Before

Width:  |  Height:  |  Size: 151 B

After

Width:  |  Height:  |  Size: 151 B

Before

Width:  |  Height:  |  Size: 509 B

After

Width:  |  Height:  |  Size: 509 B

Before

Width:  |  Height:  |  Size: 263 B

After

Width:  |  Height:  |  Size: 263 B

Before

Width:  |  Height:  |  Size: 823 B

After

Width:  |  Height:  |  Size: 823 B

Before

Width:  |  Height:  |  Size: 363 B

After

Width:  |  Height:  |  Size: 363 B

Before

Width:  |  Height:  |  Size: 431 B

After

Width:  |  Height:  |  Size: 431 B

Before

Width:  |  Height:  |  Size: 591 B

After

Width:  |  Height:  |  Size: 591 B

Before

Width:  |  Height:  |  Size: 776 B

After

Width:  |  Height:  |  Size: 776 B

Before

Width:  |  Height:  |  Size: 776 B

After

Width:  |  Height:  |  Size: 776 B

Before

Width:  |  Height:  |  Size: 579 B

After

Width:  |  Height:  |  Size: 579 B

Before

Width:  |  Height:  |  Size: 418 B

After

Width:  |  Height:  |  Size: 418 B

Before

Width:  |  Height:  |  Size: 281 B

After

Width:  |  Height:  |  Size: 281 B

Before

Width:  |  Height:  |  Size: 415 B

After

Width:  |  Height:  |  Size: 415 B

Before

Width:  |  Height:  |  Size: 517 B

After

Width:  |  Height:  |  Size: 517 B

Before

Width:  |  Height:  |  Size: 686 B

After

Width:  |  Height:  |  Size: 686 B

Before

Width:  |  Height:  |  Size: 577 B

After

Width:  |  Height:  |  Size: 577 B

Before

Width:  |  Height:  |  Size: 185 B

After

Width:  |  Height:  |  Size: 185 B

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

Before

Width:  |  Height:  |  Size: 532 B

After

Width:  |  Height:  |  Size: 532 B

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Before

Width:  |  Height:  |  Size: 384 B

After

Width:  |  Height:  |  Size: 384 B

Before

Width:  |  Height:  |  Size: 747 B

After

Width:  |  Height:  |  Size: 747 B

Before

Width:  |  Height:  |  Size: 596 B

After

Width:  |  Height:  |  Size: 596 B

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Before

Width:  |  Height:  |  Size: 204 B

After

Width:  |  Height:  |  Size: 204 B

Before

Width:  |  Height:  |  Size: 620 B

After

Width:  |  Height:  |  Size: 620 B

Before

Width:  |  Height:  |  Size: 755 B

After

Width:  |  Height:  |  Size: 755 B

Before

Width:  |  Height:  |  Size: 392 B

After

Width:  |  Height:  |  Size: 392 B

Before

Width:  |  Height:  |  Size: 659 B

After

Width:  |  Height:  |  Size: 659 B

Before

Width:  |  Height:  |  Size: 659 B

After

Width:  |  Height:  |  Size: 659 B

Before

Width:  |  Height:  |  Size: 659 B

After

Width:  |  Height:  |  Size: 659 B

Before

Width:  |  Height:  |  Size: 830 B

After

Width:  |  Height:  |  Size: 830 B

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before

Width:  |  Height:  |  Size: 226 B

After

Width:  |  Height:  |  Size: 226 B

Before

Width:  |  Height:  |  Size: 311 B

After

Width:  |  Height:  |  Size: 311 B

Before

Width:  |  Height:  |  Size: 685 B

After

Width:  |  Height:  |  Size: 685 B

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Before

Width:  |  Height:  |  Size: 672 B

After

Width:  |  Height:  |  Size: 672 B

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Before

Width:  |  Height:  |  Size: 346 B

After

Width:  |  Height:  |  Size: 346 B

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Before

Width:  |  Height:  |  Size: 588 B

After

Width:  |  Height:  |  Size: 588 B

Before

Width:  |  Height:  |  Size: 488 B

After

Width:  |  Height:  |  Size: 488 B

Before

Width:  |  Height:  |  Size: 520 B

After

Width:  |  Height:  |  Size: 520 B

Before

Width:  |  Height:  |  Size: 186 B

After

Width:  |  Height:  |  Size: 186 B

Before

Width:  |  Height:  |  Size: 990 B

After

Width:  |  Height:  |  Size: 990 B

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Before

Width:  |  Height:  |  Size: 245 B

After

Width:  |  Height:  |  Size: 245 B

Before

Width:  |  Height:  |  Size: 656 B

After

Width:  |  Height:  |  Size: 656 B

Some files were not shown because too many files have changed in this diff Show More