Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4217286b72 | ||
|
|
28a4517ec6 | ||
|
|
b00b2ded4f | ||
|
|
7b6d5b1429 | ||
|
|
7210db19e9 | ||
|
|
90d2b26426 | ||
|
|
6beba2c04f | ||
|
|
b8a0ecca98 | ||
|
|
ad10d3a126 | ||
|
|
a48274f82b | ||
|
|
6b25b7e95e | ||
|
|
030a3a7446 | ||
|
|
1a0e7f1e63 | ||
|
|
677fb6032b | ||
|
|
49aa48ce58 |
@@ -1,10 +0,0 @@
|
||||
import { Plugin } from "./index"
|
||||
|
||||
export const ExamplePlugin: Plugin = async ({ app, client, $ }) => {
|
||||
return {
|
||||
permission: {},
|
||||
async "chat.params"(input, output) {
|
||||
output.topP = 1
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -83,7 +83,7 @@ And run.
|
||||
|
||||
```bash
|
||||
$ bun install
|
||||
$ bun run packages/opencode/src/index.ts
|
||||
$ bun dev
|
||||
```
|
||||
|
||||
#### Development Notes
|
||||
|
||||
@@ -39,3 +39,4 @@
|
||||
| 2025-08-03 | 131,397 (+3,533) | 150,451 (+1,215) | 281,848 (+4,748) |
|
||||
| 2025-08-04 | 136,266 (+4,869) | 153,260 (+2,809) | 289,526 (+7,678) |
|
||||
| 2025-08-05 | 141,596 (+5,330) | 155,752 (+2,492) | 297,348 (+7,822) |
|
||||
| 2025-08-06 | 147,067 (+5,471) | 158,309 (+2,557) | 305,376 (+8,028) |
|
||||
|
||||
@@ -120,7 +120,7 @@
|
||||
"lang-map": "0.4.0",
|
||||
"luxon": "3.6.1",
|
||||
"marked": "15.0.12",
|
||||
"marked-shiki": "1.2.0",
|
||||
"marked-shiki": "1.2.1",
|
||||
"rehype-autolink-headings": "7.1.0",
|
||||
"remeda": "2.26.0",
|
||||
"sharp": "0.32.5",
|
||||
@@ -139,9 +139,6 @@
|
||||
"sharp",
|
||||
"esbuild",
|
||||
],
|
||||
"patchedDependencies": {
|
||||
"marked-shiki@1.2.0": "patches/marked-shiki@1.2.0.patch",
|
||||
},
|
||||
"catalog": {
|
||||
"@tsconfig/node22": "22.0.2",
|
||||
"@types/node": "22.13.9",
|
||||
@@ -1174,7 +1171,7 @@
|
||||
|
||||
"marked": ["marked@15.0.12", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA=="],
|
||||
|
||||
"marked-shiki": ["marked-shiki@1.2.0", "", { "peerDependencies": { "marked": ">=7.0.0", "shiki": ">=1.0.0" } }, "sha512-N924hp8veE6Mc91g5/kCNVoTU7TkeJfB2G2XEWb+k1fVA0Bck2T0rVt93d39BlOYH6ohP4Q9BFlPk+UkblhXbg=="],
|
||||
"marked-shiki": ["marked-shiki@1.2.1", "", { "peerDependencies": { "marked": ">=7.0.0", "shiki": ">=1.0.0" } }, "sha512-yHxYQhPY5oYaIRnROn98foKhuClark7M373/VpLxiy5TrDu9Jd/LsMwo8w+U91Up4oDb9IXFrP0N1MFRz8W/DQ=="],
|
||||
|
||||
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
|
||||
|
||||
|
||||
@@ -43,7 +43,5 @@
|
||||
"protobufjs",
|
||||
"sharp"
|
||||
],
|
||||
"patchedDependencies": {
|
||||
"marked-shiki@1.2.0": "patches/marked-shiki@1.2.0.patch"
|
||||
}
|
||||
"patchedDependencies": {}
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
@@ -0,0 +1,12 @@
|
||||
<svg width="289" height="50" viewBox="0 0 289 50" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M264.5 0H288.5V8.5H272.5V16.5H288.5V25H272.5V33H288.5V41.5H264.5V0Z" fill="white"/>
|
||||
<path d="M248.5 0H224.5V41.5H248.5V33H232.5V8.5H248.5V0Z" fill="white"/>
|
||||
<path d="M256.5 8.5H248.5V33H256.5V8.5Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M184.5 0H216.5V41.5H184.5V0ZM208.5 8.5H192.5V33H208.5V8.5Z" fill="white"/>
|
||||
<path d="M144.5 8.5H136.5V41.5H144.5V8.5Z" fill="white"/>
|
||||
<path d="M136.5 0H112.5V41.5H120.5V8.5H136.5V0Z" fill="white"/>
|
||||
<path d="M80.5 0H104.5V8.5H88.5V16.5H104.5V25H88.5V33H104.5V41.5H80.5V0Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M40.5 0H72.5V41.5H48.5V49.5H40.5V0ZM64.5 8.5H48.5V33H64.5V8.5Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.5 0H32.5V41.5955H0.5V0ZM24.5 8.5H8.5V33H24.5V8.5Z" fill="white"/>
|
||||
<path d="M152.5 0H176.5V8.5H160.5V33H176.5V41.5H152.5V0Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 981 B |
@@ -0,0 +1,12 @@
|
||||
<svg width="289" height="50" viewBox="0 0 289 50" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M264.5 0H288.5V8.5H272.5V16.5H288.5V25H272.5V33H288.5V41.5H264.5V0Z" fill="black"/>
|
||||
<path d="M248.5 0H224.5V41.5H248.5V33H232.5V8.5H248.5V0Z" fill="black"/>
|
||||
<path d="M256.5 8.5H248.5V33H256.5V8.5Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M184.5 0H216.5V41.5H184.5V0ZM208.5 8.5H192.5V33H208.5V8.5Z" fill="black"/>
|
||||
<path d="M144.5 8.5H136.5V41.5H144.5V8.5Z" fill="black"/>
|
||||
<path d="M136.5 0H112.5V41.5H120.5V8.5H136.5V0Z" fill="black"/>
|
||||
<path d="M80.5 0H104.5V8.5H88.5V16.5H104.5V25H88.5V33H104.5V41.5H80.5V0Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M40.5 0H72.5V41.5H48.5V49.5H40.5V0ZM64.5 8.5H48.5V33H64.5V8.5Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.5 0H32.5V41.5955H0.5V0ZM24.5 8.5H8.5V33H24.5V8.5Z" fill="black"/>
|
||||
<path d="M152.5 0H176.5V8.5H160.5V33H176.5V41.5H152.5V0Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 981 B |
@@ -0,0 +1,18 @@
|
||||
<svg width="289" height="50" viewBox="0 0 289 50" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.5 16.5H24.5V33H8.5V16.5Z" fill="white" fill-opacity="0.2"/>
|
||||
<path d="M48.5 16.5H64.5V33H48.5V16.5Z" fill="white" fill-opacity="0.2"/>
|
||||
<path d="M120.5 16.5H136.5V33H120.5V16.5Z" fill="white" fill-opacity="0.2"/>
|
||||
<path d="M160.5 16.5H176.5V33H160.5V16.5Z" fill="white" fill-opacity="0.2"/>
|
||||
<path d="M192.5 16.5H208.5V33H192.5V16.5Z" fill="white" fill-opacity="0.2"/>
|
||||
<path d="M232.5 16.5H248.5V33H232.5V16.5Z" fill="white" fill-opacity="0.2"/>
|
||||
<path d="M264.5 0H288.5V8.5H272.5V16.5H288.5V25H272.5V33H288.5V41.5H264.5V0Z" fill="white" fill-opacity="0.95"/>
|
||||
<path d="M248.5 0H224.5V41.5H248.5V33H232.5V8.5H248.5V0Z" fill="white" fill-opacity="0.95"/>
|
||||
<path d="M256.5 8.5H248.5V33H256.5V8.5Z" fill="white" fill-opacity="0.95"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M184.5 0H216.5V41.5H184.5V0ZM208.5 8.5H192.5V33H208.5V8.5Z" fill="white" fill-opacity="0.95"/>
|
||||
<path d="M144.5 8.5H136.5V41.5H144.5V8.5Z" fill="white" fill-opacity="0.5"/>
|
||||
<path d="M136.5 0H112.5V41.5H120.5V8.5H136.5V0Z" fill="white" fill-opacity="0.5"/>
|
||||
<path d="M80.5 0H104.5V8.5H88.5V16.5H104.5V25H88.5V33H104.5V41.5H80.5V0Z" fill="white" fill-opacity="0.5"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M40.5 0H72.5V41.5H48.5V49.5H40.5V0ZM64.5 8.5H48.5V33H64.5V8.5Z" fill="white" fill-opacity="0.5"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.5 0H32.5V41.5955H0.5V0ZM24.5 8.5H8.5V33H24.5V8.5Z" fill="white" fill-opacity="0.5"/>
|
||||
<path d="M152.5 0H176.5V8.5H160.5V33H176.5V41.5H152.5V0Z" fill="white" fill-opacity="0.95"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
@@ -0,0 +1,18 @@
|
||||
<svg width="288" height="50" viewBox="0 0 288 50" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 16.5H24V33H8V16.5Z" fill="black" fill-opacity="0.15"/>
|
||||
<path d="M48 16.5H64V33H48V16.5Z" fill="black" fill-opacity="0.15"/>
|
||||
<path d="M120 16.5H136V33H120V16.5Z" fill="black" fill-opacity="0.15"/>
|
||||
<path d="M160 16.5H176V33H160V16.5Z" fill="black" fill-opacity="0.15"/>
|
||||
<path d="M192 16.5H208V33H192V16.5Z" fill="black" fill-opacity="0.15"/>
|
||||
<path d="M232 16.5H248V33H232V16.5Z" fill="black" fill-opacity="0.15"/>
|
||||
<path d="M264 0H288V8.5H272V16.5H288V25H272V33H288V41.5H264V0Z" fill="black" fill-opacity="0.95"/>
|
||||
<path d="M248 0H224V41.5H248V33H232V8.5H248V0Z" fill="black" fill-opacity="0.95"/>
|
||||
<path d="M256 8.5H248V33H256V8.5Z" fill="black" fill-opacity="0.95"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M184 0H216V41.5H184V0ZM208 8.5H192V33H208V8.5Z" fill="black" fill-opacity="0.95"/>
|
||||
<path d="M144 8.5H136V41.5H144V8.5Z" fill="black" fill-opacity="0.55"/>
|
||||
<path d="M136 0H112V41.5H120V8.5H136V0Z" fill="black" fill-opacity="0.55"/>
|
||||
<path d="M80 0H104V8.5H88V16.5H104V25H88V33H104V41.5H80V0Z" fill="black" fill-opacity="0.55"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M40 0H72V41.5H48V49.5H40V0ZM64 8.5H48V33H64V8.5Z" fill="black" fill-opacity="0.55"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 0H32V41.5955H0V0ZM24 8.5H8V33H24V8.5Z" fill="black" fill-opacity="0.55"/>
|
||||
<path d="M152 0H176V8.5H160V33H176V41.5H152V0Z" fill="black" fill-opacity="0.95"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,4 @@
|
||||
<svg width="70" height="70" viewBox="0 0 70 70" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 13H35V58H0V13ZM26.25 22.1957H8.75V48.701H26.25V22.1957Z" fill="white"/>
|
||||
<path d="M43.75 13H70V22.1957H52.5V48.701H70V57.8967H43.75V13Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 306 B |
@@ -0,0 +1,4 @@
|
||||
<svg width="70" height="70" viewBox="0 0 70 70" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 13H35V58H0V13ZM26.25 22.1957H8.75V48.701H26.25V22.1957Z" fill="black"/>
|
||||
<path d="M43.75 13H70V22.1957H52.5V48.701H70V57.8967H43.75V13Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 306 B |
@@ -0,0 +1,4 @@
|
||||
<svg width="64" height="42" viewBox="0 0 64 42" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 0H32V41.5955H0V0ZM24 8.5H8V33H24V8.5Z" fill="white"/>
|
||||
<path d="M40 0H64V8.5H48V33H64V41.5H40V0Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 267 B |
@@ -0,0 +1,4 @@
|
||||
<svg width="64" height="42" viewBox="0 0 64 42" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 0H32V41.5955H0V0ZM24 8.5H8V33H24V8.5Z" fill="black"/>
|
||||
<path d="M40 0H64V8.5H48V33H64V41.5H40V0Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 267 B |
@@ -103,7 +103,8 @@ export const RunCommand = cmd({
|
||||
}
|
||||
UI.empty()
|
||||
|
||||
const { providerID, modelID } = args.model ? Provider.parseModel(args.model) : await Provider.defaultModel()
|
||||
const mode = args.mode ? await Mode.get(args.mode) : await Mode.list().then((x) => x[0])
|
||||
const { providerID, modelID } = args.model ? Provider.parseModel(args.model) : mode.model ?? await Provider.defaultModel()
|
||||
UI.println(UI.Style.TEXT_NORMAL_BOLD + "@ ", UI.Style.TEXT_NORMAL + `${providerID}/${modelID}`)
|
||||
UI.empty()
|
||||
|
||||
@@ -156,18 +157,13 @@ export const RunCommand = cmd({
|
||||
UI.error(err)
|
||||
})
|
||||
|
||||
const mode = args.mode ? await Mode.get(args.mode) : await Mode.list().then((x) => x[0])
|
||||
|
||||
const messageID = Identifier.ascending("message")
|
||||
const result = await Session.chat({
|
||||
sessionID: session.id,
|
||||
messageID,
|
||||
...(mode.model
|
||||
? mode.model
|
||||
: {
|
||||
providerID,
|
||||
modelID,
|
||||
}),
|
||||
providerID,
|
||||
modelID,
|
||||
mode: mode.name,
|
||||
parts: [
|
||||
{
|
||||
|
||||
@@ -224,7 +224,7 @@ export namespace Config {
|
||||
})
|
||||
export type Layout = z.infer<typeof Layout>
|
||||
|
||||
export const Permission = z.union([z.literal("ask"), z.literal("allow")])
|
||||
export const Permission = z.union([z.literal("ask"), z.literal("allow"), z.literal("deny")])
|
||||
export type Permission = z.infer<typeof Permission>
|
||||
|
||||
export const Info = z
|
||||
@@ -233,6 +233,7 @@ export namespace Config {
|
||||
theme: z.string().optional().describe("Theme name to use for the interface"),
|
||||
keybinds: Keybinds.optional().describe("Custom keybind configurations"),
|
||||
plugin: z.string().array().optional(),
|
||||
snapshot: z.boolean().optional(),
|
||||
share: z
|
||||
.enum(["manual", "auto", "disabled"])
|
||||
.optional()
|
||||
|
||||
@@ -48,6 +48,17 @@ export namespace Session {
|
||||
|
||||
const OUTPUT_TOKEN_MAX = 32_000
|
||||
|
||||
const parentSessionTitlePrefix = "New session - "
|
||||
const childSessionTitlePrefix = "Child session - "
|
||||
|
||||
function createDefaultTitle(isChild = false) {
|
||||
return (isChild ? childSessionTitlePrefix : parentSessionTitlePrefix) + new Date().toISOString()
|
||||
}
|
||||
|
||||
function isDefaultTitle(title: string) {
|
||||
return title.startsWith(parentSessionTitlePrefix)
|
||||
}
|
||||
|
||||
export const Info = z
|
||||
.object({
|
||||
id: Identifier.schema("session"),
|
||||
@@ -153,7 +164,7 @@ export namespace Session {
|
||||
id: Identifier.descending("session"),
|
||||
version: Installation.VERSION,
|
||||
parentID,
|
||||
title: (parentID ? "Child session - " : "New Session - ") + new Date().toISOString(),
|
||||
title: createDefaultTitle(!!parentID),
|
||||
time: {
|
||||
created: Date.now(),
|
||||
updated: Date.now(),
|
||||
@@ -631,7 +642,7 @@ export namespace Session {
|
||||
const lastSummary = msgs.findLast((msg) => msg.info.role === "assistant" && msg.info.summary === true)
|
||||
if (lastSummary) msgs = msgs.filter((msg) => msg.info.id >= lastSummary.info.id)
|
||||
|
||||
if (msgs.length === 1 && !session.parentID) {
|
||||
if (msgs.length === 1 && !session.parentID && isDefaultTitle(session.title)) {
|
||||
const small = (await Provider.getSmallModel(input.providerID)) ?? model
|
||||
generateText({
|
||||
maxOutputTokens: small.info.reasoning ? 1024 : 20,
|
||||
@@ -717,7 +728,7 @@ export namespace Session {
|
||||
|
||||
const enabledTools = pipe(
|
||||
mode.tools,
|
||||
mergeDeep(ToolRegistry.enabled(input.providerID, input.modelID)),
|
||||
mergeDeep(await ToolRegistry.enabled(input.providerID, input.modelID)),
|
||||
mergeDeep(input.tools ?? {}),
|
||||
)
|
||||
for (const item of await ToolRegistry.tools(input.providerID, input.modelID)) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import fs from "fs/promises"
|
||||
import { Log } from "../util/log"
|
||||
import { Global } from "../global"
|
||||
import { z } from "zod"
|
||||
import { Config } from "../config/config"
|
||||
|
||||
export namespace Snapshot {
|
||||
const log = Log.create({ service: "snapshot" })
|
||||
@@ -26,6 +27,8 @@ export namespace Snapshot {
|
||||
export async function track() {
|
||||
const app = App.info()
|
||||
if (!app.git) return
|
||||
const cfg = await Config.get()
|
||||
if (cfg.snapshot === false) return
|
||||
const git = gitdir()
|
||||
if (await fs.mkdir(git, { recursive: true })) {
|
||||
await $`git init`
|
||||
|
||||
@@ -93,7 +93,7 @@ export const BashTool = Tool.define("bash", {
|
||||
|
||||
// always allow cd if it passes above check
|
||||
if (!needsAsk && command[0] !== "cd") {
|
||||
const ask = (() => {
|
||||
const action = (() => {
|
||||
for (const [pattern, value] of Object.entries(permissions)) {
|
||||
const match = Wildcard.match(node.text, pattern)
|
||||
log.info("checking", { text: node.text.trim(), pattern, match })
|
||||
@@ -101,7 +101,12 @@ export const BashTool = Tool.define("bash", {
|
||||
}
|
||||
return "ask"
|
||||
})()
|
||||
if (ask === "ask") needsAsk = true
|
||||
if (action === "deny") {
|
||||
throw new Error(
|
||||
"The user has specifically restricted access to this command, you are not allowed to execute it.",
|
||||
)
|
||||
}
|
||||
if (action === "ask") needsAsk = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import { TodoWriteTool, TodoReadTool } from "./todo"
|
||||
import { WebFetchTool } from "./webfetch"
|
||||
import { WriteTool } from "./write"
|
||||
import { InvalidTool } from "./invalid"
|
||||
import { Config } from "../config/config"
|
||||
|
||||
export namespace ToolRegistry {
|
||||
const ALL = [
|
||||
@@ -65,11 +66,22 @@ export namespace ToolRegistry {
|
||||
return result
|
||||
}
|
||||
|
||||
export function enabled(_providerID: string, modelID: string): Record<string, boolean> {
|
||||
export async function enabled(_providerID: string, modelID: string): Promise<Record<string, boolean>> {
|
||||
const cfg = await Config.get()
|
||||
const result: Record<string, boolean> = {}
|
||||
|
||||
if (cfg.permission?.edit === "deny") {
|
||||
result["edit"] = false
|
||||
result["patch"] = false
|
||||
result["write"] = false
|
||||
}
|
||||
if (cfg?.permission?.bash === "deny") {
|
||||
result["bash"] = false
|
||||
}
|
||||
|
||||
if (modelID.toLowerCase().includes("claude")) {
|
||||
return {
|
||||
patch: false,
|
||||
}
|
||||
result["patch"] = false
|
||||
return result
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -79,13 +91,14 @@ export namespace ToolRegistry {
|
||||
modelID.includes("o3") ||
|
||||
modelID.includes("codex")
|
||||
) {
|
||||
return {
|
||||
patch: false,
|
||||
todowrite: false,
|
||||
todoread: false,
|
||||
}
|
||||
result["patch"] = false
|
||||
result["todowrite"] = false
|
||||
result["todoread"] = false
|
||||
|
||||
return result
|
||||
}
|
||||
return {}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
function sanitizeGeminiParameters(schema: z.ZodTypeAny, visited = new Set()): z.ZodTypeAny {
|
||||
|
||||
@@ -22,6 +22,7 @@ export const TaskTool = Tool.define("task", async () => {
|
||||
const msg = await Session.getMessage(ctx.sessionID, ctx.messageID)
|
||||
if (msg.info.role !== "assistant") throw new Error("Not an assistant message")
|
||||
const agent = await Agent.get(params.subagent_type)
|
||||
if (!agent) throw new Error(`Unknown agent type: ${params.subagent_type} is not a valid agent type`)
|
||||
const messageID = Identifier.ascending("message")
|
||||
const parts: Record<string, MessageV2.ToolPart> = {}
|
||||
const unsub = Bus.subscribe(MessageV2.Event.PartUpdated, async (evt) => {
|
||||
|
||||
@@ -23,17 +23,18 @@ import (
|
||||
)
|
||||
|
||||
type blockRenderer struct {
|
||||
textColor compat.AdaptiveColor
|
||||
border bool
|
||||
borderColor *compat.AdaptiveColor
|
||||
borderLeft bool
|
||||
borderRight bool
|
||||
paddingTop int
|
||||
paddingBottom int
|
||||
paddingLeft int
|
||||
paddingRight int
|
||||
marginTop int
|
||||
marginBottom int
|
||||
textColor compat.AdaptiveColor
|
||||
backgroundColor compat.AdaptiveColor
|
||||
border bool
|
||||
borderColor *compat.AdaptiveColor
|
||||
borderLeft bool
|
||||
borderRight bool
|
||||
paddingTop int
|
||||
paddingBottom int
|
||||
paddingLeft int
|
||||
paddingRight int
|
||||
marginTop int
|
||||
marginBottom int
|
||||
}
|
||||
|
||||
type renderingOption func(*blockRenderer)
|
||||
@@ -44,6 +45,12 @@ func WithTextColor(color compat.AdaptiveColor) renderingOption {
|
||||
}
|
||||
}
|
||||
|
||||
func WithBackgroundColor(color compat.AdaptiveColor) renderingOption {
|
||||
return func(c *blockRenderer) {
|
||||
c.backgroundColor = color
|
||||
}
|
||||
}
|
||||
|
||||
func WithNoBorder() renderingOption {
|
||||
return func(c *blockRenderer) {
|
||||
c.border = false
|
||||
@@ -132,14 +139,15 @@ func renderContentBlock(
|
||||
) string {
|
||||
t := theme.CurrentTheme()
|
||||
renderer := &blockRenderer{
|
||||
textColor: t.TextMuted(),
|
||||
border: true,
|
||||
borderLeft: true,
|
||||
borderRight: false,
|
||||
paddingTop: 1,
|
||||
paddingBottom: 1,
|
||||
paddingLeft: 2,
|
||||
paddingRight: 2,
|
||||
textColor: t.TextMuted(),
|
||||
backgroundColor: t.BackgroundPanel(),
|
||||
border: true,
|
||||
borderLeft: true,
|
||||
borderRight: false,
|
||||
paddingTop: 1,
|
||||
paddingBottom: 1,
|
||||
paddingLeft: 2,
|
||||
paddingRight: 2,
|
||||
}
|
||||
for _, option := range options {
|
||||
option(renderer)
|
||||
@@ -152,7 +160,7 @@ func renderContentBlock(
|
||||
|
||||
style := styles.NewStyle().
|
||||
Foreground(renderer.textColor).
|
||||
Background(t.BackgroundPanel()).
|
||||
Background(renderer.backgroundColor).
|
||||
PaddingTop(renderer.paddingTop).
|
||||
PaddingBottom(renderer.paddingBottom).
|
||||
PaddingLeft(renderer.paddingLeft).
|
||||
@@ -211,16 +219,15 @@ func renderText(
|
||||
switch casted := message.(type) {
|
||||
case opencode.AssistantMessage:
|
||||
ts = time.UnixMilli(int64(casted.Time.Created))
|
||||
content = util.ToMarkdown(text, width, backgroundColor)
|
||||
content = util.ToMarkdown(text, width+2, t.Background())
|
||||
case opencode.UserMessage:
|
||||
ts = time.UnixMilli(int64(casted.Time.Created))
|
||||
base := styles.NewStyle().Foreground(t.Text()).Background(backgroundColor)
|
||||
text = ansi.WordwrapWc(text, width-6, " -")
|
||||
|
||||
var result strings.Builder
|
||||
lastEnd := int64(0)
|
||||
|
||||
// Apply highlighting to filenames and base style to rest of text
|
||||
// Apply highlighting to filenames and base style to rest of text BEFORE wrapping
|
||||
textLen := int64(len(text))
|
||||
for _, filePart := range fileParts {
|
||||
highlight := base.Foreground(t.Secondary())
|
||||
@@ -246,7 +253,11 @@ func renderText(
|
||||
if lastEnd < textLen {
|
||||
result.WriteString(base.Render(text[lastEnd:]))
|
||||
}
|
||||
content = base.Width(width - 6).Render(result.String())
|
||||
|
||||
// wrap styled text
|
||||
styledText := result.String()
|
||||
wrappedText := ansi.WordwrapWc(styledText, width-6, " -")
|
||||
content = base.Width(width - 6).Render(wrappedText)
|
||||
}
|
||||
|
||||
timestamp := ts.
|
||||
@@ -286,14 +297,14 @@ func renderText(
|
||||
width,
|
||||
WithTextColor(t.Text()),
|
||||
WithBorderColor(t.Secondary()),
|
||||
WithBorderRight(),
|
||||
)
|
||||
case opencode.AssistantMessage:
|
||||
return renderContentBlock(
|
||||
app,
|
||||
content,
|
||||
width,
|
||||
WithBorderColor(t.Accent()),
|
||||
width+2,
|
||||
WithNoBorder(),
|
||||
WithBackgroundColor(t.Background()),
|
||||
)
|
||||
}
|
||||
return ""
|
||||
|
||||
@@ -285,6 +285,8 @@ func generateMarkdownStyleConfig(backgroundColor compat.AdaptiveColor) ansi.Styl
|
||||
StyleBlock: ansi.StyleBlock{
|
||||
StylePrimitive: ansi.StylePrimitive{
|
||||
BlockSuffix: "\n",
|
||||
// TODO: find better way to fix markdown table renders
|
||||
BackgroundColor: stringPtr(""),
|
||||
},
|
||||
},
|
||||
CenterSeparator: stringPtr("┼"),
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/lipgloss/v2"
|
||||
)
|
||||
|
||||
// PreventHyphenBreaks replaces regular hyphens with non-breaking hyphens to prevent
|
||||
// sparse word breaks in hyphenated terms like "claude-code-action".
|
||||
// This improves readability by keeping hyphenated words together.
|
||||
// Only preserves hyphens within words, not markdown syntax like bullet points.
|
||||
func PreventHyphenBreaks(text string) string {
|
||||
// Use regex to match hyphens that are between word characters
|
||||
// This preserves hyphens in words like "claude-code-action" but not in "- [ ]"
|
||||
re := regexp.MustCompile(`(\w)-(\w)`)
|
||||
return re.ReplaceAllString(text, "$1\u2011$2")
|
||||
}
|
||||
|
||||
// RestoreHyphens converts non-breaking hyphens back to regular hyphens.
|
||||
// This should be called after text processing (like word wrapping) is complete.
|
||||
func RestoreHyphens(text string) string {
|
||||
return strings.ReplaceAll(text, "\u2011", "-")
|
||||
}
|
||||
|
||||
// ProcessTextWithHyphens applies hyphen preservation to text during processing.
|
||||
// It wraps the provided processFunc with hyphen handling.
|
||||
func ProcessTextWithHyphens(text string, processFunc func(string) string) string {
|
||||
preserved := PreventHyphenBreaks(text)
|
||||
processed := processFunc(preserved)
|
||||
return RestoreHyphens(processed)
|
||||
}
|
||||
|
||||
// GetMessageContainerFrame calculates the actual horizontal frame size
|
||||
// (padding + borders) for message containers based on current theme.
|
||||
func GetMessageContainerFrame() int {
|
||||
style := lipgloss.NewStyle().
|
||||
BorderStyle(lipgloss.ThickBorder()).
|
||||
BorderLeft(true).
|
||||
BorderRight(true).
|
||||
PaddingLeft(2).
|
||||
PaddingRight(2)
|
||||
return style.GetHorizontalFrameSize()
|
||||
}
|
||||
|
||||
// GetMarkdownContainerFrame calculates the actual horizontal frame size
|
||||
// for markdown containers based on current theme.
|
||||
func GetMarkdownContainerFrame() int {
|
||||
// Markdown containers use the same styling as message containers
|
||||
return GetMessageContainerFrame()
|
||||
}
|
||||
@@ -25,7 +25,7 @@
|
||||
"lang-map": "0.4.0",
|
||||
"luxon": "3.6.1",
|
||||
"marked": "15.0.12",
|
||||
"marked-shiki": "1.2.0",
|
||||
"marked-shiki": "1.2.1",
|
||||
"rehype-autolink-headings": "7.1.0",
|
||||
"remeda": "2.26.0",
|
||||
"sharp": "0.32.5",
|
||||
|
||||
@@ -21,6 +21,7 @@ Use the `permission.edit` key to control whether file editing operations require
|
||||
|
||||
- `"ask"` - Prompt for approval before editing files
|
||||
- `"allow"` - Allow all file editing operations without approval
|
||||
- `"deny"` - Make all file editing tools disabled and unavailable
|
||||
|
||||
```json title="opencode.json" {4}
|
||||
{
|
||||
@@ -41,7 +42,8 @@ Controls whether bash commands require user approval.
|
||||
You can specify which commands you want to have run without approval.
|
||||
:::
|
||||
|
||||
This can be configured globally or with specific patterns. Setting this to `"ask"` is the strictest mode, requiring approval for all bash commands.
|
||||
This can be configured globally or with specific patterns. Setting this to `"ask"`, requiring approval for all bash commands.
|
||||
Setting this to `"deny"` is the strictest option, blocking LLM from running that command or command pattern.
|
||||
|
||||
For example.
|
||||
|
||||
@@ -56,6 +58,19 @@ For example.
|
||||
}
|
||||
```
|
||||
|
||||
- **Disable all Terraform commands**
|
||||
|
||||
```json title="opencode.json"
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"permission": {
|
||||
"bash": {
|
||||
"terraform *": "deny"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- **Approve specific commands**
|
||||
|
||||
```json title="opencode.json"
|
||||
|
||||
@@ -82,7 +82,7 @@ export const NotificationPlugin = async ({ client, $ }) => {
|
||||
}
|
||||
```
|
||||
|
||||
We are using `osascript` to run AppleScript on macOS. Here we are using it to sends notifications.
|
||||
We are using `osascript` to run AppleScript on macOS. Here we are using it to send notifications.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
diff --git a/node_modules/marked-shiki/.bun-tag-5eae3435af8a0229 b/.bun-tag-5eae3435af8a0229
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
|
||||
diff --git a/dist/index.js b/dist/index.js
|
||||
index 535885e8569626d08f205e76c4944e4ebd0decab..92b5695c1b05fc7db5d26128c8f948d43c91f88a 100644
|
||||
--- a/dist/index.js
|
||||
+++ b/dist/index.js
|
||||
@@ -4,7 +4,7 @@ function o(s = {}) {
|
||||
async: !0,
|
||||
async walkTokens(t) {
|
||||
if (t.type !== "code" || typeof e != "function") return;
|
||||
- const [a = "text", ...i] = t.lang.split(" "), { text: c } = t, r = await e(c, a, i), l = n ? n.replace("%l", String(a).toUpperCase()).replace("%s", r).replace("%t", c) : r;
|
||||
+ const [a = "text", ...i] = t.lang?.split(" "), { text: c } = t, r = await e(c, a, i), l = n ? n.replace("%l", String(a).toUpperCase()).replace("%s", r).replace("%t", c) : r;
|
||||
Object.assign(t, {
|
||||
type: "html",
|
||||
block: !0,
|
||||