chore: format code

This commit is contained in:
GitHub Action
2025-11-08 01:59:02 +00:00
parent 16357e8041
commit 34ff87d504
182 changed files with 940 additions and 3646 deletions

View File

@@ -14,11 +14,7 @@ export const AuthCommand = cmd({
command: "auth",
describe: "manage credentials",
builder: (yargs) =>
yargs
.command(AuthLoginCommand)
.command(AuthLogoutCommand)
.command(AuthListCommand)
.demandCommand(),
yargs.command(AuthLoginCommand).command(AuthLogoutCommand).command(AuthListCommand).demandCommand(),
async handler() {},
})
@@ -64,9 +60,7 @@ export const AuthListCommand = cmd({
prompts.log.info(`${provider} ${UI.Style.TEXT_DIM}${envVar}`)
}
prompts.outro(
`${activeEnvVars.length} environment variable` + (activeEnvVars.length === 1 ? "" : "s"),
)
prompts.outro(`${activeEnvVars.length} environment variable` + (activeEnvVars.length === 1 ? "" : "s"))
}
},
})
@@ -86,9 +80,7 @@ export const AuthLoginCommand = cmd({
UI.empty()
prompts.intro("Add credential")
if (args.url) {
const wellknown = await fetch(`${args.url}/.well-known/opencode`).then(
(x) => x.json() as any,
)
const wellknown = await fetch(`${args.url}/.well-known/opencode`).then((x) => x.json() as any)
prompts.log.info(`Running \`${wellknown.auth.command.join(" ")}\``)
const proc = Bun.spawn({
cmd: wellknown.auth.command,
@@ -290,8 +282,7 @@ export const AuthLoginCommand = cmd({
if (provider === "other") {
provider = await prompts.text({
message: "Enter provider id",
validate: (x) =>
x && x.match(/^[0-9a-z-]+$/) ? undefined : "a-z, 0-9 and hyphens only",
validate: (x) => (x && x.match(/^[0-9a-z-]+$/) ? undefined : "a-z, 0-9 and hyphens only"),
})
if (prompts.isCancel(provider)) throw new UI.CancelledError()
provider = provider.replace(/^@ai-sdk\//, "")

View File

@@ -7,11 +7,7 @@ import { EOL } from "os"
export const LSPCommand = cmd({
command: "lsp",
builder: (yargs) =>
yargs
.command(DiagnosticsCommand)
.command(SymbolsCommand)
.command(DocumentSymbolsCommand)
.demandCommand(),
yargs.command(DiagnosticsCommand).command(SymbolsCommand).command(DocumentSymbolsCommand).demandCommand(),
async handler() {},
})

View File

@@ -6,8 +6,7 @@ import { cmd } from "../cmd"
export const RipgrepCommand = cmd({
command: "rg",
builder: (yargs) =>
yargs.command(TreeCommand).command(FilesCommand).command(SearchCommand).demandCommand(),
builder: (yargs) => yargs.command(TreeCommand).command(FilesCommand).command(SearchCommand).demandCommand(),
async handler() {},
})
@@ -19,9 +18,7 @@ const TreeCommand = cmd({
}),
async handler(args) {
await bootstrap(process.cwd(), async () => {
process.stdout.write(
(await Ripgrep.tree({ cwd: Instance.directory, limit: args.limit })) + EOL,
)
process.stdout.write((await Ripgrep.tree({ cwd: Instance.directory, limit: args.limit })) + EOL)
})
},
})

View File

@@ -4,8 +4,7 @@ import { cmd } from "../cmd"
export const SnapshotCommand = cmd({
command: "snapshot",
builder: (yargs) =>
yargs.command(TrackCommand).command(PatchCommand).command(DiffCommand).demandCommand(),
builder: (yargs) => yargs.command(TrackCommand).command(PatchCommand).command(DiffCommand).demandCommand(),
async handler() {},
})

View File

@@ -189,9 +189,7 @@ export const GithubInstallCommand = cmd({
async function getAppInfo() {
const project = Instance.project
if (project.vcs !== "git") {
prompts.log.error(
`Could not find git repository. Please run this command from a git repository.`,
)
prompts.log.error(`Could not find git repository. Please run this command from a git repository.`)
throw new UI.CancelledError()
}
@@ -204,13 +202,9 @@ export const GithubInstallCommand = cmd({
// ie. git@github.com:sst/opencode
// ie. ssh://git@github.com/sst/opencode.git
// ie. ssh://git@github.com/sst/opencode
const parsed = info.match(
/^(?:(?:https?|ssh):\/\/)?(?:git@)?github\.com[:/]([^/]+)\/([^/.]+?)(?:\.git)?$/,
)
const parsed = info.match(/^(?:(?:https?|ssh):\/\/)?(?:git@)?github\.com[:/]([^/]+)\/([^/.]+?)(?:\.git)?$/)
if (!parsed) {
prompts.log.error(
`Could not find git repository. Please run this command from a git repository.`,
)
prompts.log.error(`Could not find git repository. Please run this command from a git repository.`)
throw new UI.CancelledError()
}
const [, owner, repo] = parsed
@@ -451,9 +445,7 @@ export const GithubRunCommand = cmd({
const summary = await summarize(response)
await pushToLocalBranch(summary)
}
const hasShared = prData.comments.nodes.some((c) =>
c.body.includes(`${shareBaseUrl}/s/${shareId}`),
)
const hasShared = prData.comments.nodes.some((c) => c.body.includes(`${shareBaseUrl}/s/${shareId}`))
await updateComment(`${response}${footer({ image: !hasShared })}`)
}
// Fork PR
@@ -465,9 +457,7 @@ export const GithubRunCommand = cmd({
const summary = await summarize(response)
await pushToForkBranch(summary, prData)
}
const hasShared = prData.comments.nodes.some((c) =>
c.body.includes(`${shareBaseUrl}/s/${shareId}`),
)
const hasShared = prData.comments.nodes.some((c) => c.body.includes(`${shareBaseUrl}/s/${shareId}`))
await updateComment(`${response}${footer({ image: !hasShared })}`)
}
}
@@ -557,12 +547,8 @@ export const GithubRunCommand = cmd({
// ie. <img alt="Image" src="https://github.com/user-attachments/assets/xxxx" />
// ie. [api.json](https://github.com/user-attachments/files/21433810/api.json)
// ie. ![Image](https://github.com/user-attachments/assets/xxxx)
const mdMatches = prompt.matchAll(
/!?\[.*?\]\((https:\/\/github\.com\/user-attachments\/[^)]+)\)/gi,
)
const tagMatches = prompt.matchAll(
/<img .*?src="(https:\/\/github\.com\/user-attachments\/[^"]+)" \/>/gi,
)
const mdMatches = prompt.matchAll(/!?\[.*?\]\((https:\/\/github\.com\/user-attachments\/[^)]+)\)/gi)
const tagMatches = prompt.matchAll(/<img .*?src="(https:\/\/github\.com\/user-attachments\/[^"]+)" \/>/gi)
const matches = [...mdMatches, ...tagMatches].sort((a, b) => a.index - b.index)
console.log("Images", JSON.stringify(matches, null, 2))
@@ -587,10 +573,7 @@ export const GithubRunCommand = cmd({
// Replace img tag with file path, ie. @image.png
const replacement = `@${filename}`
prompt =
prompt.slice(0, start + offset) +
replacement +
prompt.slice(start + offset + tag.length)
prompt = prompt.slice(0, start + offset) + replacement + prompt.slice(start + offset + tag.length)
offset += replacement.length - tag.length
const contentType = res.headers.get("content-type")
@@ -873,8 +856,7 @@ Co-authored-by: ${actor} <${actor}@users.noreply.github.com>"`
throw new Error(`Failed to check permissions for user ${actor}: ${error}`)
}
if (!["admin", "write"].includes(permission))
throw new Error(`User ${actor} does not have write permissions`)
if (!["admin", "write"].includes(permission)) throw new Error(`User ${actor} does not have write permissions`)
}
async function createComment() {
@@ -922,9 +904,7 @@ Co-authored-by: ${actor} <${actor}@users.noreply.github.com>"`
return `<a href="${shareBaseUrl}/s/${shareId}"><img width="200" alt="${titleAlt}" src="https://social-cards.sst.dev/opencode-share/${title64}.png?model=${providerID}/${modelID}&version=${session.version}&id=${shareId}" /></a>\n`
})()
const shareUrl = shareId
? `[opencode session](${shareBaseUrl}/s/${shareId})&nbsp;&nbsp;|&nbsp;&nbsp;`
: ""
const shareUrl = shareId ? `[opencode session](${shareBaseUrl}/s/${shareId})&nbsp;&nbsp;|&nbsp;&nbsp;` : ""
return `\n\n${image}${shareUrl}[github run](${runUrl})`
}
@@ -1100,13 +1080,9 @@ query($owner: String!, $repo: String!, $number: Int!) {
})
.map((c) => `- ${c.author.login} at ${c.createdAt}: ${c.body}`)
const files = (pr.files.nodes || []).map(
(f) => `- ${f.path} (${f.changeType}) +${f.additions}/-${f.deletions}`,
)
const files = (pr.files.nodes || []).map((f) => `- ${f.path} (${f.changeType}) +${f.additions}/-${f.deletions}`)
const reviewData = (pr.reviews.nodes || []).map((r) => {
const comments = (r.comments.nodes || []).map(
(c) => ` - ${c.path}:${c.line ?? "?"}: ${c.body}`,
)
const comments = (r.comments.nodes || []).map((c) => ` - ${c.path}:${c.line ?? "?"}: ${c.body}`)
return [
`- ${r.author.login} at ${r.submittedAt}:`,
` - Review body: ${r.body}`,
@@ -1128,15 +1104,9 @@ query($owner: String!, $repo: String!, $number: Int!) {
`Deletions: ${pr.deletions}`,
`Total Commits: ${pr.commits.totalCount}`,
`Changed Files: ${pr.files.nodes.length} files`,
...(comments.length > 0
? ["<pull_request_comments>", ...comments, "</pull_request_comments>"]
: []),
...(files.length > 0
? ["<pull_request_changed_files>", ...files, "</pull_request_changed_files>"]
: []),
...(reviewData.length > 0
? ["<pull_request_reviews>", ...reviewData, "</pull_request_reviews>"]
: []),
...(comments.length > 0 ? ["<pull_request_comments>", ...comments, "</pull_request_comments>"] : []),
...(files.length > 0 ? ["<pull_request_changed_files>", ...files, "</pull_request_changed_files>"] : []),
...(reviewData.length > 0 ? ["<pull_request_reviews>", ...reviewData, "</pull_request_reviews>"] : []),
"</pull_request>",
].join("\n")
}

View File

@@ -138,9 +138,7 @@ export const RunCommand = cmd({
const outputJsonEvent = (type: string, data: any) => {
if (args.format === "json") {
process.stdout.write(
JSON.stringify({ type, timestamp: Date.now(), sessionID, ...data }) + EOL,
)
process.stdout.write(JSON.stringify({ type, timestamp: Date.now(), sessionID, ...data }) + EOL)
return true
}
return false
@@ -160,9 +158,7 @@ export const RunCommand = cmd({
const [tool, color] = TOOL[part.tool] ?? [part.tool, UI.Style.TEXT_INFO_BOLD]
const title =
part.state.title ||
(Object.keys(part.state.input).length > 0
? JSON.stringify(part.state.input)
: "Unknown")
(Object.keys(part.state.input).length > 0 ? JSON.stringify(part.state.input) : "Unknown")
printEvent(color, tool, title)
if (part.tool === "bash" && part.state.output?.trim()) {
UI.println()
@@ -215,10 +211,7 @@ export const RunCommand = cmd({
],
initialValue: "once",
}).catch(() => "reject")
const response = (result.toString().includes("cancel") ? "reject" : result) as
| "once"
| "always"
| "reject"
const response = (result.toString().includes("cancel") ? "reject" : result) as "once" | "always" | "reject"
await sdk.postSessionIdPermissionsPermissionId({
path: { id: sessionID, permissionID: permission.id },
body: { response },
@@ -280,10 +273,7 @@ export const RunCommand = cmd({
}
const cfgResult = await sdk.config.get()
if (
cfgResult.data &&
(cfgResult.data.share === "auto" || Flag.OPENCODE_AUTO_SHARE || args.share)
) {
if (cfgResult.data && (cfgResult.data.share === "auto" || Flag.OPENCODE_AUTO_SHARE || args.share)) {
const shareResult = await sdk.session.share({ path: { id: sessionID } }).catch((error) => {
if (error instanceof Error && error.message.includes("disabled")) {
UI.println(UI.Style.TEXT_DANGER_BOLD + "! " + error.message)
@@ -336,10 +326,7 @@ export const RunCommand = cmd({
}
const cfgResult = await sdk.config.get()
if (
cfgResult.data &&
(cfgResult.data.share === "auto" || Flag.OPENCODE_AUTO_SHARE || args.share)
) {
if (cfgResult.data && (cfgResult.data.share === "auto" || Flag.OPENCODE_AUTO_SHARE || args.share)) {
const shareResult = await sdk.session.share({ path: { id: sessionID } }).catch((error) => {
if (error instanceof Error && error.message.includes("disabled")) {
UI.println(UI.Style.TEXT_DANGER_BOLD + "! " + error.message)

View File

@@ -68,9 +68,7 @@ async function getAllSessions(): Promise<Session.Info[]> {
if (!project) continue
const sessionKeys = await Storage.list(["session", project.id])
const projectSessions = await Promise.all(
sessionKeys.map((key) => Storage.read<Session.Info>(key)),
)
const projectSessions = await Promise.all(sessionKeys.map((key) => Storage.read<Session.Info>(key)))
for (const session of projectSessions) {
if (session) {
@@ -87,16 +85,12 @@ async function aggregateSessionStats(days?: number, projectFilter?: string): Pro
const DAYS_IN_SECOND = 24 * 60 * 60 * 1000
const cutoffTime = days ? Date.now() - days * DAYS_IN_SECOND : 0
let filteredSessions = days
? sessions.filter((session) => session.time.updated >= cutoffTime)
: sessions
let filteredSessions = days ? sessions.filter((session) => session.time.updated >= cutoffTime) : sessions
if (projectFilter !== undefined) {
if (projectFilter === "") {
const currentProject = await getCurrentProject()
filteredSessions = filteredSessions.filter(
(session) => session.projectID === currentProject.id,
)
filteredSessions = filteredSessions.filter((session) => session.projectID === currentProject.id)
} else {
filteredSessions = filteredSessions.filter((session) => session.projectID === projectFilter)
}
@@ -125,9 +119,7 @@ async function aggregateSessionStats(days?: number, projectFilter?: string): Pro
}
if (filteredSessions.length > 1000) {
console.log(
`Large dataset detected (${filteredSessions.length} sessions). This may take a while...`,
)
console.log(`Large dataset detected (${filteredSessions.length} sessions). This may take a while...`)
}
if (filteredSessions.length === 0) {
@@ -262,8 +254,7 @@ export function displayStats(stats: SessionStats, toolLimit?: number) {
const percentage = ((count / totalToolUsage) * 100).toFixed(1)
const maxToolLength = 18
const truncatedTool =
tool.length > maxToolLength ? tool.substring(0, maxToolLength - 2) + ".." : tool
const truncatedTool = tool.length > maxToolLength ? tool.substring(0, maxToolLength - 2) + ".." : tool
const toolName = truncatedTool.padEnd(maxToolLength)
const content = ` ${toolName} ${bar.padEnd(20)} ${count.toString().padStart(3)} (${percentage.padStart(4)}%)`

View File

@@ -115,11 +115,7 @@ export function tui(input: {
render(
() => {
return (
<ErrorBoundary
fallback={(error, reset) => (
<ErrorComponent error={error} reset={reset} onExit={onExit} />
)}
>
<ErrorBoundary fallback={(error, reset) => <ErrorComponent error={error} reset={reset} onExit={onExit} />}>
<ExitProvider onExit={onExit}>
<KVProvider>
<ToastProvider>
@@ -413,12 +409,7 @@ function App() {
flexShrink={0}
>
<box flexDirection="row">
<box
flexDirection="row"
backgroundColor={theme.backgroundElement}
paddingLeft={1}
paddingRight={1}
>
<box flexDirection="row" backgroundColor={theme.backgroundElement} paddingLeft={1} paddingRight={1}>
<text fg={theme.textMuted}>open</text>
<text fg={theme.text} attributes={TextAttributes.BOLD}>
code{" "}
@@ -434,11 +425,7 @@ function App() {
tab
</text>
<text fg={local.agent.color(local.agent.current().name)}>{""}</text>
<text
bg={local.agent.color(local.agent.current().name)}
fg={theme.background}
wrapMode={undefined}
>
<text bg={local.agent.color(local.agent.current().name)} fg={theme.background} wrapMode={undefined}>
<span style={{ bold: true }}> {local.agent.current().name.toUpperCase()}</span>
<span> AGENT </span>
</text>

View File

@@ -52,11 +52,7 @@ export function DialogModel() {
description: provider.name,
category: provider.name,
})),
filter(
(x) =>
Boolean(ref()?.filter) ||
!local.model.recent().find((y) => isDeepEqual(y, x.value)),
),
filter((x) => Boolean(ref()?.filter) || !local.model.recent().find((y) => isDeepEqual(y, x.value))),
),
),
),

View File

@@ -20,9 +20,7 @@ export function DialogSessionList() {
const deleteKeybind = "ctrl+d"
const currentSessionID = createMemo(() =>
route.data.type === "session" ? route.data.sessionID : undefined,
)
const currentSessionID = createMemo(() => (route.data.type === "session" ? route.data.sessionID : undefined))
const options = createMemo(() => {
const today = new Date().toDateString()

View File

@@ -77,10 +77,7 @@ export function DialogStatus() {
</For>
</box>
)}
<Show
when={enabledFormatters().length > 0}
fallback={<text fg={theme.text}>No Formatters</text>}
>
<Show when={enabledFormatters().length > 0} fallback={<text fg={theme.text}>No Formatters</text>}>
<box>
<text fg={theme.text}>{enabledFormatters().length} Formatters</text>
<For each={enabledFormatters()}>

View File

@@ -3,19 +3,9 @@ import { TextAttributes } from "@opentui/core"
import { For } from "solid-js"
import { useTheme } from "@tui/context/theme"
const LOGO_LEFT = [
` `,
`█▀▀█ █▀▀█ █▀▀█ █▀▀▄`,
`█░░█ █░░█ █▀▀▀ █░░█`,
`▀▀▀▀ █▀▀▀ ▀▀▀▀ ▀ ▀`,
]
const LOGO_LEFT = [` `, `█▀▀█ █▀▀█ █▀▀█ █▀▀▄`, `█░░█ █░░█ █▀▀▀ █░░█`, `▀▀▀▀ █▀▀▀ ▀▀▀▀ ▀ ▀`]
const LOGO_RIGHT = [
``,
`█▀▀▀ █▀▀█ █▀▀█ █▀▀█`,
`█░░░ █░░█ █░░█ █▀▀▀`,
`▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀`,
]
const LOGO_RIGHT = [``, `█▀▀▀ █▀▀█ █▀▀█ █▀▀█`, `█░░░ █░░█ █░░█ █▀▀▀`, `▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀`]
export function Logo() {
const { theme } = useTheme()

View File

@@ -83,12 +83,7 @@ export function Autocomplete(props: {
const extmarkStart = store.index
const extmarkEnd = extmarkStart + Bun.stringWidth(virtualText)
const styleId =
part.type === "file"
? props.fileStyleId
: part.type === "agent"
? props.agentStyleId
: undefined
const styleId = part.type === "file" ? props.fileStyleId : part.type === "agent" ? props.agentStyleId : undefined
const extmarkId = input.extmarks.create({
start: extmarkStart,
@@ -185,9 +180,7 @@ export function Autocomplete(props: {
)
})
const session = createMemo(() =>
props.sessionID ? sync.session.get(props.sessionID) : undefined,
)
const session = createMemo(() => (props.sessionID ? sync.session.get(props.sessionID) : undefined))
const commands = createMemo((): AutocompleteOption[] => {
const results: AutocompleteOption[] = []
const s = session()
@@ -324,9 +317,7 @@ export function Autocomplete(props: {
const options = createMemo(() => {
const mixed: AutocompleteOption[] = (
store.visible === "@"
? [...agents(), ...(files.loading ? files.latest || [] : files())]
: [...commands()]
store.visible === "@" ? [...agents(), ...(files.loading ? files.latest || [] : files())] : [...commands()]
).filter((x) => x.disabled !== true)
const currentFilter = filter()
if (!currentFilter) return mixed.slice(0, 10)
@@ -393,9 +384,7 @@ export function Autocomplete(props: {
return
}
// Check if a space was typed after the trigger character
const currentText = props
.input()
.getTextRange(store.index + 1, props.input().cursorOffset + 1)
const currentText = props.input().getTextRange(store.index + 1, props.input().cursorOffset + 1)
if (currentText.includes(" ")) {
hide()
}
@@ -433,13 +422,8 @@ export function Autocomplete(props: {
if (e.name === "@") {
const cursorOffset = props.input().cursorOffset
const charBeforeCursor =
cursorOffset === 0
? undefined
: props.input().getTextRange(cursorOffset - 1, cursorOffset)
const canTrigger =
charBeforeCursor === undefined ||
charBeforeCursor === "" ||
/\s/.test(charBeforeCursor)
cursorOffset === 0 ? undefined : props.input().getTextRange(cursorOffset - 1, cursorOffset)
const canTrigger = charBeforeCursor === undefined || charBeforeCursor === "" || /\s/.test(charBeforeCursor)
if (canTrigger) show("@")
}
@@ -487,10 +471,7 @@ export function Autocomplete(props: {
{option.display}
</text>
<Show when={option.description}>
<text
fg={index() === store.selected ? theme.background : theme.textMuted}
wrapMode="none"
>
<text fg={index() === store.selected ? theme.background : theme.textMuted} wrapMode="none">
{option.description}
</text>
</Show>

View File

@@ -334,9 +334,7 @@ export function Prompt(props: PromptProps) {
// Expand pasted text inline before submitting
const allExtmarks = input.extmarks.getAllForTypeId(promptPartTypeId)
const sortedExtmarks = allExtmarks.sort(
(a: { start: number }, b: { start: number }) => b.start - a.start,
)
const sortedExtmarks = allExtmarks.sort((a: { start: number }, b: { start: number }) => b.start - a.start)
for (const extmark of sortedExtmarks) {
const partIndex = store.extmarkToPartIndex.get(extmark.id)
@@ -499,28 +497,15 @@ export function Prompt(props: PromptProps) {
<box
flexDirection="row"
{...SplitBorder}
borderColor={
keybind.leader ? theme.accent : store.mode === "shell" ? theme.secondary : theme.border
}
borderColor={keybind.leader ? theme.accent : store.mode === "shell" ? theme.secondary : theme.border}
justifyContent="space-evenly"
>
<box
backgroundColor={theme.backgroundElement}
width={3}
height="100%"
alignItems="center"
paddingTop={1}
>
<box backgroundColor={theme.backgroundElement} width={3} height="100%" alignItems="center" paddingTop={1}>
<text attributes={TextAttributes.BOLD} fg={theme.primary}>
{store.mode === "normal" ? ">" : "!"}
</text>
</box>
<box
paddingTop={1}
paddingBottom={1}
backgroundColor={theme.backgroundElement}
flexGrow={1}
>
<box paddingTop={1} paddingBottom={1} backgroundColor={theme.backgroundElement} flexGrow={1}>
<textarea
placeholder={
props.showPlaceholder
@@ -575,10 +560,7 @@ export function Prompt(props: PromptProps) {
return
}
if (store.mode === "shell") {
if (
(e.name === "backspace" && input.visualCursor.offset === 0) ||
e.name === "escape"
) {
if ((e.name === "backspace" && input.visualCursor.offset === 0) || e.name === "escape") {
setStore("mode", "normal")
e.preventDefault()
return
@@ -588,8 +570,7 @@ export function Prompt(props: PromptProps) {
if (!autocomplete.visible) {
if (
(keybind.match("history_previous", e) && input.cursorOffset === 0) ||
(keybind.match("history_next", e) &&
input.cursorOffset === input.plainText.length)
(keybind.match("history_next", e) && input.cursorOffset === input.plainText.length)
) {
const direction = keybind.match("history_previous", e) ? -1 : 1
const item = history.move(direction, input.plainText)
@@ -605,12 +586,8 @@ export function Prompt(props: PromptProps) {
return
}
if (keybind.match("history_previous", e) && input.visualCursor.visualRow === 0)
input.cursorOffset = 0
if (
keybind.match("history_next", e) &&
input.visualCursor.visualRow === input.height - 1
)
if (keybind.match("history_previous", e) && input.visualCursor.visualRow === 0) input.cursorOffset = 0
if (keybind.match("history_next", e) && input.visualCursor.visualRow === input.height - 1)
input.cursorOffset = input.plainText.length
}
}}
@@ -701,12 +678,7 @@ export function Prompt(props: PromptProps) {
syntaxStyle={syntax()}
/>
</box>
<box
backgroundColor={theme.backgroundElement}
width={1}
justifyContent="center"
alignItems="center"
></box>
<box backgroundColor={theme.backgroundElement} width={1} justifyContent="center" alignItems="center"></box>
</box>
<box flexDirection="row" justifyContent="space-between">
<text flexShrink={0} wrapMode="none" fg={theme.text}>
@@ -727,8 +699,7 @@ export function Prompt(props: PromptProps) {
<Match when={props.hint}>{props.hint!}</Match>
<Match when={true}>
<text fg={theme.text}>
{keybind.print("command_list")}{" "}
<span style={{ fg: theme.textMuted }}>commands</span>
{keybind.print("command_list")} <span style={{ fg: theme.textMuted }}>commands</span>
</text>
</Match>
</Switch>

View File

@@ -22,9 +22,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
return !!provider?.models[model.modelID]
}
function getFirstValidModel(
...modelFns: (() => { providerID: string; modelID: string } | undefined)[]
) {
function getFirstValidModel(...modelFns: (() => { providerID: string; modelID: string } | undefined)[]) {
for (const modelFn of modelFns) {
const model = modelFn()
if (!model) continue
@@ -213,9 +211,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
const current = currentModel()
if (!current) return
const recent = modelStore.recent
const index = recent.findIndex(
(x) => x.providerID === current.providerID && x.modelID === current.modelID,
)
const index = recent.findIndex((x) => x.providerID === current.providerID && x.modelID === current.modelID)
if (index === -1) return
let next = index + direction
if (next < 0) next = recent.length - 1

View File

@@ -146,12 +146,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
}
const result = Binary.search(messages, event.properties.info.id, (m) => m.id)
if (result.found) {
setStore(
"message",
event.properties.info.sessionID,
result.index,
reconcile(event.properties.info),
)
setStore("message", event.properties.info.sessionID, result.index, reconcile(event.properties.info))
break
}
setStore(
@@ -186,12 +181,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
}
const result = Binary.search(parts, event.properties.part.id, (p) => p.id)
if (result.found) {
setStore(
"part",
event.properties.part.messageID,
result.index,
reconcile(event.properties.part),
)
setStore("part", event.properties.part.messageID, result.index, reconcile(event.properties.part))
break
}
setStore(

View File

@@ -528,13 +528,7 @@ function generateSyntax(theme: Theme) {
},
},
{
scope: [
"variable.builtin",
"type.builtin",
"function.builtin",
"module.builtin",
"constant.builtin",
],
scope: ["variable.builtin", "type.builtin", "function.builtin", "module.builtin", "constant.builtin"],
style: {
foreground: theme.error,
},

View File

@@ -26,11 +26,7 @@ export function Home() {
</Match>
<Match when={true}>
<span style={{ fg: theme.success }}></span>{" "}
{Locale.pluralize(
Object.values(sync.data.mcp).length,
"{} mcp server",
"{} mcp servers",
)}
{Locale.pluralize(Object.values(sync.data.mcp).length, "{} mcp server", "{} mcp servers")}
</Match>
</Switch>
</text>
@@ -39,14 +35,7 @@ export function Home() {
)
return (
<box
flexGrow={1}
justifyContent="center"
alignItems="center"
paddingLeft={2}
paddingRight={2}
gap={1}
>
<box flexGrow={1} justifyContent="center" alignItems="center" paddingLeft={2} paddingRight={2} gap={1}>
<Logo />
<box width={39}>
<HelpRow keybind="command_list">Commands</HelpRow>

View File

@@ -7,9 +7,7 @@ import { useRoute } from "@tui/context/route"
export function DialogMessage(props: { messageID: string; sessionID: string }) {
const sync = useSync()
const sdk = useSDK()
const message = createMemo(() =>
sync.data.message[props.sessionID]?.find((x) => x.id === props.messageID),
)
const message = createMemo(() => sync.data.message[props.sessionID]?.find((x) => x.id === props.messageID))
const route = useRoute()
return (

View File

@@ -19,9 +19,7 @@ export function DialogTimeline(props: { sessionID: string; onMove: (messageID: s
const result = [] as DialogSelectOption<string>[]
for (const message of messages) {
if (message.role !== "user") continue
const part = (sync.data.part[message.id] ?? []).find(
(x) => x.type === "text" && !x.synthetic,
) as TextPart
const part = (sync.data.part[message.id] ?? []).find((x) => x.type === "text" && !x.synthetic) as TextPart
if (!part) continue
result.push({
title: part.text.replace(/\n/g, " "),
@@ -35,11 +33,5 @@ export function DialogTimeline(props: { sessionID: string; onMove: (messageID: s
return result
})
return (
<DialogSelect
onMove={(option) => props.onMove(option.value)}
title="Timeline"
options={options()}
/>
)
return <DialogSelect onMove={(option) => props.onMove(option.value)} title="Timeline" options={options()} />
}

View File

@@ -46,16 +46,10 @@ export function Header() {
})
const context = createMemo(() => {
const last = messages().findLast(
(x) => x.role === "assistant" && x.tokens.output > 0,
) as AssistantMessage
const last = messages().findLast((x) => x.role === "assistant" && x.tokens.output > 0) as AssistantMessage
if (!last) return
const total =
last.tokens.input +
last.tokens.output +
last.tokens.reasoning +
last.tokens.cache.read +
last.tokens.cache.write
last.tokens.input + last.tokens.output + last.tokens.reasoning + last.tokens.cache.read + last.tokens.cache.write
const model = sync.data.provider.find((x) => x.id === last.providerID)?.models[last.modelID]
let result = total.toLocaleString()
if (model?.limit.context) {
@@ -67,13 +61,7 @@ export function Header() {
const { theme } = useTheme()
return (
<box
paddingLeft={1}
paddingRight={1}
{...SplitBorder}
borderColor={theme.backgroundElement}
flexShrink={0}
>
<box paddingLeft={1} paddingRight={1} {...SplitBorder} borderColor={theme.backgroundElement} flexShrink={0}>
<Show
when={shareEnabled()}
fallback={

View File

@@ -19,14 +19,7 @@ import { SplitBorder } from "@tui/component/border"
import { useTheme } from "@tui/context/theme"
import { BoxRenderable, ScrollBoxRenderable, addDefaultParsers } from "@opentui/core"
import { Prompt, type PromptRef } from "@tui/component/prompt"
import type {
AssistantMessage,
Part,
ToolPart,
UserMessage,
TextPart,
ReasoningPart,
} from "@opencode-ai/sdk"
import type { AssistantMessage, Part, ToolPart, UserMessage, TextPart, ReasoningPart } from "@opencode-ai/sdk"
import { useLocal } from "@tui/context/local"
import { Locale } from "@/util/locale"
import type { Tool } from "@/tool/tool"
@@ -41,13 +34,7 @@ import type { EditTool } from "@/tool/edit"
import type { PatchTool } from "@/tool/patch"
import type { WebFetchTool } from "@/tool/webfetch"
import type { TaskTool } from "@/tool/task"
import {
useKeyboard,
useRenderer,
useTerminalDimensions,
type BoxProps,
type JSX,
} from "@opentui/solid"
import { useKeyboard, useRenderer, useTerminalDimensions, type BoxProps, type JSX } from "@opentui/solid"
import { useSDK } from "@tui/context/sdk"
import { useCommandDialog } from "@tui/component/dialog-command"
import { Shimmer } from "@tui/ui/shimmer"
@@ -653,14 +640,7 @@ export function Session() {
conceal,
}}
>
<box
flexDirection="row"
paddingBottom={1}
paddingTop={1}
paddingLeft={2}
paddingRight={2}
gap={2}
>
<box flexDirection="row" paddingBottom={1} paddingTop={1} paddingLeft={2} paddingRight={2} gap={2}>
<box flexGrow={1} gap={1}>
<Show when={session()}>
<Show when={session().parentID}>
@@ -675,19 +655,13 @@ export function Session() {
paddingRight={2}
>
<text fg={theme.text}>
Previous{" "}
<span style={{ fg: theme.textMuted }}>
{keybind.print("session_child_cycle_reverse")}
</span>
Previous <span style={{ fg: theme.textMuted }}>{keybind.print("session_child_cycle_reverse")}</span>
</text>
<text fg={theme.text}>
<b>Viewing subagent session</b>
</text>
<text fg={theme.text}>
<span style={{ fg: theme.textMuted }}>
{keybind.print("session_child_cycle")}
</span>{" "}
Next
<span style={{ fg: theme.textMuted }}>{keybind.print("session_child_cycle")}</span> Next
</text>
</box>
</Show>
@@ -743,18 +717,12 @@ export function Session() {
paddingTop={1}
paddingBottom={1}
paddingLeft={2}
backgroundColor={
hover() ? theme.backgroundElement : theme.backgroundPanel
}
backgroundColor={hover() ? theme.backgroundElement : theme.backgroundPanel}
>
<text fg={theme.textMuted}>{revert()!.reverted.length} message reverted</text>
<text fg={theme.textMuted}>
{revert()!.reverted.length} message reverted
</text>
<text fg={theme.textMuted}>
<span style={{ fg: theme.text }}>
{keybind.print("messages_redo")}
</span>{" "}
or /redo to restore
<span style={{ fg: theme.text }}>{keybind.print("messages_redo")}</span> or /redo to
restore
</text>
<Show when={revert()!.diffFiles?.length}>
<box marginTop={1}>
@@ -763,16 +731,10 @@ export function Session() {
<text>
{file.filename}
<Show when={file.additions > 0}>
<span style={{ fg: theme.diffAdded }}>
{" "}
+{file.additions}
</span>
<span style={{ fg: theme.diffAdded }}> +{file.additions}</span>
</Show>
<Show when={file.deletions > 0}>
<span style={{ fg: theme.diffRemoved }}>
{" "}
-{file.deletions}
</span>
<span style={{ fg: theme.diffRemoved }}> -{file.deletions}</span>
</Show>
</text>
)}
@@ -792,9 +754,7 @@ export function Session() {
index={index()}
onMouseUp={() => {
if (renderer.getSelection()?.getSelectedText()) return
dialog.replace(() => (
<DialogMessage messageID={message.id} sessionID={route.sessionID} />
))
dialog.replace(() => <DialogMessage messageID={message.id} sessionID={route.sessionID} />)
}}
message={message as UserMessage}
parts={sync.data.part[message.id] ?? []}
@@ -850,9 +810,7 @@ function UserMessage(props: {
index: number
pending?: string
}) {
const text = createMemo(
() => props.parts.flatMap((x) => (x.type === "text" && !x.synthetic ? [x] : []))[0],
)
const text = createMemo(() => props.parts.flatMap((x) => (x.type === "text" && !x.synthetic ? [x] : []))[0])
const files = createMemo(() => props.parts.flatMap((x) => (x.type === "file" ? [x] : [])))
const sync = useSync()
const { theme } = useTheme()
@@ -893,14 +851,8 @@ function UserMessage(props: {
})
return (
<text fg={theme.text}>
<span style={{ bg: bg(), fg: theme.background }}>
{" "}
{MIME_BADGE[file.mime] ?? file.mime}{" "}
</span>
<span style={{ bg: theme.backgroundElement, fg: theme.textMuted }}>
{" "}
{file.filename}{" "}
</span>
<span style={{ bg: bg(), fg: theme.background }}> {MIME_BADGE[file.mime] ?? file.mime} </span>
<span style={{ bg: theme.backgroundElement, fg: theme.textMuted }}> {file.filename} </span>
</text>
)
}}
@@ -911,16 +863,9 @@ function UserMessage(props: {
{sync.data.config.username ?? "You"}{" "}
<Show
when={queued()}
fallback={
<span style={{ fg: theme.textMuted }}>
({Locale.time(props.message.time.created)})
</span>
}
fallback={<span style={{ fg: theme.textMuted }}>({Locale.time(props.message.time.created)})</span>}
>
<span style={{ bg: theme.accent, fg: theme.backgroundPanel, bold: true }}>
{" "}
QUEUED{" "}
</span>
<span style={{ bg: theme.accent, fg: theme.backgroundPanel, bold: true }}> QUEUED </span>
</Show>
</text>
</box>
@@ -960,8 +905,7 @@ function AssistantMessage(props: { message: AssistantMessage; parts: Part[]; las
<Show
when={
!props.message.time.completed ||
(props.last &&
props.parts.some((item) => item.type === "step-finish" && item.reason === "tool-calls"))
(props.last && props.parts.some((item) => item.type === "step-finish" && item.reason === "tool-calls"))
}
>
<box
@@ -973,9 +917,7 @@ function AssistantMessage(props: { message: AssistantMessage; parts: Part[]; las
customBorderChars={SplitBorder.customBorderChars}
borderColor={theme.backgroundElement}
>
<text fg={local.agent.color(props.message.mode)}>
{Locale.titlecase(props.message.mode)}
</text>
<text fg={local.agent.color(props.message.mode)}>{Locale.titlecase(props.message.mode)}</text>
<Shimmer text={`${props.message.modelID}`} color={theme.text} />
</box>
</Show>
@@ -987,9 +929,7 @@ function AssistantMessage(props: { message: AssistantMessage; parts: Part[]; las
>
<box paddingLeft={3}>
<text marginTop={1}>
<span style={{ fg: local.agent.color(props.message.mode) }}>
{Locale.titlecase(props.message.mode)}
</span>{" "}
<span style={{ fg: local.agent.color(props.message.mode) }}>{Locale.titlecase(props.message.mode)}</span>{" "}
<span style={{ fg: theme.textMuted }}>{props.message.modelID}</span>
</text>
</box>
@@ -1016,12 +956,7 @@ function ReasoningPart(props: { part: ReasoningPart; message: AssistantMessage }
customBorderChars={SplitBorder.customBorderChars}
borderColor={theme.backgroundPanel}
>
<box
paddingTop={1}
paddingBottom={1}
paddingLeft={2}
backgroundColor={theme.backgroundPanel}
>
<box paddingTop={1} paddingBottom={1} paddingLeft={2} backgroundColor={theme.backgroundPanel}>
<text fg={theme.text}>{props.part.text.trim()}</text>
</box>
</box>
@@ -1261,16 +1196,10 @@ ToolRegistry.register<typeof WriteTool>({
</ToolTitle>
<box flexDirection="row">
<box flexShrink={0}>
<For each={numbers()}>
{(value) => <text style={{ fg: theme.textMuted }}>{value}</text>}
</For>
<For each={numbers()}>{(value) => <text style={{ fg: theme.textMuted }}>{value}</text>}</For>
</box>
<box paddingLeft={1} flexGrow={1}>
<code
filetype={filetype(props.input.filePath!)}
syntaxStyle={syntax()}
content={code()}
/>
<code filetype={filetype(props.input.filePath!)} syntaxStyle={syntax()} content={code()} />
</box>
</box>
</>
@@ -1285,8 +1214,7 @@ ToolRegistry.register<typeof GlobTool>({
return (
<>
<ToolTitle icon="✱" fallback="Finding files..." when={props.input.pattern}>
Glob "{props.input.pattern}"{" "}
<Show when={props.input.path}>in {normalizePath(props.input.path)} </Show>
Glob "{props.input.pattern}" <Show when={props.input.path}>in {normalizePath(props.input.path)} </Show>
<Show when={props.metadata.count}>({props.metadata.count} matches)</Show>
</ToolTitle>
</>
@@ -1300,8 +1228,7 @@ ToolRegistry.register<typeof GrepTool>({
render(props) {
return (
<ToolTitle icon="✱" fallback="Searching content..." when={props.input.pattern}>
Grep "{props.input.pattern}"{" "}
<Show when={props.input.path}>in {normalizePath(props.input.path)} </Show>
Grep "{props.input.pattern}" <Show when={props.input.path}>in {normalizePath(props.input.path)} </Show>
<Show when={props.metadata.matches}>({props.metadata.matches} matches)</Show>
</ToolTitle>
)
@@ -1337,11 +1264,7 @@ ToolRegistry.register<typeof TaskTool>({
return (
<>
<ToolTitle
icon="%"
fallback="Delegating..."
when={props.input.subagent_type ?? props.input.description}
>
<ToolTitle icon="%" fallback="Delegating..." when={props.input.subagent_type ?? props.input.description}>
Task [{props.input.subagent_type ?? "unknown"}] {props.input.description}
</ToolTitle>
<Show when={props.metadata.summary?.length}>

View File

@@ -22,16 +22,10 @@ export function Sidebar(props: { sessionID: string }) {
})
const context = createMemo(() => {
const last = messages().findLast(
(x) => x.role === "assistant" && x.tokens.output > 0,
) as AssistantMessage
const last = messages().findLast((x) => x.role === "assistant" && x.tokens.output > 0) as AssistantMessage
if (!last) return
const total =
last.tokens.input +
last.tokens.output +
last.tokens.reasoning +
last.tokens.cache.read +
last.tokens.cache.write
last.tokens.input + last.tokens.output + last.tokens.reasoning + last.tokens.cache.read + last.tokens.cache.write
const model = sync.data.provider.find((x) => x.id === last.providerID)?.models[last.modelID]
return {
tokens: total.toLocaleString(),
@@ -84,9 +78,7 @@ export function Sidebar(props: { sessionID: string }) {
<span style={{ fg: theme.textMuted }}>
<Switch>
<Match when={item.status === "connected"}>Connected</Match>
<Match when={item.status === "failed" && item}>
{(val) => <i>{val().error}</i>}
</Match>
<Match when={item.status === "failed" && item}>{(val) => <i>{val().error}</i>}</Match>
<Match when={item.status === "disabled"}>Disabled in configuration</Match>
</Switch>
</span>
@@ -162,9 +154,7 @@ export function Sidebar(props: { sessionID: string }) {
</text>
<For each={todo()}>
{(todo) => (
<text
style={{ fg: todo.status === "in_progress" ? theme.success : theme.textMuted }}
>
<text style={{ fg: todo.status === "in_progress" ? theme.success : theme.textMuted }}>
[{todo.status === "completed" ? "✓" : " "}] {todo.content}
</text>
)}

View File

@@ -41,12 +41,7 @@ export const TuiSpawnCommand = cmd({
)
cwd = new URL("../../../../", import.meta.url).pathname
} else cmd.push(process.execPath)
cmd.push(
"attach",
server.url.toString(),
"--dir",
args.project ? path.resolve(args.project) : process.cwd(),
)
cmd.push("attach", server.url.toString(), "--dir", args.project ? path.resolve(args.project) : process.cwd())
const proc = Bun.spawn({
cmd,
cwd,

View File

@@ -99,9 +99,7 @@ export const TuiThreadCommand = cmd({
const worker = new Worker(workerPath, {
env: Object.fromEntries(
Object.entries(process.env).filter(
(entry): entry is [string, string] => entry[1] !== undefined,
),
Object.entries(process.env).filter((entry): entry is [string, string] => entry[1] !== undefined),
),
})
worker.onerror = console.error

View File

@@ -53,9 +53,7 @@ export function DialogConfirm(props: DialogConfirmProps) {
dialog.clear()
}}
>
<text fg={key === store.active ? theme.background : theme.textMuted}>
{Locale.titlecase(key)}
</text>
<text fg={key === store.active ? theme.background : theme.textMuted}>{Locale.titlecase(key)}</text>
</box>
)}
</For>

View File

@@ -20,17 +20,10 @@ export function DialogHelp() {
<text fg={theme.textMuted}>esc/enter</text>
</box>
<box paddingBottom={1}>
<text fg={theme.textMuted}>
Press Ctrl+P to see all available actions and commands in any context.
</text>
<text fg={theme.textMuted}>Press Ctrl+P to see all available actions and commands in any context.</text>
</box>
<box flexDirection="row" justifyContent="flex-end" paddingBottom={1}>
<box
paddingLeft={3}
paddingRight={3}
backgroundColor={theme.primary}
onMouseUp={() => dialog.clear()}
>
<box paddingLeft={3} paddingRight={3} backgroundColor={theme.primary} onMouseUp={() => dialog.clear()}>
<text fg={theme.background}>ok</text>
</box>
</box>

View File

@@ -57,8 +57,7 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
const result = pipe(
props.options,
filter((x) => x.disabled !== true),
(x) =>
!needle ? x : fuzzysort.go(needle, x, { keys: ["title", "category"] }).map((x) => x.obj),
(x) => (!needle ? x : fuzzysort.go(needle, x, { keys: ["title", "category"] }).map((x) => x.obj)),
)
return result
})
@@ -214,15 +213,11 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
props.onSelect?.(option)
}}
onMouseOver={() => {
const index = filtered().findIndex((x) =>
isDeepEqual(x.value, option.value),
)
const index = filtered().findIndex((x) => isDeepEqual(x.value, option.value))
if (index === -1) return
moveTo(index)
}}
backgroundColor={
active() ? (option.bg ?? theme.primary) : RGBA.fromInts(0, 0, 0, 0)
}
backgroundColor={active() ? (option.bg ?? theme.primary) : RGBA.fromInts(0, 0, 0, 0)}
paddingLeft={1}
paddingRight={1}
gap={1}
@@ -230,9 +225,7 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
<Option
title={option.title}
footer={option.footer}
description={
option.description !== category ? option.description : undefined
}
description={option.description !== category ? option.description : undefined}
active={active()}
current={isDeepEqual(option.value, props.current)}
/>
@@ -248,9 +241,7 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
<For each={props.keybind ?? []}>
{(item) => (
<text>
<span style={{ fg: theme.text, attributes: TextAttributes.BOLD }}>
{Keybind.toString(item.keybind)}
</span>
<span style={{ fg: theme.text, attributes: TextAttributes.BOLD }}>{Keybind.toString(item.keybind)}</span>
<span style={{ fg: theme.textMuted }}> {item.title}</span>
</text>
)}
@@ -284,10 +275,7 @@ function Option(props: {
wrapMode="none"
>
{Locale.truncate(props.title, 62)}
<span style={{ fg: props.active ? theme.background : theme.textMuted }}>
{" "}
{props.description}
</span>
<span style={{ fg: props.active ? theme.background : theme.textMuted }}> {props.description}</span>
</text>
<Show when={props.footer}>
<box flexShrink={0}>

View File

@@ -5,10 +5,7 @@ import { join } from "node:path"
import { CliRenderer } from "@opentui/core"
export namespace Editor {
export async function open(opts: {
value: string
renderer: CliRenderer
}): Promise<string | undefined> {
export async function open(opts: { value: string; renderer: CliRenderer }): Promise<string | undefined> {
const editor = process.env["EDITOR"]
if (!editor) return

View File

@@ -27,9 +27,7 @@ export const UpgradeCommand = {
const detectedMethod = await Installation.method()
const method = (args.method as Installation.Method) ?? detectedMethod
if (method === "unknown") {
prompts.log.error(
`opencode is installed to ${process.execPath} and may be managed by a package manager`,
)
prompts.log.error(`opencode is installed to ${process.execPath} and may be managed by a package manager`)
const install = await prompts.select({
message: "Install anyways?",
options: [

View File

@@ -56,11 +56,7 @@ export const WebCommand = cmd({
if (hostname === "0.0.0.0") {
// Show localhost for local access
const localhostUrl = `http://localhost:${server.port}`
UI.println(
UI.Style.TEXT_INFO_BOLD + " Local access: ",
UI.Style.TEXT_NORMAL,
localhostUrl,
)
UI.println(UI.Style.TEXT_INFO_BOLD + " Local access: ", UI.Style.TEXT_NORMAL, localhostUrl)
// Show network IPs for remote access
const networkIPs = getNetworkIPs()