Compare commits

..

19 Commits

Author SHA1 Message Date
Dax Raad 1a553e525f enable prompt caching for anthropic 2025-06-16 11:41:54 -04:00
adamdottv 3c4e966216 fix: spinner background color 2025-06-16 10:03:44 -05:00
Dax Raad 0721620ed8 docs: readme 2025-06-16 10:44:48 -04:00
Thomas Meire 9fc6734f32 ignore: remove log files and add them to gitignore (#138) 2025-06-16 09:30:07 -04:00
Jacob e1733a423d fix: typo and literal wording in packages/opencode/AGENTS.md (#134) 2025-06-16 08:18:29 -05:00
Dax Raad d42e3db7e0 docs: update README 2025-06-15 21:43:20 -04:00
Dax Raad cdb26f6d83 docs: readme 2025-06-15 21:39:02 -04:00
Dax Raad fe05edaa79 enhance ripgrep files function with query filtering and limit support
🤖 Generated with [opencode](https://opencode.ai)

Co-Authored-By: opencode <noreply@opencode.ai>
2025-06-15 21:26:32 -04:00
Dax Raad 7d174767b0 first pass making system prompt less fast 2025-06-15 20:25:04 -04:00
George Potoshin c5eefd1752 Fix: Improve Help UI Readability (Issue #99) (#117) 2025-06-15 18:38:44 -05:00
adamdottv 77a6b3bdd6 fix: background color rendering issues 2025-06-15 15:07:05 -05:00
Pierre B. 7effff56c0 fix: spelling, grammar and typos (#121) 2025-06-15 14:23:18 -05:00
Dax Raad e30fba0d3c Improve LSP server initialization with timeout handling and skip failed servers
🤖 Generated with [opencode](https://opencode.ai)

Co-Authored-By: opencode <noreply@opencode.ai>
2025-06-15 13:52:57 -04:00
Dax Raad 7fbb2ca9a6 ignore: add timer log helper 2025-06-15 13:33:24 -04:00
Dax Raad 230d0a1510 fix postinstall script for node 2025-06-15 13:11:11 -04:00
Pierre B. 46ff2c0ae0 chore: ignore intellij, vscode (#122) 2025-06-15 10:40:34 -05:00
adamdottv b8a89dab0f fix: background color rendering issues 2025-06-15 05:57:15 -05:00
szymon 7351e12886 remove .DS_Store (#112) 2025-06-15 05:34:46 -05:00
Dax Raad 38879dee2d beginning of upgrade command 2025-06-14 22:05:41 -04:00
30 changed files with 509 additions and 729 deletions
+4
View File
@@ -3,3 +3,7 @@ node_modules
.opencode
.sst
.env
.idea
.vscode
app.log
gopls.log
+19 -6
View File
@@ -2,7 +2,7 @@
AI coding agent, built for the terminal.
⚠️ **Note:** version 0.1.x is a full rewrite and we do not have proper documentation for it yet. Should have this out week of June 17th 2025 📚
⚠️ **Note:** version 0.1.x is a full rewrite, and we do not have proper documentation for it yet. Should have this out week of June 17th 2025 📚
### Installation
@@ -20,9 +20,9 @@ paru -S opencode-bin # Arch Linux
### Providers
The recommended approach is to sign up for claude pro or max and do `opencode auth login` and select Anthropic. It is the most cost effective way to use this tool.
The recommended approach is to sign up for claude pro or max and do `opencode auth login` and select Anthropic. It is the most cost-effective way to use this tool.
Additionally opencode is powered by the provider list at [models.dev](https://models.dev) so you can use `opencode auth login` to configure api keys for any provider you'd like to use. This is stored in `~/.local/share/opencode/auth.json`
Additionally, opencode is powered by the provider list at [models.dev](https://models.dev) so you can use `opencode auth login` to configure api keys for any provider you'd like to use. This is stored in `~/.local/share/opencode/auth.json`
```bash
$ opencode auth login
@@ -47,7 +47,7 @@ If there are additional providers you want to use you can submit a PR to the [mo
### Project Config
Project configuration is optional. You can place an `opencode.json` file in the root of your repo and it will be loaded.
Project configuration is optional. You can place an `opencode.json` file in the root of your repo, and it will be loaded.
```json title="opencode.json"
{
@@ -78,7 +78,7 @@ Project configuration is optional. You can place an `opencode.json` file in the
#### Providers
You can use opencode with any provider listed at [here](https://ai-sdk.dev/providers/ai-sdk-providers). Use the npm package name as the key in your config. Note we use v5 of the ai-sdk and not all providers support that yet.
You can use opencode with any provider listed at [here](https://ai-sdk.dev/providers/ai-sdk-providers). Use the npm package name as the key in your config.
```json title="opencode.json"
{
@@ -118,7 +118,7 @@ $ bun run src/index.ts
#### How do I use this with OpenRouter
OpenRouter is not yet in the models.dev database but you can configure it manually.
OpenRouter is not yet in the models.dev database, but you can configure it manually.
```json title="opencode.json"
{
@@ -138,3 +138,16 @@ OpenRouter is not yet in the models.dev database but you can configure it manual
}
}
```
#### How is this different than claude code?
It is very similar to claude code in terms of capability - here are the key differences:
- 100% open source
- Not coupled to any provider. Although anthropic is recommended opencode can be used with openai, google or even local models. As models evolve the gaps between them will close and pricing will drop so being provider agnostic is important.
- TUI focus - opencode is built by neovim users and the creators of https://terminal.shop - we are going to push the limits of what's possible in the terminal
- client/server architecture - this means the tui frontend is just the first of many. For example, opencode can run on your computer and you can drive it remotely from a mobile app
#### Windows Support
There are some minor problems blocking opencode from working on windows. We will fix them soon - would need to use wsl for now.
+6 -6
View File
@@ -1,4 +1,4 @@
# OpenCode Agent Guidelines
# opencode agent guidelines
## Build/Test Commands
@@ -21,12 +21,12 @@
- Try to keep things in one function unless composable or reusable
- DO NOT do unnecessary destructuring of variables
- DO NOT use else statements unless necessary
- DO NOT use try catch if it can be avoided
- AVOID try catch where possible
- AVOID else statements
- DO NOT use `else` statements unless necessary
- DO NOT use `try`/`catch` if it can be avoided
- AVOID `try`/`catch` where possible
- AVOID `else` statements
- AVOID using `any` type
- AVOID let statements
- AVOID `let` statements
- PREFER single word variable names where possible
- Use as many bun apis as possible like Bun.file()
+2 -2
View File
@@ -64,7 +64,7 @@ for (const [os, arch] of targets) {
await $`mkdir -p ./dist/${pkg.name}`
await $`cp -r ./bin ./dist/${pkg.name}/bin`
await $`cp ./script/postinstall.js ./dist/${pkg.name}/postinstall.js`
await $`cp ./script/postinstall.mjs ./dist/${pkg.name}/postinstall.mjs`
await Bun.file(`./dist/${pkg.name}/package.json`).write(
JSON.stringify(
{
@@ -73,7 +73,7 @@ await Bun.file(`./dist/${pkg.name}/package.json`).write(
[pkg.name]: `./bin/${pkg.name}`,
},
scripts: {
postinstall: "node ./postinstall.js",
postinstall: "node ./postinstall.mjs",
},
version,
optionalDependencies,
-3
View File
@@ -115,9 +115,6 @@ export const RunCommand = {
})
const { providerID, modelID } = await Provider.defaultModel()
setTimeout(() => {
Session.abort(session.id)
}, 8000)
await Session.chat({
sessionID: session.id,
providerID,
+192
View File
@@ -0,0 +1,192 @@
import type { Argv } from "yargs"
import { UI } from "../ui"
import { VERSION } from "../version"
import path from "path"
import fs from "fs/promises"
import os from "os"
import * as prompts from "@clack/prompts"
import { Global } from "../../global"
const API = "https://api.github.com/repos/sst/opencode"
interface Release {
tag_name: string
name: string
assets: Array<{
name: string
browser_download_url: string
}>
}
function asset(): string {
const platform = os.platform()
const arch = os.arch()
if (platform === "darwin") {
return arch === "arm64"
? "opencode-darwin-arm64.zip"
: "opencode-darwin-x64.zip"
}
if (platform === "linux") {
return arch === "arm64"
? "opencode-linux-arm64.zip"
: "opencode-linux-x64.zip"
}
if (platform === "win32") {
return "opencode-windows-x64.zip"
}
throw new Error(`Unsupported platform: ${platform}-${arch}`)
}
function compare(current: string, latest: string): number {
const a = current.replace(/^v/, "")
const b = latest.replace(/^v/, "")
const aParts = a.split(".").map(Number)
const bParts = b.split(".").map(Number)
for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
const aPart = aParts[i] || 0
const bPart = bParts[i] || 0
if (aPart < bPart) return -1
if (aPart > bPart) return 1
}
return 0
}
async function latest(): Promise<Release> {
const response = await fetch(`${API}/releases/latest`)
if (!response.ok) {
throw new Error(`Failed to fetch latest release: ${response.statusText}`)
}
return response.json()
}
async function specific(version: string): Promise<Release> {
const tag = version.startsWith("v") ? version : `v${version}`
const response = await fetch(`${API}/releases/tags/${tag}`)
if (!response.ok) {
throw new Error(`Failed to fetch release ${tag}: ${response.statusText}`)
}
return response.json()
}
async function download(url: string): Promise<string> {
const response = await fetch(url)
if (!response.ok) {
throw new Error(`Failed to download: ${response.statusText}`)
}
const buffer = await response.arrayBuffer()
const temp = path.join(Global.Path.cache, `opencode-update-${Date.now()}.zip`)
await Bun.write(temp, buffer)
const extractDir = path.join(
Global.Path.cache,
`opencode-extract-${Date.now()}`,
)
await fs.mkdir(extractDir, { recursive: true })
const proc = Bun.spawn(["unzip", "-o", temp, "-d", extractDir], {
stdout: "pipe",
stderr: "pipe",
})
const result = await proc.exited
if (result !== 0) {
throw new Error("Failed to extract update")
}
await fs.unlink(temp)
const binary = path.join(extractDir, "opencode")
await fs.chmod(binary, 0o755)
return binary
}
export const UpgradeCommand = {
command: "upgrade [target]",
describe: "Upgrade opencode to the latest version or a specific version",
builder: (yargs: Argv) => {
return yargs.positional("target", {
describe: "Specific version to upgrade to (e.g., '0.1.48' or 'v0.1.48')",
type: "string",
})
},
handler: async (args: { target?: string }) => {
UI.empty()
UI.println(UI.logo(" "))
UI.empty()
prompts.intro("Upgrade")
if (!process.execPath.includes(path.join(".opencode", "bin")) && false) {
prompts.log.error(
`opencode is installed to ${process.execPath} and seems to be managed by a package manager`,
)
prompts.outro("Done")
return
}
const release = args.target
? await specific(args.target).catch(() => {})
: await latest().catch(() => {})
if (!release) {
prompts.log.error("Failed to fetch release information")
prompts.outro("Done")
return
}
const target = release.tag_name
if (VERSION !== "dev" && compare(VERSION, target) >= 0) {
prompts.log.success(`Already up to date`)
prompts.outro("Done")
return
}
prompts.log.info(`From ${VERSION}${target}`)
const name = asset()
const found = release.assets.find((a) => a.name === name)
if (!found) {
prompts.log.error(`No binary found for platform: ${name}`)
prompts.outro("Done")
return
}
const spinner = prompts.spinner()
spinner.start("Downloading update...")
const downloadPath = await download(found.browser_download_url).catch(
() => {},
)
if (!downloadPath) {
spinner.stop("Download failed")
prompts.log.error("Download failed")
prompts.outro("Done")
return
}
spinner.stop("Download complete")
const renamed = await fs
.rename(downloadPath, process.execPath)
.catch(() => {})
if (renamed === undefined) {
prompts.log.error("Install failed")
await fs.unlink(downloadPath).catch(() => {})
prompts.outro("Done")
return
}
prompts.log.success(`Successfully upgraded to ${target}`)
prompts.outro("Done")
},
}
-193
View File
@@ -1,193 +0,0 @@
import { createCli, type TrpcCliMeta } from "trpc-cli"
import { initTRPC } from "@trpc/server"
import { z } from "zod"
import { Server } from "../server/server"
import { AuthAnthropic } from "../auth/anthropic"
import { UI } from "./ui"
import { App } from "../app/app"
import { Bus } from "../bus"
import { Provider } from "../provider/provider"
import { Session } from "../session"
import { Share } from "../share/share"
import { Message } from "../session/message"
import { VERSION } from "./version"
import { LSP } from "../lsp"
import fs from "fs/promises"
import path from "path"
const t = initTRPC.meta<TrpcCliMeta>().create()
export const router = t.router({
generate: t.procedure
.meta({
description: "Generate OpenAPI and event specs",
})
.input(z.object({}))
.mutation(async () => {
const specs = await Server.openapi()
const dir = "gen"
await fs.rmdir(dir, { recursive: true }).catch(() => {})
await fs.mkdir(dir, { recursive: true })
await Bun.write(
path.join(dir, "openapi.json"),
JSON.stringify(specs, null, 2),
)
return "Generated OpenAPI specs in gen/ directory"
}),
run: t.procedure
.meta({
description: "Run OpenCode with a message",
})
.input(
z.object({
message: z.array(z.string()).default([]).describe("Message to send"),
session: z.string().optional().describe("Session ID to continue"),
}),
)
.mutation(
async ({ input }: { input: { message: string[]; session?: string } }) => {
const message = input.message.join(" ")
await App.provide(
{
cwd: process.cwd(),
version: "0.0.0",
},
async () => {
await Share.init()
const session = input.session
? await Session.get(input.session)
: await Session.create()
UI.println(UI.Style.TEXT_HIGHLIGHT_BOLD + "◍ OpenCode", VERSION)
UI.empty()
UI.println(UI.Style.TEXT_NORMAL_BOLD + "> ", message)
UI.empty()
UI.println(
UI.Style.TEXT_INFO_BOLD +
"~ https://dev.opencode.ai/s?id=" +
session.id.slice(-8),
)
UI.empty()
function printEvent(color: string, type: string, title: string) {
UI.println(
color + `|`,
UI.Style.TEXT_NORMAL +
UI.Style.TEXT_DIM +
` ${type.padEnd(7, " ")}`,
"",
UI.Style.TEXT_NORMAL + title,
)
}
Bus.subscribe(Message.Event.PartUpdated, async (message) => {
const part = message.properties.part
if (
part.type === "tool-invocation" &&
part.toolInvocation.state === "result"
) {
if (part.toolInvocation.toolName === "opencode_todowrite")
return
const args = part.toolInvocation.args as any
const tool = part.toolInvocation.toolName
if (tool === "opencode_edit")
printEvent(UI.Style.TEXT_SUCCESS_BOLD, "Edit", args.filePath)
if (tool === "opencode_bash")
printEvent(
UI.Style.TEXT_WARNING_BOLD,
"Execute",
args.command,
)
if (tool === "opencode_read")
printEvent(UI.Style.TEXT_INFO_BOLD, "Read", args.filePath)
if (tool === "opencode_write")
printEvent(
UI.Style.TEXT_SUCCESS_BOLD,
"Create",
args.filePath,
)
if (tool === "opencode_list")
printEvent(UI.Style.TEXT_INFO_BOLD, "List", args.path)
if (tool === "opencode_glob")
printEvent(
UI.Style.TEXT_INFO_BOLD,
"Glob",
args.pattern + (args.path ? " in " + args.path : ""),
)
}
if (part.type === "text") {
if (part.text.includes("\n")) {
UI.empty()
UI.println(part.text)
UI.empty()
return
}
printEvent(UI.Style.TEXT_NORMAL_BOLD, "Text", part.text)
}
})
const { providerID, modelID } = await Provider.defaultModel()
await Session.chat({
sessionID: session.id,
providerID,
modelID,
parts: [
{
type: "text",
text: message,
},
],
})
UI.empty()
},
)
return "Session completed"
},
),
scrap: t.procedure
.meta({
description: "Test command for scraping files",
})
.input(
z.object({
file: z.string().describe("File to process"),
}),
)
.mutation(async ({ input }: { input: { file: string } }) => {
await App.provide({ cwd: process.cwd(), version: VERSION }, async () => {
await LSP.touchFile(input.file, true)
await LSP.diagnostics()
})
return `Processed file: ${input.file}`
}),
login: t.router({
anthropic: t.procedure
.meta({
description: "Login to Anthropic",
})
.input(z.object({}))
.mutation(async () => {
const { url, verifier } = await AuthAnthropic.authorize()
UI.println("Login to Anthropic")
UI.println("Open the following URL in your browser:")
UI.println(url)
UI.println("")
const code = await UI.input("Paste the authorization code here: ")
await AuthAnthropic.exchange(code, verifier)
return "Successfully logged in to Anthropic"
}),
}),
})
export function createOpenCodeCli() {
return createCli({ router })
}
+7 -9
View File
@@ -3,9 +3,9 @@ import { NamedError } from "../util/error"
export namespace UI {
const LOGO = [
`█▀▀█ █▀▀█ █▀▀ █▀▀▄ █▀▀ █▀▀█ █▀▀▄ █▀▀`,
`█░░█ █░░█ █▀▀ █░░█ █░░ █░░█ █░░█ █▀▀`,
`▀▀▀▀ █▀▀▀ ▀▀▀ ▀ ▀ ▀▀▀ ▀▀▀▀ ▀▀▀ ▀▀▀`,
[`█▀▀█ █▀▀█ █▀▀ █▀▀▄ `, `█▀▀ █▀▀█ █▀▀▄ █▀▀`],
[`█░░█ █░░█ █▀▀ █░░█ `, `█░░ █░░█ █░░█ █▀▀`],
[`▀▀▀▀ █▀▀▀ ▀▀▀ ▀ ▀ `, `▀▀▀ ▀▀▀▀ ▀▀▀ ▀▀▀`],
]
export const CancelledError = NamedError.create("UICancelledError", z.void())
@@ -48,12 +48,10 @@ export namespace UI {
const result = []
for (const row of LOGO) {
if (pad) result.push(pad)
for (let i = 0; i < row.length; i++) {
const color =
i > 18 ? Bun.color("white", "ansi") : Bun.color("gray", "ansi")
const char = row[i]
result.push(color + char)
}
result.push(Bun.color("gray", "ansi"))
result.push(row[0])
result.push("\x1b[0m")
result.push(row[1])
result.push("\n")
}
return result.join("").trimEnd()
+7 -3
View File
@@ -116,11 +116,15 @@ export namespace Fzf {
return filepath
}
export async function search(cwd: string, query: string) {
const results = await $`${await filepath()} --filter ${query}`
export async function search(input: {
cwd: string
query: string
limit?: number
}) {
const results = await $`${await filepath()} --filter=${input.query}`
.quiet()
.throws(false)
.cwd(cwd)
.cwd(input.cwd)
.text()
const split = results
.trim()
+16
View File
@@ -5,6 +5,8 @@ import fs from "fs/promises"
import { z } from "zod"
import { NamedError } from "../util/error"
import { lazy } from "../util/lazy"
import { $ } from "bun"
import { Fzf } from "./fzf"
export namespace Ripgrep {
const PLATFORM = {
@@ -111,4 +113,18 @@ export namespace Ripgrep {
const { filepath } = await state()
return filepath
}
export async function files(input: {
cwd: string
query?: string
limit?: number
}) {
const commands = [`${await filepath()} --files --hidden --glob='!.git/*'`]
if (input.query)
commands.push(`${await Fzf.filepath()} --filter=${input.query}`)
if (input.limit) commands.push(`head -n ${input.limit}`)
const joined = commands.join(" | ")
const result = await $`${{ raw: joined }}`.cwd(input.cwd).text()
return result.split("\n").filter(Boolean)
}
}
+3 -1
View File
@@ -13,6 +13,7 @@ import { VERSION } from "./cli/version"
import { ScrapCommand } from "./cli/cmd/scrap"
import { Log } from "./util/log"
import { AuthCommand, AuthLoginCommand } from "./cli/cmd/auth"
import { UpgradeCommand } from "./cli/cmd/upgrade"
import { Provider } from "./provider/provider"
import { UI } from "./cli/ui"
@@ -33,7 +34,7 @@ const cli = yargs(hideBin(process.argv))
.usage("\n" + UI.logo())
.command({
command: "$0 [project]",
describe: "Start OpenCode TUI",
describe: "Start opencode TUI",
builder: (yargs) =>
yargs.positional("project", {
type: "string",
@@ -102,6 +103,7 @@ const cli = yargs(hideBin(process.argv))
.command(GenerateCommand)
.command(ScrapCommand)
.command(AuthCommand)
.command(UpgradeCommand)
.fail((msg, err) => {
if (
msg.startsWith("Unknown argument") ||
+39 -23
View File
@@ -11,6 +11,7 @@ import { LANGUAGE_EXTENSIONS } from "./language"
import { Bus } from "../bus"
import z from "zod"
import type { LSPServer } from "./server"
import { NamedError } from "../util/error"
export namespace LSPClient {
const log = Log.create({ service: "lsp.client" })
@@ -19,6 +20,13 @@ export namespace LSPClient {
export type Diagnostic = VSCodeDiagnostic
export const InitializeError = NamedError.create(
"LSPInitializeError",
z.object({
serverID: z.string(),
}),
)
export const Event = {
Diagnostics: Bus.event(
"lsp.client.diagnostics",
@@ -52,32 +60,40 @@ export namespace LSPClient {
})
connection.listen()
await connection.sendRequest("initialize", {
processId: server.process.pid,
workspaceFolders: [
{
name: "workspace",
uri: "file://" + app.path.cwd,
},
],
initializationOptions: {
...server.initialization,
},
capabilities: {
workspace: {
configuration: true,
},
textDocument: {
synchronization: {
didOpen: true,
didChange: true,
log.info("sending initialize", { id: serverID })
await Promise.race([
connection.sendRequest("initialize", {
processId: server.process.pid,
workspaceFolders: [
{
name: "workspace",
uri: "file://" + app.path.cwd,
},
publishDiagnostics: {
versionSupport: true,
],
initializationOptions: {
...server.initialization,
},
capabilities: {
workspace: {
configuration: true,
},
textDocument: {
synchronization: {
didOpen: true,
didChange: true,
},
publishDiagnostics: {
versionSupport: true,
},
},
},
},
})
}),
new Promise((_, reject) => {
setTimeout(() => {
reject(new InitializeError({ serverID }))
}, 5_000)
}),
])
await connection.sendNotification("initialized", {})
log.info("initialized")
+12 -3
View File
@@ -12,9 +12,10 @@ export namespace LSP {
async () => {
log.info("initializing")
const clients = new Map<string, LSPClient.Info>()
const skip = new Set<string>()
return {
clients,
skip,
}
},
async (state) => {
@@ -31,11 +32,19 @@ export namespace LSP {
x.extensions.includes(extension),
)
for (const match of matches) {
if (s.skip.has(match.id)) continue
const existing = s.clients.get(match.id)
if (existing) continue
const handle = await match.spawn(App.info())
if (!handle) continue
const client = await LSPClient.create(match.id, handle)
if (!handle) {
s.skip.add(match.id)
continue
}
const client = await LSPClient.create(match.id, handle).catch(() => {})
if (!client) {
s.skip.add(match.id)
continue
}
s.clients.set(match.id, client)
}
if (waitForDiagnostics) {
+6 -6
View File
@@ -82,7 +82,7 @@ export namespace Provider {
>()
const sdk = new Map<string, SDK>()
log.info("loading")
log.info("init")
function mergeProvider(
id: string,
@@ -167,7 +167,7 @@ export namespace Provider {
}
for (const providerID of Object.keys(providers)) {
log.info("loaded", { providerID })
log.info("found", { providerID })
}
return {
@@ -183,6 +183,9 @@ export namespace Provider {
async function getSDK(providerID: string) {
return (async () => {
using _ = log.time("getSDK", {
providerID,
})
const s = await state()
const existing = s.sdk.get(providerID)
if (existing) return existing
@@ -202,7 +205,7 @@ export namespace Provider {
const s = await state()
if (s.models.has(key)) return s.models.get(key)!
log.info("loading", {
log.info("getModel", {
providerID,
modelID,
})
@@ -265,7 +268,6 @@ export namespace Provider {
}
const TOOLS = [
BashTool,
EditTool,
WebFetchTool,
GlobTool,
@@ -278,9 +280,7 @@ export namespace Provider {
EditTool,
// MultiEditTool,
WriteTool,
TodoWriteTool,
TaskTool,
TodoReadTool,
]
const TOOL_MAPPING: Record<string, Tool.Info[]> = {
anthropic: TOOLS.filter((t) => t.id !== "opencode.patch"),
+6 -1
View File
@@ -14,6 +14,7 @@ import { mapValues } from "remeda"
import { NamedError } from "../util/error"
import { Fzf } from "../external/fzf"
import { ModelsDev } from "../provider/models"
import { Ripgrep } from "../external/ripgrep"
const ERRORS = {
400: {
@@ -457,7 +458,11 @@ export namespace Server {
async (c) => {
const body = c.req.valid("json")
const app = App.info()
const result = await Fzf.search(app.path.cwd, body.query)
const result = await Ripgrep.files({
cwd: app.path.cwd,
query: body.query,
limit: 10,
})
return c.json(result)
},
)
+34 -2
View File
@@ -13,6 +13,7 @@ import {
type LanguageModelUsage,
type CoreMessage,
type UIMessage,
type LanguageModelV1Middleware,
} from "ai"
import { z, ZodSchema } from "zod"
import { Decimal } from "decimal.js"
@@ -234,6 +235,13 @@ export namespace Session {
(x): CoreMessage => ({
role: "system",
content: x,
providerOptions: {
...(input.providerID === "anthropic"
? {
cacheControl: { type: "ephemeral" },
}
: {}),
},
}),
),
...convertToCoreMessages([
@@ -270,7 +278,7 @@ export namespace Session {
msgs.push(msg)
const system = input.system ?? SystemPrompt.provider(input.providerID)
system.push(...(await SystemPrompt.environment(input.sessionID)))
system.push(...(await SystemPrompt.environment()))
system.push(...(await SystemPrompt.custom()))
const next: Message.Info = {
@@ -379,6 +387,24 @@ export namespace Session {
}
let text: Message.TextPart | undefined
await Bun.write(
"/tmp/message.json",
JSON.stringify(
[
...system.map(
(x): CoreMessage => ({
role: "system",
content: x,
}),
),
...convertToCoreMessages(
msgs.map(toUIMessage).filter((x) => x.parts.length > 0),
),
],
null,
2,
),
)
const result = streamText({
onStepFinish: async (step) => {
log.info("step finish", {
@@ -447,6 +473,13 @@ export namespace Session {
(x): CoreMessage => ({
role: "system",
content: x,
providerOptions: {
...(input.providerID === "anthropic"
? {
cacheControl: { type: "ephemeral" },
}
: {}),
},
}),
),
...convertToCoreMessages(
@@ -455,7 +488,6 @@ export namespace Session {
],
temperature: model.info.id === "codex-mini-latest" ? undefined : 0,
tools: {
...(await MCP.tools()),
...tools,
},
model: model.language,
+52 -3
View File
@@ -1,5 +1,5 @@
import { App } from "../app/app"
import { ListTool } from "../tool/ls"
import { Ripgrep } from "../external/ripgrep"
import { Filesystem } from "../util/filesystem"
import PROMPT_ANTHROPIC from "./prompt/anthropic.txt"
@@ -22,8 +22,57 @@ export namespace SystemPrompt {
return result
}
export async function environment(sessionID: string) {
export async function environment() {
const app = App.info()
const tree = async () => {
const files = await Ripgrep.files({
cwd: app.path.cwd,
})
type Node = {
children: Record<string, Node>
}
const root: Node = {
children: {},
}
for (const file of files) {
const parts = file.split("/")
let node = root
for (const part of parts) {
const existing = node.children[part]
if (existing) {
node = existing
continue
}
node.children[part] = {
children: {},
}
node = node.children[part]
}
}
function render(path: string[], node: Node): string {
// if (path.length === 3) return "\t".repeat(path.length) + "..."
const lines: string[] = []
const entries = Object.entries(node.children).sort(([a], [b]) =>
a.localeCompare(b),
)
for (const [name, child] of entries) {
const currentPath = [...path, name]
const indent = "\t".repeat(path.length)
const hasChildren = Object.keys(child.children).length > 0
lines.push(`${indent}${name}` + (hasChildren ? "/" : ""))
if (hasChildren) lines.push(render(currentPath, child))
}
return lines.join("\n")
}
const result = render([], root)
return result
}
return [
[
`Here is some useful information about the environment you are running in:`,
@@ -34,7 +83,7 @@ export namespace SystemPrompt {
` Today's date: ${new Date().toDateString()}`,
`</env>`,
`<project>`,
` ${app.git ? await ListTool.execute({ path: app.path.cwd, ignore: [] }, { sessionID: sessionID, messageID: "", abort: AbortSignal.any([]) }).then((x) => x.output) : ""}`,
` ${app.git ? await tree() : ""}`,
`</project>`,
].join("\n"),
]
+6 -5
View File
@@ -4,7 +4,7 @@ import { App } from "../app/app"
import * as path from "path"
import DESCRIPTION from "./ls.txt"
const IGNORE_PATTERNS = [
export const IGNORE_PATTERNS = [
"node_modules/",
"__pycache__/",
".git/",
@@ -18,6 +18,8 @@ const IGNORE_PATTERNS = [
".vscode/",
]
const LIMIT = 100
export const ListTool = Tool.define({
id: "opencode.list",
description: DESCRIPTION,
@@ -41,12 +43,11 @@ export const ListTool = Tool.define({
const files = []
for await (const file of glob.scan({ cwd: searchPath, dot: true })) {
if (file.startsWith(".") || IGNORE_PATTERNS.some((p) => file.includes(p)))
continue
if (IGNORE_PATTERNS.some((p) => file.includes(p))) continue
if (params.ignore?.some((pattern) => new Bun.Glob(pattern).match(file)))
continue
files.push(file)
if (files.length >= 1000) break
if (files.length >= LIMIT) break
}
// Build directory structure
@@ -100,7 +101,7 @@ export const ListTool = Tool.define({
return {
metadata: {
count: files.length,
truncated: files.length >= 1000,
truncated: files.length >= LIMIT,
title: path.relative(app.path.root, searchPath),
},
output,
+17
View File
@@ -83,6 +83,23 @@ export namespace Log {
clone() {
return Log.create({ ...tags })
},
time(message: string, extra?: Record<string, any>) {
const now = Date.now()
result.info(message, { status: "started", ...extra })
function stop() {
result.info(message, {
status: "completed",
duration: Date.now() - now,
...extra,
})
}
return {
stop,
[Symbol.dispose]() {
stop()
},
}
},
}
return result
-219
View File
@@ -1,219 +0,0 @@
time=2025-05-30T22:01:45.386-04:00 level=DEBUG msg="Set theme from config" theme=opencode
time=2025-05-30T22:01:45.391-04:00 level=INFO msg="Reading directory: /home/thdxr"
time=2025-05-30T22:01:50.683-04:00 level=INFO msg="Cancelling all subscriptions"
time=2025-05-30T22:01:50.683-04:00 level=INFO msg="subscription cancelled" name=status
time=2025-05-30T22:01:50.683-04:00 level=INFO msg="All subscription goroutines completed successfully"
time=2025-05-30T22:01:50.683-04:00 level=INFO msg="TUI message channel closed"
time=2025-05-30T22:01:50.683-04:00 level=INFO msg="All goroutines cleaned up"
time=2025-05-30T22:01:50.683-04:00 level=INFO msg="TUI exited" result="{width:98 height:57 currentPage:chat previousPage: pages:map[chat:0xc00013b450] loadedPages:map[chat:true] status:{app:0xc0002e05b0 queue:[] width:98 messageTTL:4000000000 activeUntil:{wall:0 ext:0 loc:<nil>}} app:0xc0002e05b0 showPermissions:false permissions:0xc000159408 showHelp:false help:0xc0006822d0 showQuit:true quit:0xc00024b479 showSessionDialog:false sessionDialog:0xc0001f0240 showCommandDialog:false commandDialog:0xc0003cbba0 commands:[{ID:init Title:Initialize Project Description:Create/Update the CONTEXT.md memory file Handler:0xb6a7a0} {ID:compact_conversation Title:Compact Conversation Description:Summarize the current session to save tokens Handler:0xb6a620}] showModelDialog:false modelDialog:0xc0001f45a0 showInitDialog:false initDialog:{width:98 height:57 selected:0 keys:{Tab:{keys:[] help:{Key: Desc:} disabled:false} Left:{keys:[] help:{Key: Desc:} disabled:false} Right:{keys:[] help:{Key: Desc:} disabled:false} Enter:{keys:[] help:{Key: Desc:} disabled:false} Escape:{keys:[] help:{Key: Desc:} disabled:false} Y:{keys:[] help:{Key: Desc:} disabled:false} N:{keys:[] help:{Key: Desc:} disabled:false}}} showFilepicker:false filepicker:0xc0000d4c88 showThemeDialog:false themeDialog:0xc0001f0480 showMultiArgumentsDialog:false multiArgumentsDialog:{width:0 height:0 inputs:[] focusIndex:0 keys:{Enter:{keys:[] help:{Key: Desc:} disabled:false} Escape:{keys:[] help:{Key: Desc:} disabled:false}} commandID: content: argNames:[]} showToolsDialog:false toolsDialog:0xc0001f04c0}"
time=2025-05-30T22:13:24.046-04:00 level=DEBUG msg="Set theme from config" theme=opencode
time=2025-05-30T22:13:24.051-04:00 level=INFO msg="Reading directory: /home/thdxr"
time=2025-05-30T22:13:25.991-04:00 level=INFO msg="Cancelling all subscriptions"
time=2025-05-30T22:13:25.991-04:00 level=INFO msg="subscription cancelled" name=status
time=2025-05-30T22:13:25.991-04:00 level=INFO msg="All subscription goroutines completed successfully"
time=2025-05-30T22:13:25.991-04:00 level=INFO msg="TUI message channel closed"
time=2025-05-30T22:13:25.991-04:00 level=INFO msg="All goroutines cleaned up"
time=2025-05-30T22:13:25.991-04:00 level=INFO msg="TUI exited" result="{width:199 height:57 currentPage:chat previousPage: pages:map[chat:0xc00025f950] loadedPages:map[chat:true] status:{app:0xc0000ca230 queue:[] width:199 messageTTL:4000000000 activeUntil:{wall:0 ext:0 loc:<nil>}} app:0xc0000ca230 showPermissions:false permissions:0xc00029f908 showHelp:false help:0xc00045d9b0 showQuit:true quit:0xc0005a0be9 showSessionDialog:false sessionDialog:0xc00012e3c0 showCommandDialog:false commandDialog:0xc0004379e0 commands:[{ID:init Title:Initialize Project Description:Create/Update the CONTEXT.md memory file Handler:0xb6a7a0} {ID:compact_conversation Title:Compact Conversation Description:Summarize the current session to save tokens Handler:0xb6a620}] showModelDialog:false modelDialog:0xc0002f2e60 showInitDialog:false initDialog:{width:199 height:57 selected:0 keys:{Tab:{keys:[] help:{Key: Desc:} disabled:false} Left:{keys:[] help:{Key: Desc:} disabled:false} Right:{keys:[] help:{Key: Desc:} disabled:false} Enter:{keys:[] help:{Key: Desc:} disabled:false} Escape:{keys:[] help:{Key: Desc:} disabled:false} Y:{keys:[] help:{Key: Desc:} disabled:false} N:{keys:[] help:{Key: Desc:} disabled:false}}} showFilepicker:false filepicker:0xc00013ac88 showThemeDialog:false themeDialog:0xc00012e600 showMultiArgumentsDialog:false multiArgumentsDialog:{width:0 height:0 inputs:[] focusIndex:0 keys:{Enter:{keys:[] help:{Key: Desc:} disabled:false} Escape:{keys:[] help:{Key: Desc:} disabled:false}} commandID: content: argNames:[]} showToolsDialog:false toolsDialog:0xc00012e640}"
time=2025-05-31T16:00:29.137-04:00 level=DEBUG msg="Set theme from config" theme=opencode
time=2025-05-31T16:00:29.141-04:00 level=INFO msg="Reading directory: /home/thdxr"
time=2025-05-31T16:00:36.530-04:00 level=INFO msg="Cancelling all subscriptions"
time=2025-05-31T16:00:36.531-04:00 level=INFO msg="subscription cancelled" name=status
time=2025-05-31T16:00:36.531-04:00 level=INFO msg="All subscription goroutines completed successfully"
time=2025-05-31T16:00:36.531-04:00 level=INFO msg="TUI message channel closed"
time=2025-05-31T16:00:36.531-04:00 level=INFO msg="All goroutines cleaned up"
time=2025-05-31T16:00:36.531-04:00 level=INFO msg="TUI exited" result="{width:106 height:54 currentPage:chat previousPage: pages:map[chat:0xc000157450] loadedPages:map[chat:true] status:{app:0xc00020c5b0 queue:[] width:106 messageTTL:4000000000 activeUntil:{wall:0 ext:0 loc:<nil>}} app:0xc00020c5b0 showPermissions:false permissions:0xc000175408 showHelp:false help:0xc00070c270 showQuit:true quit:0xc000299979 showSessionDialog:false sessionDialog:0xc0001f02c0 showCommandDialog:false commandDialog:0xc0003cbba0 commands:[{ID:init Title:Initialize Project Description:Create/Update the CONTEXT.md memory file Handler:0xb6a7a0} {ID:compact_conversation Title:Compact Conversation Description:Summarize the current session to save tokens Handler:0xb6a620}] showModelDialog:false modelDialog:0xc00021a5a0 showInitDialog:false initDialog:{width:106 height:54 selected:0 keys:{Tab:{keys:[] help:{Key: Desc:} disabled:false} Left:{keys:[] help:{Key: Desc:} disabled:false} Right:{keys:[] help:{Key: Desc:} disabled:false} Enter:{keys:[] help:{Key: Desc:} disabled:false} Escape:{keys:[] help:{Key: Desc:} disabled:false} Y:{keys:[] help:{Key: Desc:} disabled:false} N:{keys:[] help:{Key: Desc:} disabled:false}}} showFilepicker:false filepicker:0xc0000d4c88 showThemeDialog:false themeDialog:0xc0001f0500 showMultiArgumentsDialog:false multiArgumentsDialog:{width:0 height:0 inputs:[] focusIndex:0 keys:{Enter:{keys:[] help:{Key: Desc:} disabled:false} Escape:{keys:[] help:{Key: Desc:} disabled:false}} commandID: content: argNames:[]} showToolsDialog:false toolsDialog:0xc0001f0540}"
time=2025-05-31T16:06:20.089-04:00 level=DEBUG msg="Set theme from config" theme=opencode
time=2025-05-31T16:06:20.094-04:00 level=INFO msg="Reading directory: /home/thdxr"
time=2025-05-31T16:06:20.095-04:00 level=ERROR msg="Failed to subscribe to events" error="Get \"http://localhost:16713/event\": dial tcp [::1]:16713: connect: connection refused"
time=2025-05-31T17:54:04.009-04:00 level=DEBUG msg="Set theme from config" theme=opencode
time=2025-05-31T17:54:04.014-04:00 level=INFO msg="Reading directory: /home/thdxr"
time=2025-05-31T17:54:06.337-04:00 level=INFO msg="Cancelling all subscriptions"
time=2025-05-31T17:54:06.337-04:00 level=INFO msg="subscription cancelled" name=status
time=2025-05-31T17:54:06.337-04:00 level=INFO msg="All subscription goroutines completed successfully"
time=2025-05-31T17:54:06.337-04:00 level=INFO msg="TUI message channel closed"
time=2025-05-31T17:54:06.337-04:00 level=INFO msg="All goroutines cleaned up"
time=2025-05-31T17:54:06.337-04:00 level=INFO msg="TUI exited" result="{width:106 height:25 currentPage:chat previousPage: pages:map[chat:0xc0002332c0] loadedPages:map[chat:true] status:{app:0xc0002b1810 queue:[] width:106 messageTTL:4000000000 activeUntil:{wall:0 ext:0 loc:<nil>}} app:0xc0002b1810 showPermissions:false permissions:0xc000267408 showHelp:false help:0xc00048dbc0 showQuit:true quit:0xc0004a2719 showSessionDialog:false sessionDialog:0xc000319ec0 showCommandDialog:false commandDialog:0xc000387980 commands:[{ID:init Title:Initialize Project Description:Create/Update the CONTEXT.md memory file Handler:0xb6a7a0} {ID:compact_conversation Title:Compact Conversation Description:Summarize the current session to save tokens Handler:0xb6a620}] showModelDialog:false modelDialog:0xc0000c6960 showInitDialog:false initDialog:{width:106 height:25 selected:0 keys:{Tab:{keys:[] help:{Key: Desc:} disabled:false} Left:{keys:[] help:{Key: Desc:} disabled:false} Right:{keys:[] help:{Key: Desc:} disabled:false} Enter:{keys:[] help:{Key: Desc:} disabled:false} Escape:{keys:[] help:{Key: Desc:} disabled:false} Y:{keys:[] help:{Key: Desc:} disabled:false} N:{keys:[] help:{Key: Desc:} disabled:false}}} showFilepicker:false filepicker:0xc0000d6c88 showThemeDialog:false themeDialog:0xc0000ac480 showMultiArgumentsDialog:false multiArgumentsDialog:{width:0 height:0 inputs:[] focusIndex:0 keys:{Enter:{keys:[] help:{Key: Desc:} disabled:false} Escape:{keys:[] help:{Key: Desc:} disabled:false}} commandID: content: argNames:[]} showToolsDialog:false toolsDialog:0xc0000ac4c0}"
time=2025-05-31T17:54:17.103-04:00 level=DEBUG msg="Set theme from config" theme=opencode
time=2025-05-31T17:54:17.108-04:00 level=INFO msg="Reading directory: /home/thdxr"
time=2025-05-31T17:54:18.391-04:00 level=INFO msg="Cancelling all subscriptions"
time=2025-05-31T17:54:18.392-04:00 level=INFO msg="subscription cancelled" name=status
time=2025-05-31T17:54:18.392-04:00 level=INFO msg="All subscription goroutines completed successfully"
time=2025-05-31T17:54:18.392-04:00 level=INFO msg="TUI message channel closed"
time=2025-05-31T17:54:18.392-04:00 level=INFO msg="All goroutines cleaned up"
time=2025-05-31T17:54:18.392-04:00 level=INFO msg="TUI exited" result="{width:106 height:25 currentPage:chat previousPage: pages:map[chat:0xc00042a960] loadedPages:map[chat:true] status:{app:0xc000163ce0 queue:[] width:106 messageTTL:4000000000 activeUntil:{wall:0 ext:0 loc:<nil>}} app:0xc000163ce0 showPermissions:false permissions:0xc0001df408 showHelp:false help:0xc0005198f0 showQuit:true quit:0xc0003a5ef9 showSessionDialog:false sessionDialog:0xc000323840 showCommandDialog:false commandDialog:0xc00043b0e0 commands:[{ID:init Title:Initialize Project Description:Create/Update the CONTEXT.md memory file Handler:0xb6a7a0} {ID:compact_conversation Title:Compact Conversation Description:Summarize the current session to save tokens Handler:0xb6a620}] showModelDialog:false modelDialog:0xc0004028c0 showInitDialog:false initDialog:{width:106 height:25 selected:0 keys:{Tab:{keys:[] help:{Key: Desc:} disabled:false} Left:{keys:[] help:{Key: Desc:} disabled:false} Right:{keys:[] help:{Key: Desc:} disabled:false} Enter:{keys:[] help:{Key: Desc:} disabled:false} Escape:{keys:[] help:{Key: Desc:} disabled:false} Y:{keys:[] help:{Key: Desc:} disabled:false} N:{keys:[] help:{Key: Desc:} disabled:false}}} showFilepicker:false filepicker:0xc0000d6c88 showThemeDialog:false themeDialog:0xc000323a80 showMultiArgumentsDialog:false multiArgumentsDialog:{width:0 height:0 inputs:[] focusIndex:0 keys:{Enter:{keys:[] help:{Key: Desc:} disabled:false} Escape:{keys:[] help:{Key: Desc:} disabled:false}} commandID: content: argNames:[]} showToolsDialog:false toolsDialog:0xc000323ac0}"
time=2025-05-31T17:59:54.360-04:00 level=DEBUG msg="Set theme from config" theme=opencode
time=2025-05-31T17:59:54.364-04:00 level=INFO msg="Reading directory: /home/thdxr"
time=2025-05-31T17:59:55.814-04:00 level=INFO msg="Cancelling all subscriptions"
time=2025-05-31T17:59:55.815-04:00 level=INFO msg="subscription cancelled" name=status
time=2025-05-31T17:59:55.815-04:00 level=INFO msg="All subscription goroutines completed successfully"
time=2025-05-31T17:59:55.815-04:00 level=INFO msg="TUI message channel closed"
time=2025-05-31T17:59:55.815-04:00 level=INFO msg="All goroutines cleaned up"
time=2025-05-31T17:59:55.815-04:00 level=INFO msg="TUI exited" result="{width:106 height:25 currentPage:chat previousPage: pages:map[chat:0xc0002787d0] loadedPages:map[chat:true] status:{app:0xc0003fed90 queue:[] width:106 messageTTL:4000000000 activeUntil:{wall:0 ext:0 loc:<nil>}} app:0xc0003fed90 showPermissions:false permissions:0xc0002b1908 showHelp:false help:0xc000126150 showQuit:true quit:0xc00011d439 showSessionDialog:false sessionDialog:0xc00025e380 showCommandDialog:false commandDialog:0xc00047fc00 commands:[{ID:init Title:Initialize Project Description:Create/Update the CONTEXT.md memory file Handler:0xb6a7a0} {ID:compact_conversation Title:Compact Conversation Description:Summarize the current session to save tokens Handler:0xb6a620}] showModelDialog:false modelDialog:0xc0002f6d20 showInitDialog:false initDialog:{width:106 height:25 selected:0 keys:{Tab:{keys:[] help:{Key: Desc:} disabled:false} Left:{keys:[] help:{Key: Desc:} disabled:false} Right:{keys:[] help:{Key: Desc:} disabled:false} Enter:{keys:[] help:{Key: Desc:} disabled:false} Escape:{keys:[] help:{Key: Desc:} disabled:false} Y:{keys:[] help:{Key: Desc:} disabled:false} N:{keys:[] help:{Key: Desc:} disabled:false}}} showFilepicker:false filepicker:0xc0001b2c88 showThemeDialog:false themeDialog:0xc00025e5c0 showMultiArgumentsDialog:false multiArgumentsDialog:{width:0 height:0 inputs:[] focusIndex:0 keys:{Enter:{keys:[] help:{Key: Desc:} disabled:false} Escape:{keys:[] help:{Key: Desc:} disabled:false}} commandID: content: argNames:[]} showToolsDialog:false toolsDialog:0xc00025e600}"
time=2025-05-31T17:59:56.746-04:00 level=DEBUG msg="Set theme from config" theme=opencode
time=2025-05-31T17:59:56.750-04:00 level=INFO msg="Reading directory: /home/thdxr"
time=2025-05-31T18:00:10.757-04:00 level=INFO msg="Cancelling all subscriptions"
time=2025-05-31T18:00:10.757-04:00 level=INFO msg="subscription cancelled" name=status
time=2025-05-31T18:00:10.757-04:00 level=INFO msg="All subscription goroutines completed successfully"
time=2025-05-31T18:00:10.757-04:00 level=INFO msg="TUI message channel closed"
time=2025-05-31T18:00:10.757-04:00 level=INFO msg="All goroutines cleaned up"
time=2025-05-31T18:00:10.757-04:00 level=INFO msg="TUI exited" result="{width:211 height:54 currentPage:chat previousPage: pages:map[chat:0xc00053b090] loadedPages:map[chat:true] status:{app:0xc000300cb0 queue:[] width:211 messageTTL:4000000000 activeUntil:{wall:0 ext:0 loc:<nil>}} app:0xc000300cb0 showPermissions:false permissions:0xc0002c5408 showHelp:false help:0xc000682f90 showQuit:true quit:0xc0006134d9 showSessionDialog:false sessionDialog:0xc00031f980 showCommandDialog:false commandDialog:0xc0003d9520 commands:[{ID:init Title:Initialize Project Description:Create/Update the CONTEXT.md memory file Handler:0xb6a7a0} {ID:compact_conversation Title:Compact Conversation Description:Summarize the current session to save tokens Handler:0xb6a620}] showModelDialog:false modelDialog:0xc000395220 showInitDialog:false initDialog:{width:211 height:54 selected:0 keys:{Tab:{keys:[] help:{Key: Desc:} disabled:false} Left:{keys:[] help:{Key: Desc:} disabled:false} Right:{keys:[] help:{Key: Desc:} disabled:false} Enter:{keys:[] help:{Key: Desc:} disabled:false} Escape:{keys:[] help:{Key: Desc:} disabled:false} Y:{keys:[] help:{Key: Desc:} disabled:false} N:{keys:[] help:{Key: Desc:} disabled:false}}} showFilepicker:false filepicker:0xc00013ac88 showThemeDialog:false themeDialog:0xc00031fbc0 showMultiArgumentsDialog:false multiArgumentsDialog:{width:0 height:0 inputs:[] focusIndex:0 keys:{Enter:{keys:[] help:{Key: Desc:} disabled:false} Escape:{keys:[] help:{Key: Desc:} disabled:false}} commandID: content: argNames:[]} showToolsDialog:false toolsDialog:0xc00031fc00}"
time=2025-05-31T18:35:42.289-04:00 level=DEBUG msg="Set theme from config" theme=opencode
time=2025-05-31T18:35:42.294-04:00 level=INFO msg="Reading directory: /home/thdxr"
time=2025-05-31T18:36:54.773-04:00 level=INFO msg="Cancelling all subscriptions"
time=2025-05-31T18:36:54.773-04:00 level=INFO msg="subscription cancelled" name=status
time=2025-05-31T18:36:54.773-04:00 level=INFO msg="All subscription goroutines completed successfully"
time=2025-05-31T18:36:54.773-04:00 level=INFO msg="TUI message channel closed"
time=2025-05-31T18:36:54.773-04:00 level=INFO msg="All goroutines cleaned up"
time=2025-05-31T18:36:54.773-04:00 level=INFO msg="TUI exited" result="{width:106 height:25 currentPage:chat previousPage: pages:map[chat:0xc00012f0e0] loadedPages:map[chat:true] status:{app:0xc0002aa070 queue:[] width:106 messageTTL:4000000000 activeUntil:{wall:0 ext:0 loc:<nil>}} app:0xc0002aa070 showPermissions:false permissions:0xc000267408 showHelp:false help:0xc00041b8f0 showQuit:true quit:0xc000345ee9 showSessionDialog:false sessionDialog:0xc00032ba40 showCommandDialog:false commandDialog:0xc00043b300 commands:[{ID:init Title:Initialize Project Description:Create/Update the CONTEXT.md memory file Handler:0xb6a7a0} {ID:compact_conversation Title:Compact Conversation Description:Summarize the current session to save tokens Handler:0xb6a620}] showModelDialog:false modelDialog:0xc000426f00 showInitDialog:false initDialog:{width:106 height:25 selected:0 keys:{Tab:{keys:[] help:{Key: Desc:} disabled:false} Left:{keys:[] help:{Key: Desc:} disabled:false} Right:{keys:[] help:{Key: Desc:} disabled:false} Enter:{keys:[] help:{Key: Desc:} disabled:false} Escape:{keys:[] help:{Key: Desc:} disabled:false} Y:{keys:[] help:{Key: Desc:} disabled:false} N:{keys:[] help:{Key: Desc:} disabled:false}}} showFilepicker:false filepicker:0xc0000d6c88 showThemeDialog:false themeDialog:0xc00032bc80 showMultiArgumentsDialog:false multiArgumentsDialog:{width:0 height:0 inputs:[] focusIndex:0 keys:{Enter:{keys:[] help:{Key: Desc:} disabled:false} Escape:{keys:[] help:{Key: Desc:} disabled:false}} commandID: content: argNames:[]} showToolsDialog:false toolsDialog:0xc00032bcc0}"
time=2025-05-31T18:36:56.011-04:00 level=DEBUG msg="Set theme from config" theme=opencode
time=2025-05-31T18:36:56.015-04:00 level=INFO msg="Reading directory: /home/thdxr"
time=2025-05-31T18:37:44.063-04:00 level=INFO msg="Cancelling all subscriptions"
time=2025-05-31T18:37:44.064-04:00 level=INFO msg="subscription cancelled" name=status
time=2025-05-31T18:37:44.064-04:00 level=INFO msg="All subscription goroutines completed successfully"
time=2025-05-31T18:37:44.064-04:00 level=INFO msg="TUI message channel closed"
time=2025-05-31T18:37:44.064-04:00 level=INFO msg="All goroutines cleaned up"
time=2025-05-31T18:37:44.064-04:00 level=INFO msg="TUI exited" result="{width:211 height:54 currentPage:chat previousPage: pages:map[chat:0xc000420280] loadedPages:map[chat:true] status:{app:0xc0002d8000 queue:[] width:211 messageTTL:4000000000 activeUntil:{wall:0 ext:0 loc:<nil>}} app:0xc0002d8000 showPermissions:false permissions:0xc000271408 showHelp:false help:0xc00048da70 showQuit:true quit:0xc000390809 showSessionDialog:false sessionDialog:0xc000323b80 showCommandDialog:false commandDialog:0xc0003e5920 commands:[{ID:init Title:Initialize Project Description:Create/Update the CONTEXT.md memory file Handler:0xb6a7a0} {ID:compact_conversation Title:Compact Conversation Description:Summarize the current session to save tokens Handler:0xb6a620}] showModelDialog:false modelDialog:0xc00025f9a0 showInitDialog:false initDialog:{width:211 height:54 selected:0 keys:{Tab:{keys:[] help:{Key: Desc:} disabled:false} Left:{keys:[] help:{Key: Desc:} disabled:false} Right:{keys:[] help:{Key: Desc:} disabled:false} Enter:{keys:[] help:{Key: Desc:} disabled:false} Escape:{keys:[] help:{Key: Desc:} disabled:false} Y:{keys:[] help:{Key: Desc:} disabled:false} N:{keys:[] help:{Key: Desc:} disabled:false}}} showFilepicker:false filepicker:0xc0000d6c88 showThemeDialog:false themeDialog:0xc000323dc0 showMultiArgumentsDialog:false multiArgumentsDialog:{width:0 height:0 inputs:[] focusIndex:0 keys:{Enter:{keys:[] help:{Key: Desc:} disabled:false} Escape:{keys:[] help:{Key: Desc:} disabled:false}} commandID: content: argNames:[]} showToolsDialog:false toolsDialog:0xc000323e00}"
time=2025-05-31T20:32:32.443-04:00 level=DEBUG msg="Set theme from config" theme=opencode
time=2025-05-31T20:32:32.448-04:00 level=INFO msg="Reading directory: /home/thdxr"
time=2025-05-31T20:33:09.783-04:00 level=INFO msg="Cancelling all subscriptions"
time=2025-05-31T20:33:09.783-04:00 level=INFO msg="subscription cancelled" name=status
time=2025-05-31T20:33:09.783-04:00 level=INFO msg="All subscription goroutines completed successfully"
time=2025-05-31T20:33:09.783-04:00 level=INFO msg="TUI message channel closed"
time=2025-05-31T20:33:09.783-04:00 level=INFO msg="All goroutines cleaned up"
time=2025-05-31T20:33:09.783-04:00 level=INFO msg="TUI exited" result="{width:199 height:56 currentPage:chat previousPage: pages:map[chat:0xc00032c960] loadedPages:map[chat:true] status:{app:0xc000279420 queue:[] width:199 messageTTL:4000000000 activeUntil:{wall:0 ext:0 loc:<nil>}} app:0xc000279420 showPermissions:false permissions:0xc0001fb408 showHelp:false help:0xc000154150 showQuit:true quit:0xc000528849 showSessionDialog:false sessionDialog:0xc000309e40 showCommandDialog:false commandDialog:0xc0003a3800 commands:[{ID:init Title:Initialize Project Description:Create/Update the CONTEXT.md memory file Handler:0xb6a800} {ID:compact_conversation Title:Compact Conversation Description:Summarize the current session to save tokens Handler:0xb6a680}] showModelDialog:false modelDialog:0xc0002e7cc0 showInitDialog:false initDialog:{width:199 height:56 selected:0 keys:{Tab:{keys:[] help:{Key: Desc:} disabled:false} Left:{keys:[] help:{Key: Desc:} disabled:false} Right:{keys:[] help:{Key: Desc:} disabled:false} Enter:{keys:[] help:{Key: Desc:} disabled:false} Escape:{keys:[] help:{Key: Desc:} disabled:false} Y:{keys:[] help:{Key: Desc:} disabled:false} N:{keys:[] help:{Key: Desc:} disabled:false}}} showFilepicker:false filepicker:0xc0000d4c88 showThemeDialog:false themeDialog:0xc0000ac400 showMultiArgumentsDialog:false multiArgumentsDialog:{width:0 height:0 inputs:[] focusIndex:0 keys:{Enter:{keys:[] help:{Key: Desc:} disabled:false} Escape:{keys:[] help:{Key: Desc:} disabled:false}} commandID: content: argNames:[]} showToolsDialog:false toolsDialog:0xc0000ac440}"
time=2025-06-01T14:37:36.423-04:00 level=DEBUG msg="Set theme from config" theme=opencode
time=2025-06-01T14:37:36.427-04:00 level=INFO msg="Reading directory: /home/thdxr"
time=2025-06-01T14:38:19.951-04:00 level=INFO msg="Cancelling all subscriptions"
time=2025-06-01T14:38:19.951-04:00 level=INFO msg="subscription cancelled" name=status
time=2025-06-01T14:38:19.951-04:00 level=INFO msg="All subscription goroutines completed successfully"
time=2025-06-01T14:38:19.951-04:00 level=INFO msg="TUI message channel closed"
time=2025-06-01T14:38:19.951-04:00 level=INFO msg="All goroutines cleaned up"
time=2025-06-01T14:38:19.951-04:00 level=INFO msg="TUI exited" result="{width:199 height:56 currentPage:chat previousPage: pages:map[chat:0xc00035b9f0] loadedPages:map[chat:true] status:{app:0xc000226d90 queue:[] width:199 messageTTL:4000000000 activeUntil:{wall:0 ext:0 loc:<nil>}} app:0xc000226d90 showPermissions:false permissions:0xc00027f908 showHelp:false help:0xc0005139e0 showQuit:true quit:0xc000510d49 showSessionDialog:false sessionDialog:0xc0001e84c0 showCommandDialog:false commandDialog:0xc00051a160 commands:[{ID:init Title:Initialize Project Description:Create/Update the CONTEXT.md memory file Handler:0xb6a800} {ID:compact_conversation Title:Compact Conversation Description:Summarize the current session to save tokens Handler:0xb6a680}] showModelDialog:false modelDialog:0xc0002675e0 showInitDialog:false initDialog:{width:199 height:56 selected:0 keys:{Tab:{keys:[] help:{Key: Desc:} disabled:false} Left:{keys:[] help:{Key: Desc:} disabled:false} Right:{keys:[] help:{Key: Desc:} disabled:false} Enter:{keys:[] help:{Key: Desc:} disabled:false} Escape:{keys:[] help:{Key: Desc:} disabled:false} Y:{keys:[] help:{Key: Desc:} disabled:false} N:{keys:[] help:{Key: Desc:} disabled:false}}} showFilepicker:false filepicker:0xc00013ac88 showThemeDialog:false themeDialog:0xc0001e8700 showMultiArgumentsDialog:false multiArgumentsDialog:{width:0 height:0 inputs:[] focusIndex:0 keys:{Enter:{keys:[] help:{Key: Desc:} disabled:false} Escape:{keys:[] help:{Key: Desc:} disabled:false}} commandID: content: argNames:[]} showToolsDialog:false toolsDialog:0xc0001e8740}"
time=2025-06-01T14:38:50.886-04:00 level=DEBUG msg="Set theme from config" theme=opencode
time=2025-06-01T14:38:50.891-04:00 level=INFO msg="Reading directory: /home/thdxr"
time=2025-06-01T14:38:53.495-04:00 level=INFO msg="Cancelling all subscriptions"
time=2025-06-01T14:38:53.495-04:00 level=INFO msg="subscription cancelled" name=status
time=2025-06-01T14:38:53.495-04:00 level=INFO msg="All subscription goroutines completed successfully"
time=2025-06-01T14:38:53.495-04:00 level=INFO msg="TUI message channel closed"
time=2025-06-01T14:38:53.495-04:00 level=INFO msg="All goroutines cleaned up"
time=2025-06-01T14:38:53.495-04:00 level=INFO msg="TUI exited" result="{width:199 height:56 currentPage:chat previousPage: pages:map[chat:0xc0005ac8c0] loadedPages:map[chat:true] status:{app:0xc0002796c0 queue:[] width:199 messageTTL:4000000000 activeUntil:{wall:0 ext:0 loc:<nil>}} app:0xc0002796c0 showPermissions:false permissions:0xc00028b408 showHelp:false help:0xc000490d80 showQuit:true quit:0xc000582589 showSessionDialog:false sessionDialog:0xc0003359c0 showCommandDialog:false commandDialog:0xc00042d480 commands:[{ID:init Title:Initialize Project Description:Create/Update the CONTEXT.md memory file Handler:0xb6a800} {ID:compact_conversation Title:Compact Conversation Description:Summarize the current session to save tokens Handler:0xb6a680}] showModelDialog:false modelDialog:0xc000389360 showInitDialog:false initDialog:{width:199 height:56 selected:0 keys:{Tab:{keys:[] help:{Key: Desc:} disabled:false} Left:{keys:[] help:{Key: Desc:} disabled:false} Right:{keys:[] help:{Key: Desc:} disabled:false} Enter:{keys:[] help:{Key: Desc:} disabled:false} Escape:{keys:[] help:{Key: Desc:} disabled:false} Y:{keys:[] help:{Key: Desc:} disabled:false} N:{keys:[] help:{Key: Desc:} disabled:false}}} showFilepicker:false filepicker:0xc0000d4c88 showThemeDialog:false themeDialog:0xc000335c00 showMultiArgumentsDialog:false multiArgumentsDialog:{width:0 height:0 inputs:[] focusIndex:0 keys:{Enter:{keys:[] help:{Key: Desc:} disabled:false} Escape:{keys:[] help:{Key: Desc:} disabled:false}} commandID: content: argNames:[]} showToolsDialog:false toolsDialog:0xc000335c80}"
time=2025-06-01T14:39:49.852-04:00 level=DEBUG msg="Set theme from config" theme=opencode
time=2025-06-01T14:39:49.856-04:00 level=INFO msg="Reading directory: /home/thdxr"
time=2025-06-01T14:39:57.071-04:00 level=INFO msg="Cancelling all subscriptions"
time=2025-06-01T14:39:57.071-04:00 level=INFO msg="subscription cancelled" name=status
time=2025-06-01T14:39:57.071-04:00 level=INFO msg="All subscription goroutines completed successfully"
time=2025-06-01T14:39:57.071-04:00 level=INFO msg="TUI message channel closed"
time=2025-06-01T14:39:57.071-04:00 level=INFO msg="All goroutines cleaned up"
time=2025-06-01T14:39:57.071-04:00 level=INFO msg="TUI exited" result="{width:199 height:56 currentPage:chat previousPage: pages:map[chat:0xc000616f00] loadedPages:map[chat:true] status:{app:0xc000333490 queue:[] width:199 messageTTL:4000000000 activeUntil:{wall:0 ext:0 loc:<nil>}} app:0xc000333490 showPermissions:false permissions:0xc0004faa08 showHelp:false help:0xc000471140 showQuit:true quit:0xc000459299 showSessionDialog:false sessionDialog:0xc000352500 showCommandDialog:false commandDialog:0xc00041ed80 commands:[{ID:init Title:Initialize Project Description:Create/Update the CONTEXT.md memory file Handler:0xb6a800} {ID:compact_conversation Title:Compact Conversation Description:Summarize the current session to save tokens Handler:0xb6a680}] showModelDialog:false modelDialog:0xc000515a40 showInitDialog:false initDialog:{width:199 height:56 selected:0 keys:{Tab:{keys:[] help:{Key: Desc:} disabled:false} Left:{keys:[] help:{Key: Desc:} disabled:false} Right:{keys:[] help:{Key: Desc:} disabled:false} Enter:{keys:[] help:{Key: Desc:} disabled:false} Escape:{keys:[] help:{Key: Desc:} disabled:false} Y:{keys:[] help:{Key: Desc:} disabled:false} N:{keys:[] help:{Key: Desc:} disabled:false}}} showFilepicker:false filepicker:0xc00013ac88 showThemeDialog:false themeDialog:0xc000352740 showMultiArgumentsDialog:false multiArgumentsDialog:{width:0 height:0 inputs:[] focusIndex:0 keys:{Enter:{keys:[] help:{Key: Desc:} disabled:false} Escape:{keys:[] help:{Key: Desc:} disabled:false}} commandID: content: argNames:[]} showToolsDialog:false toolsDialog:0xc000352780}"
time=2025-06-01T14:40:21.954-04:00 level=DEBUG msg="Set theme from config" theme=opencode
time=2025-06-01T14:40:21.958-04:00 level=INFO msg="Reading directory: /home/thdxr"
time=2025-06-01T14:41:29.195-04:00 level=INFO msg="Cancelling all subscriptions"
time=2025-06-01T14:41:29.195-04:00 level=INFO msg="subscription cancelled" name=status
time=2025-06-01T14:41:29.195-04:00 level=INFO msg="All subscription goroutines completed successfully"
time=2025-06-01T14:41:29.195-04:00 level=INFO msg="TUI message channel closed"
time=2025-06-01T14:41:29.195-04:00 level=INFO msg="All goroutines cleaned up"
time=2025-06-01T14:41:29.195-04:00 level=INFO msg="TUI exited" result="{width:199 height:56 currentPage:chat previousPage: pages:map[chat:0xc0002cc280] loadedPages:map[chat:true] status:{app:0xc0002e64d0 queue:[] width:199 messageTTL:4000000000 activeUntil:{wall:0 ext:0 loc:<nil>}} app:0xc0002e64d0 showPermissions:false permissions:0xc00026f408 showHelp:false help:0xc00051c1b0 showQuit:true quit:0xc00051a819 showSessionDialog:false sessionDialog:0xc00030fec0 showCommandDialog:false commandDialog:0xc00042d760 commands:[{ID:init Title:Initialize Project Description:Create/Update the CONTEXT.md memory file Handler:0xb6a800} {ID:compact_conversation Title:Compact Conversation Description:Summarize the current session to save tokens Handler:0xb6a680}] showModelDialog:false modelDialog:0xc0002ce1e0 showInitDialog:false initDialog:{width:199 height:56 selected:0 keys:{Tab:{keys:[] help:{Key: Desc:} disabled:false} Left:{keys:[] help:{Key: Desc:} disabled:false} Right:{keys:[] help:{Key: Desc:} disabled:false} Enter:{keys:[] help:{Key: Desc:} disabled:false} Escape:{keys:[] help:{Key: Desc:} disabled:false} Y:{keys:[] help:{Key: Desc:} disabled:false} N:{keys:[] help:{Key: Desc:} disabled:false}}} showFilepicker:false filepicker:0xc0000d4c88 showThemeDialog:false themeDialog:0xc0000ac480 showMultiArgumentsDialog:false multiArgumentsDialog:{width:0 height:0 inputs:[] focusIndex:0 keys:{Enter:{keys:[] help:{Key: Desc:} disabled:false} Escape:{keys:[] help:{Key: Desc:} disabled:false}} commandID: content: argNames:[]} showToolsDialog:false toolsDialog:0xc0000ac4c0}"
time=2025-06-01T14:58:27.272-04:00 level=DEBUG msg="Set theme from config" theme=opencode
time=2025-06-01T14:58:27.276-04:00 level=INFO msg="Reading directory: /home/thdxr"
time=2025-06-01T14:58:59.711-04:00 level=INFO msg="Cancelling all subscriptions"
time=2025-06-01T14:58:59.711-04:00 level=INFO msg="subscription cancelled" name=status
time=2025-06-01T14:58:59.711-04:00 level=INFO msg="All subscription goroutines completed successfully"
time=2025-06-01T14:58:59.711-04:00 level=INFO msg="TUI message channel closed"
time=2025-06-01T14:58:59.711-04:00 level=INFO msg="All goroutines cleaned up"
time=2025-06-01T14:58:59.711-04:00 level=INFO msg="TUI exited" result="{width:199 height:56 currentPage:chat previousPage: pages:map[chat:0xc000316280] loadedPages:map[chat:true] status:{app:0xc0002b5810 queue:[] width:199 messageTTL:4000000000 activeUntil:{wall:0 ext:0 loc:<nil>}} app:0xc0002b5810 showPermissions:false permissions:0xc000269408 showHelp:false help:0xc000490e10 showQuit:true quit:0xc00047a929 showSessionDialog:false sessionDialog:0xc0000adb40 showCommandDialog:false commandDialog:0xc0003e59c0 commands:[{ID:init Title:Initialize Project Description:Create/Update the CONTEXT.md memory file Handler:0xb6a800} {ID:compact_conversation Title:Compact Conversation Description:Summarize the current session to save tokens Handler:0xb6a680}] showModelDialog:false modelDialog:0xc00024fd60 showInitDialog:false initDialog:{width:199 height:56 selected:0 keys:{Tab:{keys:[] help:{Key: Desc:} disabled:false} Left:{keys:[] help:{Key: Desc:} disabled:false} Right:{keys:[] help:{Key: Desc:} disabled:false} Enter:{keys:[] help:{Key: Desc:} disabled:false} Escape:{keys:[] help:{Key: Desc:} disabled:false} Y:{keys:[] help:{Key: Desc:} disabled:false} N:{keys:[] help:{Key: Desc:} disabled:false}}} showFilepicker:false filepicker:0xc0000d4c88 showThemeDialog:false themeDialog:0xc0000add80 showMultiArgumentsDialog:false multiArgumentsDialog:{width:0 height:0 inputs:[] focusIndex:0 keys:{Enter:{keys:[] help:{Key: Desc:} disabled:false} Escape:{keys:[] help:{Key: Desc:} disabled:false}} commandID: content: argNames:[]} showToolsDialog:false toolsDialog:0xc0000addc0}"
time=2025-06-01T15:02:54.453-04:00 level=DEBUG msg="Set theme from config" theme=opencode
time=2025-06-01T15:02:54.458-04:00 level=INFO msg="Reading directory: /home/thdxr"
time=2025-06-01T15:02:56.136-04:00 level=INFO msg="Cancelling all subscriptions"
time=2025-06-01T15:02:56.136-04:00 level=INFO msg="subscription cancelled" name=status
time=2025-06-01T15:02:56.136-04:00 level=INFO msg="All subscription goroutines completed successfully"
time=2025-06-01T15:02:56.136-04:00 level=INFO msg="TUI message channel closed"
time=2025-06-01T15:02:56.136-04:00 level=INFO msg="All goroutines cleaned up"
time=2025-06-01T15:02:56.136-04:00 level=INFO msg="TUI exited" result="{width:199 height:56 currentPage:chat previousPage: pages:map[chat:0xc000392ff0] loadedPages:map[chat:true] status:{app:0xc0001ecc40 queue:[] width:199 messageTTL:4000000000 activeUntil:{wall:0 ext:0 loc:<nil>}} app:0xc0001ecc40 showPermissions:false permissions:0xc000205408 showHelp:false help:0xc00051c0c0 showQuit:true quit:0xc0003b3f49 showSessionDialog:false sessionDialog:0xc000319980 showCommandDialog:false commandDialog:0xc00042d220 commands:[{ID:init Title:Initialize Project Description:Create/Update the CONTEXT.md memory file Handler:0xb6b340} {ID:compact_conversation Title:Compact Conversation Description:Summarize the current session to save tokens Handler:0xb6b1c0}] showModelDialog:false modelDialog:0xc0005c52c0 showInitDialog:false initDialog:{width:199 height:56 selected:0 keys:{Tab:{keys:[] help:{Key: Desc:} disabled:false} Left:{keys:[] help:{Key: Desc:} disabled:false} Right:{keys:[] help:{Key: Desc:} disabled:false} Enter:{keys:[] help:{Key: Desc:} disabled:false} Escape:{keys:[] help:{Key: Desc:} disabled:false} Y:{keys:[] help:{Key: Desc:} disabled:false} N:{keys:[] help:{Key: Desc:} disabled:false}}} showFilepicker:false filepicker:0xc0000d4c88 showThemeDialog:false themeDialog:0xc000319bc0 showMultiArgumentsDialog:false multiArgumentsDialog:{width:0 height:0 inputs:[] focusIndex:0 keys:{Enter:{keys:[] help:{Key: Desc:} disabled:false} Escape:{keys:[] help:{Key: Desc:} disabled:false}} commandID: content: argNames:[]} showToolsDialog:false toolsDialog:0xc000319c00}"
time=2025-06-01T15:02:57.053-04:00 level=DEBUG msg="Set theme from config" theme=opencode
time=2025-06-01T15:02:57.057-04:00 level=INFO msg="Reading directory: /home/thdxr"
time=2025-06-01T15:02:58.135-04:00 level=INFO msg="Cancelling all subscriptions"
time=2025-06-01T15:02:58.135-04:00 level=INFO msg="subscription cancelled" name=status
time=2025-06-01T15:02:58.135-04:00 level=INFO msg="All subscription goroutines completed successfully"
time=2025-06-01T15:02:58.135-04:00 level=INFO msg="TUI message channel closed"
time=2025-06-01T15:02:58.135-04:00 level=INFO msg="All goroutines cleaned up"
time=2025-06-01T15:02:58.135-04:00 level=INFO msg="TUI exited" result="{width:199 height:56 currentPage:chat previousPage: pages:map[chat:0xc0004411d0] loadedPages:map[chat:true] status:{app:0xc00023ee70 queue:[] width:199 messageTTL:4000000000 activeUntil:{wall:0 ext:0 loc:<nil>}} app:0xc00023ee70 showPermissions:false permissions:0xc000177408 showHelp:false help:0xc000520030 showQuit:true quit:0xc000314929 showSessionDialog:false sessionDialog:0xc000319d00 showCommandDialog:false commandDialog:0xc0003e5860 commands:[{ID:init Title:Initialize Project Description:Create/Update the CONTEXT.md memory file Handler:0xb6b340} {ID:compact_conversation Title:Compact Conversation Description:Summarize the current session to save tokens Handler:0xb6b1c0}] showModelDialog:false modelDialog:0xc0002c9a40 showInitDialog:false initDialog:{width:199 height:56 selected:0 keys:{Tab:{keys:[] help:{Key: Desc:} disabled:false} Left:{keys:[] help:{Key: Desc:} disabled:false} Right:{keys:[] help:{Key: Desc:} disabled:false} Enter:{keys:[] help:{Key: Desc:} disabled:false} Escape:{keys:[] help:{Key: Desc:} disabled:false} Y:{keys:[] help:{Key: Desc:} disabled:false} N:{keys:[] help:{Key: Desc:} disabled:false}}} showFilepicker:false filepicker:0xc0000d4c88 showThemeDialog:false themeDialog:0xc000319f40 showMultiArgumentsDialog:false multiArgumentsDialog:{width:0 height:0 inputs:[] focusIndex:0 keys:{Enter:{keys:[] help:{Key: Desc:} disabled:false} Escape:{keys:[] help:{Key: Desc:} disabled:false}} commandID: content: argNames:[]} showToolsDialog:false toolsDialog:0xc0000ac000}"
time=2025-06-01T15:15:13.582-04:00 level=DEBUG msg="Set theme from config" theme=opencode
time=2025-06-01T15:15:13.587-04:00 level=INFO msg="Reading directory: /home/thdxr"
time=2025-06-01T15:15:19.009-04:00 level=INFO msg="Cancelling all subscriptions"
time=2025-06-01T15:15:19.010-04:00 level=INFO msg="subscription cancelled" name=status
time=2025-06-01T15:15:19.010-04:00 level=INFO msg="All subscription goroutines completed successfully"
time=2025-06-01T15:15:19.010-04:00 level=INFO msg="TUI message channel closed"
time=2025-06-01T15:15:19.010-04:00 level=INFO msg="All goroutines cleaned up"
time=2025-06-01T15:15:19.010-04:00 level=INFO msg="TUI exited" result="{width:199 height:56 currentPage:chat previousPage: pages:map[chat:0xc0001490e0] loadedPages:map[chat:true] status:{app:0xc0001efb90 queue:[] width:199 messageTTL:4000000000 activeUntil:{wall:0 ext:0 loc:<nil>}} app:0xc0001efb90 showPermissions:false permissions:0xc000167408 showHelp:false help:0xc00052c1b0 showQuit:true quit:0xc000254629 showSessionDialog:false sessionDialog:0xc00030fe80 showCommandDialog:false commandDialog:0xc0003a3420 commands:[{ID:init Title:Initialize Project Description:Create/Update the CONTEXT.md memory file Handler:0xb6b340} {ID:compact_conversation Title:Compact Conversation Description:Summarize the current session to save tokens Handler:0xb6b1c0}] showModelDialog:false modelDialog:0xc0000c6640 showInitDialog:false initDialog:{width:199 height:56 selected:0 keys:{Tab:{keys:[] help:{Key: Desc:} disabled:false} Left:{keys:[] help:{Key: Desc:} disabled:false} Right:{keys:[] help:{Key: Desc:} disabled:false} Enter:{keys:[] help:{Key: Desc:} disabled:false} Escape:{keys:[] help:{Key: Desc:} disabled:false} Y:{keys:[] help:{Key: Desc:} disabled:false} N:{keys:[] help:{Key: Desc:} disabled:false}}} showFilepicker:false filepicker:0xc0000d4c88 showThemeDialog:false themeDialog:0xc0000ac440 showMultiArgumentsDialog:false multiArgumentsDialog:{width:0 height:0 inputs:[] focusIndex:0 keys:{Enter:{keys:[] help:{Key: Desc:} disabled:false} Escape:{keys:[] help:{Key: Desc:} disabled:false}} commandID: content: argNames:[]} showToolsDialog:false toolsDialog:0xc0000ac480}"
time=2025-06-01T15:15:20.678-04:00 level=DEBUG msg="Set theme from config" theme=opencode
time=2025-06-01T15:15:20.683-04:00 level=INFO msg="Reading directory: /home/thdxr"
time=2025-06-01T15:15:23.252-04:00 level=INFO msg="Cancelling all subscriptions"
time=2025-06-01T15:15:23.253-04:00 level=INFO msg="subscription cancelled" name=status
time=2025-06-01T15:15:23.253-04:00 level=INFO msg="All subscription goroutines completed successfully"
time=2025-06-01T15:15:23.253-04:00 level=INFO msg="TUI message channel closed"
time=2025-06-01T15:15:23.253-04:00 level=INFO msg="All goroutines cleaned up"
time=2025-06-01T15:15:23.253-04:00 level=INFO msg="TUI exited" result="{width:199 height:56 currentPage:chat previousPage: pages:map[chat:0xc0002c47d0] loadedPages:map[chat:true] status:{app:0xc0003363f0 queue:[] width:199 messageTTL:4000000000 activeUntil:{wall:0 ext:0 loc:<nil>}} app:0xc0003363f0 showPermissions:false permissions:0xc0002f3408 showHelp:false help:0xc0007055f0 showQuit:true quit:0xc00041c9b9 showSessionDialog:false sessionDialog:0xc00033bd00 showCommandDialog:false commandDialog:0xc000437700 commands:[{ID:init Title:Initialize Project Description:Create/Update the CONTEXT.md memory file Handler:0xb6b340} {ID:compact_conversation Title:Compact Conversation Description:Summarize the current session to save tokens Handler:0xb6b1c0}] showModelDialog:false modelDialog:0xc0003d9c20 showInitDialog:false initDialog:{width:199 height:56 selected:0 keys:{Tab:{keys:[] help:{Key: Desc:} disabled:false} Left:{keys:[] help:{Key: Desc:} disabled:false} Right:{keys:[] help:{Key: Desc:} disabled:false} Enter:{keys:[] help:{Key: Desc:} disabled:false} Escape:{keys:[] help:{Key: Desc:} disabled:false} Y:{keys:[] help:{Key: Desc:} disabled:false} N:{keys:[] help:{Key: Desc:} disabled:false}}} showFilepicker:false filepicker:0xc00013ac88 showThemeDialog:false themeDialog:0xc00033bf40 showMultiArgumentsDialog:false multiArgumentsDialog:{width:0 height:0 inputs:[] focusIndex:0 keys:{Enter:{keys:[] help:{Key: Desc:} disabled:false} Escape:{keys:[] help:{Key: Desc:} disabled:false}} commandID: content: argNames:[]} showToolsDialog:false toolsDialog:0xc00034a140}"
time=2025-06-02T11:40:21.643-04:00 level=DEBUG msg="Set theme from config" theme=opencode
time=2025-06-02T11:40:21.648-04:00 level=INFO msg="Reading directory: /home/thdxr"
time=2025-06-02T11:40:24.684-04:00 level=INFO msg="Cancelling all subscriptions"
time=2025-06-02T11:40:24.684-04:00 level=INFO msg="subscription cancelled" name=status
time=2025-06-02T11:40:24.684-04:00 level=INFO msg="All subscription goroutines completed successfully"
time=2025-06-02T11:40:24.684-04:00 level=INFO msg="TUI message channel closed"
time=2025-06-02T11:40:24.684-04:00 level=INFO msg="All goroutines cleaned up"
time=2025-06-02T11:40:24.684-04:00 level=INFO msg="TUI exited" result="{width:347 height:89 currentPage:chat previousPage: pages:map[chat:0xc00020d180] loadedPages:map[chat:true] status:{app:0xc0002a8230 queue:[] width:347 messageTTL:4000000000 activeUntil:{wall:0 ext:0 loc:<nil>}} app:0xc0002a8230 showPermissions:false permissions:0xc000239408 showHelp:false help:0xc00051c1e0 showQuit:true quit:0xc000598909 showSessionDialog:false sessionDialog:0xc000309f00 showCommandDialog:false commandDialog:0xc0003a3660 commands:[{ID:init Title:Initialize Project Description:Create/Update the CONTEXT.md memory file Handler:0xb6b340} {ID:compact_conversation Title:Compact Conversation Description:Summarize the current session to save tokens Handler:0xb6b1c0}] showModelDialog:false modelDialog:0xc0000c4a00 showInitDialog:false initDialog:{width:347 height:89 selected:0 keys:{Tab:{keys:[] help:{Key: Desc:} disabled:false} Left:{keys:[] help:{Key: Desc:} disabled:false} Right:{keys:[] help:{Key: Desc:} disabled:false} Enter:{keys:[] help:{Key: Desc:} disabled:false} Escape:{keys:[] help:{Key: Desc:} disabled:false} Y:{keys:[] help:{Key: Desc:} disabled:false} N:{keys:[] help:{Key: Desc:} disabled:false}}} showFilepicker:false filepicker:0xc0000d4c88 showThemeDialog:false themeDialog:0xc0000aa4c0 showMultiArgumentsDialog:false multiArgumentsDialog:{width:0 height:0 inputs:[] focusIndex:0 keys:{Enter:{keys:[] help:{Key: Desc:} disabled:false} Escape:{keys:[] help:{Key: Desc:} disabled:false}} commandID: content: argNames:[]} showToolsDialog:false toolsDialog:0xc0000aa500}"
time=2025-06-02T11:40:55.224-04:00 level=DEBUG msg="Set theme from config" theme=opencode
time=2025-06-02T11:40:55.228-04:00 level=INFO msg="Reading directory: /home/thdxr"
time=2025-06-02T11:41:03.512-04:00 level=INFO msg="Cancelling all subscriptions"
time=2025-06-02T11:41:03.512-04:00 level=INFO msg="subscription cancelled" name=status
time=2025-06-02T11:41:03.512-04:00 level=INFO msg="All subscription goroutines completed successfully"
time=2025-06-02T11:41:03.512-04:00 level=INFO msg="TUI message channel closed"
time=2025-06-02T11:41:03.512-04:00 level=INFO msg="All goroutines cleaned up"
time=2025-06-02T11:41:03.512-04:00 level=INFO msg="TUI exited" result="{width:347 height:89 currentPage:chat previousPage: pages:map[chat:0xc0001467d0] loadedPages:map[chat:true] status:{app:0xc0004feee0 queue:[] width:347 messageTTL:4000000000 activeUntil:{wall:0 ext:0 loc:<nil>}} app:0xc0004feee0 showPermissions:false permissions:0xc000167408 showHelp:false help:0xc00059cd50 showQuit:true quit:0xc00038aaa9 showSessionDialog:false sessionDialog:0xc00030ff00 showCommandDialog:false commandDialog:0xc0003e5aa0 commands:[{ID:init Title:Initialize Project Description:Create/Update the CONTEXT.md memory file Handler:0xb6b340} {ID:compact_conversation Title:Compact Conversation Description:Summarize the current session to save tokens Handler:0xb6b1c0}] showModelDialog:false modelDialog:0xc00029be00 showInitDialog:false initDialog:{width:347 height:89 selected:0 keys:{Tab:{keys:[] help:{Key: Desc:} disabled:false} Left:{keys:[] help:{Key: Desc:} disabled:false} Right:{keys:[] help:{Key: Desc:} disabled:false} Enter:{keys:[] help:{Key: Desc:} disabled:false} Escape:{keys:[] help:{Key: Desc:} disabled:false} Y:{keys:[] help:{Key: Desc:} disabled:false} N:{keys:[] help:{Key: Desc:} disabled:false}}} showFilepicker:false filepicker:0xc0000d4c88 showThemeDialog:false themeDialog:0xc0000aa4c0 showMultiArgumentsDialog:false multiArgumentsDialog:{width:0 height:0 inputs:[] focusIndex:0 keys:{Enter:{keys:[] help:{Key: Desc:} disabled:false} Escape:{keys:[] help:{Key: Desc:} disabled:false}} commandID: content: argNames:[]} showToolsDialog:false toolsDialog:0xc0000aa500}"
time=2025-06-02T11:41:05.131-04:00 level=DEBUG msg="Set theme from config" theme=opencode
time=2025-06-02T11:41:05.136-04:00 level=INFO msg="Reading directory: /home/thdxr"
time=2025-06-02T11:41:06.417-04:00 level=INFO msg="Cancelling all subscriptions"
time=2025-06-02T11:41:06.417-04:00 level=INFO msg="subscription cancelled" name=status
time=2025-06-02T11:41:06.417-04:00 level=INFO msg="All subscription goroutines completed successfully"
time=2025-06-02T11:41:06.417-04:00 level=INFO msg="TUI message channel closed"
time=2025-06-02T11:41:06.417-04:00 level=INFO msg="All goroutines cleaned up"
time=2025-06-02T11:41:06.417-04:00 level=INFO msg="TUI exited" result="{width:347 height:89 currentPage:chat previousPage: pages:map[chat:0xc0002527d0] loadedPages:map[chat:true] status:{app:0xc0002e0d90 queue:[] width:347 messageTTL:4000000000 activeUntil:{wall:0 ext:0 loc:<nil>}} app:0xc0002e0d90 showPermissions:false permissions:0xc00027b408 showHelp:false help:0xc0004900c0 showQuit:true quit:0xc00047ae69 showSessionDialog:false sessionDialog:0xc000319f40 showCommandDialog:false commandDialog:0xc00042d880 commands:[{ID:init Title:Initialize Project Description:Create/Update the CONTEXT.md memory file Handler:0xb6b340} {ID:compact_conversation Title:Compact Conversation Description:Summarize the current session to save tokens Handler:0xb6b1c0}] showModelDialog:false modelDialog:0xc000357e00 showInitDialog:false initDialog:{width:347 height:89 selected:0 keys:{Tab:{keys:[] help:{Key: Desc:} disabled:false} Left:{keys:[] help:{Key: Desc:} disabled:false} Right:{keys:[] help:{Key: Desc:} disabled:false} Enter:{keys:[] help:{Key: Desc:} disabled:false} Escape:{keys:[] help:{Key: Desc:} disabled:false} Y:{keys:[] help:{Key: Desc:} disabled:false} N:{keys:[] help:{Key: Desc:} disabled:false}}} showFilepicker:false filepicker:0xc0000d4c88 showThemeDialog:false themeDialog:0xc0000aa500 showMultiArgumentsDialog:false multiArgumentsDialog:{width:0 height:0 inputs:[] focusIndex:0 keys:{Enter:{keys:[] help:{Key: Desc:} disabled:false} Escape:{keys:[] help:{Key: Desc:} disabled:false}} commandID: content: argNames:[]} showToolsDialog:false toolsDialog:0xc0000aa540}"
time=2025-06-02T19:36:04.879-04:00 level=DEBUG msg="Set theme from config" theme=opencode
time=2025-06-02T19:36:04.883-04:00 level=INFO msg="Reading directory: /home/thdxr"
time=2025-06-02T19:36:07.068-04:00 level=INFO msg="Cancelling all subscriptions"
time=2025-06-02T19:36:07.068-04:00 level=INFO msg="subscription cancelled" name=status
time=2025-06-02T19:36:07.068-04:00 level=INFO msg="All subscription goroutines completed successfully"
time=2025-06-02T19:36:07.068-04:00 level=INFO msg="TUI message channel closed"
time=2025-06-02T19:36:07.068-04:00 level=INFO msg="All goroutines cleaned up"
time=2025-06-02T19:36:07.068-04:00 level=INFO msg="TUI exited" result="{width:145 height:36 currentPage:chat previousPage: pages:map[chat:0xc000544b40] loadedPages:map[chat:true] status:{app:0xc000249b90 queue:[] width:145 messageTTL:4000000000 activeUntil:{wall:0 ext:0 loc:<nil>}} app:0xc000249b90 showPermissions:false permissions:0xc000207408 showHelp:false help:0xc00011a1e0 showQuit:true quit:0xc0003890b9 showSessionDialog:false sessionDialog:0xc000319f40 showCommandDialog:false commandDialog:0xc0003e5520 commands:[{ID:init Title:Initialize Project Description:Create/Update the CONTEXT.md memory file Handler:0xb6b340} {ID:compact_conversation Title:Compact Conversation Description:Summarize the current session to save tokens Handler:0xb6b1c0}] showModelDialog:false modelDialog:0xc000547220 showInitDialog:false initDialog:{width:145 height:36 selected:0 keys:{Tab:{keys:[] help:{Key: Desc:} disabled:false} Left:{keys:[] help:{Key: Desc:} disabled:false} Right:{keys:[] help:{Key: Desc:} disabled:false} Enter:{keys:[] help:{Key: Desc:} disabled:false} Escape:{keys:[] help:{Key: Desc:} disabled:false} Y:{keys:[] help:{Key: Desc:} disabled:false} N:{keys:[] help:{Key: Desc:} disabled:false}}} showFilepicker:false filepicker:0xc0000d4c88 showThemeDialog:false themeDialog:0xc0000ac500 showMultiArgumentsDialog:false multiArgumentsDialog:{width:0 height:0 inputs:[] focusIndex:0 keys:{Enter:{keys:[] help:{Key: Desc:} disabled:false} Escape:{keys:[] help:{Key: Desc:} disabled:false}} commandID: content: argNames:[]} showToolsDialog:false toolsDialog:0xc0000ac540}"
time=2025-06-02T19:44:20.524-04:00 level=DEBUG msg="Set theme from config" theme=opencode
time=2025-06-02T19:44:20.529-04:00 level=INFO msg="Reading directory: /home/thdxr"
time=2025-06-02T19:45:43.720-04:00 level=INFO msg="Cancelling all subscriptions"
time=2025-06-02T19:45:43.720-04:00 level=INFO msg="subscription cancelled" name=status
time=2025-06-02T19:45:43.720-04:00 level=INFO msg="All subscription goroutines completed successfully"
time=2025-06-02T19:45:43.720-04:00 level=INFO msg="TUI message channel closed"
time=2025-06-02T19:45:43.720-04:00 level=INFO msg="All goroutines cleaned up"
time=2025-06-02T19:45:43.720-04:00 level=INFO msg="TUI exited" result="{width:145 height:36 currentPage:chat previousPage: pages:map[chat:0xc0001f87d0] loadedPages:map[chat:true] status:{app:0xc000270cb0 queue:[] width:145 messageTTL:4000000000 activeUntil:{wall:0 ext:0 loc:<nil>}} app:0xc000270cb0 showPermissions:false permissions:0xc00022f408 showHelp:false help:0xc000490e70 showQuit:true quit:0xc000388ab9 showSessionDialog:false sessionDialog:0xc000319f00 showCommandDialog:false commandDialog:0xc0003e55e0 commands:[{ID:init Title:Initialize Project Description:Create/Update the CONTEXT.md memory file Handler:0xb6b340} {ID:compact_conversation Title:Compact Conversation Description:Summarize the current session to save tokens Handler:0xb6b1c0}] showModelDialog:false modelDialog:0xc00030bd60 showInitDialog:false initDialog:{width:145 height:36 selected:0 keys:{Tab:{keys:[] help:{Key: Desc:} disabled:false} Left:{keys:[] help:{Key: Desc:} disabled:false} Right:{keys:[] help:{Key: Desc:} disabled:false} Enter:{keys:[] help:{Key: Desc:} disabled:false} Escape:{keys:[] help:{Key: Desc:} disabled:false} Y:{keys:[] help:{Key: Desc:} disabled:false} N:{keys:[] help:{Key: Desc:} disabled:false}}} showFilepicker:false filepicker:0xc0000d4c88 showThemeDialog:false themeDialog:0xc0000ac4c0 showMultiArgumentsDialog:false multiArgumentsDialog:{width:0 height:0 inputs:[] focusIndex:0 keys:{Enter:{keys:[] help:{Key: Desc:} disabled:false} Escape:{keys:[] help:{Key: Desc:} disabled:false}} commandID: content: argNames:[]} showToolsDialog:false toolsDialog:0xc0000ac500}"
time=2025-06-02T19:45:47.456-04:00 level=DEBUG msg="Set theme from config" theme=opencode
time=2025-06-02T19:45:47.462-04:00 level=INFO msg="Reading directory: /home/thdxr"
time=2025-06-02T19:46:50.039-04:00 level=INFO msg="Cancelling all subscriptions"
time=2025-06-02T19:46:50.039-04:00 level=INFO msg="subscription cancelled" name=status
time=2025-06-02T19:46:50.039-04:00 level=INFO msg="All subscription goroutines completed successfully"
time=2025-06-02T19:46:50.039-04:00 level=INFO msg="TUI message channel closed"
time=2025-06-02T19:46:50.039-04:00 level=INFO msg="All goroutines cleaned up"
time=2025-06-02T19:46:50.039-04:00 level=INFO msg="TUI exited" result="{width:145 height:36 currentPage:chat previousPage: pages:map[chat:0xc00035b9f0] loadedPages:map[chat:true] status:{app:0xc0000ec230 queue:[] width:145 messageTTL:4000000000 activeUntil:{wall:0 ext:0 loc:<nil>}} app:0xc0000ec230 showPermissions:false permissions:0xc0005e5408 showHelp:false help:0xc0005ad950 showQuit:true quit:0xc0005a0c09 showSessionDialog:false sessionDialog:0xc00012e440 showCommandDialog:false commandDialog:0xc0003c2160 commands:[{ID:init Title:Initialize Project Description:Create/Update the CONTEXT.md memory file Handler:0xb6b340} {ID:compact_conversation Title:Compact Conversation Description:Summarize the current session to save tokens Handler:0xb6b1c0}] showModelDialog:false modelDialog:0xc0002c74a0 showInitDialog:false initDialog:{width:145 height:36 selected:0 keys:{Tab:{keys:[] help:{Key: Desc:} disabled:false} Left:{keys:[] help:{Key: Desc:} disabled:false} Right:{keys:[] help:{Key: Desc:} disabled:false} Enter:{keys:[] help:{Key: Desc:} disabled:false} Escape:{keys:[] help:{Key: Desc:} disabled:false} Y:{keys:[] help:{Key: Desc:} disabled:false} N:{keys:[] help:{Key: Desc:} disabled:false}}} showFilepicker:false filepicker:0xc00013ac88 showThemeDialog:false themeDialog:0xc00012e680 showMultiArgumentsDialog:false multiArgumentsDialog:{width:0 height:0 inputs:[] focusIndex:0 keys:{Enter:{keys:[] help:{Key: Desc:} disabled:false} Escape:{keys:[] help:{Key: Desc:} disabled:false}} commandID: content: argNames:[]} showToolsDialog:false toolsDialog:0xc00012e6c0}"
time=2025-06-02T19:47:11.433-04:00 level=DEBUG msg="Set theme from config" theme=opencode
time=2025-06-02T19:47:11.438-04:00 level=INFO msg="Reading directory: /home/thdxr"
time=2025-06-02T19:48:43.841-04:00 level=INFO msg="Cancelling all subscriptions"
time=2025-06-02T19:48:43.841-04:00 level=INFO msg="subscription cancelled" name=status
time=2025-06-02T19:48:43.842-04:00 level=INFO msg="All subscription goroutines completed successfully"
time=2025-06-02T19:48:43.842-04:00 level=INFO msg="TUI message channel closed"
time=2025-06-02T19:48:43.842-04:00 level=INFO msg="All goroutines cleaned up"
time=2025-06-02T19:48:43.842-04:00 level=INFO msg="TUI exited" result="{width:145 height:36 currentPage:chat previousPage: pages:map[chat:0xc0001f9040] loadedPages:map[chat:true] status:{app:0xc000270070 queue:[] width:145 messageTTL:4000000000 activeUntil:{wall:0 ext:0 loc:<nil>}} app:0xc000270070 showPermissions:false permissions:0xc00022f408 showHelp:false help:0xc000122090 showQuit:true quit:0xc000447c19 showSessionDialog:false sessionDialog:0xc000323b40 showCommandDialog:false commandDialog:0xc0003cb540 commands:[{ID:init Title:Initialize Project Description:Create/Update the CONTEXT.md memory file Handler:0xb6b340} {ID:compact_conversation Title:Compact Conversation Description:Summarize the current session to save tokens Handler:0xb6b1c0}] showModelDialog:false modelDialog:0xc00044d5e0 showInitDialog:false initDialog:{width:145 height:36 selected:0 keys:{Tab:{keys:[] help:{Key: Desc:} disabled:false} Left:{keys:[] help:{Key: Desc:} disabled:false} Right:{keys:[] help:{Key: Desc:} disabled:false} Enter:{keys:[] help:{Key: Desc:} disabled:false} Escape:{keys:[] help:{Key: Desc:} disabled:false} Y:{keys:[] help:{Key: Desc:} disabled:false} N:{keys:[] help:{Key: Desc:} disabled:false}}} showFilepicker:false filepicker:0xc0000d4c88 showThemeDialog:false themeDialog:0xc000323d80 showMultiArgumentsDialog:false multiArgumentsDialog:{width:0 height:0 inputs:[] focusIndex:0 keys:{Enter:{keys:[] help:{Key: Desc:} disabled:false} Escape:{keys:[] help:{Key: Desc:} disabled:false}} commandID: content: argNames:[]} showToolsDialog:false toolsDialog:0xc000323dc0}"
time=2025-06-02T19:48:57.679-04:00 level=DEBUG msg="Set theme from config" theme=opencode
time=2025-06-02T19:48:57.685-04:00 level=INFO msg="Reading directory: /home/thdxr"
time=2025-06-02T19:50:56.190-04:00 level=INFO msg="Cancelling all subscriptions"
time=2025-06-02T19:50:56.190-04:00 level=INFO msg="subscription cancelled" name=status
time=2025-06-02T19:50:56.190-04:00 level=INFO msg="All subscription goroutines completed successfully"
time=2025-06-02T19:50:56.190-04:00 level=INFO msg="TUI message channel closed"
time=2025-06-02T19:50:56.190-04:00 level=INFO msg="All goroutines cleaned up"
time=2025-06-02T19:50:56.190-04:00 level=INFO msg="TUI exited" result="{width:145 height:36 currentPage:chat previousPage: pages:map[chat:0xc000564be0] loadedPages:map[chat:true] status:{app:0xc000250d20 queue:[] width:145 messageTTL:4000000000 activeUntil:{wall:0 ext:0 loc:<nil>}} app:0xc000250d20 showPermissions:false permissions:0xc0004d6a08 showHelp:false help:0xc00061d5c0 showQuit:true quit:0xc0005578a9 showSessionDialog:false sessionDialog:0xc00032a640 showCommandDialog:false commandDialog:0xc0003e51e0 commands:[{ID:init Title:Initialize Project Description:Create/Update the CONTEXT.md memory file Handler:0xb6b340} {ID:compact_conversation Title:Compact Conversation Description:Summarize the current session to save tokens Handler:0xb6b1c0}] showModelDialog:false modelDialog:0xc000620aa0 showInitDialog:false initDialog:{width:145 height:36 selected:0 keys:{Tab:{keys:[] help:{Key: Desc:} disabled:false} Left:{keys:[] help:{Key: Desc:} disabled:false} Right:{keys:[] help:{Key: Desc:} disabled:false} Enter:{keys:[] help:{Key: Desc:} disabled:false} Escape:{keys:[] help:{Key: Desc:} disabled:false} Y:{keys:[] help:{Key: Desc:} disabled:false} N:{keys:[] help:{Key: Desc:} disabled:false}}} showFilepicker:false filepicker:0xc0000d4c88 showThemeDialog:false themeDialog:0xc00032a880 showMultiArgumentsDialog:false multiArgumentsDialog:{width:0 height:0 inputs:[] focusIndex:0 keys:{Enter:{keys:[] help:{Key: Desc:} disabled:false} Escape:{keys:[] help:{Key: Desc:} disabled:false}} commandID: content: argNames:[]} showToolsDialog:false toolsDialog:0xc00032a8c0}"
File diff suppressed because one or more lines are too long
Binary file not shown.
+15 -14
View File
@@ -258,8 +258,8 @@ func (m *editorComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
func (m *editorComponent) View() string {
t := theme.CurrentTheme()
base := styles.BaseStyle().Render
muted := styles.Muted().Render
base := styles.BaseStyle().Background(t.Background()).Render
muted := styles.Muted().Background(t.Background()).Render
promptStyle := lipgloss.NewStyle().
Padding(0, 0, 0, 1).
Bold(true).
@@ -281,7 +281,7 @@ func (m *editorComponent) View() string {
BorderBackground(t.Background()).
Render(textarea)
hint := base("enter") + muted(" send ") + base("shift") + muted("+") + base("enter") + muted(" newline")
hint := base("enter") + muted(" send ")
if m.app.IsBusy() {
hint = muted("working") + m.spinner.View() + muted(" ") + base("esc") + muted(" interrupt")
}
@@ -292,18 +292,12 @@ func (m *editorComponent) View() string {
}
space := m.width - 2 - lipgloss.Width(model) - lipgloss.Width(hint)
spacer := lipgloss.NewStyle().Width(space).Render("")
spacer := lipgloss.NewStyle().Background(t.Background()).Width(space).Render("")
info := lipgloss.JoinHorizontal(lipgloss.Left, hint, spacer, model)
info = styles.Padded().Render(info)
info := hint + spacer + model
info = styles.Padded().Background(t.Background()).Render(info)
content := lipgloss.JoinVertical(
lipgloss.Top,
// m.attachmentsContent(),
"",
textarea,
info,
)
content := strings.Join([]string{"", textarea, info}, "\n")
return content
}
@@ -461,7 +455,14 @@ func (m *editorComponent) GetValue() string {
}
func NewEditorComponent(app *app.App) layout.ModelWithView {
s := spinner.New(spinner.WithSpinner(spinner.Ellipsis), spinner.WithStyle(styles.Muted().Width(3)))
s := spinner.New(
spinner.WithSpinner(spinner.Ellipsis),
spinner.WithStyle(
styles.
Muted().
Background(theme.CurrentTheme().Background()).
Width(3)),
)
ta := createTextArea(nil)
return &editorComponent{
@@ -2,7 +2,6 @@ package chat
import (
"fmt"
"log/slog"
"path/filepath"
"slices"
"strings"
@@ -131,8 +130,8 @@ func renderContentBlock(content string, options ...renderingOption) string {
}
style := styles.BaseStyle().
MarginTop(renderer.marginTop).
MarginBottom(renderer.marginBottom).
// MarginTop(renderer.marginTop).
// MarginBottom(renderer.marginBottom).
PaddingTop(renderer.paddingTop).
PaddingBottom(renderer.paddingBottom).
PaddingLeft(renderer.paddingLeft).
@@ -180,12 +179,25 @@ func renderContentBlock(content string, options ...renderingOption) string {
layout.Current.Container.Width,
align,
content,
lipgloss.WithWhitespaceStyle(lipgloss.NewStyle().Background(t.Background())),
)
content = lipgloss.PlaceHorizontal(
layout.Current.Viewport.Width,
lipgloss.Center,
content,
lipgloss.WithWhitespaceStyle(lipgloss.NewStyle().Background(t.Background())),
)
if renderer.marginTop > 0 {
for range renderer.marginTop {
content = "\n" + content
}
}
if renderer.marginBottom > 0 {
for range renderer.marginBottom {
content = content + "\n"
}
}
return content
}
@@ -208,18 +220,11 @@ func renderText(message client.MessageInfo, text string, author string) string {
}
info := fmt.Sprintf("%s (%s)", author, timestamp)
align := lipgloss.Left
switch message.Role {
case client.User:
align = lipgloss.Right
case client.Assistant:
align = lipgloss.Left
}
textWidth := max(lipgloss.Width(text), lipgloss.Width(info))
markdownWidth := min(textWidth, width-padding-4) // -4 for the border and padding
content := toMarkdown(text, markdownWidth, t.BackgroundSubtle())
content = lipgloss.JoinVertical(align, content, info)
content = strings.Join([]string{content, info}, "\n")
// content = lipgloss.JoinVertical(align, content, info)
switch message.Role {
case client.User:
@@ -268,6 +273,7 @@ func renderToolInvocation(
PaddingRight(2).
BorderLeft(true).
BorderRight(true).
BorderBackground(t.Background()).
BorderForeground(t.BackgroundSubtle()).
BorderStyle(lipgloss.ThickBorder())
@@ -292,10 +298,6 @@ func renderToolInvocation(
}
}
if len(toolArgsMap) == 0 {
slog.Debug("no args")
}
body := ""
error := ""
finished := result != nil && *result != ""
@@ -356,6 +358,7 @@ func renderToolInvocation(
formattedDiff = strings.TrimSpace(formattedDiff)
formattedDiff = lipgloss.NewStyle().
BorderStyle(lipgloss.ThickBorder()).
BorderBackground(t.Background()).
BorderForeground(t.BackgroundSubtle()).
BorderLeft(true).
BorderRight(true).
@@ -373,6 +376,7 @@ func renderToolInvocation(
lipgloss.Center,
lipgloss.Top,
body,
lipgloss.WithWhitespaceStyle(lipgloss.NewStyle().Background(t.Background())),
)
}
}
@@ -440,7 +444,12 @@ func renderToolInvocation(
}
content := style.Render(title)
content = lipgloss.PlaceHorizontal(layout.Current.Viewport.Width, lipgloss.Center, content)
content = lipgloss.PlaceHorizontal(
layout.Current.Viewport.Width,
lipgloss.Center,
content,
lipgloss.WithWhitespaceStyle(lipgloss.NewStyle().Background(t.Background())),
)
if showResult && body != "" && error == "" {
content += "\n" + body
}
@@ -242,8 +242,8 @@ func (m *messagesComponent) header() string {
t := theme.CurrentTheme()
width := layout.Current.Container.Width
base := styles.BaseStyle().Render
muted := styles.Muted().Render
base := styles.BaseStyle().Background(t.Background()).Render
muted := styles.Muted().Background(t.Background()).Render
headerLines := []string{}
headerLines = append(headerLines, toMarkdown("# "+m.app.Session.Title, width-6, t.Background()))
if m.app.Session.Share != nil && m.app.Session.Share.Url != "" {
@@ -257,7 +257,7 @@ func (m *messagesComponent) header() string {
Width(width).
PaddingLeft(2).
PaddingRight(2).
// Background(t.BackgroundElement()).
Background(t.Background()).
BorderLeft(true).
BorderRight(true).
BorderBackground(t.Background()).
@@ -275,23 +275,25 @@ func (m *messagesComponent) View() string {
if m.rendering {
return m.viewport.View()
}
t := theme.CurrentTheme()
return lipgloss.JoinVertical(
lipgloss.Left,
lipgloss.PlaceHorizontal(m.width, lipgloss.Center, m.header()),
lipgloss.PlaceHorizontal(
m.width,
lipgloss.Center,
m.header(),
lipgloss.WithWhitespaceStyle(lipgloss.NewStyle().Background(t.Background())),
),
m.viewport.View(),
)
}
func (m *messagesComponent) home() string {
// t := theme.CurrentTheme()
baseStyle := styles.BaseStyle()
t := theme.CurrentTheme()
baseStyle := styles.BaseStyle().Background(t.Background())
base := baseStyle.Render
muted := styles.Muted().Render
muted := styles.Muted().Background(t.Background()).Render
// mark := `
// ███▀▀█
// ███ █
// ▀▀▀▀▀▀ `
open := `
█▀▀█ █▀▀█ █▀▀ █▀▀▄
█░░█ █░░█ █▀▀ █░░█
@@ -303,9 +305,8 @@ func (m *messagesComponent) home() string {
logo := lipgloss.JoinHorizontal(
lipgloss.Top,
// styles.BaseStyle().Foreground(t.Primary()).Render(mark),
styles.Muted().Render(open),
styles.BaseStyle().Render(code),
muted(open),
base(code),
)
// cwd := app.Info.Path.Cwd
// config := app.Info.Path.Config
@@ -321,7 +322,7 @@ func (m *messagesComponent) home() string {
commandLines := []string{}
for _, command := range commands {
commandLines = append(commandLines, (base(command[0]) + " " + muted(command[1])))
commandLines = append(commandLines, (base(command[0]+" ") + muted(command[1])))
}
logoAndVersion := lipgloss.JoinVertical(
@@ -341,18 +342,21 @@ func (m *messagesComponent) home() string {
lines = append(lines, commandLines...)
lines = append(lines, "")
if m.rendering {
lines = append(lines, styles.Muted().Render("Loading session..."))
lines = append(lines, base("Loading session..."))
} else {
lines = append(lines, "")
}
return lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center,
return lipgloss.Place(
m.width,
m.height,
lipgloss.Center,
lipgloss.Center,
baseStyle.Width(lipgloss.Width(logoAndVersion)).Render(
lipgloss.JoinVertical(
lipgloss.Top,
lines...,
),
))
strings.Join(lines, "\n"),
),
lipgloss.WithWhitespaceStyle(lipgloss.NewStyle().Background(t.Background())),
)
}
func (m *messagesComponent) SetSize(width, height int) tea.Cmd {
@@ -140,14 +140,15 @@ func formatTokensAndCost(tokens float32, contextWindow float32, cost float32) st
}
func (m statusComponent) View() string {
t := theme.CurrentTheme()
if m.app.Session.Id == "" {
return styles.BaseStyle().
Background(t.Background()).
Width(m.width).
Height(2).
Render("")
}
t := theme.CurrentTheme()
logo := logo()
cwd := styles.Padded().
@@ -227,6 +227,7 @@ func (c *completionDialogComponent) View() string {
BorderBottom(false).
BorderRight(true).
BorderLeft(true).
BorderBackground(t.Background()).
BorderForeground(t.BackgroundSubtle()).
Width(c.width).
Render(c.list.View())
@@ -1,139 +0,0 @@
package dialog
import (
"strings"
"github.com/charmbracelet/bubbles/v2/key"
tea "github.com/charmbracelet/bubbletea/v2"
"github.com/charmbracelet/lipgloss/v2"
"github.com/sst/opencode/internal/components/modal"
"github.com/sst/opencode/internal/layout"
"github.com/sst/opencode/internal/styles"
"github.com/sst/opencode/internal/theme"
"github.com/sst/opencode/internal/util"
)
const question = "Are you sure you want to quit?"
// QuitDialog interface for the quit confirmation dialog
type QuitDialog interface {
layout.Modal
IsQuitDialog() bool
}
type quitDialog struct {
width int
height int
modal *modal.Modal
selectedNo bool
}
type helpMapping struct {
LeftRight key.Binding
EnterSpace key.Binding
Yes key.Binding
No key.Binding
}
var helpKeys = helpMapping{
LeftRight: key.NewBinding(
key.WithKeys("left", "right", "h", "l", "tab"),
key.WithHelp("←/→", "switch options"),
),
EnterSpace: key.NewBinding(
key.WithKeys("enter", " "),
key.WithHelp("enter/space", "confirm"),
),
Yes: key.NewBinding(
key.WithKeys("y", "Y", "ctrl+c"),
key.WithHelp("y/Y", "yes"),
),
No: key.NewBinding(
key.WithKeys("n", "N"),
key.WithHelp("n/N", "no"),
),
}
func (q *quitDialog) Init() tea.Cmd {
return nil
}
func (q *quitDialog) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.WindowSizeMsg:
q.width = msg.Width
q.height = msg.Height
case tea.KeyMsg:
switch {
case key.Matches(msg, helpKeys.LeftRight):
q.selectedNo = !q.selectedNo
return q, nil
case key.Matches(msg, helpKeys.EnterSpace):
if !q.selectedNo {
return q, tea.Quit
}
return q, util.CmdHandler(modal.CloseModalMsg{})
case key.Matches(msg, helpKeys.Yes):
return q, tea.Quit
case key.Matches(msg, helpKeys.No):
return q, util.CmdHandler(modal.CloseModalMsg{})
}
}
return q, nil
}
func (q *quitDialog) Render(background string) string {
t := theme.CurrentTheme()
baseStyle := styles.BaseStyle()
yesStyle := baseStyle
noStyle := baseStyle
spacerStyle := baseStyle.Background(t.BackgroundElement())
if q.selectedNo {
noStyle = noStyle.Background(t.Primary()).Foreground(t.BackgroundElement())
yesStyle = yesStyle.Background(t.BackgroundElement()).Foreground(t.Primary())
} else {
yesStyle = yesStyle.Background(t.Primary()).Foreground(t.BackgroundElement())
noStyle = noStyle.Background(t.BackgroundElement()).Foreground(t.Primary())
}
yesButton := yesStyle.Padding(0, 1).Render("Yes")
noButton := noStyle.Padding(0, 1).Render("No")
buttons := lipgloss.JoinHorizontal(lipgloss.Left, yesButton, spacerStyle.Render(" "), noButton)
width := lipgloss.Width(question)
remainingWidth := width - lipgloss.Width(buttons)
if remainingWidth > 0 {
buttons = spacerStyle.Render(strings.Repeat(" ", remainingWidth)) + buttons
}
content := baseStyle.Render(
lipgloss.JoinVertical(
lipgloss.Center,
question,
"",
buttons,
),
)
return q.modal.Render(content, background)
}
func (q *quitDialog) Close() tea.Cmd {
return nil
}
func (q *quitDialog) IsQuitDialog() bool {
return true
}
// NewQuitDialog creates a new quit confirmation dialog
func NewQuitDialog() QuitDialog {
return &quitDialog{
selectedNo: true,
modal: modal.New(),
}
}
+11 -7
View File
@@ -3,6 +3,7 @@ package layout
import (
tea "github.com/charmbracelet/bubbletea/v2"
"github.com/charmbracelet/lipgloss/v2"
"github.com/sst/opencode/internal/theme"
)
type FlexDirection int
@@ -76,6 +77,7 @@ func (f *flexLayout) View() string {
return ""
}
t := theme.CurrentTheme()
views := make([]string, 0, len(f.panes))
for i, pane := range f.panes {
if pane == nil {
@@ -89,6 +91,7 @@ func (f *flexLayout) View() string {
paneWidth,
pane.Alignment(),
pane.View(),
lipgloss.WithWhitespaceStyle(lipgloss.NewStyle().Background(t.Background())),
)
views = append(views, view)
} else {
@@ -99,6 +102,7 @@ func (f *flexLayout) View() string {
lipgloss.Center,
pane.Alignment(),
pane.View(),
lipgloss.WithWhitespaceStyle(lipgloss.NewStyle().Background(t.Background())),
)
views = append(views, view)
}
@@ -160,14 +164,14 @@ func (f *flexLayout) SetSize(width, height int) tea.Cmd {
var cmds []tea.Cmd
currentX, currentY := 0, 0
for i, pane := range f.panes {
if pane != nil {
paneWidth, paneHeight := f.calculatePaneSize(i)
// Calculate actual position based on alignment
actualX, actualY := currentX, currentY
if f.direction == FlexDirectionHorizontal {
// In horizontal layout, vertical alignment affects Y position
// (lipgloss.Center is used for vertical alignment in JoinHorizontal)
@@ -178,7 +182,7 @@ func (f *flexLayout) SetSize(width, height int) tea.Cmd {
if pane.MaxWidth() > 0 && contentWidth > pane.MaxWidth() {
contentWidth = pane.MaxWidth()
}
switch pane.Alignment() {
case lipgloss.Center:
actualX = (f.width - contentWidth) / 2
@@ -188,16 +192,16 @@ func (f *flexLayout) SetSize(width, height int) tea.Cmd {
actualX = 0
}
}
// Set position if the pane is a *container
if c, ok := pane.(*container); ok {
c.x = actualX
c.y = actualY
}
cmd := pane.SetSize(paneWidth, paneHeight)
cmds = append(cmds, cmd)
// Update position for next pane
if f.direction == FlexDirectionHorizontal {
currentX += paneWidth