summaryrefslogtreecommitdiffhomepage
path: root/packages
diff options
context:
space:
mode:
authorAdam <[email protected]>2026-01-16 15:31:03 -0600
committerAdam <[email protected]>2026-01-20 07:33:44 -0600
commit924fc9ed803d4dfa89faed65579a5a85cd7666c0 (patch)
treeceaa1080079e4b876ff65c925bd83a8977096062 /packages
parentdf094a10ff1f1a95f66abc6bdccfa69080480afa (diff)
downloadopencode-924fc9ed803d4dfa89faed65579a5a85cd7666c0.tar.gz
opencode-924fc9ed803d4dfa89faed65579a5a85cd7666c0.zip
wip(app): settings
Diffstat (limited to 'packages')
-rw-r--r--packages/app/src/components/prompt-input.tsx1
-rw-r--r--packages/app/src/components/settings-permissions.tsx151
-rw-r--r--packages/app/src/context/global-sync.tsx219
-rw-r--r--packages/opencode/src/config/config.ts108
-rw-r--r--packages/opencode/src/file/watcher.ts23
-rw-r--r--packages/opencode/src/server/event.ts7
-rw-r--r--packages/opencode/src/server/server.ts5
-rw-r--r--packages/sdk/js/src/v2/gen/sdk.gen.ts39
-rw-r--r--packages/sdk/js/src/v2/gen/types.gen.ts41
9 files changed, 490 insertions, 104 deletions
diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx
index 56bbdc8cb..072ef0bdd 100644
--- a/packages/app/src/components/prompt-input.tsx
+++ b/packages/app/src/components/prompt-input.tsx
@@ -255,7 +255,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
createEffect(() => {
params.id
- editorRef.focus()
if (params.id) return
const interval = setInterval(() => {
setStore("placeholder", (prev) => (prev + 1) % PLACEHOLDERS.length)
diff --git a/packages/app/src/components/settings-permissions.tsx b/packages/app/src/components/settings-permissions.tsx
index 67c3bfb62..f5ee76650 100644
--- a/packages/app/src/components/settings-permissions.tsx
+++ b/packages/app/src/components/settings-permissions.tsx
@@ -1,12 +1,153 @@
-import { Component } from "solid-js"
+import { Select } from "@opencode-ai/ui/select"
+import { showToast } from "@opencode-ai/ui/toast"
+import { Component, For, createMemo, type JSX } from "solid-js"
+import { useGlobalSync } from "@/context/global-sync"
+
+type PermissionAction = "allow" | "ask" | "deny"
+
+type PermissionObject = Record<string, PermissionAction>
+type PermissionValue = PermissionAction | PermissionObject | string[] | undefined
+type PermissionMap = Record<string, PermissionValue>
+
+type PermissionItem = {
+ id: string
+ title: string
+ description: string
+}
+
+const ACTIONS: Array<{ value: PermissionAction; label: string }> = [
+ { value: "allow", label: "Allow" },
+ { value: "ask", label: "Ask" },
+ { value: "deny", label: "Deny" },
+]
+
+const ITEMS: PermissionItem[] = [
+ { id: "read", title: "Read", description: "Reading a file (matches the file path)" },
+ { id: "edit", title: "Edit", description: "Modify files, including edits, writes, patches, and multi-edits" },
+ { id: "glob", title: "Glob", description: "Match files using glob patterns" },
+ { id: "grep", title: "Grep", description: "Search file contents using regular expressions" },
+ { id: "list", title: "List", description: "List files within a directory" },
+ { id: "bash", title: "Bash", description: "Run shell commands" },
+ { id: "task", title: "Task", description: "Launch sub-agents" },
+ { id: "skill", title: "Skill", description: "Load a skill by name" },
+ { id: "lsp", title: "LSP", description: "Run language server queries" },
+ { id: "todoread", title: "Todo Read", description: "Read the todo list" },
+ { id: "todowrite", title: "Todo Write", description: "Update the todo list" },
+ { id: "webfetch", title: "Web Fetch", description: "Fetch content from a URL" },
+ { id: "websearch", title: "Web Search", description: "Search the web" },
+ { id: "codesearch", title: "Code Search", description: "Search code on the web" },
+ { id: "external_directory", title: "External Directory", description: "Access files outside the project directory" },
+ { id: "doom_loop", title: "Doom Loop", description: "Detect repeated tool calls with identical input" },
+]
+
+const VALID_ACTIONS = new Set<PermissionAction>(["allow", "ask", "deny"])
+
+function toMap(value: unknown): PermissionMap {
+ if (value && typeof value === "object" && !Array.isArray(value)) return value as PermissionMap
+
+ const action = getAction(value)
+ if (action) return { "*": action }
+
+ return {}
+}
+
+function getAction(value: unknown): PermissionAction | undefined {
+ if (typeof value === "string" && VALID_ACTIONS.has(value as PermissionAction)) return value as PermissionAction
+ return
+}
+
+function getRuleDefault(value: unknown): PermissionAction | undefined {
+ const action = getAction(value)
+ if (action) return action
+
+ if (!value || typeof value !== "object" || Array.isArray(value)) return
+
+ return getAction((value as Record<string, unknown>)["*"])
+}
export const SettingsPermissions: Component = () => {
+ const globalSync = useGlobalSync()
+
+ const permission = createMemo(() => {
+ return toMap(globalSync.data.config.permission)
+ })
+
+ const actionFor = (id: string): PermissionAction => {
+ const value = permission()[id]
+ const direct = getRuleDefault(value)
+ if (direct) return direct
+
+ const wildcard = getRuleDefault(permission()["*"])
+ if (wildcard) return wildcard
+
+ return "allow"
+ }
+
+ const setPermission = async (id: string, action: PermissionAction) => {
+ const before = globalSync.data.config.permission
+ const map = toMap(before)
+ const existing = map[id]
+
+ const nextValue =
+ existing && typeof existing === "object" && !Array.isArray(existing) ? { ...existing, "*": action } : action
+
+ globalSync.set("config", "permission", { ...map, [id]: nextValue })
+ globalSync.updateConfig({ permission: { [id]: nextValue } }).catch((err: unknown) => {
+ globalSync.set("config", "permission", before)
+ const message = err instanceof Error ? err.message : String(err)
+ showToast({ title: "Failed to update permissions", description: message })
+ })
+ }
+
+ return (
+ <div class="flex flex-col h-full overflow-y-auto no-scrollbar">
+ <div class="sticky top-0 z-10 bg-background-base border-b border-border-weak-base">
+ <div class="flex flex-col gap-1 p-8 max-w-[720px]">
+ <h2 class="text-16-medium text-text-strong">Permissions</h2>
+ <p class="text-14-regular text-text-weak">Control what tools the server can use by default.</p>
+ </div>
+ </div>
+
+ <div class="flex flex-col gap-6 p-8 pt-6 max-w-[720px]">
+ <div class="flex flex-col gap-2">
+ <h3 class="text-14-medium text-text-strong">Appearance</h3>
+ <div class="border border-border-weak-base rounded-lg overflow-hidden">
+ <For each={ITEMS}>
+ {(item) => (
+ <SettingsRow title={item.title} description={item.description}>
+ <Select
+ options={ACTIONS}
+ current={ACTIONS.find((o) => o.value === actionFor(item.id))}
+ value={(o) => o.value}
+ label={(o) => o.label}
+ onSelect={(option) => option && setPermission(item.id, option.value)}
+ variant="secondary"
+ size="small"
+ />
+ </SettingsRow>
+ )}
+ </For>
+ </div>
+ </div>
+ </div>
+ </div>
+ )
+}
+
+interface SettingsRowProps {
+ title: string
+ description: string
+ children: JSX.Element
+}
+
+const SettingsRow: Component<SettingsRowProps> = (props) => {
return (
- <div class="flex flex-col h-full overflow-y-auto">
- <div class="flex flex-col gap-6 p-6 max-w-[600px]">
- <h2 class="text-16-medium text-text-strong">Permissions</h2>
- <p class="text-14-regular text-text-weak">Permission settings will be configurable here.</p>
+ <div class="flex items-center justify-between gap-4 px-4 py-3 border-b border-border-weak-base last:border-none">
+ <div class="flex flex-col gap-0.5">
+ <span class="text-14-medium text-text-strong">{props.title}</span>
+ <span class="text-12-regular text-text-weak">{props.description}</span>
</div>
+ <div class="flex-shrink-0">{props.children}</div>
</div>
)
}
diff --git a/packages/app/src/context/global-sync.tsx b/packages/app/src/context/global-sync.tsx
index 96f8c63ea..b13855906 100644
--- a/packages/app/src/context/global-sync.tsx
+++ b/packages/app/src/context/global-sync.tsx
@@ -101,12 +101,29 @@ function createGlobalSync() {
project: Project[]
provider: ProviderListResponse
provider_auth: ProviderAuthResponse
+ config: Config
+ reload: undefined | "pending" | "complete"
}>({
ready: false,
path: { state: "", config: "", worktree: "", directory: "", home: "" },
project: [],
provider: { all: [], connected: [], default: {} },
provider_auth: {},
+ config: {},
+ reload: undefined,
+ })
+ let bootstrapQueue: string[] = []
+
+ createEffect(async () => {
+ if (globalStore.reload !== "complete") return
+ if (bootstrapQueue.length) {
+ for (const directory of bootstrapQueue) {
+ bootstrapInstance(directory)
+ }
+ bootstrap()
+ }
+ bootstrapQueue = []
+ setGlobalStore("reload", undefined)
})
const children: Record<string, [Store<State>, SetStoreFunction<State>]> = {}
@@ -205,6 +222,8 @@ function createGlobalSync() {
throwOnError: true,
})
+ setStore("status", "loading")
+
createEffect(() => {
if (!cache.ready()) return
const cached = cache.store.value
@@ -230,91 +249,99 @@ function createGlobalSync() {
agent: () => sdk.app.agents().then((x) => setStore("agent", x.data ?? [])),
config: () => sdk.config.get().then((x) => setStore("config", x.data!)),
}
- await Promise.all(Object.values(blockingRequests).map((p) => retry(p).catch((e) => setGlobalStore("error", e))))
- .then(() => {
- if (store.status !== "complete") setStore("status", "partial")
- // non-blocking
- Promise.all([
- sdk.path.get().then((x) => setStore("path", x.data!)),
- sdk.command.list().then((x) => setStore("command", x.data ?? [])),
- sdk.session.status().then((x) => setStore("session_status", x.data!)),
- loadSessions(directory),
- sdk.mcp.status().then((x) => setStore("mcp", x.data!)),
- sdk.lsp.status().then((x) => setStore("lsp", x.data!)),
- sdk.vcs.get().then((x) => {
- const next = x.data ?? store.vcs
- setStore("vcs", next)
- if (next?.branch) cache.setStore("value", next)
- }),
- sdk.permission.list().then((x) => {
- const grouped: Record<string, PermissionRequest[]> = {}
- for (const perm of x.data ?? []) {
- if (!perm?.id || !perm.sessionID) continue
- const existing = grouped[perm.sessionID]
- if (existing) {
- existing.push(perm)
- continue
- }
- grouped[perm.sessionID] = [perm]
- }
-
- batch(() => {
- for (const sessionID of Object.keys(store.permission)) {
- if (grouped[sessionID]) continue
- setStore("permission", sessionID, [])
- }
- for (const [sessionID, permissions] of Object.entries(grouped)) {
- setStore(
- "permission",
- sessionID,
- reconcile(
- permissions
- .filter((p) => !!p?.id)
- .slice()
- .sort((a, b) => a.id.localeCompare(b.id)),
- { key: "id" },
- ),
- )
- }
- })
- }),
- sdk.question.list().then((x) => {
- const grouped: Record<string, QuestionRequest[]> = {}
- for (const question of x.data ?? []) {
- if (!question?.id || !question.sessionID) continue
- const existing = grouped[question.sessionID]
- if (existing) {
- existing.push(question)
- continue
- }
- grouped[question.sessionID] = [question]
- }
-
- batch(() => {
- for (const sessionID of Object.keys(store.question)) {
- if (grouped[sessionID]) continue
- setStore("question", sessionID, [])
- }
- for (const [sessionID, questions] of Object.entries(grouped)) {
- setStore(
- "question",
- sessionID,
- reconcile(
- questions
- .filter((q) => !!q?.id)
- .slice()
- .sort((a, b) => a.id.localeCompare(b.id)),
- { key: "id" },
- ),
- )
- }
- })
- }),
- ]).then(() => {
- setStore("status", "complete")
+
+ try {
+ await Promise.all(Object.values(blockingRequests).map((p) => retry(p)))
+ } catch (err) {
+ console.error("Failed to bootstrap instance", err)
+ const project = getFilename(directory)
+ const message = err instanceof Error ? err.message : String(err)
+ showToast({ title: `Failed to reload ${project}`, description: message })
+ setStore("status", "partial")
+ return
+ }
+
+ if (store.status !== "complete") setStore("status", "partial")
+
+ Promise.all([
+ sdk.path.get().then((x) => setStore("path", x.data!)),
+ sdk.command.list().then((x) => setStore("command", x.data ?? [])),
+ sdk.session.status().then((x) => setStore("session_status", x.data!)),
+ loadSessions(directory),
+ sdk.mcp.status().then((x) => setStore("mcp", x.data!)),
+ sdk.lsp.status().then((x) => setStore("lsp", x.data!)),
+ sdk.vcs.get().then((x) => {
+ const next = x.data ?? store.vcs
+ setStore("vcs", next)
+ if (next?.branch) cache.setStore("value", next)
+ }),
+ sdk.permission.list().then((x) => {
+ const grouped: Record<string, PermissionRequest[]> = {}
+ for (const perm of x.data ?? []) {
+ if (!perm?.id || !perm.sessionID) continue
+ const existing = grouped[perm.sessionID]
+ if (existing) {
+ existing.push(perm)
+ continue
+ }
+ grouped[perm.sessionID] = [perm]
+ }
+
+ batch(() => {
+ for (const sessionID of Object.keys(store.permission)) {
+ if (grouped[sessionID]) continue
+ setStore("permission", sessionID, [])
+ }
+ for (const [sessionID, permissions] of Object.entries(grouped)) {
+ setStore(
+ "permission",
+ sessionID,
+ reconcile(
+ permissions
+ .filter((p) => !!p?.id)
+ .slice()
+ .sort((a, b) => a.id.localeCompare(b.id)),
+ { key: "id" },
+ ),
+ )
+ }
})
- })
- .catch((e) => setGlobalStore("error", e))
+ }),
+ sdk.question.list().then((x) => {
+ const grouped: Record<string, QuestionRequest[]> = {}
+ for (const question of x.data ?? []) {
+ if (!question?.id || !question.sessionID) continue
+ const existing = grouped[question.sessionID]
+ if (existing) {
+ existing.push(question)
+ continue
+ }
+ grouped[question.sessionID] = [question]
+ }
+
+ batch(() => {
+ for (const sessionID of Object.keys(store.question)) {
+ if (grouped[sessionID]) continue
+ setStore("question", sessionID, [])
+ }
+ for (const [sessionID, questions] of Object.entries(grouped)) {
+ setStore(
+ "question",
+ sessionID,
+ reconcile(
+ questions
+ .filter((q) => !!q?.id)
+ .slice()
+ .sort((a, b) => a.id.localeCompare(b.id)),
+ { key: "id" },
+ ),
+ )
+ }
+ })
+ }),
+ ]).then(() => {
+ setStore("status", "complete")
+ })
}
const unsub = globalSDK.event.listen((e) => {
@@ -324,6 +351,7 @@ function createGlobalSync() {
if (directory === "global") {
switch (event?.type) {
case "global.disposed": {
+ if (globalStore.reload) return
bootstrap()
break
}
@@ -345,9 +373,16 @@ function createGlobalSync() {
return
}
- const [store, setStore] = child(directory)
+ const existing = children[directory]
+ if (!existing) return
+
+ const [store, setStore] = existing
switch (event.type) {
case "server.instance.disposed": {
+ if (globalStore.reload) {
+ bootstrapQueue.push(directory)
+ return
+ }
bootstrapInstance(directory)
break
}
@@ -592,6 +627,11 @@ function createGlobalSync() {
}),
),
retry(() =>
+ globalSDK.client.global.configGet().then((x) => {
+ setGlobalStore("config", x.data!)
+ }),
+ ),
+ retry(() =>
globalSDK.client.project.list().then(async (x) => {
const projects = (x.data ?? [])
.filter((p) => !!p?.id)
@@ -631,6 +671,7 @@ function createGlobalSync() {
return {
data: globalStore,
+ set: setGlobalStore,
get ready() {
return globalStore.ready
},
@@ -639,6 +680,14 @@ function createGlobalSync() {
},
child,
bootstrap,
+ updateConfig: async (config: Config) => {
+ setGlobalStore("reload", "pending")
+ const response = await globalSDK.client.global.configUpdate({ config })
+ setTimeout(() => {
+ setGlobalStore("reload", "complete")
+ }, 1000)
+ return response
+ },
project: {
loadSessions,
},
diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts
index ddb3af4b0..b2142e29b 100644
--- a/packages/opencode/src/config/config.ts
+++ b/packages/opencode/src/config/config.ts
@@ -12,7 +12,13 @@ import { lazy } from "../util/lazy"
import { NamedError } from "@opencode-ai/util/error"
import { Flag } from "../flag/flag"
import { Auth } from "../auth"
-import { type ParseError as JsoncParseError, parse as parseJsonc, printParseErrorCode } from "jsonc-parser"
+import {
+ type ParseError as JsoncParseError,
+ applyEdits,
+ modify,
+ parse as parseJsonc,
+ printParseErrorCode,
+} from "jsonc-parser"
import { Instance } from "../project/instance"
import { LSPServer } from "../lsp/server"
import { BunProc } from "@/bun"
@@ -20,6 +26,8 @@ import { Installation } from "@/installation"
import { ConfigMarkdown } from "./markdown"
import { existsSync } from "fs"
import { Bus } from "@/bus"
+import { GlobalBus } from "@/bus/global"
+import { Event } from "../server/event"
export namespace Config {
const log = Log.create({ service: "config" })
@@ -1242,6 +1250,10 @@ export namespace Config {
return state().then((x) => x.config)
}
+ export async function getGlobal() {
+ return global()
+ }
+
export async function update(config: Info) {
const filepath = path.join(Instance.directory, "config.json")
const existing = await loadFile(filepath)
@@ -1249,6 +1261,100 @@ export namespace Config {
await Instance.dispose()
}
+ function globalConfigFile() {
+ const candidates = ["opencode.jsonc", "opencode.json", "config.json"].map((file) =>
+ path.join(Global.Path.config, file),
+ )
+ for (const file of candidates) {
+ if (existsSync(file)) return file
+ }
+ return candidates[0]
+ }
+
+ function isRecord(value: unknown): value is Record<string, unknown> {
+ return !!value && typeof value === "object" && !Array.isArray(value)
+ }
+
+ function patchJsonc(input: string, patch: unknown, path: string[] = []): string {
+ if (!isRecord(patch)) {
+ const edits = modify(input, path, patch, {
+ formattingOptions: {
+ insertSpaces: true,
+ tabSize: 2,
+ },
+ })
+ return applyEdits(input, edits)
+ }
+
+ return Object.entries(patch).reduce((result, [key, value]) => {
+ if (value === undefined) return result
+ return patchJsonc(result, value, [...path, key])
+ }, input)
+ }
+
+ function parseConfig(text: string, filepath: string): Info {
+ const errors: JsoncParseError[] = []
+ const data = parseJsonc(text, errors, { allowTrailingComma: true })
+ if (errors.length) {
+ const lines = text.split("\n")
+ const errorDetails = errors
+ .map((e) => {
+ const beforeOffset = text.substring(0, e.offset).split("\n")
+ const line = beforeOffset.length
+ const column = beforeOffset[beforeOffset.length - 1].length + 1
+ const problemLine = lines[line - 1]
+
+ const error = `${printParseErrorCode(e.error)} at line ${line}, column ${column}`
+ if (!problemLine) return error
+
+ return `${error}\n Line ${line}: ${problemLine}\n${"".padStart(column + 9)}^`
+ })
+ .join("\n")
+
+ throw new JsonError({
+ path: filepath,
+ message: `\n--- JSONC Input ---\n${text}\n--- Errors ---\n${errorDetails}\n--- End ---`,
+ })
+ }
+
+ const parsed = Info.safeParse(data)
+ if (parsed.success) return parsed.data
+
+ throw new InvalidError({
+ path: filepath,
+ issues: parsed.error.issues,
+ })
+ }
+
+ export async function updateGlobal(config: Info) {
+ const filepath = globalConfigFile()
+ const before = await Bun.file(filepath)
+ .text()
+ .catch((err) => {
+ if (err.code === "ENOENT") return "{}"
+ throw new JsonError({ path: filepath }, { cause: err })
+ })
+
+ if (!filepath.endsWith(".jsonc")) {
+ const existing = parseConfig(before, filepath)
+ await Bun.write(filepath, JSON.stringify(mergeDeep(existing, config), null, 2))
+ } else {
+ const next = patchJsonc(before, config)
+ parseConfig(next, filepath)
+ await Bun.write(filepath, next)
+ }
+
+ global.reset()
+ await Instance.disposeAll()
+ GlobalBus.emit("event", {
+ directory: "global",
+ payload: {
+ type: Event.Disposed.type,
+ properties: {},
+ },
+ })
+ }
+
export async function directories() {
return state().then((x) => x.directories)
}
diff --git a/packages/opencode/src/file/watcher.ts b/packages/opencode/src/file/watcher.ts
index 44f8a0a3a..c4a474777 100644
--- a/packages/opencode/src/file/watcher.ts
+++ b/packages/opencode/src/file/watcher.ts
@@ -32,11 +32,16 @@ export namespace FileWatcher {
),
}
- const watcher = lazy(() => {
- const binding = require(
- `@parcel/watcher-${process.platform}-${process.arch}${process.platform === "linux" ? `-${OPENCODE_LIBC || "glibc"}` : ""}`,
- )
- return createWrapper(binding) as typeof import("@parcel/watcher")
+ const watcher = lazy((): typeof import("@parcel/watcher") | undefined => {
+ try {
+ const binding = require(
+ `@parcel/watcher-${process.platform}-${process.arch}${process.platform === "linux" ? `-${OPENCODE_LIBC || "glibc"}` : ""}`,
+ )
+ return createWrapper(binding) as typeof import("@parcel/watcher")
+ } catch (error) {
+ log.error("failed to load watcher binding", { error })
+ return
+ }
})
const state = Instance.state(
@@ -54,6 +59,10 @@ export namespace FileWatcher {
return {}
}
log.info("watcher backend", { platform: process.platform, backend })
+
+ const w = watcher()
+ if (!w) return {}
+
const subscribe: ParcelWatcher.SubscribeCallback = (err, evts) => {
if (err) return
for (const evt of evts) {
@@ -67,7 +76,7 @@ export namespace FileWatcher {
const cfgIgnores = cfg.watcher?.ignore ?? []
if (Flag.OPENCODE_EXPERIMENTAL_FILEWATCHER) {
- const pending = watcher().subscribe(Instance.directory, subscribe, {
+ const pending = w.subscribe(Instance.directory, subscribe, {
ignore: [...FileIgnore.PATTERNS, ...cfgIgnores],
backend,
})
@@ -89,7 +98,7 @@ export namespace FileWatcher {
if (vcsDir && !cfgIgnores.includes(".git") && !cfgIgnores.includes(vcsDir)) {
const gitDirContents = await readdir(vcsDir).catch(() => [])
const ignoreList = gitDirContents.filter((entry) => entry !== "HEAD")
- const pending = watcher().subscribe(vcsDir, subscribe, {
+ const pending = w.subscribe(vcsDir, subscribe, {
ignore: ignoreList,
backend,
})
diff --git a/packages/opencode/src/server/event.ts b/packages/opencode/src/server/event.ts
new file mode 100644
index 000000000..49325b2bb
--- /dev/null
+++ b/packages/opencode/src/server/event.ts
@@ -0,0 +1,7 @@
+import { BusEvent } from "@/bus/bus-event"
+import z from "zod"
+
+export const Event = {
+ Connected: BusEvent.define("server.connected", z.object({})),
+ Disposed: BusEvent.define("global.disposed", z.object({})),
+}
diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts
index 28dec7f40..15b7f829b 100644
--- a/packages/opencode/src/server/server.ts
+++ b/packages/opencode/src/server/server.ts
@@ -54,11 +54,6 @@ export namespace Server {
return _url ?? new URL("http://localhost:4096")
}
- export const Event = {
- Connected: BusEvent.define("server.connected", z.object({})),
- Disposed: BusEvent.define("global.disposed", z.object({})),
- }
-
const app = new Hono()
export const App: () => Hono = lazy(
() =>
diff --git a/packages/sdk/js/src/v2/gen/sdk.gen.ts b/packages/sdk/js/src/v2/gen/sdk.gen.ts
index 706d0f9c2..139940ef6 100644
--- a/packages/sdk/js/src/v2/gen/sdk.gen.ts
+++ b/packages/sdk/js/src/v2/gen/sdk.gen.ts
@@ -32,6 +32,9 @@ import type {
FindSymbolsResponses,
FindTextResponses,
FormatterStatusResponses,
+ GlobalConfigGetResponses,
+ GlobalConfigUpdateErrors,
+ GlobalConfigUpdateResponses,
GlobalDisposeResponses,
GlobalEventResponses,
GlobalHealthResponses,
@@ -249,6 +252,42 @@ export class Global extends HeyApiClient {
...options,
})
}
+
+ /**
+ * Get global configuration
+ *
+ * Retrieve the global OpenCode configuration settings and preferences.
+ */
+ public configGet<ThrowOnError extends boolean = false>(options?: Options<never, ThrowOnError>) {
+ return (options?.client ?? this.client).get<GlobalConfigGetResponses, unknown, ThrowOnError>({
+ url: "/global/config",
+ ...options,
+ })
+ }
+
+ /**
+ * Update global configuration
+ *
+ * Update global OpenCode configuration settings and preferences.
+ */
+ public configUpdate<ThrowOnError extends boolean = false>(
+ parameters?: {
+ config?: Config2
+ },
+ options?: Options<never, ThrowOnError>,
+ ) {
+ const params = buildClientParams([parameters], [{ args: [{ key: "config", map: "body" }] }])
+ return (options?.client ?? this.client).patch<GlobalConfigUpdateResponses, GlobalConfigUpdateErrors, ThrowOnError>({
+ url: "/global/config",
+ ...options,
+ ...params,
+ headers: {
+ "Content-Type": "application/json",
+ ...options?.headers,
+ ...params.headers,
+ },
+ })
+ }
}
export class Project extends HeyApiClient {
diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts
index b7e72fbad..736af471f 100644
--- a/packages/sdk/js/src/v2/gen/types.gen.ts
+++ b/packages/sdk/js/src/v2/gen/types.gen.ts
@@ -2189,6 +2189,47 @@ export type GlobalDisposeResponses = {
export type GlobalDisposeResponse = GlobalDisposeResponses[keyof GlobalDisposeResponses]
+export type GlobalConfigGetData = {
+ body?: never
+ path?: never
+ query?: never
+ url: "/global/config"
+}
+
+export type GlobalConfigGetResponses = {
+ /**
+ * Global config
+ */
+ 200: Config
+}
+
+export type GlobalConfigGetResponse = GlobalConfigGetResponses[keyof GlobalConfigGetResponses]
+
+export type GlobalConfigUpdateData = {
+ body?: Config
+ path?: never
+ query?: never
+ url: "/global/config"
+}
+
+export type GlobalConfigUpdateErrors = {
+ /**
+ * Bad request
+ */
+ 400: BadRequestError
+}
+
+export type GlobalConfigUpdateError = GlobalConfigUpdateErrors[keyof GlobalConfigUpdateErrors]
+
+export type GlobalConfigUpdateResponses = {
+ /**
+ * Successfully updated global config
+ */
+ 200: Config
+}
+
+export type GlobalConfigUpdateResponse = GlobalConfigUpdateResponses[keyof GlobalConfigUpdateResponses]
+
export type ProjectListData = {
body?: never
path?: never