Browse Source

wip(app): settings

Adam 1 month ago
parent
commit
924fc9ed80

+ 0 - 1
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)

+ 146 - 5
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>
   )
 }

+ 134 - 85
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
       }
@@ -591,6 +626,11 @@ function createGlobalSync() {
           setGlobalStore("path", x.data!)
         }),
       ),
+      retry(() =>
+        globalSDK.client.global.configGet().then((x) => {
+          setGlobalStore("config", x.data!)
+        }),
+      ),
       retry(() =>
         globalSDK.client.project.list().then(async (x) => {
           const projects = (x.data ?? [])
@@ -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,
     },

+ 107 - 1
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)
   }

+ 16 - 7
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,
         })

+ 7 - 0
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({})),
+}

+ 0 - 5
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(
     () =>

+ 39 - 0
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 {

+ 41 - 0
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