diff --git a/.github/last-synced-tag b/.github/last-synced-tag index 54edb843c69..bdb0f889a77 100644 --- a/.github/last-synced-tag +++ b/.github/last-synced-tag @@ -1 +1 @@ -v1.1.19 +v1.1.20 diff --git a/.ralph-done b/.ralph-done new file mode 100644 index 00000000000..e69de29bb2d diff --git a/AGENTS.md b/AGENTS.md index 5b1cfc528c3..c7c64e5568a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -16,6 +16,8 @@ - Avoid testing logic directly inside Solid.js `.tsx` files if they import JSX runtimes, as `bun test` may fail with `jsxDEV` errors. - Separate pure logic into `.ts` files (e.g., `theme-utils.ts`) and test those instead. +- Run tests from `packages/opencode` directory - the repo root has a guard file that prevents test execution. +- `bun run lint` in `packages/opencode` runs the full test suite with coverage. ## Upstream Merge Operations diff --git a/STATS.md b/STATS.md index ac4b788bae0..b6e03b01b04 100644 --- a/STATS.md +++ b/STATS.md @@ -200,3 +200,4 @@ | 2026-01-11 | 2,836,394 (+204,371) | 1,530,479 (+26,809) | 4,366,873 (+231,180) | | 2026-01-12 | 3,053,594 (+217,200) | 1,553,671 (+23,192) | 4,607,265 (+240,392) | | 2026-01-13 | 3,297,078 (+243,484) | 1,595,062 (+41,391) | 4,892,140 (+284,875) | +| 2026-01-14 | 3,568,928 (+271,850) | 1,645,362 (+50,300) | 5,214,290 (+322,150) | diff --git a/flake.lock b/flake.lock index 3e4611cf5e9..5ef276f0a08 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1768178648, - "narHash": "sha256-kz/F6mhESPvU1diB7tOM3nLcBfQe7GU7GQCymRlTi/s=", + "lastModified": 1768302833, + "narHash": "sha256-h5bRFy9bco+8QcK7rGoOiqMxMbmn21moTACofNLRMP4=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "3fbab70c6e69c87ea2b6e48aa6629da2aa6a23b0", + "rev": "61db79b0c6b838d9894923920b612048e1201926", "type": "github" }, "original": { diff --git a/infra/console.ts b/infra/console.ts index 1368ef202aa..17e4deab6e2 100644 --- a/infra/console.ts +++ b/infra/console.ts @@ -122,6 +122,7 @@ const ZEN_MODELS = [ ] const ZEN_BLACK = new sst.Secret("ZEN_BLACK") const STRIPE_SECRET_KEY = new sst.Secret("STRIPE_SECRET_KEY") +const STRIPE_PUBLISHABLE_KEY = new sst.Secret("STRIPE_PUBLISHABLE_KEY") const AUTH_API_URL = new sst.Linkable("AUTH_API_URL", { properties: { value: auth.url.apply((url) => url!) }, }) @@ -177,6 +178,7 @@ new sst.cloudflare.x.SolidStart("Console", { //VITE_DOCS_URL: web.url.apply((url) => url!), //VITE_API_URL: gateway.url.apply((url) => url!), VITE_AUTH_URL: auth.url.apply((url) => url!), + VITE_STRIPE_PUBLISHABLE_KEY: STRIPE_PUBLISHABLE_KEY.value, }, transform: { server: { diff --git a/nix/hashes.json b/nix/hashes.json index a25b9376e5d..df6cd8069f0 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,6 +1,6 @@ { "nodeModules": { - "x86_64-linux": "sha256-x6A/XT1i3bjakfAj0A1wV4n2s9rpflMDceTeppdP6tE=", - "aarch64-darwin": "sha256-RkamQYbpjJqpHHf76em9lPgeI9k4/kaCf7T+4xHaizY=" + "x86_64-linux": "sha256-wENwhwRVfgoVyA9YNGcG+fAfu46JxK4xvNgiPbRt//s=", + "aarch64-darwin": "sha256-vm1DYl1erlbaqz5NHHlnZEMuFmidr/UkS84nIqLJ96Q=" } } diff --git a/packages/app/package.json b/packages/app/package.json index 391531101ae..866a565c132 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/app", - "version": "1.1.19", + "version": "1.1.20", "description": "", "type": "module", "exports": { diff --git a/packages/app/src/context/global-sync.tsx b/packages/app/src/context/global-sync.tsx index b62927c3f39..597cc3c63df 100644 --- a/packages/app/src/context/global-sync.tsx +++ b/packages/app/src/context/global-sync.tsx @@ -42,6 +42,7 @@ type State = { config: Config path: Path session: Session[] + sessionTotal: number session_status: { [sessionID: string]: SessionStatus } @@ -110,6 +111,7 @@ function createGlobalSync() { agent: [], command: [], session: [], + sessionTotal: 0, session_status: {}, session_diff: {}, todo: {}, @@ -131,8 +133,10 @@ function createGlobalSync() { async function loadSessions(directory: string) { const [store, setStore] = child(directory) - globalSDK.client.session - .list({ directory }) + const limit = store.limit + + return globalSDK.client.session + .list({ directory, roots: true }) .then((x) => { const fourHoursAgo = Date.now() - 4 * 60 * 60 * 1000 const data = Array.isArray(x.data) ? x.data : [] @@ -143,10 +147,12 @@ function createGlobalSync() { .sort((a, b) => a.id.localeCompare(b.id)) // Include up to the limit, plus any updated in the last 4 hours const sessions = nonArchived.filter((s, i) => { - if (i < store.limit) return true + if (i < limit) return true const updated = new Date(s.time?.updated ?? s.time?.created).getTime() return updated > fourHoursAgo }) + // Store total session count (used for "load more" pagination) + setStore("sessionTotal", nonArchived.length) setStore("session", reconcile(sessions, { key: "id" })) }) .catch((err) => { diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx index 98b0c7b2ae8..e706931f78d 100644 --- a/packages/app/src/pages/layout.tsx +++ b/packages/app/src/pages/layout.tsx @@ -955,7 +955,7 @@ export default function Layout(props: ParentProps) { .toSorted(sortSessions), ) const rootSessions = createMemo(() => sessions().filter((s) => !s.parentID)) - const hasMoreSessions = createMemo(() => store.session.length >= store.limit) + const hasMoreSessions = createMemo(() => store.sessionTotal > store.session.length) const loadMoreSessions = async () => { setProjectStore("limit", (limit) => limit + 5) await globalSync.project.loadSessions(props.project.worktree) diff --git a/packages/console/app/package.json b/packages/console/app/package.json index dff2adef7ac..918277eaff2 100644 --- a/packages/console/app/package.json +++ b/packages/console/app/package.json @@ -1,12 +1,12 @@ { "name": "@opencode-ai/console-app", - "version": "1.1.19", + "version": "1.1.20", "type": "module", "license": "MIT", "scripts": { "typecheck": "tsgo --noEmit", "dev": "vite dev --host 0.0.0.0", - "dev:remote": "VITE_AUTH_URL=https://auth.dev.opencode.ai bun sst shell --stage=dev bun dev", + "dev:remote": "VITE_AUTH_URL=https://auth.dev.opencode.ai VITE_STRIPE_PUBLISHABLE_KEY=pk_test_51RtuLNE7fOCwHSD4mewwzFejyytjdGoSDK7CAvhbffwaZnPbNb2rwJICw6LTOXCmWO320fSNXvb5NzI08RZVkAxd00syfqrW7t bun sst shell --stage=dev bun dev", "build": "./script/generate-sitemap.ts && vite build && ../../opencode/script/schema.ts ./.output/public/config.json", "start": "vite start" }, @@ -23,10 +23,12 @@ "@solidjs/meta": "catalog:", "@solidjs/router": "catalog:", "@solidjs/start": "catalog:", + "@stripe/stripe-js": "8.6.1", "chart.js": "4.5.1", "nitro": "3.0.1-alpha.1", "solid-js": "catalog:", "solid-list": "0.3.0", + "solid-stripe": "0.8.1", "vite": "catalog:", "zod": "catalog:" }, diff --git a/packages/console/app/src/routes/auth/callback.ts b/packages/console/app/src/routes/auth/[...callback].ts similarity index 91% rename from packages/console/app/src/routes/auth/callback.ts rename to packages/console/app/src/routes/auth/[...callback].ts index 9b7296791d4..36a9c5194d0 100644 --- a/packages/console/app/src/routes/auth/callback.ts +++ b/packages/console/app/src/routes/auth/[...callback].ts @@ -5,6 +5,7 @@ import { useAuthSession } from "~/context/auth" export async function GET(input: APIEvent) { const url = new URL(input.request.url) + try { const code = url.searchParams.get("code") if (!code) throw new Error("No code found") @@ -27,7 +28,7 @@ export async function GET(input: APIEvent) { current: id, } }) - return redirect("/auth") + return redirect(url.pathname === "/auth/callback" ? "/auth" : url.pathname.replace("/auth/callback", "")) } catch (e: any) { return new Response( JSON.stringify({ diff --git a/packages/console/app/src/routes/auth/authorize.ts b/packages/console/app/src/routes/auth/authorize.ts index 166466ef859..0f0651ae36b 100644 --- a/packages/console/app/src/routes/auth/authorize.ts +++ b/packages/console/app/src/routes/auth/authorize.ts @@ -2,6 +2,9 @@ import type { APIEvent } from "@solidjs/start/server" import { AuthClient } from "~/context/auth" export async function GET(input: APIEvent) { - const result = await AuthClient.authorize(new URL("./callback", input.request.url).toString(), "code") + const url = new URL(input.request.url) + const cont = url.searchParams.get("continue") ?? "" + const callbackUrl = new URL(`./callback${cont}`, input.request.url) + const result = await AuthClient.authorize(callbackUrl.toString(), "code") return Response.redirect(result.url, 302) } diff --git a/packages/console/app/src/routes/black/index.css b/packages/console/app/src/routes/black.css similarity index 55% rename from packages/console/app/src/routes/black/index.css rename to packages/console/app/src/routes/black.css index 418598792fb..72bf7a65785 100644 --- a/packages/console/app/src/routes/black/index.css +++ b/packages/console/app/src/routes/black.css @@ -36,24 +36,73 @@ width: 100%; flex-grow: 1; + [data-slot="hero"] { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + gap: 8px; + margin-top: 40px; + padding: 0 20px; + + @media (min-width: 768px) { + margin-top: 60px; + } + + h1 { + color: rgba(255, 255, 255, 0.92); + font-size: 18px; + font-style: normal; + font-weight: 400; + line-height: 160%; + margin: 0; + + @media (min-width: 768px) { + font-size: 24px; + } + } + + p { + color: rgba(255, 255, 255, 0.59); + font-size: 15px; + font-style: normal; + font-weight: 400; + line-height: 160%; + margin: 0; + + @media (min-width: 768px) { + font-size: 18px; + } + } + } + [data-slot="hero-black"] { - margin-top: 110px; + margin-top: 40px; + padding: 0 20px; @media (min-width: 768px) { - margin-top: 150px; + margin-top: 60px; + } + + svg { + width: 100%; + max-width: 540px; + height: auto; + filter: drop-shadow(0 0 20px rgba(255, 255, 255, 0.1)); } } [data-slot="cta"] { display: flex; flex-direction: column; - gap: 32px; + gap: 16px; align-items: center; text-align: center; - margin-top: -18px; + margin-top: -40px; + width: 100%; @media (min-width: 768px) { - margin-top: 40px; + margin-top: -20px; } [data-slot="heading"] { @@ -328,6 +377,290 @@ } } } + + /* Subscribe page styles */ + [data-slot="subscribe-form"] { + display: flex; + flex-direction: column; + gap: 32px; + align-items: center; + margin-top: -18px; + width: 100%; + max-width: 540px; + padding: 0 20px; + + @media (min-width: 768px) { + margin-top: 40px; + padding: 0; + } + + [data-slot="form-card"] { + width: 100%; + border: 1px solid rgba(255, 255, 255, 0.17); + border-radius: 4px; + padding: 24px; + display: flex; + flex-direction: column; + gap: 20px; + } + + [data-slot="plan-header"] { + display: flex; + flex-direction: column; + gap: 8px; + } + + [data-slot="title"] { + color: rgba(255, 255, 255, 0.92); + font-size: 16px; + font-weight: 400; + margin-bottom: 8px; + } + + [data-slot="icon"] { + color: rgba(255, 255, 255, 0.59); + } + + [data-slot="price"] { + display: flex; + flex-wrap: wrap; + align-items: baseline; + gap: 8px; + } + + [data-slot="amount"] { + color: rgba(255, 255, 255, 0.92); + font-size: 24px; + font-weight: 500; + } + + [data-slot="period"] { + color: rgba(255, 255, 255, 0.59); + font-size: 14px; + } + + [data-slot="multiplier"] { + color: rgba(255, 255, 255, 0.39); + font-size: 14px; + + &::before { + content: "·"; + margin: 0 8px; + } + } + + [data-slot="divider"] { + height: 1px; + background: rgba(255, 255, 255, 0.17); + } + + [data-slot="section-title"] { + color: rgba(255, 255, 255, 0.92); + font-size: 16px; + font-weight: 400; + } + + [data-slot="tax-id-section"] { + display: flex; + flex-direction: column; + gap: 8px; + + [data-slot="label"] { + color: rgba(255, 255, 255, 0.59); + font-size: 14px; + } + + [data-slot="input"] { + width: 100%; + height: 44px; + padding: 0 12px; + background: #1a1a1a; + border: 1px solid rgba(255, 255, 255, 0.17); + border-radius: 4px; + color: #ffffff; + font-family: var(--font-mono); + font-size: 14px; + outline: none; + transition: border-color 0.15s ease; + + &::placeholder { + color: rgba(255, 255, 255, 0.39); + } + + &:focus { + border-color: rgba(255, 255, 255, 0.35); + } + } + } + + [data-slot="checkout-form"] { + display: flex; + flex-direction: column; + gap: 20px; + } + + [data-slot="error"] { + color: #ff6b6b; + font-size: 14px; + } + + [data-slot="submit-button"] { + width: 100%; + height: 48px; + background: rgba(255, 255, 255, 0.92); + border: none; + border-radius: 4px; + color: #000; + font-family: var(--font-mono); + font-size: 16px; + font-weight: 500; + cursor: pointer; + transition: background 0.15s ease; + + &:hover:not(:disabled) { + background: #e0e0e0; + } + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + } + } + + [data-slot="charge-notice"] { + color: #d4a500; + font-size: 14px; + text-align: center; + } + + [data-slot="success"] { + display: flex; + flex-direction: column; + gap: 24px; + + [data-slot="title"] { + color: rgba(255, 255, 255, 0.92); + font-size: 18px; + font-weight: 400; + margin: 0; + } + + [data-slot="details"] { + display: flex; + flex-direction: column; + gap: 16px; + + > div { + display: flex; + justify-content: space-between; + align-items: baseline; + gap: 16px; + } + + dt { + color: rgba(255, 255, 255, 0.59); + font-size: 14px; + font-weight: 400; + } + + dd { + color: rgba(255, 255, 255, 0.92); + font-size: 14px; + font-weight: 400; + margin: 0; + text-align: right; + } + } + + [data-slot="charge-notice"] { + color: #d4a500; + font-size: 14px; + text-align: left; + } + } + + [data-slot="loading"] { + display: flex; + justify-content: center; + padding: 40px 0; + + p { + color: rgba(255, 255, 255, 0.59); + font-size: 14px; + } + } + + [data-slot="fine-print"] { + color: rgba(255, 255, 255, 0.39); + text-align: center; + font-size: 13px; + font-style: italic; + + a { + color: rgba(255, 255, 255, 0.39); + text-decoration: underline; + } + } + + [data-slot="workspace-picker"] { + [data-slot="workspace-list"] { + width: 100%; + padding: 0; + margin: 0; + list-style: none; + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 8px; + align-self: stretch; + outline: none; + overflow-y: auto; + max-height: 240px; + scrollbar-width: none; + + &::-webkit-scrollbar { + display: none; + } + + [data-slot="workspace-item"] { + width: 100%; + display: flex; + padding: 8px 12px; + align-items: center; + gap: 8px; + align-self: stretch; + cursor: pointer; + + [data-slot="selected-icon"] { + visibility: hidden; + color: rgba(255, 255, 255, 0.39); + font-family: "IBM Plex Mono", monospace; + font-size: 16px; + font-style: normal; + font-weight: 400; + line-height: 160%; + } + + span:last-child { + color: rgba(255, 255, 255, 0.92); + font-size: 16px; + font-style: normal; + font-weight: 400; + line-height: 160%; + } + + &:hover, + &[data-active="true"] { + background: #161616; + + [data-slot="selected-icon"] { + visibility: visible; + } + } + } + } + } + } } [data-component="footer"] { diff --git a/packages/console/app/src/routes/black.tsx b/packages/console/app/src/routes/black.tsx new file mode 100644 index 00000000000..5a5b139dd53 --- /dev/null +++ b/packages/console/app/src/routes/black.tsx @@ -0,0 +1,166 @@ +import { A, createAsync, RouteSectionProps } from "@solidjs/router" +import { createMemo } from "solid-js" +import { github } from "~/lib/github" +import { config } from "~/config" +import "./black.css" + +export default function BlackLayout(props: RouteSectionProps) { + const githubData = createAsync(() => github()) + const starCount = createMemo(() => + githubData()?.stars + ? new Intl.NumberFormat("en-US", { + notation: "compact", + compactDisplay: "short", + }).format(githubData()!.stars!) + : config.github.starsFormatted.compact, + ) + + return ( +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+

Access all the world's best coding models

+

Including Claude, GPT, Gemini and more

+
+
+ + + + + + + + + + + + + + + + + + +
+ {props.children} +
+ +
+ ) +} diff --git a/packages/console/app/src/routes/black/common.tsx b/packages/console/app/src/routes/black/common.tsx new file mode 100644 index 00000000000..950531da179 --- /dev/null +++ b/packages/console/app/src/routes/black/common.tsx @@ -0,0 +1,43 @@ +import { Match, Switch } from "solid-js" + +export const plans = [ + { id: "20", multiplier: null }, + { id: "100", multiplier: "6x more usage than Black 20" }, + { id: "200", multiplier: "21x more usage than Black 20" }, +] as const + +export type PlanID = (typeof plans)[number]["id"] +export type Plan = (typeof plans)[number] + +export function PlanIcon(props: { plan: string }) { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ) +} diff --git a/packages/console/app/src/routes/black/index.tsx b/packages/console/app/src/routes/black/index.tsx index f5a375adf87..5d924a64bc3 100644 --- a/packages/console/app/src/routes/black/index.tsx +++ b/packages/console/app/src/routes/black/index.tsx @@ -1,276 +1,80 @@ -import { A, createAsync, useSearchParams } from "@solidjs/router" -import "./index.css" +import { A, useSearchParams } from "@solidjs/router" import { Title } from "@solidjs/meta" -import { github } from "~/lib/github" import { createMemo, createSignal, For, Match, Show, Switch } from "solid-js" -import { config } from "~/config" - -const plans = [ - { id: "20", amount: 20, multiplier: null }, - { id: "100", amount: 100, multiplier: "6x more usage than Black 20" }, - { id: "200", amount: 200, multiplier: "21x more usage than Black 20" }, -] as const - -function PlanIcon(props: { plan: string }) { - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ) -} +import { PlanIcon, plans } from "./common" export default function Black() { const [params] = useSearchParams() - const [selected, setSelected] = createSignal(params.plan as string | null) + const [selected, setSelected] = createSignal((params.plan as string) || null) const selectedPlan = createMemo(() => plans.find((p) => p.id === selected())) - const githubData = createAsync(() => github()) - const starCount = createMemo(() => - githubData()?.stars - ? new Intl.NumberFormat("en-US", { - notation: "compact", - compactDisplay: "short", - }).format(githubData()!.stars!) - : config.github.starsFormatted.compact, - ) - return ( -
+ <> opencode -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
-
- - - - - - - - - - - - - - - - - - -
-
-
-

- Access all the world's best coding models -

-

Including Claude, GPT, Gemini, and more

-
- - -
- - {(plan) => ( - - )} - -
-

- Prices shown don't include applicable tax · Terms of Service -

-
- - {(plan) => ( -
-
+
+ + +
+ + {(plan) => ( + - - Continue - -
+ + )} + +
+

+ Prices shown don't include applicable tax · Terms of Service +

+ + + {(plan) => ( +
+
+
+
-

- Prices shown don't include applicable tax · Terms of Service +

+ ${plan().id}{" "} + per person billed monthly + + {plan().multiplier} +

+
    +
  • Your subscription will not start immediately
  • +
  • You will be added to the waitlist and activated soon
  • +
  • Your card will be only charged when your subscription is activated
  • +
  • Usage limits apply, heavily automated use may reach limits sooner
  • +
  • Subscriptions for individuals, contact Enterprise for teams
  • +
  • Limits may be adjusted and plans may be discontinued in the future
  • +
  • Cancel your subscription at anytime
  • +
+
+ + + Continue + +
- )} - - -
-
- -
+

+ Prices shown don't include applicable tax · Terms of Service +

+
+ )} + + + + ) } diff --git a/packages/console/app/src/routes/black/subscribe/[plan].tsx b/packages/console/app/src/routes/black/subscribe/[plan].tsx new file mode 100644 index 00000000000..5e13799dd4d --- /dev/null +++ b/packages/console/app/src/routes/black/subscribe/[plan].tsx @@ -0,0 +1,450 @@ +import { A, createAsync, query, redirect, useParams } from "@solidjs/router" +import { Title } from "@solidjs/meta" +import { createEffect, createSignal, For, Match, Show, Switch } from "solid-js" +import { type Stripe, type PaymentMethod, loadStripe } from "@stripe/stripe-js" +import { Elements, PaymentElement, useStripe, useElements, AddressElement } from "solid-stripe" +import { PlanID, plans } from "../common" +import { getActor, useAuthSession } from "~/context/auth" +import { withActor } from "~/context/auth.withActor" +import { Actor } from "@opencode-ai/console-core/actor.js" +import { and, Database, eq, isNull } from "@opencode-ai/console-core/drizzle/index.js" +import { WorkspaceTable } from "@opencode-ai/console-core/schema/workspace.sql.js" +import { UserTable } from "@opencode-ai/console-core/schema/user.sql.js" +import { createList } from "solid-list" +import { Modal } from "~/component/modal" +import { BillingTable } from "@opencode-ai/console-core/schema/billing.sql.js" +import { Billing } from "@opencode-ai/console-core/billing.js" + +const plansMap = Object.fromEntries(plans.map((p) => [p.id, p])) as Record +const stripePromise = loadStripe(import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY!) + +const getWorkspaces = query(async () => { + "use server" + const actor = await getActor() + if (actor.type === "public") throw redirect("/auth/authorize?continue=/black/subscribe") + return withActor(async () => { + return Database.use((tx) => + tx + .select({ + id: WorkspaceTable.id, + name: WorkspaceTable.name, + slug: WorkspaceTable.slug, + billing: { + customerID: BillingTable.customerID, + paymentMethodID: BillingTable.paymentMethodID, + paymentMethodType: BillingTable.paymentMethodType, + paymentMethodLast4: BillingTable.paymentMethodLast4, + subscriptionID: BillingTable.subscriptionID, + timeSubscriptionBooked: BillingTable.timeSubscriptionBooked, + }, + }) + .from(UserTable) + .innerJoin(WorkspaceTable, eq(UserTable.workspaceID, WorkspaceTable.id)) + .innerJoin(BillingTable, eq(WorkspaceTable.id, BillingTable.workspaceID)) + .where( + and( + eq(UserTable.accountID, Actor.account()), + isNull(WorkspaceTable.timeDeleted), + isNull(UserTable.timeDeleted), + ), + ), + ) + }) +}, "black.subscribe.workspaces") + +const createSetupIntent = async (input: { plan: string; workspaceID: string }) => { + "use server" + const { plan, workspaceID } = input + + if (!plan || !["20", "100", "200"].includes(plan)) return { error: "Invalid plan" } + if (!workspaceID) return { error: "Workspace ID is required" } + + return withActor(async () => { + const session = await useAuthSession() + const account = session.data.account?.[session.data.current ?? ""] + const email = account?.email + + const customer = await Database.use((tx) => + tx + .select({ + customerID: BillingTable.customerID, + subscriptionID: BillingTable.subscriptionID, + }) + .from(BillingTable) + .where(eq(BillingTable.workspaceID, workspaceID)) + .then((rows) => rows[0]), + ) + if (customer?.subscriptionID) { + return { error: "This workspace already has a subscription" } + } + + let customerID = customer?.customerID + if (!customerID) { + const customer = await Billing.stripe().customers.create({ + email, + metadata: { + workspaceID, + }, + }) + customerID = customer.id + await Database.use((tx) => + tx + .update(BillingTable) + .set({ + customerID, + }) + .where(eq(BillingTable.workspaceID, workspaceID)), + ) + } + + const intent = await Billing.stripe().setupIntents.create({ + customer: customerID, + payment_method_types: ["card"], + metadata: { + workspaceID, + }, + }) + + return { clientSecret: intent.client_secret ?? undefined } + }, workspaceID) +} + +const bookSubscription = async (input: { + workspaceID: string + plan: PlanID + paymentMethodID: string + paymentMethodType: string + paymentMethodLast4?: string +}) => { + "use server" + return withActor( + () => + Database.use((tx) => + tx + .update(BillingTable) + .set({ + paymentMethodID: input.paymentMethodID, + paymentMethodType: input.paymentMethodType, + paymentMethodLast4: input.paymentMethodLast4, + subscriptionPlan: input.plan, + timeSubscriptionBooked: new Date(), + }) + .where(eq(BillingTable.workspaceID, input.workspaceID)), + ), + input.workspaceID, + ) +} + +interface SuccessData { + plan: string + paymentMethodType: string + paymentMethodLast4?: string +} + +function Failure(props: { message: string }) { + return ( +
+

Uh oh! {props.message}

+
+ ) +} + +function Success(props: SuccessData) { + return ( +
+

You're on the OpenCode Black waitlist

+
+
+
Subscription plan
+
OpenCode Black {props.plan}
+
+
+
Amount
+
${props.plan} per month
+
+
+
Payment method
+
+ {props.paymentMethodType}}> + + {props.paymentMethodType} - {props.paymentMethodLast4} + + +
+
+
+
Date joined
+
{new Date().toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" })}
+
+
+

Your card will be charged when your subscription is activated

+
+ ) +} + +function IntentForm(props: { plan: PlanID; workspaceID: string; onSuccess: (data: SuccessData) => void }) { + const stripe = useStripe() + const elements = useElements() + const [error, setError] = createSignal(undefined) + const [loading, setLoading] = createSignal(false) + + const handleSubmit = async (e: Event) => { + e.preventDefault() + if (!stripe() || !elements()) return + + setLoading(true) + setError(undefined) + + const result = await elements()!.submit() + if (result.error) { + setError(result.error.message ?? "An error occurred") + setLoading(false) + return + } + + const { error: confirmError, setupIntent } = await stripe()!.confirmSetup({ + elements: elements()!, + confirmParams: { + expand: ["payment_method"], + payment_method_data: { + allow_redisplay: "always", + }, + }, + redirect: "if_required", + }) + + if (confirmError) { + setError(confirmError.message ?? "An error occurred") + setLoading(false) + return + } + + // TODO + console.log(setupIntent) + if (setupIntent?.status === "succeeded") { + const pm = setupIntent.payment_method as PaymentMethod + + await bookSubscription({ + workspaceID: props.workspaceID, + plan: props.plan, + paymentMethodID: pm.id, + paymentMethodType: pm.type, + paymentMethodLast4: pm.card?.last4, + }) + + props.onSuccess({ + plan: props.plan, + paymentMethodType: pm.type, + paymentMethodLast4: pm.card?.last4, + }) + } + + setLoading(false) + } + + return ( +
+ + + +

{error()}

+
+ +

You will only be charged when your subscription is activated

+ + ) +} + +export default function BlackSubscribe() { + const workspaces = createAsync(() => getWorkspaces()) + const [selectedWorkspace, setSelectedWorkspace] = createSignal(undefined) + const [success, setSuccess] = createSignal(undefined) + const [failure, setFailure] = createSignal(undefined) + const [clientSecret, setClientSecret] = createSignal(undefined) + const [stripe, setStripe] = createSignal(undefined) + const params = useParams() + const planData = plansMap[(params.plan as PlanID) ?? "20"] ?? plansMap["20"] + const plan = planData.id + + // Resolve stripe promise once + createEffect(() => { + stripePromise.then((s) => { + if (s) setStripe(s) + }) + }) + + // Auto-select if only one workspace + createEffect(() => { + const ws = workspaces() + if (ws?.length === 1 && !selectedWorkspace()) { + setSelectedWorkspace(ws[0].id) + } + }) + + // Fetch setup intent when workspace is selected (unless workspace already has payment method) + createEffect(async () => { + const id = selectedWorkspace() + if (!id) return + + const ws = workspaces()?.find((w) => w.id === id) + if (ws?.billing?.subscriptionID) { + setFailure("This workspace already has a subscription") + return + } + if (ws?.billing?.paymentMethodID) { + if (!ws?.billing?.timeSubscriptionBooked) { + await bookSubscription({ + workspaceID: id, + plan: planData.id, + paymentMethodID: ws.billing.paymentMethodID!, + paymentMethodType: ws.billing.paymentMethodType!, + paymentMethodLast4: ws.billing.paymentMethodLast4 ?? undefined, + }) + } + setSuccess({ + plan: planData.id, + paymentMethodType: ws.billing.paymentMethodType!, + paymentMethodLast4: ws.billing.paymentMethodLast4 ?? undefined, + }) + return + } + + const result = await createSetupIntent({ plan, workspaceID: id }) + if (result.error) { + setFailure(result.error) + } else if ("clientSecret" in result) { + setClientSecret(result.clientSecret) + } + }) + + // Keyboard navigation for workspace picker + const { active, setActive, onKeyDown } = createList({ + items: () => workspaces()?.map((w) => w.id) ?? [], + initialActive: null, + }) + + const handleSelectWorkspace = (id: string) => { + setSelectedWorkspace(id) + } + + let listRef: HTMLUListElement | undefined + + // Show workspace picker if multiple workspaces and none selected + const showWorkspacePicker = () => { + const ws = workspaces() + return ws && ws.length > 1 && !selectedWorkspace() + } + + return ( + <> + Subscribe to OpenCode Black +
+
+ + {(data) => } + {(data) => } + + <> +
+

Subscribe to OpenCode Black

+

+ ${planData.id} per month + + {planData.multiplier} + +

+
+
+

Payment method

+ + +

{selectedWorkspace() ? "Loading payment form..." : "Select a workspace to continue"}

+
+ } + > + + + + + +
+
+
+ + {/* Workspace picker modal */} + {}} title="Select a workspace for this plan"> +
+
    { + if (e.key === "Enter" && active()) { + handleSelectWorkspace(active()!) + } else { + onKeyDown(e) + } + }} + > + + {(workspace) => ( +
  • setActive(workspace.id)} + onClick={() => handleSelectWorkspace(workspace.id)} + > + [*] + {workspace.name || workspace.slug} +
  • + )} +
    +
+
+
+

+ Prices shown don't include applicable tax · Terms of Service +

+
+ + ) +} diff --git a/packages/console/core/migrations/0051_jazzy_green_goblin.sql b/packages/console/core/migrations/0051_jazzy_green_goblin.sql new file mode 100644 index 00000000000..cadb4a709e5 --- /dev/null +++ b/packages/console/core/migrations/0051_jazzy_green_goblin.sql @@ -0,0 +1 @@ +ALTER TABLE `billing` ADD `time_subscription_booked` timestamp(3); \ No newline at end of file diff --git a/packages/console/core/migrations/0052_aromatic_agent_zero.sql b/packages/console/core/migrations/0052_aromatic_agent_zero.sql new file mode 100644 index 00000000000..c53ba5e2b9c --- /dev/null +++ b/packages/console/core/migrations/0052_aromatic_agent_zero.sql @@ -0,0 +1 @@ +ALTER TABLE `billing` ADD `subscription_plan` enum('20','100','200'); \ No newline at end of file diff --git a/packages/console/core/migrations/meta/0051_snapshot.json b/packages/console/core/migrations/meta/0051_snapshot.json new file mode 100644 index 00000000000..0f904791623 --- /dev/null +++ b/packages/console/core/migrations/meta/0051_snapshot.json @@ -0,0 +1,1228 @@ +{ + "version": "5", + "dialect": "mysql", + "id": "14cbf4c8-55f1-4488-956f-56fb5ccb3a5a", + "prevId": "a0d18802-c390-47d4-98f1-d1f83869cf0c", + "tables": { + "account": { + "name": "account", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "account_id_pk": { + "name": "account_id_pk", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "auth": { + "name": "auth", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "enum('email','github','google')", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject": { + "name": "subject", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "account_id": { + "name": "account_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "provider": { + "name": "provider", + "columns": ["provider", "subject"], + "isUnique": true + }, + "account_id": { + "name": "account_id", + "columns": ["account_id"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "auth_id_pk": { + "name": "auth_id_pk", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "benchmark": { + "name": "benchmark", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "model": { + "name": "model", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "agent": { + "name": "agent", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "result": { + "name": "result", + "type": "mediumtext", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "time_created": { + "name": "time_created", + "columns": ["time_created"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "benchmark_id_pk": { + "name": "benchmark_id_pk", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "billing": { + "name": "billing", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "customer_id": { + "name": "customer_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payment_method_id": { + "name": "payment_method_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payment_method_type": { + "name": "payment_method_type", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payment_method_last4": { + "name": "payment_method_last4", + "type": "varchar(4)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "balance": { + "name": "balance", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "monthly_limit": { + "name": "monthly_limit", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "monthly_usage": { + "name": "monthly_usage", + "type": "bigint", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "time_monthly_usage_updated": { + "name": "time_monthly_usage_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reload": { + "name": "reload", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reload_trigger": { + "name": "reload_trigger", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reload_amount": { + "name": "reload_amount", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reload_error": { + "name": "reload_error", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "time_reload_error": { + "name": "time_reload_error", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "time_reload_locked_till": { + "name": "time_reload_locked_till", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "subscription_id": { + "name": "subscription_id", + "type": "varchar(28)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "subscription_coupon_id": { + "name": "subscription_coupon_id", + "type": "varchar(28)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "time_subscription_booked": { + "name": "time_subscription_booked", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "global_customer_id": { + "name": "global_customer_id", + "columns": ["customer_id"], + "isUnique": true + }, + "global_subscription_id": { + "name": "global_subscription_id", + "columns": ["subscription_id"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "billing_workspace_id_id_pk": { + "name": "billing_workspace_id_id_pk", + "columns": ["workspace_id", "id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "payment": { + "name": "payment", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "customer_id": { + "name": "customer_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "invoice_id": { + "name": "invoice_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payment_id": { + "name": "payment_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "amount": { + "name": "amount", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_refunded": { + "name": "time_refunded", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enrichment": { + "name": "enrichment", + "type": "json", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "payment_workspace_id_id_pk": { + "name": "payment_workspace_id_id_pk", + "columns": ["workspace_id", "id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "subscription": { + "name": "subscription", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "rolling_usage": { + "name": "rolling_usage", + "type": "bigint", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "fixed_usage": { + "name": "fixed_usage", + "type": "bigint", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "time_rolling_updated": { + "name": "time_rolling_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "time_fixed_updated": { + "name": "time_fixed_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "workspace_user_id": { + "name": "workspace_user_id", + "columns": ["workspace_id", "user_id"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "subscription_workspace_id_id_pk": { + "name": "subscription_workspace_id_id_pk", + "columns": ["workspace_id", "id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "usage": { + "name": "usage", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "model": { + "name": "model", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "input_tokens": { + "name": "input_tokens", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "output_tokens": { + "name": "output_tokens", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reasoning_tokens": { + "name": "reasoning_tokens", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cache_read_tokens": { + "name": "cache_read_tokens", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cache_write_5m_tokens": { + "name": "cache_write_5m_tokens", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cache_write_1h_tokens": { + "name": "cache_write_1h_tokens", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cost": { + "name": "cost", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key_id": { + "name": "key_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enrichment": { + "name": "enrichment", + "type": "json", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "usage_time_created": { + "name": "usage_time_created", + "columns": ["workspace_id", "time_created"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "usage_workspace_id_id_pk": { + "name": "usage_workspace_id_id_pk", + "columns": ["workspace_id", "id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "ip_rate_limit": { + "name": "ip_rate_limit", + "columns": { + "ip": { + "name": "ip", + "type": "varchar(45)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "interval": { + "name": "interval", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "count": { + "name": "count", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "ip_rate_limit_ip_interval_pk": { + "name": "ip_rate_limit_ip_interval_pk", + "columns": ["ip", "interval"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "ip": { + "name": "ip", + "columns": { + "ip": { + "name": "ip", + "type": "varchar(45)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "usage": { + "name": "usage", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "ip_ip_pk": { + "name": "ip_ip_pk", + "columns": ["ip"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "key": { + "name": "key", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_used": { + "name": "time_used", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "global_key": { + "name": "global_key", + "columns": ["key"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "key_workspace_id_id_pk": { + "name": "key_workspace_id_id_pk", + "columns": ["workspace_id", "id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "model": { + "name": "model", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "model": { + "name": "model", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "model_workspace_model": { + "name": "model_workspace_model", + "columns": ["workspace_id", "model"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "model_workspace_id_id_pk": { + "name": "model_workspace_id_id_pk", + "columns": ["workspace_id", "id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "provider": { + "name": "provider", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "credentials": { + "name": "credentials", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "workspace_provider": { + "name": "workspace_provider", + "columns": ["workspace_id", "provider"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "provider_workspace_id_id_pk": { + "name": "provider_workspace_id_id_pk", + "columns": ["workspace_id", "id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "account_id": { + "name": "account_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_seen": { + "name": "time_seen", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "color": { + "name": "color", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "enum('admin','member')", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "monthly_limit": { + "name": "monthly_limit", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "monthly_usage": { + "name": "monthly_usage", + "type": "bigint", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "time_monthly_usage_updated": { + "name": "time_monthly_usage_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "user_account_id": { + "name": "user_account_id", + "columns": ["workspace_id", "account_id"], + "isUnique": true + }, + "user_email": { + "name": "user_email", + "columns": ["workspace_id", "email"], + "isUnique": true + }, + "global_account_id": { + "name": "global_account_id", + "columns": ["account_id"], + "isUnique": false + }, + "global_email": { + "name": "global_email", + "columns": ["email"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "user_workspace_id_id_pk": { + "name": "user_workspace_id_id_pk", + "columns": ["workspace_id", "id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "workspace": { + "name": "workspace", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "slug": { + "name": "slug", + "columns": ["slug"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "workspace_id": { + "name": "workspace_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + } + }, + "views": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "tables": {}, + "indexes": {} + } +} diff --git a/packages/console/core/migrations/meta/0052_snapshot.json b/packages/console/core/migrations/meta/0052_snapshot.json new file mode 100644 index 00000000000..339e153eba8 --- /dev/null +++ b/packages/console/core/migrations/meta/0052_snapshot.json @@ -0,0 +1,1235 @@ +{ + "version": "5", + "dialect": "mysql", + "id": "00774acd-a1e5-49c0-b296-cacc9506a566", + "prevId": "14cbf4c8-55f1-4488-956f-56fb5ccb3a5a", + "tables": { + "account": { + "name": "account", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "account_id_pk": { + "name": "account_id_pk", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "auth": { + "name": "auth", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "enum('email','github','google')", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject": { + "name": "subject", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "account_id": { + "name": "account_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "provider": { + "name": "provider", + "columns": ["provider", "subject"], + "isUnique": true + }, + "account_id": { + "name": "account_id", + "columns": ["account_id"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "auth_id_pk": { + "name": "auth_id_pk", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "benchmark": { + "name": "benchmark", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "model": { + "name": "model", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "agent": { + "name": "agent", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "result": { + "name": "result", + "type": "mediumtext", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "time_created": { + "name": "time_created", + "columns": ["time_created"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "benchmark_id_pk": { + "name": "benchmark_id_pk", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "billing": { + "name": "billing", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "customer_id": { + "name": "customer_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payment_method_id": { + "name": "payment_method_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payment_method_type": { + "name": "payment_method_type", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payment_method_last4": { + "name": "payment_method_last4", + "type": "varchar(4)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "balance": { + "name": "balance", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "monthly_limit": { + "name": "monthly_limit", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "monthly_usage": { + "name": "monthly_usage", + "type": "bigint", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "time_monthly_usage_updated": { + "name": "time_monthly_usage_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reload": { + "name": "reload", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reload_trigger": { + "name": "reload_trigger", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reload_amount": { + "name": "reload_amount", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reload_error": { + "name": "reload_error", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "time_reload_error": { + "name": "time_reload_error", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "time_reload_locked_till": { + "name": "time_reload_locked_till", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "subscription_id": { + "name": "subscription_id", + "type": "varchar(28)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "subscription_coupon_id": { + "name": "subscription_coupon_id", + "type": "varchar(28)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "subscription_plan": { + "name": "subscription_plan", + "type": "enum('20','100','200')", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "time_subscription_booked": { + "name": "time_subscription_booked", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "global_customer_id": { + "name": "global_customer_id", + "columns": ["customer_id"], + "isUnique": true + }, + "global_subscription_id": { + "name": "global_subscription_id", + "columns": ["subscription_id"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "billing_workspace_id_id_pk": { + "name": "billing_workspace_id_id_pk", + "columns": ["workspace_id", "id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "payment": { + "name": "payment", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "customer_id": { + "name": "customer_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "invoice_id": { + "name": "invoice_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payment_id": { + "name": "payment_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "amount": { + "name": "amount", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_refunded": { + "name": "time_refunded", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enrichment": { + "name": "enrichment", + "type": "json", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "payment_workspace_id_id_pk": { + "name": "payment_workspace_id_id_pk", + "columns": ["workspace_id", "id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "subscription": { + "name": "subscription", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "rolling_usage": { + "name": "rolling_usage", + "type": "bigint", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "fixed_usage": { + "name": "fixed_usage", + "type": "bigint", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "time_rolling_updated": { + "name": "time_rolling_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "time_fixed_updated": { + "name": "time_fixed_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "workspace_user_id": { + "name": "workspace_user_id", + "columns": ["workspace_id", "user_id"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "subscription_workspace_id_id_pk": { + "name": "subscription_workspace_id_id_pk", + "columns": ["workspace_id", "id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "usage": { + "name": "usage", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "model": { + "name": "model", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "input_tokens": { + "name": "input_tokens", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "output_tokens": { + "name": "output_tokens", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reasoning_tokens": { + "name": "reasoning_tokens", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cache_read_tokens": { + "name": "cache_read_tokens", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cache_write_5m_tokens": { + "name": "cache_write_5m_tokens", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cache_write_1h_tokens": { + "name": "cache_write_1h_tokens", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cost": { + "name": "cost", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key_id": { + "name": "key_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enrichment": { + "name": "enrichment", + "type": "json", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "usage_time_created": { + "name": "usage_time_created", + "columns": ["workspace_id", "time_created"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "usage_workspace_id_id_pk": { + "name": "usage_workspace_id_id_pk", + "columns": ["workspace_id", "id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "ip_rate_limit": { + "name": "ip_rate_limit", + "columns": { + "ip": { + "name": "ip", + "type": "varchar(45)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "interval": { + "name": "interval", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "count": { + "name": "count", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "ip_rate_limit_ip_interval_pk": { + "name": "ip_rate_limit_ip_interval_pk", + "columns": ["ip", "interval"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "ip": { + "name": "ip", + "columns": { + "ip": { + "name": "ip", + "type": "varchar(45)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "usage": { + "name": "usage", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "ip_ip_pk": { + "name": "ip_ip_pk", + "columns": ["ip"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "key": { + "name": "key", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_used": { + "name": "time_used", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "global_key": { + "name": "global_key", + "columns": ["key"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "key_workspace_id_id_pk": { + "name": "key_workspace_id_id_pk", + "columns": ["workspace_id", "id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "model": { + "name": "model", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "model": { + "name": "model", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "model_workspace_model": { + "name": "model_workspace_model", + "columns": ["workspace_id", "model"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "model_workspace_id_id_pk": { + "name": "model_workspace_id_id_pk", + "columns": ["workspace_id", "id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "provider": { + "name": "provider", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "credentials": { + "name": "credentials", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "workspace_provider": { + "name": "workspace_provider", + "columns": ["workspace_id", "provider"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "provider_workspace_id_id_pk": { + "name": "provider_workspace_id_id_pk", + "columns": ["workspace_id", "id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "account_id": { + "name": "account_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_seen": { + "name": "time_seen", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "color": { + "name": "color", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "enum('admin','member')", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "monthly_limit": { + "name": "monthly_limit", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "monthly_usage": { + "name": "monthly_usage", + "type": "bigint", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "time_monthly_usage_updated": { + "name": "time_monthly_usage_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "user_account_id": { + "name": "user_account_id", + "columns": ["workspace_id", "account_id"], + "isUnique": true + }, + "user_email": { + "name": "user_email", + "columns": ["workspace_id", "email"], + "isUnique": true + }, + "global_account_id": { + "name": "global_account_id", + "columns": ["account_id"], + "isUnique": false + }, + "global_email": { + "name": "global_email", + "columns": ["email"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "user_workspace_id_id_pk": { + "name": "user_workspace_id_id_pk", + "columns": ["workspace_id", "id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "workspace": { + "name": "workspace", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "slug": { + "name": "slug", + "columns": ["slug"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "workspace_id": { + "name": "workspace_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + } + }, + "views": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "tables": {}, + "indexes": {} + } +} diff --git a/packages/console/core/migrations/meta/_journal.json b/packages/console/core/migrations/meta/_journal.json index 9982daef5ea..cdf4f63906d 100644 --- a/packages/console/core/migrations/meta/_journal.json +++ b/packages/console/core/migrations/meta/_journal.json @@ -358,6 +358,20 @@ "when": 1767931290031, "tag": "0050_bumpy_mephistopheles", "breakpoints": true + }, + { + "idx": 51, + "version": "5", + "when": 1768341152722, + "tag": "0051_jazzy_green_goblin", + "breakpoints": true + }, + { + "idx": 52, + "version": "5", + "when": 1768343920467, + "tag": "0052_aromatic_agent_zero", + "breakpoints": true } ] } diff --git a/packages/console/core/package.json b/packages/console/core/package.json index 5e42246099e..4bd49d75805 100644 --- a/packages/console/core/package.json +++ b/packages/console/core/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/console-core", - "version": "1.1.19", + "version": "1.1.20", "private": true, "type": "module", "license": "MIT", diff --git a/packages/console/core/src/schema/billing.sql.ts b/packages/console/core/src/schema/billing.sql.ts index 6c2cfcf96f8..f1300f8498b 100644 --- a/packages/console/core/src/schema/billing.sql.ts +++ b/packages/console/core/src/schema/billing.sql.ts @@ -1,4 +1,4 @@ -import { bigint, boolean, index, int, json, mysqlTable, uniqueIndex, varchar } from "drizzle-orm/mysql-core" +import { bigint, boolean, index, int, json, mysqlEnum, mysqlTable, uniqueIndex, varchar } from "drizzle-orm/mysql-core" import { timestamps, ulid, utc, workspaceColumns } from "../drizzle/types" import { workspaceIndexes } from "./workspace.sql" @@ -23,6 +23,8 @@ export const BillingTable = mysqlTable( timeReloadLockedTill: utc("time_reload_locked_till"), subscriptionID: varchar("subscription_id", { length: 28 }), subscriptionCouponID: varchar("subscription_coupon_id", { length: 28 }), + subscriptionPlan: mysqlEnum("subscription_plan", ["20", "100", "200"] as const), + timeSubscriptionBooked: utc("time_subscription_booked"), }, (table) => [ ...workspaceIndexes(table), diff --git a/packages/console/core/sst-env.d.ts b/packages/console/core/sst-env.d.ts index 96fada3e3c0..b8e50a26113 100644 --- a/packages/console/core/sst-env.d.ts +++ b/packages/console/core/sst-env.d.ts @@ -78,6 +78,10 @@ declare module "sst" { "type": "sst.sst.Secret" "value": string } + "STRIPE_PUBLISHABLE_KEY": { + "type": "sst.sst.Secret" + "value": string + } "STRIPE_SECRET_KEY": { "type": "sst.sst.Secret" "value": string diff --git a/packages/console/function/package.json b/packages/console/function/package.json index 976e9e59d6a..15402867388 100644 --- a/packages/console/function/package.json +++ b/packages/console/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-function", - "version": "1.1.19", + "version": "1.1.20", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/console/function/sst-env.d.ts b/packages/console/function/sst-env.d.ts index 96fada3e3c0..b8e50a26113 100644 --- a/packages/console/function/sst-env.d.ts +++ b/packages/console/function/sst-env.d.ts @@ -78,6 +78,10 @@ declare module "sst" { "type": "sst.sst.Secret" "value": string } + "STRIPE_PUBLISHABLE_KEY": { + "type": "sst.sst.Secret" + "value": string + } "STRIPE_SECRET_KEY": { "type": "sst.sst.Secret" "value": string diff --git a/packages/console/mail/package.json b/packages/console/mail/package.json index e0b5097dad5..13164c6d1b7 100644 --- a/packages/console/mail/package.json +++ b/packages/console/mail/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-mail", - "version": "1.1.19", + "version": "1.1.20", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", diff --git a/packages/console/resource/sst-env.d.ts b/packages/console/resource/sst-env.d.ts index 96fada3e3c0..b8e50a26113 100644 --- a/packages/console/resource/sst-env.d.ts +++ b/packages/console/resource/sst-env.d.ts @@ -78,6 +78,10 @@ declare module "sst" { "type": "sst.sst.Secret" "value": string } + "STRIPE_PUBLISHABLE_KEY": { + "type": "sst.sst.Secret" + "value": string + } "STRIPE_SECRET_KEY": { "type": "sst.sst.Secret" "value": string diff --git a/packages/desktop/package.json b/packages/desktop/package.json index 864d849709e..b326c9d98d4 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@shuvcode/desktop", "private": true, - "version": "1.1.19", + "version": "1.1.20", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/enterprise/package.json b/packages/enterprise/package.json index a5c357b3e60..db9d75a015d 100644 --- a/packages/enterprise/package.json +++ b/packages/enterprise/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/enterprise", - "version": "1.1.19", + "version": "1.1.20", "private": true, "type": "module", "license": "MIT", diff --git a/packages/enterprise/sst-env.d.ts b/packages/enterprise/sst-env.d.ts index 96fada3e3c0..b8e50a26113 100644 --- a/packages/enterprise/sst-env.d.ts +++ b/packages/enterprise/sst-env.d.ts @@ -78,6 +78,10 @@ declare module "sst" { "type": "sst.sst.Secret" "value": string } + "STRIPE_PUBLISHABLE_KEY": { + "type": "sst.sst.Secret" + "value": string + } "STRIPE_SECRET_KEY": { "type": "sst.sst.Secret" "value": string diff --git a/packages/extensions/zed/extension.toml b/packages/extensions/zed/extension.toml index db8c34f593f..6d9a654dbd8 100644 --- a/packages/extensions/zed/extension.toml +++ b/packages/extensions/zed/extension.toml @@ -1,7 +1,7 @@ id = "opencode" name = "OpenCode" description = "The open source coding agent." -version = "1.1.19" +version = "1.1.20" schema_version = 1 authors = ["Anomaly"] repository = "https://github.com/anomalyco/opencode" @@ -11,26 +11,26 @@ name = "OpenCode" icon = "./icons/opencode.svg" [agent_servers.opencode.targets.darwin-aarch64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.19/opencode-darwin-arm64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.20/opencode-darwin-arm64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.darwin-x86_64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.19/opencode-darwin-x64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.20/opencode-darwin-x64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-aarch64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.19/opencode-linux-arm64.tar.gz" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.20/opencode-linux-arm64.tar.gz" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-x86_64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.19/opencode-linux-x64.tar.gz" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.20/opencode-linux-x64.tar.gz" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.windows-x86_64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.19/opencode-windows-x64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.20/opencode-windows-x64.zip" cmd = "./opencode.exe" args = ["acp"] diff --git a/packages/function/package.json b/packages/function/package.json index b0b3e1ce51d..3c14b60fd56 100644 --- a/packages/function/package.json +++ b/packages/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/function", - "version": "1.1.19", + "version": "1.1.20", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/function/sst-env.d.ts b/packages/function/sst-env.d.ts index 96fada3e3c0..b8e50a26113 100644 --- a/packages/function/sst-env.d.ts +++ b/packages/function/sst-env.d.ts @@ -78,6 +78,10 @@ declare module "sst" { "type": "sst.sst.Secret" "value": string } + "STRIPE_PUBLISHABLE_KEY": { + "type": "sst.sst.Secret" + "value": string + } "STRIPE_SECRET_KEY": { "type": "sst.sst.Secret" "value": string diff --git a/packages/opencode/openapi.json b/packages/opencode/openapi.json new file mode 100644 index 00000000000..86ca149ec29 --- /dev/null +++ b/packages/opencode/openapi.json @@ -0,0 +1,12102 @@ +{ + "openapi": "3.1.1", + "info": { + "title": "opencode", + "description": "opencode api", + "version": "1.0.0" + }, + "paths": { + "/global/health": { + "get": { + "operationId": "global.health", + "summary": "Get health", + "description": "Get health information about the OpenCode server.", + "responses": { + "200": { + "description": "Health information", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "healthy": { + "type": "boolean", + "const": true + }, + "version": { + "type": "string" + } + }, + "required": [ + "healthy", + "version" + ] + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.global.health({\n ...\n})" + } + ] + } + }, + "/global/event": { + "get": { + "operationId": "global.event", + "summary": "Get global events", + "description": "Subscribe to global events from the OpenCode system using server-sent events.", + "responses": { + "200": { + "description": "Event stream", + "content": { + "text/event-stream": { + "schema": { + "$ref": "#/components/schemas/GlobalEvent" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.global.event({\n ...\n})" + } + ] + } + }, + "/global/dispose": { + "post": { + "operationId": "global.dispose", + "summary": "Dispose instance", + "description": "Clean up and dispose all OpenCode instances, releasing all resources.", + "responses": { + "200": { + "description": "Global disposed", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.global.dispose({\n ...\n})" + } + ] + } + }, + "/project": { + "get": { + "operationId": "project.list", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "List all projects", + "description": "Get a list of projects that have been opened with OpenCode.", + "responses": { + "200": { + "description": "List of projects", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Project" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.project.list({\n ...\n})" + } + ] + }, + "post": { + "operationId": "project.create", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Create project", + "description": "Create a new project directory and initialize it as a git repository, or add an existing directory as a project.", + "responses": { + "200": { + "description": "Created or added project information", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectCreateResult" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "path": { + "type": "string", + "minLength": 1 + }, + "name": { + "type": "string" + }, + "repo": { + "type": "string" + }, + "degit": { + "type": "boolean" + } + }, + "required": [ + "path" + ] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.project.create({\n ...\n})" + } + ] + } + }, + "/project/current": { + "get": { + "operationId": "project.current", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Get current project", + "description": "Retrieve the currently active project that OpenCode is working with.", + "responses": { + "200": { + "description": "Current project information", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Project" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.project.current({\n ...\n})" + } + ] + } + }, + "/project/{projectID}": { + "patch": { + "operationId": "project.update", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "projectID", + "schema": { + "type": "string" + }, + "required": true + } + ], + "summary": "Update project", + "description": "Update project properties such as name, icon and color.", + "responses": { + "200": { + "description": "Updated project information", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Project" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "icon": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "color": { + "type": "string" + } + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.project.update({\n ...\n})" + } + ] + } + }, + "/project/browse": { + "get": { + "operationId": "project.browse", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "query", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "limit", + "schema": { + "default": 50, + "type": "number" + } + } + ], + "summary": "Browse directories", + "description": "Browse directories from the user's home directory to find potential projects. Supports fuzzy search filtering.", + "responses": { + "200": { + "description": "List of directories", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DirectoryInfo" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.project.browse({\n ...\n})" + } + ] + } + }, + "/pty": { + "get": { + "operationId": "pty.list", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "List PTY sessions", + "description": "Get a list of all active pseudo-terminal (PTY) sessions managed by OpenCode.", + "responses": { + "200": { + "description": "List of sessions", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Pty" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.pty.list({\n ...\n})" + } + ] + }, + "post": { + "operationId": "pty.create", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Create PTY session", + "description": "Create a new pseudo-terminal (PTY) session for running shell commands and processes.", + "responses": { + "200": { + "description": "Created session", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pty" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "command": { + "type": "string" + }, + "args": { + "type": "array", + "items": { + "type": "string" + } + }, + "cwd": { + "type": "string" + }, + "title": { + "type": "string" + }, + "env": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string" + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.pty.create({\n ...\n})" + } + ] + } + }, + "/pty/{ptyID}": { + "get": { + "operationId": "pty.get", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "ptyID", + "schema": { + "type": "string" + }, + "required": true + } + ], + "summary": "Get PTY session", + "description": "Retrieve detailed information about a specific pseudo-terminal (PTY) session.", + "responses": { + "200": { + "description": "Session info", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pty" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.pty.get({\n ...\n})" + } + ] + }, + "put": { + "operationId": "pty.update", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "ptyID", + "schema": { + "type": "string" + }, + "required": true + } + ], + "summary": "Update PTY session", + "description": "Update properties of an existing pseudo-terminal (PTY) session.", + "responses": { + "200": { + "description": "Updated session", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pty" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "size": { + "type": "object", + "properties": { + "rows": { + "type": "number" + }, + "cols": { + "type": "number" + } + }, + "required": [ + "rows", + "cols" + ] + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.pty.update({\n ...\n})" + } + ] + }, + "delete": { + "operationId": "pty.remove", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "ptyID", + "schema": { + "type": "string" + }, + "required": true + } + ], + "summary": "Remove PTY session", + "description": "Remove and terminate a specific pseudo-terminal (PTY) session.", + "responses": { + "200": { + "description": "Session removed", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.pty.remove({\n ...\n})" + } + ] + } + }, + "/pty/{ptyID}/connect": { + "get": { + "operationId": "pty.connect", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "ptyID", + "schema": { + "type": "string" + }, + "required": true + } + ], + "summary": "Connect to PTY session", + "description": "Establish a WebSocket connection to interact with a pseudo-terminal (PTY) session in real-time.", + "responses": { + "200": { + "description": "Connected session", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.pty.connect({\n ...\n})" + } + ] + } + }, + "/config": { + "get": { + "operationId": "config.get", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Get configuration", + "description": "Retrieve the current OpenCode configuration settings and preferences.", + "responses": { + "200": { + "description": "Get config info", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Config" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.config.get({\n ...\n})" + } + ] + }, + "patch": { + "operationId": "config.update", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Update configuration", + "description": "Update OpenCode configuration settings and preferences.", + "responses": { + "200": { + "description": "Successfully updated config", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Config" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Config" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.config.update({\n ...\n})" + } + ] + } + }, + "/experimental/tool/ids": { + "get": { + "operationId": "tool.ids", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "List tool IDs", + "description": "Get a list of all available tool IDs, including both built-in tools and dynamically registered tools.", + "responses": { + "200": { + "description": "Tool IDs", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ToolIDs" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.tool.ids({\n ...\n})" + } + ] + } + }, + "/experimental/tool": { + "get": { + "operationId": "tool.list", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "provider", + "schema": { + "type": "string" + }, + "required": true + }, + { + "in": "query", + "name": "model", + "schema": { + "type": "string" + }, + "required": true + } + ], + "summary": "List tools", + "description": "Get a list of available tools with their JSON schema parameters for a specific provider and model combination.", + "responses": { + "200": { + "description": "Tools", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ToolList" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.tool.list({\n ...\n})" + } + ] + } + }, + "/instance/dispose": { + "post": { + "operationId": "instance.dispose", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Dispose instance", + "description": "Clean up and dispose the current OpenCode instance, releasing all resources.", + "responses": { + "200": { + "description": "Instance disposed", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.instance.dispose({\n ...\n})" + } + ] + } + }, + "/path": { + "get": { + "operationId": "path.get", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Get paths", + "description": "Retrieve the current working directory and related path information for the OpenCode instance.", + "responses": { + "200": { + "description": "Path", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Path" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.path.get({\n ...\n})" + } + ] + } + }, + "/experimental/worktree": { + "post": { + "operationId": "worktree.create", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Create worktree", + "description": "Create a new git worktree for the current project.", + "responses": { + "200": { + "description": "Worktree created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Worktree" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorktreeCreateInput" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.worktree.create({\n ...\n})" + } + ] + }, + "get": { + "operationId": "worktree.list", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "List worktrees", + "description": "List all sandbox worktrees for the current project.", + "responses": { + "200": { + "description": "List of worktree directories", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.worktree.list({\n ...\n})" + } + ] + } + }, + "/vcs": { + "get": { + "operationId": "vcs.get", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Get VCS info", + "description": "Retrieve version control system (VCS) information for the current project, such as git branch.", + "responses": { + "200": { + "description": "VCS info", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VcsInfo" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.vcs.get({\n ...\n})" + } + ] + } + }, + "/session": { + "get": { + "operationId": "session.list", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "start", + "schema": { + "type": "number" + }, + "description": "Filter sessions updated on or after this timestamp (milliseconds since epoch)" + }, + { + "in": "query", + "name": "search", + "schema": { + "type": "string" + }, + "description": "Filter sessions by title (case-insensitive)" + }, + { + "in": "query", + "name": "limit", + "schema": { + "type": "number" + }, + "description": "Maximum number of sessions to return" + } + ], + "summary": "List sessions", + "description": "Get a list of all OpenCode sessions, sorted by most recently updated.", + "responses": { + "200": { + "description": "List of sessions", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Session" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.list({\n ...\n})" + } + ] + }, + "post": { + "operationId": "session.create", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Create session", + "description": "Create a new OpenCode session for interacting with AI assistants and managing conversations.", + "responses": { + "200": { + "description": "Successfully created session", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Session" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "parentID": { + "type": "string", + "pattern": "^ses.*" + }, + "title": { + "type": "string" + }, + "permission": { + "$ref": "#/components/schemas/PermissionRuleset" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.create({\n ...\n})" + } + ] + } + }, + "/session/status": { + "get": { + "operationId": "session.status", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Get session status", + "description": "Retrieve the current status of all sessions, including active, idle, and completed states.", + "responses": { + "200": { + "description": "Get session status", + "content": { + "application/json": { + "schema": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "$ref": "#/components/schemas/SessionStatus" + } + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.status({\n ...\n})" + } + ] + } + }, + "/session/{sessionID}": { + "get": { + "operationId": "session.get", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "sessionID", + "schema": { + "type": "string", + "pattern": "^ses.*" + }, + "required": true + } + ], + "summary": "Get session", + "description": "Retrieve detailed information about a specific OpenCode session.", + "tags": [ + "Session" + ], + "responses": { + "200": { + "description": "Get session", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Session" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.get({\n ...\n})" + } + ] + }, + "delete": { + "operationId": "session.delete", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "sessionID", + "schema": { + "type": "string", + "pattern": "^ses.*" + }, + "required": true + } + ], + "summary": "Delete session", + "description": "Delete a session and permanently remove all associated data, including messages and history.", + "responses": { + "200": { + "description": "Successfully deleted session", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.delete({\n ...\n})" + } + ] + }, + "patch": { + "operationId": "session.update", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "sessionID", + "schema": { + "type": "string" + }, + "required": true + } + ], + "summary": "Update session", + "description": "Update properties of an existing session, such as title or other metadata.", + "responses": { + "200": { + "description": "Successfully updated session", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Session" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "time": { + "type": "object", + "properties": { + "archived": { + "type": "number" + } + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.update({\n ...\n})" + } + ] + } + }, + "/session/{sessionID}/children": { + "get": { + "operationId": "session.children", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "sessionID", + "schema": { + "type": "string", + "pattern": "^ses.*" + }, + "required": true + } + ], + "summary": "Get session children", + "tags": [ + "Session" + ], + "description": "Retrieve all child sessions that were forked from the specified parent session.", + "responses": { + "200": { + "description": "List of children", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Session" + } + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.children({\n ...\n})" + } + ] + } + }, + "/session/{sessionID}/todo": { + "get": { + "operationId": "session.todo", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "sessionID", + "schema": { + "type": "string" + }, + "required": true, + "description": "Session ID" + } + ], + "summary": "Get session todos", + "description": "Retrieve the todo list associated with a specific session, showing tasks and action items.", + "responses": { + "200": { + "description": "Todo list", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Todo" + } + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.todo({\n ...\n})" + } + ] + } + }, + "/session/{sessionID}/init": { + "post": { + "operationId": "session.init", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "sessionID", + "schema": { + "type": "string" + }, + "required": true, + "description": "Session ID" + } + ], + "summary": "Initialize session", + "description": "Analyze the current application and create an AGENTS.md file with project-specific agent configurations.", + "responses": { + "200": { + "description": "200", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "modelID": { + "type": "string" + }, + "providerID": { + "type": "string" + }, + "messageID": { + "type": "string", + "pattern": "^msg.*" + } + }, + "required": [ + "modelID", + "providerID", + "messageID" + ] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.init({\n ...\n})" + } + ] + } + }, + "/session/{sessionID}/fork": { + "post": { + "operationId": "session.fork", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "sessionID", + "schema": { + "type": "string", + "pattern": "^ses.*" + }, + "required": true + } + ], + "summary": "Fork session", + "description": "Create a new session by forking an existing session at a specific message point.", + "responses": { + "200": { + "description": "200", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Session" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "messageID": { + "type": "string", + "pattern": "^msg.*" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.fork({\n ...\n})" + } + ] + } + }, + "/session/{sessionID}/abort": { + "post": { + "operationId": "session.abort", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "sessionID", + "schema": { + "type": "string" + }, + "required": true + } + ], + "summary": "Abort session", + "description": "Abort an active session and stop any ongoing AI processing or command execution.", + "responses": { + "200": { + "description": "Aborted session", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.abort({\n ...\n})" + } + ] + } + }, + "/session/{sessionID}/share": { + "post": { + "operationId": "session.share", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "sessionID", + "schema": { + "type": "string" + }, + "required": true + } + ], + "summary": "Share session", + "description": "Create a shareable link for a session, allowing others to view the conversation.", + "responses": { + "200": { + "description": "Successfully shared session", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Session" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.share({\n ...\n})" + } + ] + }, + "delete": { + "operationId": "session.unshare", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "sessionID", + "schema": { + "type": "string", + "pattern": "^ses.*" + }, + "required": true + } + ], + "summary": "Unshare session", + "description": "Remove the shareable link for a session, making it private again.", + "responses": { + "200": { + "description": "Successfully unshared session", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Session" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.unshare({\n ...\n})" + } + ] + } + }, + "/session/{sessionID}/diff": { + "get": { + "operationId": "session.diff", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "sessionID", + "schema": { + "type": "string" + }, + "required": true, + "description": "Session ID" + }, + { + "in": "query", + "name": "messageID", + "schema": { + "type": "string", + "pattern": "^msg.*" + } + } + ], + "summary": "Get session diff", + "description": "Get all file changes (diffs) made during this session.", + "responses": { + "200": { + "description": "List of diffs", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FileDiff" + } + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.diff({\n ...\n})" + } + ] + } + }, + "/session/{sessionID}/summarize": { + "post": { + "operationId": "session.summarize", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "sessionID", + "schema": { + "type": "string" + }, + "required": true, + "description": "Session ID" + } + ], + "summary": "Summarize session", + "description": "Generate a concise summary of the session using AI compaction to preserve key information.", + "responses": { + "200": { + "description": "Summarized session", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "providerID": { + "type": "string" + }, + "modelID": { + "type": "string" + }, + "auto": { + "default": false, + "type": "boolean" + } + }, + "required": [ + "providerID", + "modelID" + ] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.summarize({\n ...\n})" + } + ] + } + }, + "/session/{sessionID}/message": { + "get": { + "operationId": "session.messages", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "sessionID", + "schema": { + "type": "string" + }, + "required": true, + "description": "Session ID" + }, + { + "in": "query", + "name": "limit", + "schema": { + "type": "number" + } + } + ], + "summary": "Get session messages", + "description": "Retrieve all messages in a session, including user prompts and AI responses.", + "responses": { + "200": { + "description": "List of messages", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "info": { + "$ref": "#/components/schemas/Message" + }, + "parts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Part" + } + } + }, + "required": [ + "info", + "parts" + ] + } + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.messages({\n ...\n})" + } + ] + }, + "post": { + "operationId": "session.prompt", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "sessionID", + "schema": { + "type": "string" + }, + "required": true, + "description": "Session ID" + } + ], + "summary": "Send message", + "description": "Create and send a new message to a session, streaming the AI response.", + "responses": { + "200": { + "description": "Created message", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "info": { + "$ref": "#/components/schemas/AssistantMessage" + }, + "parts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Part" + } + } + }, + "required": [ + "info", + "parts" + ] + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "messageID": { + "type": "string", + "pattern": "^msg.*" + }, + "model": { + "type": "object", + "properties": { + "providerID": { + "type": "string" + }, + "modelID": { + "type": "string" + } + }, + "required": [ + "providerID", + "modelID" + ] + }, + "agent": { + "type": "string" + }, + "noReply": { + "type": "boolean" + }, + "tools": { + "description": "@deprecated tools and permissions have been merged, you can set permissions on the session itself now", + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "boolean" + } + }, + "system": { + "type": "string" + }, + "variant": { + "type": "string" + }, + "parts": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/TextPartInput" + }, + { + "$ref": "#/components/schemas/FilePartInput" + }, + { + "$ref": "#/components/schemas/AgentPartInput" + }, + { + "$ref": "#/components/schemas/SubtaskPartInput" + } + ] + } + } + }, + "required": [ + "parts" + ] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.prompt({\n ...\n})" + } + ] + } + }, + "/session/{sessionID}/message/{messageID}": { + "get": { + "operationId": "session.message", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "sessionID", + "schema": { + "type": "string" + }, + "required": true, + "description": "Session ID" + }, + { + "in": "path", + "name": "messageID", + "schema": { + "type": "string" + }, + "required": true, + "description": "Message ID" + } + ], + "summary": "Get message", + "description": "Retrieve a specific message from a session by its message ID.", + "responses": { + "200": { + "description": "Message", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "info": { + "$ref": "#/components/schemas/Message" + }, + "parts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Part" + } + } + }, + "required": [ + "info", + "parts" + ] + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.message({\n ...\n})" + } + ] + } + }, + "/session/{sessionID}/message/{messageID}/part/{partID}": { + "delete": { + "operationId": "part.delete", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "sessionID", + "schema": { + "type": "string" + }, + "required": true, + "description": "Session ID" + }, + { + "in": "path", + "name": "messageID", + "schema": { + "type": "string" + }, + "required": true, + "description": "Message ID" + }, + { + "in": "path", + "name": "partID", + "schema": { + "type": "string" + }, + "required": true, + "description": "Part ID" + } + ], + "description": "Delete a part from a message", + "responses": { + "200": { + "description": "Successfully deleted part", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.part.delete({\n ...\n})" + } + ] + }, + "patch": { + "operationId": "part.update", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "sessionID", + "schema": { + "type": "string" + }, + "required": true, + "description": "Session ID" + }, + { + "in": "path", + "name": "messageID", + "schema": { + "type": "string" + }, + "required": true, + "description": "Message ID" + }, + { + "in": "path", + "name": "partID", + "schema": { + "type": "string" + }, + "required": true, + "description": "Part ID" + } + ], + "description": "Update a part in a message", + "responses": { + "200": { + "description": "Successfully updated part", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Part" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Part" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.part.update({\n ...\n})" + } + ] + } + }, + "/session/{sessionID}/prompt_async": { + "post": { + "operationId": "session.prompt_async", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "sessionID", + "schema": { + "type": "string" + }, + "required": true, + "description": "Session ID" + } + ], + "summary": "Send async message", + "description": "Create and send a new message to a session asynchronously, starting the session if needed and returning immediately.", + "responses": { + "204": { + "description": "Prompt accepted" + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "messageID": { + "type": "string", + "pattern": "^msg.*" + }, + "model": { + "type": "object", + "properties": { + "providerID": { + "type": "string" + }, + "modelID": { + "type": "string" + } + }, + "required": [ + "providerID", + "modelID" + ] + }, + "agent": { + "type": "string" + }, + "noReply": { + "type": "boolean" + }, + "tools": { + "description": "@deprecated tools and permissions have been merged, you can set permissions on the session itself now", + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "boolean" + } + }, + "system": { + "type": "string" + }, + "variant": { + "type": "string" + }, + "parts": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/TextPartInput" + }, + { + "$ref": "#/components/schemas/FilePartInput" + }, + { + "$ref": "#/components/schemas/AgentPartInput" + }, + { + "$ref": "#/components/schemas/SubtaskPartInput" + } + ] + } + } + }, + "required": [ + "parts" + ] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.prompt_async({\n ...\n})" + } + ] + } + }, + "/session/{sessionID}/command": { + "post": { + "operationId": "session.command", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "sessionID", + "schema": { + "type": "string" + }, + "required": true, + "description": "Session ID" + } + ], + "summary": "Send command", + "description": "Send a new command to a session for execution by the AI assistant.", + "responses": { + "200": { + "description": "Created message", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "info": { + "$ref": "#/components/schemas/AssistantMessage" + }, + "parts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Part" + } + } + }, + "required": [ + "info", + "parts" + ] + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "messageID": { + "type": "string", + "pattern": "^msg.*" + }, + "agent": { + "type": "string" + }, + "model": { + "type": "string" + }, + "arguments": { + "type": "string" + }, + "command": { + "type": "string" + }, + "variant": { + "type": "string" + }, + "parts": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string", + "const": "file" + }, + "mime": { + "type": "string" + }, + "filename": { + "type": "string" + }, + "url": { + "type": "string" + }, + "source": { + "$ref": "#/components/schemas/FilePartSource" + } + }, + "required": [ + "type", + "mime", + "url" + ] + } + ] + } + } + }, + "required": [ + "arguments", + "command" + ] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.command({\n ...\n})" + } + ] + } + }, + "/session/{sessionID}/shell": { + "post": { + "operationId": "session.shell", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "sessionID", + "schema": { + "type": "string" + }, + "required": true, + "description": "Session ID" + } + ], + "summary": "Run shell command", + "description": "Execute a shell command within the session context and return the AI's response.", + "responses": { + "200": { + "description": "Created message", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AssistantMessage" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "agent": { + "type": "string" + }, + "model": { + "type": "object", + "properties": { + "providerID": { + "type": "string" + }, + "modelID": { + "type": "string" + } + }, + "required": [ + "providerID", + "modelID" + ] + }, + "command": { + "type": "string" + } + }, + "required": [ + "agent", + "command" + ] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.shell({\n ...\n})" + } + ] + } + }, + "/session/{sessionID}/revert": { + "post": { + "operationId": "session.revert", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "sessionID", + "schema": { + "type": "string" + }, + "required": true + } + ], + "summary": "Revert message", + "description": "Revert a specific message in a session, undoing its effects and restoring the previous state.", + "responses": { + "200": { + "description": "Updated session", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Session" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "messageID": { + "type": "string", + "pattern": "^msg.*" + }, + "partID": { + "type": "string", + "pattern": "^prt.*" + } + }, + "required": [ + "messageID" + ] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.revert({\n ...\n})" + } + ] + } + }, + "/session/{sessionID}/unrevert": { + "post": { + "operationId": "session.unrevert", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "sessionID", + "schema": { + "type": "string" + }, + "required": true + } + ], + "summary": "Restore reverted messages", + "description": "Restore all previously reverted messages in a session.", + "responses": { + "200": { + "description": "Updated session", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Session" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.unrevert({\n ...\n})" + } + ] + } + }, + "/session/{sessionID}/permissions/{permissionID}": { + "post": { + "operationId": "permission.respond", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "sessionID", + "schema": { + "type": "string" + }, + "required": true + }, + { + "in": "path", + "name": "permissionID", + "schema": { + "type": "string" + }, + "required": true + } + ], + "summary": "Respond to permission", + "deprecated": true, + "description": "Approve or deny a permission request from the AI assistant.", + "responses": { + "200": { + "description": "Permission processed successfully", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "response": { + "type": "string", + "enum": [ + "once", + "always", + "reject" + ] + } + }, + "required": [ + "response" + ] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.permission.respond({\n ...\n})" + } + ] + } + }, + "/permission/{requestID}/reply": { + "post": { + "operationId": "permission.reply", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "requestID", + "schema": { + "type": "string" + }, + "required": true + } + ], + "summary": "Respond to permission request", + "description": "Approve or deny a permission request from the AI assistant.", + "responses": { + "200": { + "description": "Permission processed successfully", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "reply": { + "type": "string", + "enum": [ + "once", + "always", + "reject" + ] + }, + "message": { + "type": "string" + } + }, + "required": [ + "reply" + ] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.permission.reply({\n ...\n})" + } + ] + } + }, + "/permission": { + "get": { + "operationId": "permission.list", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "List pending permissions", + "description": "Get all pending permission requests across all sessions.", + "responses": { + "200": { + "description": "List of pending permissions", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PermissionRequest" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.permission.list({\n ...\n})" + } + ] + } + }, + "/question": { + "get": { + "operationId": "question.list", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "List pending questions", + "description": "Get all pending question requests across all sessions.", + "responses": { + "200": { + "description": "List of pending questions", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/QuestionRequest" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.question.list({\n ...\n})" + } + ] + } + }, + "/question/{requestID}/reply": { + "post": { + "operationId": "question.reply", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "requestID", + "schema": { + "type": "string" + }, + "required": true + } + ], + "summary": "Reply to question request", + "description": "Provide answers to a question request from the AI assistant.", + "responses": { + "200": { + "description": "Question answered successfully", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "answers": { + "description": "User answers in order of questions (each answer is an array of selected labels)", + "type": "array", + "items": { + "$ref": "#/components/schemas/QuestionAnswer" + } + } + }, + "required": [ + "answers" + ] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.question.reply({\n ...\n})" + } + ] + } + }, + "/question/{requestID}/reject": { + "post": { + "operationId": "question.reject", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "requestID", + "schema": { + "type": "string" + }, + "required": true + } + ], + "summary": "Reject question request", + "description": "Reject a question request from the AI assistant.", + "responses": { + "200": { + "description": "Question rejected successfully", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.question.reject({\n ...\n})" + } + ] + } + }, + "/command": { + "get": { + "operationId": "command.list", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "List commands", + "description": "Get a list of all available commands in the OpenCode system.", + "responses": { + "200": { + "description": "List of commands", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Command" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.command.list({\n ...\n})" + } + ] + } + }, + "/config/providers": { + "get": { + "operationId": "config.providers", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "List config providers", + "description": "Get a list of all configured AI providers and their default models.", + "responses": { + "200": { + "description": "List of providers", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "providers": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Provider" + } + }, + "default": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string" + } + } + }, + "required": [ + "providers", + "default" + ] + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.config.providers({\n ...\n})" + } + ] + } + }, + "/provider": { + "get": { + "operationId": "provider.list", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "List providers", + "description": "Get a list of all available AI providers, including both available and connected ones.", + "responses": { + "200": { + "description": "List of providers", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "all": { + "type": "array", + "items": { + "type": "object", + "properties": { + "api": { + "type": "string" + }, + "name": { + "type": "string" + }, + "env": { + "type": "array", + "items": { + "type": "string" + } + }, + "id": { + "type": "string" + }, + "npm": { + "type": "string" + }, + "models": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "family": { + "type": "string" + }, + "release_date": { + "type": "string" + }, + "attachment": { + "type": "boolean" + }, + "reasoning": { + "type": "boolean" + }, + "temperature": { + "type": "boolean" + }, + "tool_call": { + "type": "boolean" + }, + "interleaved": { + "anyOf": [ + { + "type": "boolean", + "const": true + }, + { + "type": "object", + "properties": { + "field": { + "type": "string", + "enum": [ + "reasoning_content", + "reasoning_details" + ] + } + }, + "required": [ + "field" + ], + "additionalProperties": false + } + ] + }, + "cost": { + "type": "object", + "properties": { + "input": { + "type": "number" + }, + "output": { + "type": "number" + }, + "cache_read": { + "type": "number" + }, + "cache_write": { + "type": "number" + }, + "context_over_200k": { + "type": "object", + "properties": { + "input": { + "type": "number" + }, + "output": { + "type": "number" + }, + "cache_read": { + "type": "number" + }, + "cache_write": { + "type": "number" + } + }, + "required": [ + "input", + "output" + ] + } + }, + "required": [ + "input", + "output" + ] + }, + "limit": { + "type": "object", + "properties": { + "context": { + "type": "number" + }, + "output": { + "type": "number" + } + }, + "required": [ + "context", + "output" + ] + }, + "modalities": { + "type": "object", + "properties": { + "input": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "text", + "audio", + "image", + "video", + "pdf" + ] + } + }, + "output": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "text", + "audio", + "image", + "video", + "pdf" + ] + } + } + }, + "required": [ + "input", + "output" + ] + }, + "experimental": { + "type": "boolean" + }, + "status": { + "type": "string", + "enum": [ + "alpha", + "beta", + "deprecated" + ] + }, + "options": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + }, + "headers": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string" + } + }, + "provider": { + "type": "object", + "properties": { + "npm": { + "type": "string" + } + }, + "required": [ + "npm" + ] + }, + "variants": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + } + } + }, + "required": [ + "id", + "name", + "release_date", + "attachment", + "reasoning", + "temperature", + "tool_call", + "limit", + "options" + ] + } + } + }, + "required": [ + "name", + "env", + "id", + "models" + ] + } + }, + "default": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string" + } + }, + "connected": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "all", + "default", + "connected" + ] + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.provider.list({\n ...\n})" + } + ] + } + }, + "/provider/auth": { + "get": { + "operationId": "provider.auth", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Get provider auth methods", + "description": "Retrieve available authentication methods for all AI providers.", + "responses": { + "200": { + "description": "Provider auth methods", + "content": { + "application/json": { + "schema": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ProviderAuthMethod" + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.provider.auth({\n ...\n})" + } + ] + } + }, + "/provider/{providerID}/oauth/authorize": { + "post": { + "operationId": "provider.oauth.authorize", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "providerID", + "schema": { + "type": "string" + }, + "required": true, + "description": "Provider ID" + } + ], + "summary": "OAuth authorize", + "description": "Initiate OAuth authorization for a specific AI provider to get an authorization URL.", + "responses": { + "200": { + "description": "Authorization URL and method", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProviderAuthAuthorization" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "method": { + "description": "Auth method index", + "type": "number" + } + }, + "required": [ + "method" + ] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.provider.oauth.authorize({\n ...\n})" + } + ] + } + }, + "/provider/{providerID}/oauth/callback": { + "post": { + "operationId": "provider.oauth.callback", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "providerID", + "schema": { + "type": "string" + }, + "required": true, + "description": "Provider ID" + } + ], + "summary": "OAuth callback", + "description": "Handle the OAuth callback from a provider after user authorization.", + "responses": { + "200": { + "description": "OAuth callback processed successfully", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "method": { + "description": "Auth method index", + "type": "number" + }, + "code": { + "description": "OAuth authorization code", + "type": "string" + } + }, + "required": [ + "method" + ] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.provider.oauth.callback({\n ...\n})" + } + ] + } + }, + "/find": { + "get": { + "operationId": "find.text", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "pattern", + "schema": { + "type": "string" + }, + "required": true + } + ], + "summary": "Find text", + "description": "Search for text patterns across files in the project using ripgrep.", + "responses": { + "200": { + "description": "Matches", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "path": { + "type": "object", + "properties": { + "text": { + "type": "string" + } + }, + "required": [ + "text" + ] + }, + "lines": { + "type": "object", + "properties": { + "text": { + "type": "string" + } + }, + "required": [ + "text" + ] + }, + "line_number": { + "type": "number" + }, + "absolute_offset": { + "type": "number" + }, + "submatches": { + "type": "array", + "items": { + "type": "object", + "properties": { + "match": { + "type": "object", + "properties": { + "text": { + "type": "string" + } + }, + "required": [ + "text" + ] + }, + "start": { + "type": "number" + }, + "end": { + "type": "number" + } + }, + "required": [ + "match", + "start", + "end" + ] + } + } + }, + "required": [ + "path", + "lines", + "line_number", + "absolute_offset", + "submatches" + ] + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.find.text({\n ...\n})" + } + ] + } + }, + "/find/file": { + "get": { + "operationId": "find.files", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "query", + "schema": { + "type": "string" + }, + "required": true + }, + { + "in": "query", + "name": "dirs", + "schema": { + "type": "string", + "enum": [ + "true", + "false" + ] + } + }, + { + "in": "query", + "name": "type", + "schema": { + "type": "string", + "enum": [ + "file", + "directory" + ] + } + }, + { + "in": "query", + "name": "limit", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 200 + } + } + ], + "summary": "Find files", + "description": "Search for files or directories by name or pattern in the project directory.", + "responses": { + "200": { + "description": "File paths", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.find.files({\n ...\n})" + } + ] + } + }, + "/find/symbol": { + "get": { + "operationId": "find.symbols", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "query", + "schema": { + "type": "string" + }, + "required": true + } + ], + "summary": "Find symbols", + "description": "Search for workspace symbols like functions, classes, and variables using LSP.", + "responses": { + "200": { + "description": "Symbols", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Symbol" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.find.symbols({\n ...\n})" + } + ] + } + }, + "/file": { + "get": { + "operationId": "file.list", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "path", + "schema": { + "type": "string" + }, + "required": true + } + ], + "summary": "List files", + "description": "List files and directories in a specified path.", + "responses": { + "200": { + "description": "Files and directories", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FileNode" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.file.list({\n ...\n})" + } + ] + } + }, + "/file/content": { + "get": { + "operationId": "file.read", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "path", + "schema": { + "type": "string" + }, + "required": true + } + ], + "summary": "Read file", + "description": "Read the content of a specified file.", + "responses": { + "200": { + "description": "File content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FileContent" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.file.read({\n ...\n})" + } + ] + } + }, + "/file/status": { + "get": { + "operationId": "file.status", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Get file status", + "description": "Get the git status of all files in the project.", + "responses": { + "200": { + "description": "File status", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/File" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.file.status({\n ...\n})" + } + ] + } + }, + "/log": { + "post": { + "operationId": "app.log", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Write log", + "description": "Write a log entry to the server logs with specified level and metadata.", + "responses": { + "200": { + "description": "Log entry written successfully", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "service": { + "description": "Service name for the log entry", + "type": "string" + }, + "level": { + "description": "Log level", + "type": "string", + "enum": [ + "debug", + "info", + "error", + "warn" + ] + }, + "message": { + "description": "Log message", + "type": "string" + }, + "extra": { + "description": "Additional metadata for the log entry", + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + } + }, + "required": [ + "service", + "level", + "message" + ] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.app.log({\n ...\n})" + } + ] + } + }, + "/agent": { + "get": { + "operationId": "app.agents", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "List agents", + "description": "Get a list of all available AI agents in the OpenCode system.", + "responses": { + "200": { + "description": "List of agents", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Agent" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.app.agents({\n ...\n})" + } + ] + } + }, + "/mcp": { + "get": { + "operationId": "mcp.status", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Get MCP status", + "description": "Get the status of all Model Context Protocol (MCP) servers.", + "responses": { + "200": { + "description": "MCP server status", + "content": { + "application/json": { + "schema": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "$ref": "#/components/schemas/MCPStatus" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.mcp.status({\n ...\n})" + } + ] + }, + "post": { + "operationId": "mcp.add", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Add MCP server", + "description": "Dynamically add a new Model Context Protocol (MCP) server to the system.", + "responses": { + "200": { + "description": "MCP server added successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "$ref": "#/components/schemas/MCPStatus" + } + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "config": { + "anyOf": [ + { + "$ref": "#/components/schemas/McpLocalConfig" + }, + { + "$ref": "#/components/schemas/McpRemoteConfig" + } + ] + } + }, + "required": [ + "name", + "config" + ] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.mcp.add({\n ...\n})" + } + ] + } + }, + "/mcp/{name}/auth": { + "post": { + "operationId": "mcp.auth.start", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "name", + "required": true + } + ], + "summary": "Start MCP OAuth", + "description": "Start OAuth authentication flow for a Model Context Protocol (MCP) server.", + "responses": { + "200": { + "description": "OAuth flow started", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "authorizationUrl": { + "description": "URL to open in browser for authorization", + "type": "string" + } + }, + "required": [ + "authorizationUrl" + ] + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.mcp.auth.start({\n ...\n})" + } + ] + }, + "delete": { + "operationId": "mcp.auth.remove", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "name", + "required": true + } + ], + "summary": "Remove MCP OAuth", + "description": "Remove OAuth credentials for an MCP server", + "responses": { + "200": { + "description": "OAuth credentials removed", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "const": true + } + }, + "required": [ + "success" + ] + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.mcp.auth.remove({\n ...\n})" + } + ] + } + }, + "/mcp/{name}/auth/callback": { + "post": { + "operationId": "mcp.auth.callback", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "name", + "required": true + } + ], + "summary": "Complete MCP OAuth", + "description": "Complete OAuth authentication for a Model Context Protocol (MCP) server using the authorization code.", + "responses": { + "200": { + "description": "OAuth authentication completed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MCPStatus" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "code": { + "description": "Authorization code from OAuth callback", + "type": "string" + } + }, + "required": [ + "code" + ] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.mcp.auth.callback({\n ...\n})" + } + ] + } + }, + "/mcp/{name}/auth/authenticate": { + "post": { + "operationId": "mcp.auth.authenticate", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "name", + "required": true + } + ], + "summary": "Authenticate MCP OAuth", + "description": "Start OAuth flow and wait for callback (opens browser)", + "responses": { + "200": { + "description": "OAuth authentication completed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MCPStatus" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.mcp.auth.authenticate({\n ...\n})" + } + ] + } + }, + "/mcp/{name}/connect": { + "post": { + "operationId": "mcp.connect", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "name", + "schema": { + "type": "string" + }, + "required": true + } + ], + "description": "Connect an MCP server", + "responses": { + "200": { + "description": "MCP server connected successfully", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.mcp.connect({\n ...\n})" + } + ] + } + }, + "/mcp/{name}/disconnect": { + "post": { + "operationId": "mcp.disconnect", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "name", + "schema": { + "type": "string" + }, + "required": true + } + ], + "description": "Disconnect an MCP server", + "responses": { + "200": { + "description": "MCP server disconnected successfully", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.mcp.disconnect({\n ...\n})" + } + ] + } + }, + "/experimental/resource": { + "get": { + "operationId": "experimental.resource.list", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Get MCP resources", + "description": "Get all available MCP resources from connected servers. Optionally filter by name.", + "responses": { + "200": { + "description": "MCP resources", + "content": { + "application/json": { + "schema": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "$ref": "#/components/schemas/McpResource" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.experimental.resource.list({\n ...\n})" + } + ] + } + }, + "/lsp": { + "get": { + "operationId": "lsp.status", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Get LSP status", + "description": "Get LSP server status", + "responses": { + "200": { + "description": "LSP server status", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LSPStatus" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.lsp.status({\n ...\n})" + } + ] + } + }, + "/formatter": { + "get": { + "operationId": "formatter.status", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Get formatter status", + "description": "Get formatter status", + "responses": { + "200": { + "description": "Formatter status", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FormatterStatus" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.formatter.status({\n ...\n})" + } + ] + } + }, + "/tui/append-prompt": { + "post": { + "operationId": "tui.appendPrompt", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Append TUI prompt", + "description": "Append prompt to the TUI", + "responses": { + "200": { + "description": "Prompt processed successfully", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "text": { + "type": "string" + } + }, + "required": [ + "text" + ] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.tui.appendPrompt({\n ...\n})" + } + ] + } + }, + "/tui/open-help": { + "post": { + "operationId": "tui.openHelp", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Open help dialog", + "description": "Open the help dialog in the TUI to display user assistance information.", + "responses": { + "200": { + "description": "Help dialog opened successfully", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.tui.openHelp({\n ...\n})" + } + ] + } + }, + "/tui/open-sessions": { + "post": { + "operationId": "tui.openSessions", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Open sessions dialog", + "description": "Open the session dialog", + "responses": { + "200": { + "description": "Session dialog opened successfully", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.tui.openSessions({\n ...\n})" + } + ] + } + }, + "/tui/open-themes": { + "post": { + "operationId": "tui.openThemes", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Open themes dialog", + "description": "Open the theme dialog", + "responses": { + "200": { + "description": "Theme dialog opened successfully", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.tui.openThemes({\n ...\n})" + } + ] + } + }, + "/tui/open-models": { + "post": { + "operationId": "tui.openModels", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Open models dialog", + "description": "Open the model dialog", + "responses": { + "200": { + "description": "Model dialog opened successfully", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.tui.openModels({\n ...\n})" + } + ] + } + }, + "/tui/submit-prompt": { + "post": { + "operationId": "tui.submitPrompt", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Submit TUI prompt", + "description": "Submit the prompt", + "responses": { + "200": { + "description": "Prompt submitted successfully", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.tui.submitPrompt({\n ...\n})" + } + ] + } + }, + "/tui/clear-prompt": { + "post": { + "operationId": "tui.clearPrompt", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Clear TUI prompt", + "description": "Clear the prompt", + "responses": { + "200": { + "description": "Prompt cleared successfully", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.tui.clearPrompt({\n ...\n})" + } + ] + } + }, + "/tui/execute-command": { + "post": { + "operationId": "tui.executeCommand", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Execute TUI command", + "description": "Execute a TUI command (e.g. agent_cycle)", + "responses": { + "200": { + "description": "Command executed successfully", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "command": { + "type": "string" + } + }, + "required": [ + "command" + ] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.tui.executeCommand({\n ...\n})" + } + ] + } + }, + "/tui/show-toast": { + "post": { + "operationId": "tui.showToast", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Show TUI toast", + "description": "Show a toast notification in the TUI", + "responses": { + "200": { + "description": "Toast notification shown successfully", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "message": { + "type": "string" + }, + "variant": { + "type": "string", + "enum": [ + "info", + "success", + "warning", + "error" + ] + }, + "duration": { + "description": "Duration in milliseconds", + "default": 5000, + "type": "number" + } + }, + "required": [ + "message", + "variant" + ] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.tui.showToast({\n ...\n})" + } + ] + } + }, + "/tui/publish": { + "post": { + "operationId": "tui.publish", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Publish TUI event", + "description": "Publish a TUI event", + "responses": { + "200": { + "description": "Event published successfully", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/Event.tui.prompt.append" + }, + { + "$ref": "#/components/schemas/Event.tui.command.execute" + }, + { + "$ref": "#/components/schemas/Event.tui.toast.show" + }, + { + "$ref": "#/components/schemas/Event.tui.session.select" + } + ] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.tui.publish({\n ...\n})" + } + ] + } + }, + "/tui/select-session": { + "post": { + "operationId": "tui.selectSession", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Select session", + "description": "Navigate the TUI to display the specified session.", + "responses": { + "200": { + "description": "Session selected successfully", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "sessionID": { + "description": "Session ID to navigate to", + "type": "string", + "pattern": "^ses" + } + }, + "required": [ + "sessionID" + ] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.tui.selectSession({\n ...\n})" + } + ] + } + }, + "/tui/control/next": { + "get": { + "operationId": "tui.control.next", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Get next TUI request", + "description": "Retrieve the next TUI (Terminal User Interface) request from the queue for processing.", + "responses": { + "200": { + "description": "Next TUI request", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "path": { + "type": "string" + }, + "body": {} + }, + "required": [ + "path", + "body" + ] + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.tui.control.next({\n ...\n})" + } + ] + } + }, + "/tui/control/response": { + "post": { + "operationId": "tui.control.response", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Submit TUI response", + "description": "Submit a response to the TUI request queue to complete a pending request.", + "responses": { + "200": { + "description": "Response submitted successfully", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": {} + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.tui.control.response({\n ...\n})" + } + ] + } + }, + "/auth/{providerID}": { + "put": { + "operationId": "auth.set", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "providerID", + "schema": { + "type": "string" + }, + "required": true + } + ], + "summary": "Set auth credentials", + "description": "Set authentication credentials", + "responses": { + "200": { + "description": "Successfully set authentication credentials", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Auth" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.auth.set({\n ...\n})" + } + ] + } + }, + "/auth/info/{providerID}": { + "get": { + "operationId": "auth.info", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "providerID", + "schema": { + "type": "string" + }, + "required": true + } + ], + "summary": "Get auth info", + "description": "Get authentication metadata for a provider including email, plan, and account ID.", + "responses": { + "200": { + "description": "Auth info retrieved successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "authenticated": { + "type": "boolean" + }, + "type": { + "type": "string" + }, + "email": { + "type": "string" + }, + "plan": { + "type": "string" + }, + "accountId": { + "type": "string" + } + }, + "required": [ + "authenticated" + ] + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.auth.info({\n ...\n})" + } + ] + } + }, + "/event": { + "get": { + "operationId": "event.subscribe", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Subscribe to events", + "description": "Get events", + "responses": { + "200": { + "description": "Event stream", + "content": { + "text/event-stream": { + "schema": { + "$ref": "#/components/schemas/Event" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.event.subscribe({\n ...\n})" + } + ] + } + } + }, + "components": { + "schemas": { + "Event.installation.updated": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "installation.updated" + }, + "properties": { + "type": "object", + "properties": { + "version": { + "type": "string" + } + }, + "required": [ + "version" + ] + } + }, + "required": [ + "type", + "properties" + ] + }, + "Event.installation.update-available": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "installation.update-available" + }, + "properties": { + "type": "object", + "properties": { + "version": { + "type": "string" + } + }, + "required": [ + "version" + ] + } + }, + "required": [ + "type", + "properties" + ] + }, + "Project": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "worktree": { + "type": "string" + }, + "vcs": { + "type": "string", + "const": "git" + }, + "name": { + "type": "string" + }, + "icon": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "color": { + "type": "string" + } + } + }, + "time": { + "type": "object", + "properties": { + "created": { + "type": "number" + }, + "updated": { + "type": "number" + }, + "initialized": { + "type": "number" + } + }, + "required": [ + "created", + "updated" + ] + }, + "sandboxes": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "id", + "worktree", + "time", + "sandboxes" + ] + }, + "Event.project.updated": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "project.updated" + }, + "properties": { + "$ref": "#/components/schemas/Project" + } + }, + "required": [ + "type", + "properties" + ] + }, + "Event.server.instance.disposed": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "server.instance.disposed" + }, + "properties": { + "type": "object", + "properties": { + "directory": { + "type": "string" + } + }, + "required": [ + "directory" + ] + } + }, + "required": [ + "type", + "properties" + ] + }, + "Event.lsp.client.diagnostics": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "lsp.client.diagnostics" + }, + "properties": { + "type": "object", + "properties": { + "serverID": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "serverID", + "path" + ] + } + }, + "required": [ + "type", + "properties" + ] + }, + "Event.lsp.updated": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "lsp.updated" + }, + "properties": { + "type": "object", + "properties": {} + } + }, + "required": [ + "type", + "properties" + ] + }, + "FileDiff": { + "type": "object", + "properties": { + "file": { + "type": "string" + }, + "before": { + "type": "string" + }, + "after": { + "type": "string" + }, + "additions": { + "type": "number" + }, + "deletions": { + "type": "number" + } + }, + "required": [ + "file", + "before", + "after", + "additions", + "deletions" + ] + }, + "UserMessage": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "sessionID": { + "type": "string" + }, + "role": { + "type": "string", + "const": "user" + }, + "time": { + "type": "object", + "properties": { + "created": { + "type": "number" + } + }, + "required": [ + "created" + ] + }, + "summary": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "body": { + "type": "string" + }, + "diffs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FileDiff" + } + } + }, + "required": [ + "diffs" + ] + }, + "agent": { + "type": "string" + }, + "model": { + "type": "object", + "properties": { + "providerID": { + "type": "string" + }, + "modelID": { + "type": "string" + } + }, + "required": [ + "providerID", + "modelID" + ] + }, + "system": { + "type": "string" + }, + "tools": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "boolean" + } + }, + "variant": { + "type": "string" + } + }, + "required": [ + "id", + "sessionID", + "role", + "time", + "agent", + "model" + ] + }, + "ProviderAuthError": { + "type": "object", + "properties": { + "name": { + "type": "string", + "const": "ProviderAuthError" + }, + "data": { + "type": "object", + "properties": { + "providerID": { + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "providerID", + "message" + ] + } + }, + "required": [ + "name", + "data" + ] + }, + "UnknownError": { + "type": "object", + "properties": { + "name": { + "type": "string", + "const": "UnknownError" + }, + "data": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + }, + "required": [ + "name", + "data" + ] + }, + "MessageOutputLengthError": { + "type": "object", + "properties": { + "name": { + "type": "string", + "const": "MessageOutputLengthError" + }, + "data": { + "type": "object", + "properties": {} + } + }, + "required": [ + "name", + "data" + ] + }, + "MessageAbortedError": { + "type": "object", + "properties": { + "name": { + "type": "string", + "const": "MessageAbortedError" + }, + "data": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + }, + "required": [ + "name", + "data" + ] + }, + "APIError": { + "type": "object", + "properties": { + "name": { + "type": "string", + "const": "APIError" + }, + "data": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "statusCode": { + "type": "number" + }, + "isRetryable": { + "type": "boolean" + }, + "responseHeaders": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string" + } + }, + "responseBody": { + "type": "string" + }, + "metadata": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string" + } + } + }, + "required": [ + "message", + "isRetryable" + ] + } + }, + "required": [ + "name", + "data" + ] + }, + "AssistantMessage": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "sessionID": { + "type": "string" + }, + "role": { + "type": "string", + "const": "assistant" + }, + "time": { + "type": "object", + "properties": { + "created": { + "type": "number" + }, + "completed": { + "type": "number" + } + }, + "required": [ + "created" + ] + }, + "error": { + "anyOf": [ + { + "$ref": "#/components/schemas/ProviderAuthError" + }, + { + "$ref": "#/components/schemas/UnknownError" + }, + { + "$ref": "#/components/schemas/MessageOutputLengthError" + }, + { + "$ref": "#/components/schemas/MessageAbortedError" + }, + { + "$ref": "#/components/schemas/APIError" + } + ] + }, + "parentID": { + "type": "string" + }, + "modelID": { + "type": "string" + }, + "providerID": { + "type": "string" + }, + "mode": { + "type": "string" + }, + "agent": { + "type": "string" + }, + "path": { + "type": "object", + "properties": { + "cwd": { + "type": "string" + }, + "root": { + "type": "string" + } + }, + "required": [ + "cwd", + "root" + ] + }, + "summary": { + "type": "boolean" + }, + "cost": { + "type": "number" + }, + "tokens": { + "type": "object", + "properties": { + "input": { + "type": "number" + }, + "output": { + "type": "number" + }, + "reasoning": { + "type": "number" + }, + "cache": { + "type": "object", + "properties": { + "read": { + "type": "number" + }, + "write": { + "type": "number" + } + }, + "required": [ + "read", + "write" + ] + } + }, + "required": [ + "input", + "output", + "reasoning", + "cache" + ] + }, + "finish": { + "type": "string" + } + }, + "required": [ + "id", + "sessionID", + "role", + "time", + "parentID", + "modelID", + "providerID", + "mode", + "agent", + "path", + "cost", + "tokens" + ] + }, + "Message": { + "anyOf": [ + { + "$ref": "#/components/schemas/UserMessage" + }, + { + "$ref": "#/components/schemas/AssistantMessage" + } + ] + }, + "Event.message.updated": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "message.updated" + }, + "properties": { + "type": "object", + "properties": { + "info": { + "$ref": "#/components/schemas/Message" + } + }, + "required": [ + "info" + ] + } + }, + "required": [ + "type", + "properties" + ] + }, + "Event.message.removed": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "message.removed" + }, + "properties": { + "type": "object", + "properties": { + "sessionID": { + "type": "string" + }, + "messageID": { + "type": "string" + } + }, + "required": [ + "sessionID", + "messageID" + ] + } + }, + "required": [ + "type", + "properties" + ] + }, + "TextPart": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "sessionID": { + "type": "string" + }, + "messageID": { + "type": "string" + }, + "type": { + "type": "string", + "const": "text" + }, + "text": { + "type": "string" + }, + "synthetic": { + "type": "boolean" + }, + "ignored": { + "type": "boolean" + }, + "time": { + "type": "object", + "properties": { + "start": { + "type": "number" + }, + "end": { + "type": "number" + } + }, + "required": [ + "start" + ] + }, + "metadata": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + } + }, + "required": [ + "id", + "sessionID", + "messageID", + "type", + "text" + ] + }, + "ReasoningPart": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "sessionID": { + "type": "string" + }, + "messageID": { + "type": "string" + }, + "type": { + "type": "string", + "const": "reasoning" + }, + "text": { + "type": "string" + }, + "metadata": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + }, + "time": { + "type": "object", + "properties": { + "start": { + "type": "number" + }, + "end": { + "type": "number" + } + }, + "required": [ + "start" + ] + } + }, + "required": [ + "id", + "sessionID", + "messageID", + "type", + "text", + "time" + ] + }, + "FilePartSourceText": { + "type": "object", + "properties": { + "value": { + "type": "string" + }, + "start": { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + }, + "end": { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + } + }, + "required": [ + "value", + "start", + "end" + ] + }, + "FileSource": { + "type": "object", + "properties": { + "text": { + "$ref": "#/components/schemas/FilePartSourceText" + }, + "type": { + "type": "string", + "const": "file" + }, + "path": { + "type": "string" + } + }, + "required": [ + "text", + "type", + "path" + ] + }, + "Range": { + "type": "object", + "properties": { + "start": { + "type": "object", + "properties": { + "line": { + "type": "number" + }, + "character": { + "type": "number" + } + }, + "required": [ + "line", + "character" + ] + }, + "end": { + "type": "object", + "properties": { + "line": { + "type": "number" + }, + "character": { + "type": "number" + } + }, + "required": [ + "line", + "character" + ] + } + }, + "required": [ + "start", + "end" + ] + }, + "SymbolSource": { + "type": "object", + "properties": { + "text": { + "$ref": "#/components/schemas/FilePartSourceText" + }, + "type": { + "type": "string", + "const": "symbol" + }, + "path": { + "type": "string" + }, + "range": { + "$ref": "#/components/schemas/Range" + }, + "name": { + "type": "string" + }, + "kind": { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + } + }, + "required": [ + "text", + "type", + "path", + "range", + "name", + "kind" + ] + }, + "ResourceSource": { + "type": "object", + "properties": { + "text": { + "$ref": "#/components/schemas/FilePartSourceText" + }, + "type": { + "type": "string", + "const": "resource" + }, + "clientName": { + "type": "string" + }, + "uri": { + "type": "string" + } + }, + "required": [ + "text", + "type", + "clientName", + "uri" + ] + }, + "FilePartSource": { + "anyOf": [ + { + "$ref": "#/components/schemas/FileSource" + }, + { + "$ref": "#/components/schemas/SymbolSource" + }, + { + "$ref": "#/components/schemas/ResourceSource" + } + ] + }, + "FilePart": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "sessionID": { + "type": "string" + }, + "messageID": { + "type": "string" + }, + "type": { + "type": "string", + "const": "file" + }, + "mime": { + "type": "string" + }, + "filename": { + "type": "string" + }, + "url": { + "type": "string" + }, + "source": { + "$ref": "#/components/schemas/FilePartSource" + } + }, + "required": [ + "id", + "sessionID", + "messageID", + "type", + "mime", + "url" + ] + }, + "ToolStatePending": { + "type": "object", + "properties": { + "status": { + "type": "string", + "const": "pending" + }, + "input": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + }, + "raw": { + "type": "string" + } + }, + "required": [ + "status", + "input", + "raw" + ] + }, + "ToolStateRunning": { + "type": "object", + "properties": { + "status": { + "type": "string", + "const": "running" + }, + "input": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + }, + "title": { + "type": "string" + }, + "metadata": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + }, + "time": { + "type": "object", + "properties": { + "start": { + "type": "number" + } + }, + "required": [ + "start" + ] + } + }, + "required": [ + "status", + "input", + "time" + ] + }, + "ToolStateCompleted": { + "type": "object", + "properties": { + "status": { + "type": "string", + "const": "completed" + }, + "input": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + }, + "output": { + "type": "string" + }, + "title": { + "type": "string" + }, + "metadata": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + }, + "time": { + "type": "object", + "properties": { + "start": { + "type": "number" + }, + "end": { + "type": "number" + }, + "compacted": { + "type": "number" + } + }, + "required": [ + "start", + "end" + ] + }, + "attachments": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FilePart" + } + } + }, + "required": [ + "status", + "input", + "output", + "title", + "metadata", + "time" + ] + }, + "ToolStateError": { + "type": "object", + "properties": { + "status": { + "type": "string", + "const": "error" + }, + "input": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + }, + "error": { + "type": "string" + }, + "metadata": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + }, + "time": { + "type": "object", + "properties": { + "start": { + "type": "number" + }, + "end": { + "type": "number" + } + }, + "required": [ + "start", + "end" + ] + } + }, + "required": [ + "status", + "input", + "error", + "time" + ] + }, + "ToolState": { + "anyOf": [ + { + "$ref": "#/components/schemas/ToolStatePending" + }, + { + "$ref": "#/components/schemas/ToolStateRunning" + }, + { + "$ref": "#/components/schemas/ToolStateCompleted" + }, + { + "$ref": "#/components/schemas/ToolStateError" + } + ] + }, + "ToolPart": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "sessionID": { + "type": "string" + }, + "messageID": { + "type": "string" + }, + "type": { + "type": "string", + "const": "tool" + }, + "callID": { + "type": "string" + }, + "tool": { + "type": "string" + }, + "state": { + "$ref": "#/components/schemas/ToolState" + }, + "metadata": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + } + }, + "required": [ + "id", + "sessionID", + "messageID", + "type", + "callID", + "tool", + "state" + ] + }, + "StepStartPart": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "sessionID": { + "type": "string" + }, + "messageID": { + "type": "string" + }, + "type": { + "type": "string", + "const": "step-start" + }, + "snapshot": { + "type": "string" + } + }, + "required": [ + "id", + "sessionID", + "messageID", + "type" + ] + }, + "StepFinishPart": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "sessionID": { + "type": "string" + }, + "messageID": { + "type": "string" + }, + "type": { + "type": "string", + "const": "step-finish" + }, + "reason": { + "type": "string" + }, + "snapshot": { + "type": "string" + }, + "cost": { + "type": "number" + }, + "tokens": { + "type": "object", + "properties": { + "input": { + "type": "number" + }, + "output": { + "type": "number" + }, + "reasoning": { + "type": "number" + }, + "cache": { + "type": "object", + "properties": { + "read": { + "type": "number" + }, + "write": { + "type": "number" + } + }, + "required": [ + "read", + "write" + ] + } + }, + "required": [ + "input", + "output", + "reasoning", + "cache" + ] + } + }, + "required": [ + "id", + "sessionID", + "messageID", + "type", + "reason", + "cost", + "tokens" + ] + }, + "SnapshotPart": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "sessionID": { + "type": "string" + }, + "messageID": { + "type": "string" + }, + "type": { + "type": "string", + "const": "snapshot" + }, + "snapshot": { + "type": "string" + } + }, + "required": [ + "id", + "sessionID", + "messageID", + "type", + "snapshot" + ] + }, + "PatchPart": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "sessionID": { + "type": "string" + }, + "messageID": { + "type": "string" + }, + "type": { + "type": "string", + "const": "patch" + }, + "hash": { + "type": "string" + }, + "files": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "id", + "sessionID", + "messageID", + "type", + "hash", + "files" + ] + }, + "AgentPart": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "sessionID": { + "type": "string" + }, + "messageID": { + "type": "string" + }, + "type": { + "type": "string", + "const": "agent" + }, + "name": { + "type": "string" + }, + "source": { + "type": "object", + "properties": { + "value": { + "type": "string" + }, + "start": { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + }, + "end": { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + } + }, + "required": [ + "value", + "start", + "end" + ] + } + }, + "required": [ + "id", + "sessionID", + "messageID", + "type", + "name" + ] + }, + "RetryPart": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "sessionID": { + "type": "string" + }, + "messageID": { + "type": "string" + }, + "type": { + "type": "string", + "const": "retry" + }, + "attempt": { + "type": "number" + }, + "error": { + "$ref": "#/components/schemas/APIError" + }, + "time": { + "type": "object", + "properties": { + "created": { + "type": "number" + } + }, + "required": [ + "created" + ] + } + }, + "required": [ + "id", + "sessionID", + "messageID", + "type", + "attempt", + "error", + "time" + ] + }, + "CompactionPart": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "sessionID": { + "type": "string" + }, + "messageID": { + "type": "string" + }, + "type": { + "type": "string", + "const": "compaction" + }, + "auto": { + "type": "boolean" + } + }, + "required": [ + "id", + "sessionID", + "messageID", + "type", + "auto" + ] + }, + "Part": { + "anyOf": [ + { + "$ref": "#/components/schemas/TextPart" + }, + { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "sessionID": { + "type": "string" + }, + "messageID": { + "type": "string" + }, + "type": { + "type": "string", + "const": "subtask" + }, + "prompt": { + "type": "string" + }, + "description": { + "type": "string" + }, + "agent": { + "type": "string" + }, + "command": { + "type": "string" + }, + "model": { + "type": "object", + "properties": { + "providerID": { + "type": "string" + }, + "modelID": { + "type": "string" + } + }, + "required": [ + "providerID", + "modelID" + ] + }, + "parentAgent": { + "type": "string" + }, + "parentModel": { + "type": "object", + "properties": { + "providerID": { + "type": "string" + }, + "modelID": { + "type": "string" + } + }, + "required": [ + "providerID", + "modelID" + ] + } + }, + "required": [ + "id", + "sessionID", + "messageID", + "type", + "prompt", + "description", + "agent" + ] + }, + { + "$ref": "#/components/schemas/ReasoningPart" + }, + { + "$ref": "#/components/schemas/FilePart" + }, + { + "$ref": "#/components/schemas/ToolPart" + }, + { + "$ref": "#/components/schemas/StepStartPart" + }, + { + "$ref": "#/components/schemas/StepFinishPart" + }, + { + "$ref": "#/components/schemas/SnapshotPart" + }, + { + "$ref": "#/components/schemas/PatchPart" + }, + { + "$ref": "#/components/schemas/AgentPart" + }, + { + "$ref": "#/components/schemas/RetryPart" + }, + { + "$ref": "#/components/schemas/CompactionPart" + } + ] + }, + "Event.message.part.updated": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "message.part.updated" + }, + "properties": { + "type": "object", + "properties": { + "part": { + "$ref": "#/components/schemas/Part" + }, + "delta": { + "type": "string" + } + }, + "required": [ + "part" + ] + } + }, + "required": [ + "type", + "properties" + ] + }, + "Event.message.part.removed": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "message.part.removed" + }, + "properties": { + "type": "object", + "properties": { + "sessionID": { + "type": "string" + }, + "messageID": { + "type": "string" + }, + "partID": { + "type": "string" + } + }, + "required": [ + "sessionID", + "messageID", + "partID" + ] + } + }, + "required": [ + "type", + "properties" + ] + }, + "PermissionRequest": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^per.*" + }, + "sessionID": { + "type": "string", + "pattern": "^ses.*" + }, + "permission": { + "type": "string" + }, + "patterns": { + "type": "array", + "items": { + "type": "string" + } + }, + "metadata": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + }, + "always": { + "type": "array", + "items": { + "type": "string" + } + }, + "tool": { + "type": "object", + "properties": { + "messageID": { + "type": "string" + }, + "callID": { + "type": "string" + } + }, + "required": [ + "messageID", + "callID" + ] + } + }, + "required": [ + "id", + "sessionID", + "permission", + "patterns", + "metadata", + "always" + ] + }, + "Event.permission.asked": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "permission.asked" + }, + "properties": { + "$ref": "#/components/schemas/PermissionRequest" + } + }, + "required": [ + "type", + "properties" + ] + }, + "Event.permission.replied": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "permission.replied" + }, + "properties": { + "type": "object", + "properties": { + "sessionID": { + "type": "string" + }, + "requestID": { + "type": "string" + }, + "reply": { + "type": "string", + "enum": [ + "once", + "always", + "reject" + ] + } + }, + "required": [ + "sessionID", + "requestID", + "reply" + ] + } + }, + "required": [ + "type", + "properties" + ] + }, + "SessionStatus": { + "anyOf": [ + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "idle" + } + }, + "required": [ + "type" + ] + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "retry" + }, + "attempt": { + "type": "number" + }, + "message": { + "type": "string" + }, + "next": { + "type": "number" + } + }, + "required": [ + "type", + "attempt", + "message", + "next" + ] + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "busy" + } + }, + "required": [ + "type" + ] + } + ] + }, + "Event.session.status": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "session.status" + }, + "properties": { + "type": "object", + "properties": { + "sessionID": { + "type": "string" + }, + "status": { + "$ref": "#/components/schemas/SessionStatus" + } + }, + "required": [ + "sessionID", + "status" + ] + } + }, + "required": [ + "type", + "properties" + ] + }, + "Event.session.idle": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "session.idle" + }, + "properties": { + "type": "object", + "properties": { + "sessionID": { + "type": "string" + } + }, + "required": [ + "sessionID" + ] + } + }, + "required": [ + "type", + "properties" + ] + }, + "QuestionOption": { + "type": "object", + "properties": { + "label": { + "description": "Display text (1-5 words, concise)", + "type": "string" + }, + "description": { + "description": "Explanation of choice", + "type": "string" + } + }, + "required": [ + "label", + "description" + ] + }, + "QuestionInfo": { + "type": "object", + "properties": { + "question": { + "description": "Complete question", + "type": "string" + }, + "header": { + "description": "Very short label (max 12 chars)", + "type": "string", + "maxLength": 12 + }, + "options": { + "description": "Available choices", + "type": "array", + "items": { + "$ref": "#/components/schemas/QuestionOption" + } + }, + "multiple": { + "description": "Allow selecting multiple choices", + "type": "boolean" + }, + "custom": { + "description": "Allow typing a custom answer (default: true)", + "type": "boolean" + } + }, + "required": [ + "question", + "header", + "options" + ] + }, + "QuestionRequest": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^que.*" + }, + "sessionID": { + "type": "string", + "pattern": "^ses.*" + }, + "questions": { + "description": "Questions to ask", + "type": "array", + "items": { + "$ref": "#/components/schemas/QuestionInfo" + } + }, + "tool": { + "type": "object", + "properties": { + "messageID": { + "type": "string" + }, + "callID": { + "type": "string" + } + }, + "required": [ + "messageID", + "callID" + ] + } + }, + "required": [ + "id", + "sessionID", + "questions" + ] + }, + "Event.question.asked": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "question.asked" + }, + "properties": { + "$ref": "#/components/schemas/QuestionRequest" + } + }, + "required": [ + "type", + "properties" + ] + }, + "QuestionAnswer": { + "type": "array", + "items": { + "type": "string" + } + }, + "Event.question.replied": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "question.replied" + }, + "properties": { + "type": "object", + "properties": { + "sessionID": { + "type": "string" + }, + "requestID": { + "type": "string" + }, + "answers": { + "type": "array", + "items": { + "$ref": "#/components/schemas/QuestionAnswer" + } + } + }, + "required": [ + "sessionID", + "requestID", + "answers" + ] + } + }, + "required": [ + "type", + "properties" + ] + }, + "Event.question.rejected": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "question.rejected" + }, + "properties": { + "type": "object", + "properties": { + "sessionID": { + "type": "string" + }, + "requestID": { + "type": "string" + } + }, + "required": [ + "sessionID", + "requestID" + ] + } + }, + "required": [ + "type", + "properties" + ] + }, + "Event.session.compacted": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "session.compacted" + }, + "properties": { + "type": "object", + "properties": { + "sessionID": { + "type": "string" + } + }, + "required": [ + "sessionID" + ] + } + }, + "required": [ + "type", + "properties" + ] + }, + "Event.file.edited": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "file.edited" + }, + "properties": { + "type": "object", + "properties": { + "file": { + "type": "string" + } + }, + "required": [ + "file" + ] + } + }, + "required": [ + "type", + "properties" + ] + }, + "Todo": { + "type": "object", + "properties": { + "content": { + "description": "Brief description of the task", + "type": "string" + }, + "status": { + "description": "Current status of the task: pending, in_progress, completed, cancelled", + "type": "string" + }, + "priority": { + "description": "Priority level of the task: high, medium, low", + "type": "string" + }, + "id": { + "description": "Unique identifier for the todo item", + "type": "string" + } + }, + "required": [ + "content", + "status", + "priority", + "id" + ] + }, + "Event.todo.updated": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "todo.updated" + }, + "properties": { + "type": "object", + "properties": { + "sessionID": { + "type": "string" + }, + "todos": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Todo" + } + } + }, + "required": [ + "sessionID", + "todos" + ] + } + }, + "required": [ + "type", + "properties" + ] + }, + "Event.tui.prompt.append": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "tui.prompt.append" + }, + "properties": { + "type": "object", + "properties": { + "text": { + "type": "string" + } + }, + "required": [ + "text" + ] + } + }, + "required": [ + "type", + "properties" + ] + }, + "Event.tui.command.execute": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "tui.command.execute" + }, + "properties": { + "type": "object", + "properties": { + "command": { + "anyOf": [ + { + "type": "string", + "enum": [ + "session.list", + "session.new", + "session.share", + "session.interrupt", + "session.compact", + "session.page.up", + "session.page.down", + "session.half.page.up", + "session.half.page.down", + "session.first", + "session.last", + "prompt.clear", + "prompt.submit", + "agent.cycle" + ] + }, + { + "type": "string" + } + ] + } + }, + "required": [ + "command" + ] + } + }, + "required": [ + "type", + "properties" + ] + }, + "Event.tui.toast.show": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "tui.toast.show" + }, + "properties": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "message": { + "type": "string" + }, + "variant": { + "type": "string", + "enum": [ + "info", + "success", + "warning", + "error" + ] + }, + "duration": { + "description": "Duration in milliseconds", + "default": 5000, + "type": "number" + } + }, + "required": [ + "message", + "variant" + ] + } + }, + "required": [ + "type", + "properties" + ] + }, + "Event.tui.session.select": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "tui.session.select" + }, + "properties": { + "type": "object", + "properties": { + "sessionID": { + "description": "Session ID to navigate to", + "type": "string", + "pattern": "^ses" + } + }, + "required": [ + "sessionID" + ] + } + }, + "required": [ + "type", + "properties" + ] + }, + "Event.mcp.tools.changed": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "mcp.tools.changed" + }, + "properties": { + "type": "object", + "properties": { + "server": { + "type": "string" + } + }, + "required": [ + "server" + ] + } + }, + "required": [ + "type", + "properties" + ] + }, + "Event.command.executed": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "command.executed" + }, + "properties": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "sessionID": { + "type": "string", + "pattern": "^ses.*" + }, + "arguments": { + "type": "string" + }, + "messageID": { + "type": "string", + "pattern": "^msg.*" + } + }, + "required": [ + "name", + "sessionID", + "arguments", + "messageID" + ] + } + }, + "required": [ + "type", + "properties" + ] + }, + "Event.askquestion.requested": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "askquestion.requested" + }, + "properties": { + "type": "object", + "properties": { + "sessionID": { + "type": "string" + }, + "messageID": { + "type": "string" + }, + "callID": { + "type": "string" + }, + "questions": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "description": "Unique identifier for the question", + "type": "string" + }, + "label": { + "description": "Short tab label, e.g. 'UI Framework'", + "type": "string" + }, + "question": { + "description": "The full question to ask the user", + "type": "string" + }, + "options": { + "description": "2-8 suggested answer options", + "minItems": 2, + "maxItems": 8, + "type": "array", + "items": { + "type": "object", + "properties": { + "value": { + "description": "Short identifier for the option", + "type": "string" + }, + "label": { + "description": "Display label for the option", + "type": "string" + }, + "description": { + "description": "Additional context for the option", + "type": "string" + } + }, + "required": [ + "value", + "label" + ] + } + }, + "multiSelect": { + "description": "Allow selecting multiple options", + "type": "boolean" + } + }, + "required": [ + "id", + "label", + "question", + "options" + ] + } + } + }, + "required": [ + "sessionID", + "messageID", + "callID", + "questions" + ] + } + }, + "required": [ + "type", + "properties" + ] + }, + "Event.askquestion.answered": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "askquestion.answered" + }, + "properties": { + "type": "object", + "properties": { + "sessionID": { + "type": "string" + }, + "callID": { + "type": "string" + }, + "answers": { + "type": "array", + "items": { + "type": "object", + "properties": { + "questionId": { + "description": "ID of the question being answered", + "type": "string" + }, + "values": { + "description": "Selected option value(s)", + "type": "array", + "items": { + "type": "string" + } + }, + "customText": { + "description": "Custom text if user typed their own response", + "type": "string" + } + }, + "required": [ + "questionId", + "values" + ] + } + } + }, + "required": [ + "sessionID", + "callID", + "answers" + ] + } + }, + "required": [ + "type", + "properties" + ] + }, + "Event.askquestion.cancelled": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "askquestion.cancelled" + }, + "properties": { + "type": "object", + "properties": { + "sessionID": { + "type": "string" + }, + "callID": { + "type": "string" + } + }, + "required": [ + "sessionID", + "callID" + ] + } + }, + "required": [ + "type", + "properties" + ] + }, + "PermissionAction": { + "type": "string", + "enum": [ + "allow", + "deny", + "ask" + ] + }, + "PermissionRule": { + "type": "object", + "properties": { + "permission": { + "type": "string" + }, + "pattern": { + "type": "string" + }, + "action": { + "$ref": "#/components/schemas/PermissionAction" + } + }, + "required": [ + "permission", + "pattern", + "action" + ] + }, + "PermissionRuleset": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PermissionRule" + } + }, + "Session": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^ses.*" + }, + "slug": { + "type": "string" + }, + "projectID": { + "type": "string" + }, + "directory": { + "type": "string" + }, + "parentID": { + "type": "string", + "pattern": "^ses.*" + }, + "summary": { + "type": "object", + "properties": { + "additions": { + "type": "number" + }, + "deletions": { + "type": "number" + }, + "files": { + "type": "number" + }, + "diffs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FileDiff" + } + } + }, + "required": [ + "additions", + "deletions", + "files" + ] + }, + "share": { + "type": "object", + "properties": { + "url": { + "type": "string" + } + }, + "required": [ + "url" + ] + }, + "title": { + "type": "string" + }, + "version": { + "type": "string" + }, + "time": { + "type": "object", + "properties": { + "created": { + "type": "number" + }, + "updated": { + "type": "number" + }, + "compacting": { + "type": "number" + }, + "archived": { + "type": "number" + } + }, + "required": [ + "created", + "updated" + ] + }, + "permission": { + "$ref": "#/components/schemas/PermissionRuleset" + }, + "revert": { + "type": "object", + "properties": { + "messageID": { + "type": "string" + }, + "partID": { + "type": "string" + }, + "snapshot": { + "type": "string" + }, + "diff": { + "type": "string" + } + }, + "required": [ + "messageID" + ] + } + }, + "required": [ + "id", + "slug", + "projectID", + "directory", + "title", + "version", + "time" + ] + }, + "Event.session.created": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "session.created" + }, + "properties": { + "type": "object", + "properties": { + "info": { + "$ref": "#/components/schemas/Session" + } + }, + "required": [ + "info" + ] + } + }, + "required": [ + "type", + "properties" + ] + }, + "Event.session.updated": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "session.updated" + }, + "properties": { + "type": "object", + "properties": { + "info": { + "$ref": "#/components/schemas/Session" + } + }, + "required": [ + "info" + ] + } + }, + "required": [ + "type", + "properties" + ] + }, + "Event.session.deleted": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "session.deleted" + }, + "properties": { + "type": "object", + "properties": { + "info": { + "$ref": "#/components/schemas/Session" + } + }, + "required": [ + "info" + ] + } + }, + "required": [ + "type", + "properties" + ] + }, + "Event.session.diff": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "session.diff" + }, + "properties": { + "type": "object", + "properties": { + "sessionID": { + "type": "string" + }, + "diff": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FileDiff" + } + } + }, + "required": [ + "sessionID", + "diff" + ] + } + }, + "required": [ + "type", + "properties" + ] + }, + "Event.session.error": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "session.error" + }, + "properties": { + "type": "object", + "properties": { + "sessionID": { + "type": "string" + }, + "error": { + "anyOf": [ + { + "$ref": "#/components/schemas/ProviderAuthError" + }, + { + "$ref": "#/components/schemas/UnknownError" + }, + { + "$ref": "#/components/schemas/MessageOutputLengthError" + }, + { + "$ref": "#/components/schemas/MessageAbortedError" + }, + { + "$ref": "#/components/schemas/APIError" + } + ] + } + } + } + }, + "required": [ + "type", + "properties" + ] + }, + "Event.file.watcher.updated": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "file.watcher.updated" + }, + "properties": { + "type": "object", + "properties": { + "file": { + "type": "string" + }, + "event": { + "anyOf": [ + { + "type": "string", + "const": "add" + }, + { + "type": "string", + "const": "change" + }, + { + "type": "string", + "const": "unlink" + } + ] + } + }, + "required": [ + "file", + "event" + ] + } + }, + "required": [ + "type", + "properties" + ] + }, + "Event.vcs.branch.updated": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "vcs.branch.updated" + }, + "properties": { + "type": "object", + "properties": { + "branch": { + "type": "string" + } + } + } + }, + "required": [ + "type", + "properties" + ] + }, + "Pty": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^pty.*" + }, + "title": { + "type": "string" + }, + "command": { + "type": "string" + }, + "args": { + "type": "array", + "items": { + "type": "string" + } + }, + "cwd": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "running", + "exited" + ] + }, + "pid": { + "type": "number" + } + }, + "required": [ + "id", + "title", + "command", + "args", + "cwd", + "status", + "pid" + ] + }, + "Event.pty.created": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "pty.created" + }, + "properties": { + "type": "object", + "properties": { + "info": { + "$ref": "#/components/schemas/Pty" + } + }, + "required": [ + "info" + ] + } + }, + "required": [ + "type", + "properties" + ] + }, + "Event.pty.updated": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "pty.updated" + }, + "properties": { + "type": "object", + "properties": { + "info": { + "$ref": "#/components/schemas/Pty" + } + }, + "required": [ + "info" + ] + } + }, + "required": [ + "type", + "properties" + ] + }, + "Event.pty.exited": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "pty.exited" + }, + "properties": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^pty.*" + }, + "exitCode": { + "type": "number" + } + }, + "required": [ + "id", + "exitCode" + ] + } + }, + "required": [ + "type", + "properties" + ] + }, + "Event.pty.deleted": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "pty.deleted" + }, + "properties": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^pty.*" + } + }, + "required": [ + "id" + ] + } + }, + "required": [ + "type", + "properties" + ] + }, + "Event.server.connected": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "server.connected" + }, + "properties": { + "type": "object", + "properties": {} + } + }, + "required": [ + "type", + "properties" + ] + }, + "Event.global.disposed": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "global.disposed" + }, + "properties": { + "type": "object", + "properties": {} + } + }, + "required": [ + "type", + "properties" + ] + }, + "Event": { + "anyOf": [ + { + "$ref": "#/components/schemas/Event.installation.updated" + }, + { + "$ref": "#/components/schemas/Event.installation.update-available" + }, + { + "$ref": "#/components/schemas/Event.project.updated" + }, + { + "$ref": "#/components/schemas/Event.server.instance.disposed" + }, + { + "$ref": "#/components/schemas/Event.lsp.client.diagnostics" + }, + { + "$ref": "#/components/schemas/Event.lsp.updated" + }, + { + "$ref": "#/components/schemas/Event.message.updated" + }, + { + "$ref": "#/components/schemas/Event.message.removed" + }, + { + "$ref": "#/components/schemas/Event.message.part.updated" + }, + { + "$ref": "#/components/schemas/Event.message.part.removed" + }, + { + "$ref": "#/components/schemas/Event.permission.asked" + }, + { + "$ref": "#/components/schemas/Event.permission.replied" + }, + { + "$ref": "#/components/schemas/Event.session.status" + }, + { + "$ref": "#/components/schemas/Event.session.idle" + }, + { + "$ref": "#/components/schemas/Event.question.asked" + }, + { + "$ref": "#/components/schemas/Event.question.replied" + }, + { + "$ref": "#/components/schemas/Event.question.rejected" + }, + { + "$ref": "#/components/schemas/Event.session.compacted" + }, + { + "$ref": "#/components/schemas/Event.file.edited" + }, + { + "$ref": "#/components/schemas/Event.todo.updated" + }, + { + "$ref": "#/components/schemas/Event.tui.prompt.append" + }, + { + "$ref": "#/components/schemas/Event.tui.command.execute" + }, + { + "$ref": "#/components/schemas/Event.tui.toast.show" + }, + { + "$ref": "#/components/schemas/Event.tui.session.select" + }, + { + "$ref": "#/components/schemas/Event.mcp.tools.changed" + }, + { + "$ref": "#/components/schemas/Event.command.executed" + }, + { + "$ref": "#/components/schemas/Event.askquestion.requested" + }, + { + "$ref": "#/components/schemas/Event.askquestion.answered" + }, + { + "$ref": "#/components/schemas/Event.askquestion.cancelled" + }, + { + "$ref": "#/components/schemas/Event.session.created" + }, + { + "$ref": "#/components/schemas/Event.session.updated" + }, + { + "$ref": "#/components/schemas/Event.session.deleted" + }, + { + "$ref": "#/components/schemas/Event.session.diff" + }, + { + "$ref": "#/components/schemas/Event.session.error" + }, + { + "$ref": "#/components/schemas/Event.file.watcher.updated" + }, + { + "$ref": "#/components/schemas/Event.vcs.branch.updated" + }, + { + "$ref": "#/components/schemas/Event.pty.created" + }, + { + "$ref": "#/components/schemas/Event.pty.updated" + }, + { + "$ref": "#/components/schemas/Event.pty.exited" + }, + { + "$ref": "#/components/schemas/Event.pty.deleted" + }, + { + "$ref": "#/components/schemas/Event.server.connected" + }, + { + "$ref": "#/components/schemas/Event.global.disposed" + } + ] + }, + "GlobalEvent": { + "type": "object", + "properties": { + "directory": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/Event" + } + }, + "required": [ + "directory", + "payload" + ] + }, + "ProjectCreateResult": { + "type": "object", + "properties": { + "project": { + "$ref": "#/components/schemas/Project" + }, + "created": { + "description": "True if a new project was created, false if an existing project was added", + "type": "boolean" + } + }, + "required": [ + "project", + "created" + ] + }, + "BadRequestError": { + "type": "object", + "properties": { + "data": {}, + "errors": { + "type": "array", + "items": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + } + }, + "success": { + "type": "boolean", + "const": false + } + }, + "required": [ + "data", + "errors", + "success" + ] + }, + "NotFoundError": { + "type": "object", + "properties": { + "name": { + "type": "string", + "const": "NotFoundError" + }, + "data": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + }, + "required": [ + "name", + "data" + ] + }, + "DirectoryInfo": { + "type": "object", + "properties": { + "path": { + "type": "string" + }, + "name": { + "type": "string" + }, + "isGitRepo": { + "type": "boolean" + }, + "isExistingProject": { + "type": "boolean" + } + }, + "required": [ + "path", + "name", + "isGitRepo", + "isExistingProject" + ] + }, + "KeybindsConfig": { + "description": "Custom keybind configurations", + "type": "object", + "properties": { + "leader": { + "description": "Leader key for keybind combinations", + "default": "ctrl+x", + "type": "string" + }, + "app_exit": { + "description": "Exit the application", + "default": "ctrl+c,ctrl+d,q", + "type": "string" + }, + "editor_open": { + "description": "Open external editor", + "default": "e", + "type": "string" + }, + "theme_list": { + "description": "List available themes", + "default": "t", + "type": "string" + }, + "sidebar_toggle": { + "description": "Toggle sidebar", + "default": "b", + "type": "string" + }, + "scrollbar_toggle": { + "description": "Toggle session scrollbar", + "default": "none", + "type": "string" + }, + "username_toggle": { + "description": "Toggle username visibility", + "default": "none", + "type": "string" + }, + "status_view": { + "description": "View status", + "default": "s", + "type": "string" + }, + "session_export": { + "description": "Export session to editor", + "default": "x", + "type": "string" + }, + "session_new": { + "description": "Create a new session", + "default": "n", + "type": "string" + }, + "session_list": { + "description": "List all sessions", + "default": "l", + "type": "string" + }, + "session_timeline": { + "description": "Show session timeline", + "default": "g", + "type": "string" + }, + "session_fork": { + "description": "Fork session from message", + "default": "none", + "type": "string" + }, + "session_rename": { + "description": "Rename session", + "default": "none", + "type": "string" + }, + "session_share": { + "description": "Share current session", + "default": "none", + "type": "string" + }, + "session_unshare": { + "description": "Unshare current session", + "default": "none", + "type": "string" + }, + "session_search": { + "description": "Search in session messages", + "default": "ctrl+f", + "type": "string" + }, + "session_interrupt": { + "description": "Interrupt current session", + "default": "escape", + "type": "string" + }, + "session_compact": { + "description": "Compact the session", + "default": "c", + "type": "string" + }, + "messages_page_up": { + "description": "Scroll messages up by one page", + "default": "pageup", + "type": "string" + }, + "messages_page_down": { + "description": "Scroll messages down by one page", + "default": "pagedown", + "type": "string" + }, + "messages_half_page_up": { + "description": "Scroll messages up by half page", + "default": "ctrl+alt+u", + "type": "string" + }, + "messages_half_page_down": { + "description": "Scroll messages down by half page", + "default": "ctrl+alt+d", + "type": "string" + }, + "messages_first": { + "description": "Navigate to first message", + "default": "ctrl+g,home", + "type": "string" + }, + "messages_last": { + "description": "Navigate to last message", + "default": "ctrl+alt+g,end", + "type": "string" + }, + "messages_next": { + "description": "Navigate to next message", + "default": "none", + "type": "string" + }, + "messages_previous": { + "description": "Navigate to previous message", + "default": "none", + "type": "string" + }, + "messages_last_user": { + "description": "Navigate to last user message", + "default": "none", + "type": "string" + }, + "messages_copy": { + "description": "Copy message", + "default": "y", + "type": "string" + }, + "messages_undo": { + "description": "Undo message", + "default": "u", + "type": "string" + }, + "messages_redo": { + "description": "Redo message", + "default": "r", + "type": "string" + }, + "messages_toggle_conceal": { + "description": "Toggle code block concealment in messages", + "default": "h", + "type": "string" + }, + "tool_details": { + "description": "Toggle tool details visibility", + "default": "none", + "type": "string" + }, + "model_list": { + "description": "List available models", + "default": "m", + "type": "string" + }, + "model_cycle_recent": { + "description": "Next recently used model", + "default": "f2", + "type": "string" + }, + "model_cycle_recent_reverse": { + "description": "Previous recently used model", + "default": "shift+f2", + "type": "string" + }, + "model_cycle_favorite": { + "description": "Next favorite model", + "default": "none", + "type": "string" + }, + "model_cycle_favorite_reverse": { + "description": "Previous favorite model", + "default": "none", + "type": "string" + }, + "command_list": { + "description": "List available commands", + "default": "ctrl+p", + "type": "string" + }, + "agent_list": { + "description": "List agents", + "default": "a", + "type": "string" + }, + "agent_cycle": { + "description": "Next agent", + "default": "tab", + "type": "string" + }, + "agent_cycle_reverse": { + "description": "Previous agent", + "default": "shift+tab", + "type": "string" + }, + "variant_cycle": { + "description": "Cycle model variants", + "default": "ctrl+t", + "type": "string" + }, + "input_clear": { + "description": "Clear input field", + "default": "ctrl+c", + "type": "string" + }, + "input_paste": { + "description": "Paste from clipboard", + "default": "ctrl+v", + "type": "string" + }, + "input_submit": { + "description": "Submit input", + "default": "return", + "type": "string" + }, + "input_newline": { + "description": "Insert newline in input", + "default": "shift+return,ctrl+return,alt+return,ctrl+j", + "type": "string" + }, + "input_move_left": { + "description": "Move cursor left in input", + "default": "left,ctrl+b", + "type": "string" + }, + "input_move_right": { + "description": "Move cursor right in input", + "default": "right,ctrl+f", + "type": "string" + }, + "input_move_up": { + "description": "Move cursor up in input", + "default": "up", + "type": "string" + }, + "input_move_down": { + "description": "Move cursor down in input", + "default": "down", + "type": "string" + }, + "input_select_left": { + "description": "Select left in input", + "default": "shift+left", + "type": "string" + }, + "input_select_right": { + "description": "Select right in input", + "default": "shift+right", + "type": "string" + }, + "input_select_up": { + "description": "Select up in input", + "default": "shift+up", + "type": "string" + }, + "input_select_down": { + "description": "Select down in input", + "default": "shift+down", + "type": "string" + }, + "input_line_home": { + "description": "Move to start of line in input", + "default": "ctrl+a", + "type": "string" + }, + "input_line_end": { + "description": "Move to end of line in input", + "default": "ctrl+e", + "type": "string" + }, + "input_select_line_home": { + "description": "Select to start of line in input", + "default": "ctrl+shift+a", + "type": "string" + }, + "input_select_line_end": { + "description": "Select to end of line in input", + "default": "ctrl+shift+e", + "type": "string" + }, + "input_visual_line_home": { + "description": "Move to start of visual line in input", + "default": "alt+a", + "type": "string" + }, + "input_visual_line_end": { + "description": "Move to end of visual line in input", + "default": "alt+e", + "type": "string" + }, + "input_select_visual_line_home": { + "description": "Select to start of visual line in input", + "default": "alt+shift+a", + "type": "string" + }, + "input_select_visual_line_end": { + "description": "Select to end of visual line in input", + "default": "alt+shift+e", + "type": "string" + }, + "input_buffer_home": { + "description": "Move to start of buffer in input", + "default": "home", + "type": "string" + }, + "input_buffer_end": { + "description": "Move to end of buffer in input", + "default": "end", + "type": "string" + }, + "input_select_buffer_home": { + "description": "Select to start of buffer in input", + "default": "shift+home", + "type": "string" + }, + "input_select_buffer_end": { + "description": "Select to end of buffer in input", + "default": "shift+end", + "type": "string" + }, + "input_delete_line": { + "description": "Delete line in input", + "default": "ctrl+shift+d", + "type": "string" + }, + "input_delete_to_line_end": { + "description": "Delete to end of line in input", + "default": "ctrl+k", + "type": "string" + }, + "input_delete_to_line_start": { + "description": "Delete to start of line in input", + "default": "ctrl+u", + "type": "string" + }, + "input_backspace": { + "description": "Backspace in input", + "default": "backspace,shift+backspace", + "type": "string" + }, + "input_delete": { + "description": "Delete character in input", + "default": "ctrl+d,delete,shift+delete", + "type": "string" + }, + "input_undo": { + "description": "Undo in input", + "default": "ctrl+-,super+z", + "type": "string" + }, + "input_redo": { + "description": "Redo in input", + "default": "ctrl+.,super+shift+z", + "type": "string" + }, + "input_word_forward": { + "description": "Move word forward in input", + "default": "alt+f,alt+right,ctrl+right", + "type": "string" + }, + "input_word_backward": { + "description": "Move word backward in input", + "default": "alt+b,alt+left,ctrl+left", + "type": "string" + }, + "input_select_word_forward": { + "description": "Select word forward in input", + "default": "alt+shift+f,alt+shift+right", + "type": "string" + }, + "input_select_word_backward": { + "description": "Select word backward in input", + "default": "alt+shift+b,alt+shift+left", + "type": "string" + }, + "input_delete_word_forward": { + "description": "Delete word forward in input", + "default": "alt+d,alt+delete,ctrl+delete", + "type": "string" + }, + "input_delete_word_backward": { + "description": "Delete word backward in input", + "default": "ctrl+w,ctrl+backspace,alt+backspace", + "type": "string" + }, + "history_previous": { + "description": "Previous history item", + "default": "up", + "type": "string" + }, + "history_next": { + "description": "Next history item", + "default": "down", + "type": "string" + }, + "session_child_cycle": { + "description": "Next child session", + "default": "right", + "type": "string" + }, + "session_child_cycle_reverse": { + "description": "Previous child session", + "default": "left", + "type": "string" + }, + "session_parent": { + "description": "Go to parent session", + "default": "up", + "type": "string" + }, + "terminal_suspend": { + "description": "Suspend terminal", + "default": "ctrl+z", + "type": "string" + }, + "terminal_title_toggle": { + "description": "Toggle terminal title", + "default": "none", + "type": "string" + }, + "tips_toggle": { + "description": "Toggle tips on home screen", + "default": "h", + "type": "string" + } + }, + "additionalProperties": false + }, + "LogLevel": { + "description": "Log level", + "type": "string", + "enum": [ + "DEBUG", + "INFO", + "WARN", + "ERROR" + ] + }, + "ServerConfig": { + "description": "Server configuration for opencode serve and web commands", + "type": "object", + "properties": { + "port": { + "description": "Port to listen on", + "type": "integer", + "exclusiveMinimum": 0, + "maximum": 9007199254740991 + }, + "hostname": { + "description": "Hostname to listen on", + "type": "string" + }, + "mdns": { + "description": "Enable mDNS service discovery", + "type": "boolean" + }, + "cors": { + "description": "Additional domains to allow for CORS", + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "IdeConfig": { + "description": "IDE integration configuration for lockfile discovery and authentication", + "type": "object", + "properties": { + "lockfile_dir": { + "description": "Directory to scan for IDE lockfiles", + "type": "string" + }, + "auth_header_name": { + "description": "HTTP header name for IDE authentication", + "type": "string" + } + }, + "additionalProperties": false + }, + "PermissionActionConfig": { + "type": "string", + "enum": [ + "ask", + "allow", + "deny" + ] + }, + "PermissionObjectConfig": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "$ref": "#/components/schemas/PermissionActionConfig" + } + }, + "PermissionRuleConfig": { + "anyOf": [ + { + "$ref": "#/components/schemas/PermissionActionConfig" + }, + { + "$ref": "#/components/schemas/PermissionObjectConfig" + } + ] + }, + "PermissionConfig": { + "anyOf": [ + { + "type": "object", + "properties": { + "__originalKeys": { + "type": "array", + "items": { + "type": "string" + } + }, + "read": { + "$ref": "#/components/schemas/PermissionRuleConfig" + }, + "edit": { + "$ref": "#/components/schemas/PermissionRuleConfig" + }, + "glob": { + "$ref": "#/components/schemas/PermissionRuleConfig" + }, + "grep": { + "$ref": "#/components/schemas/PermissionRuleConfig" + }, + "list": { + "$ref": "#/components/schemas/PermissionRuleConfig" + }, + "bash": { + "$ref": "#/components/schemas/PermissionRuleConfig" + }, + "task": { + "$ref": "#/components/schemas/PermissionRuleConfig" + }, + "external_directory": { + "$ref": "#/components/schemas/PermissionRuleConfig" + }, + "todowrite": { + "$ref": "#/components/schemas/PermissionActionConfig" + }, + "todoread": { + "$ref": "#/components/schemas/PermissionActionConfig" + }, + "question": { + "$ref": "#/components/schemas/PermissionActionConfig" + }, + "webfetch": { + "$ref": "#/components/schemas/PermissionActionConfig" + }, + "websearch": { + "$ref": "#/components/schemas/PermissionActionConfig" + }, + "codesearch": { + "$ref": "#/components/schemas/PermissionActionConfig" + }, + "lsp": { + "$ref": "#/components/schemas/PermissionRuleConfig" + }, + "doom_loop": { + "$ref": "#/components/schemas/PermissionActionConfig" + } + }, + "additionalProperties": { + "$ref": "#/components/schemas/PermissionRuleConfig" + } + }, + { + "$ref": "#/components/schemas/PermissionActionConfig" + } + ] + }, + "AgentConfig": { + "type": "object", + "properties": { + "model": { + "type": "string" + }, + "temperature": { + "type": "number" + }, + "top_p": { + "type": "number" + }, + "prompt": { + "type": "string" + }, + "tools": { + "description": "@deprecated Use 'permission' field instead", + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "boolean" + } + }, + "disable": { + "type": "boolean" + }, + "description": { + "description": "Description of when to use the agent", + "type": "string" + }, + "mode": { + "type": "string", + "enum": [ + "subagent", + "primary", + "all" + ] + }, + "hidden": { + "description": "Hide this subagent from the @ autocomplete menu (default: false, only applies to mode: subagent)", + "type": "boolean" + }, + "options": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + }, + "color": { + "description": "Hex color code for the agent (e.g., #FF5733)", + "type": "string", + "pattern": "^#[0-9a-fA-F]{6}$" + }, + "steps": { + "description": "Maximum number of agentic iterations before forcing text-only response", + "type": "integer", + "exclusiveMinimum": 0, + "maximum": 9007199254740991 + }, + "maxSteps": { + "description": "@deprecated Use 'steps' field instead.", + "type": "integer", + "exclusiveMinimum": 0, + "maximum": 9007199254740991 + }, + "permission": { + "$ref": "#/components/schemas/PermissionConfig" + } + }, + "additionalProperties": {} + }, + "ProviderConfig": { + "type": "object", + "properties": { + "api": { + "type": "string" + }, + "name": { + "type": "string" + }, + "env": { + "type": "array", + "items": { + "type": "string" + } + }, + "id": { + "type": "string" + }, + "npm": { + "type": "string" + }, + "models": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "family": { + "type": "string" + }, + "release_date": { + "type": "string" + }, + "attachment": { + "type": "boolean" + }, + "reasoning": { + "type": "boolean" + }, + "temperature": { + "type": "boolean" + }, + "tool_call": { + "type": "boolean" + }, + "interleaved": { + "anyOf": [ + { + "type": "boolean", + "const": true + }, + { + "type": "object", + "properties": { + "field": { + "type": "string", + "enum": [ + "reasoning_content", + "reasoning_details" + ] + } + }, + "required": [ + "field" + ], + "additionalProperties": false + } + ] + }, + "cost": { + "type": "object", + "properties": { + "input": { + "type": "number" + }, + "output": { + "type": "number" + }, + "cache_read": { + "type": "number" + }, + "cache_write": { + "type": "number" + }, + "context_over_200k": { + "type": "object", + "properties": { + "input": { + "type": "number" + }, + "output": { + "type": "number" + }, + "cache_read": { + "type": "number" + }, + "cache_write": { + "type": "number" + } + }, + "required": [ + "input", + "output" + ] + } + }, + "required": [ + "input", + "output" + ] + }, + "limit": { + "type": "object", + "properties": { + "context": { + "type": "number" + }, + "output": { + "type": "number" + } + }, + "required": [ + "context", + "output" + ] + }, + "modalities": { + "type": "object", + "properties": { + "input": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "text", + "audio", + "image", + "video", + "pdf" + ] + } + }, + "output": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "text", + "audio", + "image", + "video", + "pdf" + ] + } + } + }, + "required": [ + "input", + "output" + ] + }, + "experimental": { + "type": "boolean" + }, + "status": { + "type": "string", + "enum": [ + "alpha", + "beta", + "deprecated" + ] + }, + "options": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + }, + "headers": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string" + } + }, + "provider": { + "type": "object", + "properties": { + "npm": { + "type": "string" + } + }, + "required": [ + "npm" + ] + }, + "variants": { + "description": "Variant-specific configuration", + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "object", + "properties": { + "disabled": { + "description": "Disable this variant for the model", + "type": "boolean" + } + }, + "additionalProperties": {} + } + } + } + } + }, + "whitelist": { + "type": "array", + "items": { + "type": "string" + } + }, + "blacklist": { + "type": "array", + "items": { + "type": "string" + } + }, + "options": { + "type": "object", + "properties": { + "apiKey": { + "type": "string" + }, + "baseURL": { + "type": "string" + }, + "enterpriseUrl": { + "description": "GitHub Enterprise URL for copilot authentication", + "type": "string" + }, + "setCacheKey": { + "description": "Enable promptCacheKey for this provider (default false)", + "type": "boolean" + }, + "timeout": { + "description": "Timeout in milliseconds for requests to this provider. Default is 300000 (5 minutes). Set to false to disable timeout.", + "anyOf": [ + { + "description": "Timeout in milliseconds for requests to this provider. Default is 300000 (5 minutes). Set to false to disable timeout.", + "type": "integer", + "exclusiveMinimum": 0, + "maximum": 9007199254740991 + }, + { + "description": "Disable timeout for this provider entirely.", + "type": "boolean", + "const": false + } + ] + } + }, + "additionalProperties": {} + } + }, + "additionalProperties": false + }, + "McpLocalConfig": { + "type": "object", + "properties": { + "type": { + "description": "Type of MCP server connection", + "type": "string", + "const": "local" + }, + "command": { + "description": "Command and arguments to run the MCP server", + "type": "array", + "items": { + "type": "string" + } + }, + "environment": { + "description": "Environment variables to set when running the MCP server", + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string" + } + }, + "enabled": { + "description": "Enable or disable the MCP server on startup", + "type": "boolean" + }, + "timeout": { + "description": "Timeout in ms for fetching tools from the MCP server. Defaults to 5000 (5 seconds) if not specified.", + "type": "integer", + "exclusiveMinimum": 0, + "maximum": 9007199254740991 + } + }, + "required": [ + "type", + "command" + ], + "additionalProperties": false + }, + "McpOAuthConfig": { + "type": "object", + "properties": { + "clientId": { + "description": "OAuth client ID. If not provided, dynamic client registration (RFC 7591) will be attempted.", + "type": "string" + }, + "clientSecret": { + "description": "OAuth client secret (if required by the authorization server)", + "type": "string" + }, + "scope": { + "description": "OAuth scopes to request during authorization", + "type": "string" + } + }, + "additionalProperties": false + }, + "McpRemoteConfig": { + "type": "object", + "properties": { + "type": { + "description": "Type of MCP server connection", + "type": "string", + "const": "remote" + }, + "url": { + "description": "URL of the remote MCP server", + "type": "string" + }, + "enabled": { + "description": "Enable or disable the MCP server on startup", + "type": "boolean" + }, + "headers": { + "description": "Headers to send with the request", + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string" + } + }, + "oauth": { + "description": "OAuth authentication configuration for the MCP server. Set to false to disable OAuth auto-detection.", + "anyOf": [ + { + "$ref": "#/components/schemas/McpOAuthConfig" + }, + { + "type": "boolean", + "const": false + } + ] + }, + "timeout": { + "description": "Timeout in ms for fetching tools from the MCP server. Defaults to 5000 (5 seconds) if not specified.", + "type": "integer", + "exclusiveMinimum": 0, + "maximum": 9007199254740991 + } + }, + "required": [ + "type", + "url" + ], + "additionalProperties": false + }, + "LayoutConfig": { + "description": "@deprecated Always uses stretch layout.", + "type": "string", + "enum": [ + "auto", + "stretch" + ] + }, + "Config": { + "type": "object", + "properties": { + "$schema": { + "description": "JSON schema reference for configuration validation", + "type": "string" + }, + "theme": { + "description": "Theme name to use for the interface", + "type": "string" + }, + "keybinds": { + "$ref": "#/components/schemas/KeybindsConfig" + }, + "logLevel": { + "$ref": "#/components/schemas/LogLevel" + }, + "tui": { + "description": "TUI specific settings", + "type": "object", + "properties": { + "scroll_speed": { + "description": "TUI scroll speed", + "type": "number", + "minimum": 0.001 + }, + "scroll_acceleration": { + "description": "Scroll acceleration settings", + "type": "object", + "properties": { + "enabled": { + "description": "Enable scroll acceleration", + "type": "boolean" + } + }, + "required": [ + "enabled" + ] + }, + "diff_style": { + "description": "Control diff rendering style: 'auto' adapts to terminal width, 'stacked' always shows single column", + "type": "string", + "enum": [ + "auto", + "stacked" + ] + }, + "density": { + "description": "Control TUI layout density: 'auto' adapts to terminal height, 'comfortable' uses standard spacing, 'compact' reduces vertical whitespace", + "default": "auto", + "type": "string", + "enum": [ + "auto", + "comfortable", + "compact" + ] + } + } + }, + "server": { + "$ref": "#/components/schemas/ServerConfig" + }, + "ide": { + "$ref": "#/components/schemas/IdeConfig" + }, + "command": { + "description": "Command configuration, see https://opencode.ai/docs/commands", + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "object", + "properties": { + "template": { + "type": "string" + }, + "description": { + "type": "string" + }, + "agent": { + "type": "string" + }, + "model": { + "type": "string" + }, + "subtask": { + "type": "boolean" + } + }, + "required": [ + "template" + ] + } + }, + "watcher": { + "type": "object", + "properties": { + "ignore": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "plugin": { + "type": "array", + "items": { + "type": "string" + } + }, + "snapshot": { + "type": "boolean" + }, + "share": { + "description": "Control sharing behavior:'manual' allows manual sharing via commands, 'auto' enables automatic sharing, 'disabled' disables all sharing", + "type": "string", + "enum": [ + "manual", + "auto", + "disabled" + ] + }, + "autoshare": { + "description": "@deprecated Use 'share' field instead. Share newly created sessions automatically", + "type": "boolean" + }, + "autoupdate": { + "description": "Automatically update to the latest version. Set to true to auto-update, false to disable, or 'notify' to show update notifications", + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "const": "notify" + } + ] + }, + "disabled_providers": { + "description": "Disable providers that are loaded automatically", + "type": "array", + "items": { + "type": "string" + } + }, + "enabled_providers": { + "description": "When set, ONLY these providers will be enabled. All other providers will be ignored", + "type": "array", + "items": { + "type": "string" + } + }, + "model": { + "description": "Model to use in the format of provider/model, eg anthropic/claude-2", + "type": "string" + }, + "small_model": { + "description": "Small model to use for tasks like title generation in the format of provider/model", + "type": "string" + }, + "default_agent": { + "description": "Default agent to use when none is specified. Must be a primary agent. Falls back to 'build' if not set or if the specified agent is invalid.", + "type": "string" + }, + "username": { + "description": "Custom username to display in conversations instead of system username", + "type": "string" + }, + "mode": { + "description": "@deprecated Use `agent` field instead.", + "type": "object", + "properties": { + "build": { + "$ref": "#/components/schemas/AgentConfig" + }, + "plan": { + "$ref": "#/components/schemas/AgentConfig" + } + }, + "additionalProperties": { + "$ref": "#/components/schemas/AgentConfig" + } + }, + "agent": { + "description": "Agent configuration, see https://opencode.ai/docs/agent", + "type": "object", + "properties": { + "plan": { + "$ref": "#/components/schemas/AgentConfig" + }, + "build": { + "$ref": "#/components/schemas/AgentConfig" + }, + "general": { + "$ref": "#/components/schemas/AgentConfig" + }, + "explore": { + "$ref": "#/components/schemas/AgentConfig" + }, + "title": { + "$ref": "#/components/schemas/AgentConfig" + }, + "summary": { + "$ref": "#/components/schemas/AgentConfig" + }, + "compaction": { + "$ref": "#/components/schemas/AgentConfig" + } + }, + "additionalProperties": { + "$ref": "#/components/schemas/AgentConfig" + } + }, + "provider": { + "description": "Custom provider configurations and model overrides", + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "$ref": "#/components/schemas/ProviderConfig" + } + }, + "mcp": { + "description": "MCP (Model Context Protocol) server configurations", + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "anyOf": [ + { + "anyOf": [ + { + "$ref": "#/components/schemas/McpLocalConfig" + }, + { + "$ref": "#/components/schemas/McpRemoteConfig" + } + ] + }, + { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + } + }, + "required": [ + "enabled" + ], + "additionalProperties": false + } + ] + } + }, + "formatter": { + "anyOf": [ + { + "type": "boolean", + "const": false + }, + { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "object", + "properties": { + "disabled": { + "type": "boolean" + }, + "command": { + "type": "array", + "items": { + "type": "string" + } + }, + "environment": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string" + } + }, + "extensions": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + ] + }, + "lsp": { + "anyOf": [ + { + "type": "boolean", + "const": false + }, + { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "anyOf": [ + { + "type": "object", + "properties": { + "disabled": { + "type": "boolean", + "const": true + } + }, + "required": [ + "disabled" + ] + }, + { + "type": "object", + "properties": { + "command": { + "type": "array", + "items": { + "type": "string" + } + }, + "extensions": { + "type": "array", + "items": { + "type": "string" + } + }, + "disabled": { + "type": "boolean" + }, + "env": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string" + } + }, + "initialization": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + } + }, + "required": [ + "command" + ] + } + ] + } + } + ] + }, + "instructions": { + "description": "Additional instruction files or patterns to include", + "type": "array", + "items": { + "type": "string" + } + }, + "layout": { + "$ref": "#/components/schemas/LayoutConfig" + }, + "permission": { + "$ref": "#/components/schemas/PermissionConfig" + }, + "tools": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "boolean" + } + }, + "enterprise": { + "type": "object", + "properties": { + "url": { + "description": "Enterprise URL", + "type": "string" + } + } + }, + "compaction": { + "type": "object", + "properties": { + "auto": { + "description": "Enable automatic compaction when context is full (default: true)", + "type": "boolean" + }, + "prune": { + "description": "Enable pruning of old tool outputs (default: true)", + "type": "boolean" + } + } + }, + "experimental": { + "type": "object", + "properties": { + "hook": { + "type": "object", + "properties": { + "file_edited": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "array", + "items": { + "type": "object", + "properties": { + "command": { + "type": "array", + "items": { + "type": "string" + } + }, + "environment": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string" + } + } + }, + "required": [ + "command" + ] + } + } + }, + "session_completed": { + "type": "array", + "items": { + "type": "object", + "properties": { + "command": { + "type": "array", + "items": { + "type": "string" + } + }, + "environment": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string" + } + } + }, + "required": [ + "command" + ] + } + } + } + }, + "chatMaxRetries": { + "description": "Number of retries for chat completions on failure", + "type": "number" + }, + "disable_paste_summary": { + "type": "boolean" + }, + "batch_tool": { + "description": "Enable the batch tool", + "type": "boolean" + }, + "openai_multi_account": { + "description": "Enable multi-account storage and switching for OpenAI OAuth", + "default": false, + "type": "boolean" + }, + "openTelemetry": { + "description": "Enable OpenTelemetry spans for AI SDK calls (using the 'experimental_telemetry' flag)", + "type": "boolean" + }, + "primary_tools": { + "description": "Tools that should only be available to primary agents.", + "type": "array", + "items": { + "type": "string" + } + }, + "continue_loop_on_deny": { + "description": "Continue the agent loop when a tool call is denied", + "type": "boolean" + }, + "mcp_timeout": { + "description": "Timeout in milliseconds for MCP server initialization", + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + }, + "skills": { + "type": "object", + "properties": { + "registries": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "github", + "clawdhub", + "url" + ] + }, + "url": { + "type": "string", + "format": "uri" + }, + "enabled": { + "default": true, + "type": "boolean" + }, + "globs": { + "default": [ + "*/SKILL.md", + "skills/**/SKILL.md" + ], + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "id", + "type", + "url" + ] + } + }, + "default_scope": { + "default": "project", + "type": "string", + "enum": [ + "user", + "project" + ] + }, + "auto_update": { + "default": false, + "type": "boolean" + } + } + } + } + } + }, + "additionalProperties": false + }, + "ToolIDs": { + "type": "array", + "items": { + "type": "string" + } + }, + "ToolListItem": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "description": { + "type": "string" + }, + "parameters": {} + }, + "required": [ + "id", + "description", + "parameters" + ] + }, + "ToolList": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ToolListItem" + } + }, + "Path": { + "type": "object", + "properties": { + "home": { + "type": "string" + }, + "state": { + "type": "string" + }, + "config": { + "type": "string" + }, + "worktree": { + "type": "string" + }, + "directory": { + "type": "string" + } + }, + "required": [ + "home", + "state", + "config", + "worktree", + "directory" + ] + }, + "Worktree": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "branch": { + "type": "string" + }, + "directory": { + "type": "string" + } + }, + "required": [ + "name", + "branch", + "directory" + ] + }, + "WorktreeCreateInput": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "startCommand": { + "type": "string" + } + } + }, + "VcsInfo": { + "type": "object", + "properties": { + "branch": { + "type": "string" + } + }, + "required": [ + "branch" + ] + }, + "TextPartInput": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string", + "const": "text" + }, + "text": { + "type": "string" + }, + "synthetic": { + "type": "boolean" + }, + "ignored": { + "type": "boolean" + }, + "time": { + "type": "object", + "properties": { + "start": { + "type": "number" + }, + "end": { + "type": "number" + } + }, + "required": [ + "start" + ] + }, + "metadata": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + } + }, + "required": [ + "type", + "text" + ] + }, + "FilePartInput": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string", + "const": "file" + }, + "mime": { + "type": "string" + }, + "filename": { + "type": "string" + }, + "url": { + "type": "string" + }, + "source": { + "$ref": "#/components/schemas/FilePartSource" + } + }, + "required": [ + "type", + "mime", + "url" + ] + }, + "AgentPartInput": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string", + "const": "agent" + }, + "name": { + "type": "string" + }, + "source": { + "type": "object", + "properties": { + "value": { + "type": "string" + }, + "start": { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + }, + "end": { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + } + }, + "required": [ + "value", + "start", + "end" + ] + } + }, + "required": [ + "type", + "name" + ] + }, + "SubtaskPartInput": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string", + "const": "subtask" + }, + "prompt": { + "type": "string" + }, + "description": { + "type": "string" + }, + "agent": { + "type": "string" + }, + "command": { + "type": "string" + }, + "model": { + "type": "object", + "properties": { + "providerID": { + "type": "string" + }, + "modelID": { + "type": "string" + } + }, + "required": [ + "providerID", + "modelID" + ] + }, + "parentAgent": { + "type": "string" + }, + "parentModel": { + "type": "object", + "properties": { + "providerID": { + "type": "string" + }, + "modelID": { + "type": "string" + } + }, + "required": [ + "providerID", + "modelID" + ] + } + }, + "required": [ + "type", + "prompt", + "description", + "agent" + ] + }, + "Command": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "agent": { + "type": "string" + }, + "model": { + "type": "string" + }, + "mcp": { + "type": "boolean" + }, + "template": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "string" + } + ] + }, + "type": { + "default": "template", + "type": "string", + "enum": [ + "template", + "plugin" + ] + }, + "subtask": { + "type": "boolean" + }, + "sessionOnly": { + "type": "boolean" + }, + "aliases": { + "type": "array", + "items": { + "type": "string" + } + }, + "hints": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "name", + "template", + "hints" + ] + }, + "Model": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "providerID": { + "type": "string" + }, + "api": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "url": { + "type": "string" + }, + "npm": { + "type": "string" + } + }, + "required": [ + "id", + "url", + "npm" + ] + }, + "name": { + "type": "string" + }, + "family": { + "type": "string" + }, + "capabilities": { + "type": "object", + "properties": { + "temperature": { + "type": "boolean" + }, + "reasoning": { + "type": "boolean" + }, + "attachment": { + "type": "boolean" + }, + "toolcall": { + "type": "boolean" + }, + "input": { + "type": "object", + "properties": { + "text": { + "type": "boolean" + }, + "audio": { + "type": "boolean" + }, + "image": { + "type": "boolean" + }, + "video": { + "type": "boolean" + }, + "pdf": { + "type": "boolean" + } + }, + "required": [ + "text", + "audio", + "image", + "video", + "pdf" + ] + }, + "output": { + "type": "object", + "properties": { + "text": { + "type": "boolean" + }, + "audio": { + "type": "boolean" + }, + "image": { + "type": "boolean" + }, + "video": { + "type": "boolean" + }, + "pdf": { + "type": "boolean" + } + }, + "required": [ + "text", + "audio", + "image", + "video", + "pdf" + ] + }, + "interleaved": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "object", + "properties": { + "field": { + "type": "string", + "enum": [ + "reasoning_content", + "reasoning_details" + ] + } + }, + "required": [ + "field" + ] + } + ] + } + }, + "required": [ + "temperature", + "reasoning", + "attachment", + "toolcall", + "input", + "output", + "interleaved" + ] + }, + "cost": { + "type": "object", + "properties": { + "input": { + "type": "number" + }, + "output": { + "type": "number" + }, + "cache": { + "type": "object", + "properties": { + "read": { + "type": "number" + }, + "write": { + "type": "number" + } + }, + "required": [ + "read", + "write" + ] + }, + "experimentalOver200K": { + "type": "object", + "properties": { + "input": { + "type": "number" + }, + "output": { + "type": "number" + }, + "cache": { + "type": "object", + "properties": { + "read": { + "type": "number" + }, + "write": { + "type": "number" + } + }, + "required": [ + "read", + "write" + ] + } + }, + "required": [ + "input", + "output", + "cache" + ] + } + }, + "required": [ + "input", + "output", + "cache" + ] + }, + "limit": { + "type": "object", + "properties": { + "context": { + "type": "number" + }, + "output": { + "type": "number" + } + }, + "required": [ + "context", + "output" + ] + }, + "status": { + "type": "string", + "enum": [ + "alpha", + "beta", + "deprecated", + "active" + ] + }, + "options": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + }, + "headers": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string" + } + }, + "release_date": { + "type": "string" + }, + "variants": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + } + } + }, + "required": [ + "id", + "providerID", + "api", + "name", + "capabilities", + "cost", + "limit", + "status", + "options", + "headers", + "release_date" + ] + }, + "Provider": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "source": { + "type": "string", + "enum": [ + "env", + "config", + "custom", + "api" + ] + }, + "env": { + "type": "array", + "items": { + "type": "string" + } + }, + "key": { + "type": "string" + }, + "options": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + }, + "models": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "$ref": "#/components/schemas/Model" + } + } + }, + "required": [ + "id", + "name", + "source", + "env", + "options", + "models" + ] + }, + "ProviderAuthMethod": { + "type": "object", + "properties": { + "type": { + "anyOf": [ + { + "type": "string", + "const": "oauth" + }, + { + "type": "string", + "const": "api" + } + ] + }, + "label": { + "type": "string" + } + }, + "required": [ + "type", + "label" + ] + }, + "ProviderAuthAuthorization": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "method": { + "anyOf": [ + { + "type": "string", + "const": "auto" + }, + { + "type": "string", + "const": "code" + } + ] + }, + "instructions": { + "type": "string" + } + }, + "required": [ + "url", + "method", + "instructions" + ] + }, + "Symbol": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "kind": { + "type": "number" + }, + "location": { + "type": "object", + "properties": { + "uri": { + "type": "string" + }, + "range": { + "$ref": "#/components/schemas/Range" + } + }, + "required": [ + "uri", + "range" + ] + } + }, + "required": [ + "name", + "kind", + "location" + ] + }, + "FileNode": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "absolute": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "file", + "directory" + ] + }, + "ignored": { + "type": "boolean" + } + }, + "required": [ + "name", + "path", + "absolute", + "type", + "ignored" + ] + }, + "FileContent": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "text" + }, + "content": { + "type": "string" + }, + "diff": { + "type": "string" + }, + "patch": { + "type": "object", + "properties": { + "oldFileName": { + "type": "string" + }, + "newFileName": { + "type": "string" + }, + "oldHeader": { + "type": "string" + }, + "newHeader": { + "type": "string" + }, + "hunks": { + "type": "array", + "items": { + "type": "object", + "properties": { + "oldStart": { + "type": "number" + }, + "oldLines": { + "type": "number" + }, + "newStart": { + "type": "number" + }, + "newLines": { + "type": "number" + }, + "lines": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "oldStart", + "oldLines", + "newStart", + "newLines", + "lines" + ] + } + }, + "index": { + "type": "string" + } + }, + "required": [ + "oldFileName", + "newFileName", + "hunks" + ] + }, + "encoding": { + "type": "string", + "const": "base64" + }, + "mimeType": { + "type": "string" + } + }, + "required": [ + "type", + "content" + ] + }, + "File": { + "type": "object", + "properties": { + "path": { + "type": "string" + }, + "added": { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + }, + "removed": { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + }, + "status": { + "type": "string", + "enum": [ + "added", + "deleted", + "modified" + ] + } + }, + "required": [ + "path", + "added", + "removed", + "status" + ] + }, + "Agent": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "mode": { + "type": "string", + "enum": [ + "subagent", + "primary", + "all" + ] + }, + "native": { + "type": "boolean" + }, + "hidden": { + "type": "boolean" + }, + "topP": { + "type": "number" + }, + "temperature": { + "type": "number" + }, + "color": { + "type": "string" + }, + "permission": { + "$ref": "#/components/schemas/PermissionRuleset" + }, + "model": { + "type": "object", + "properties": { + "modelID": { + "type": "string" + }, + "providerID": { + "type": "string" + } + }, + "required": [ + "modelID", + "providerID" + ] + }, + "prompt": { + "type": "string" + }, + "options": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + }, + "steps": { + "type": "integer", + "exclusiveMinimum": 0, + "maximum": 9007199254740991 + } + }, + "required": [ + "name", + "mode", + "permission", + "options" + ] + }, + "MCPStatusConnected": { + "type": "object", + "properties": { + "status": { + "type": "string", + "const": "connected" + } + }, + "required": [ + "status" + ] + }, + "MCPStatusDisabled": { + "type": "object", + "properties": { + "status": { + "type": "string", + "const": "disabled" + } + }, + "required": [ + "status" + ] + }, + "MCPStatusFailed": { + "type": "object", + "properties": { + "status": { + "type": "string", + "const": "failed" + }, + "error": { + "type": "string" + } + }, + "required": [ + "status", + "error" + ] + }, + "MCPStatusNeedsAuth": { + "type": "object", + "properties": { + "status": { + "type": "string", + "const": "needs_auth" + } + }, + "required": [ + "status" + ] + }, + "MCPStatusNeedsClientRegistration": { + "type": "object", + "properties": { + "status": { + "type": "string", + "const": "needs_client_registration" + }, + "error": { + "type": "string" + } + }, + "required": [ + "status", + "error" + ] + }, + "MCPStatus": { + "anyOf": [ + { + "$ref": "#/components/schemas/MCPStatusConnected" + }, + { + "$ref": "#/components/schemas/MCPStatusDisabled" + }, + { + "$ref": "#/components/schemas/MCPStatusFailed" + }, + { + "$ref": "#/components/schemas/MCPStatusNeedsAuth" + }, + { + "$ref": "#/components/schemas/MCPStatusNeedsClientRegistration" + } + ] + }, + "McpResource": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "uri": { + "type": "string" + }, + "description": { + "type": "string" + }, + "mimeType": { + "type": "string" + }, + "client": { + "type": "string" + } + }, + "required": [ + "name", + "uri", + "client" + ] + }, + "LSPStatus": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "root": { + "type": "string" + }, + "status": { + "anyOf": [ + { + "type": "string", + "const": "connected" + }, + { + "type": "string", + "const": "error" + } + ] + } + }, + "required": [ + "id", + "name", + "root", + "status" + ] + }, + "FormatterStatus": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "extensions": { + "type": "array", + "items": { + "type": "string" + } + }, + "enabled": { + "type": "boolean" + } + }, + "required": [ + "name", + "extensions", + "enabled" + ] + }, + "OAuth": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "oauth" + }, + "refresh": { + "type": "string" + }, + "access": { + "type": "string" + }, + "expires": { + "type": "number" + }, + "accountId": { + "type": "string" + }, + "enterpriseUrl": { + "type": "string" + }, + "email": { + "type": "string" + }, + "name": { + "type": "string" + }, + "plan": { + "type": "string" + }, + "orgName": { + "type": "string" + } + }, + "required": [ + "type", + "refresh", + "access", + "expires" + ] + }, + "ApiAuth": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "api" + }, + "key": { + "type": "string" + } + }, + "required": [ + "type", + "key" + ] + }, + "WellKnownAuth": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "wellknown" + }, + "key": { + "type": "string" + }, + "token": { + "type": "string" + } + }, + "required": [ + "type", + "key", + "token" + ] + }, + "Auth": { + "anyOf": [ + { + "$ref": "#/components/schemas/OAuth" + }, + { + "$ref": "#/components/schemas/ApiAuth" + }, + { + "$ref": "#/components/schemas/WellKnownAuth" + } + ] + } + } + } +} \ No newline at end of file diff --git a/packages/opencode/package.json b/packages/opencode/package.json index bf960934ef5..f2423a8c4f2 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.1.19", + "version": "1.1.20", "name": "opencode", "type": "module", "license": "MIT", diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts index 6847d29abe5..52896136ee9 100644 --- a/packages/opencode/src/agent/agent.ts +++ b/packages/opencode/src/agent/agent.ts @@ -13,6 +13,8 @@ import PROMPT_SUMMARY from "./prompt/summary.txt" import PROMPT_TITLE from "./prompt/title.txt" import { PermissionNext } from "@/permission/next" import { mergeDeep, pipe, sortBy, values } from "remeda" +import { Global } from "@/global" +import path from "path" export namespace Agent { export const Info = z @@ -88,9 +90,13 @@ export namespace Agent { PermissionNext.fromConfig({ question: "allow", plan_exit: "allow", + external_directory: { + [path.join(Global.Path.data, "plans", "*")]: "allow", + }, edit: { "*": "deny", ".opencode/plans/*.md": "allow", + [path.relative(Instance.worktree, path.join(Global.Path.data, "plans/*.md"))]: "allow", }, }), user, diff --git a/packages/opencode/src/auth/index.ts b/packages/opencode/src/auth/index.ts index 3fd28305368..110ca0ec2bf 100644 --- a/packages/opencode/src/auth/index.ts +++ b/packages/opencode/src/auth/index.ts @@ -2,9 +2,12 @@ import path from "path" import { Global } from "../global" import fs from "fs/promises" import z from "zod" +import { Log } from "../util/log" export const OAUTH_DUMMY_KEY = "opencode-oauth-dummy-key" +const log = Log.create({ service: "auth" }) + export namespace Auth { export const Oauth = z .object({ @@ -14,6 +17,10 @@ export namespace Auth { expires: z.number(), accountId: z.string().optional(), enterpriseUrl: z.string().optional(), + email: z.string().optional(), + name: z.string().optional(), + plan: z.string().optional(), + orgName: z.string().optional(), }) .meta({ ref: "OAuth" }) @@ -39,7 +46,16 @@ export namespace Auth { export async function get(providerID: string) { const auth = await all() - return auth[providerID] + const entry = auth[providerID] + if (entry || providerID !== "openai") return entry + const legacy = auth.codex + if (legacy) { + log.info("auth migration: using legacy codex entry", { providerID: "openai" }) + } + if (legacy?.type === "oauth") { + await set("openai", legacy) + } + return legacy } export async function all(): Promise> { diff --git a/packages/opencode/src/cli/cmd/tui/component/account-badge.tsx b/packages/opencode/src/cli/cmd/tui/component/account-badge.tsx new file mode 100644 index 00000000000..c7b2da315f1 --- /dev/null +++ b/packages/opencode/src/cli/cmd/tui/component/account-badge.tsx @@ -0,0 +1,22 @@ +import { createMemo } from "solid-js" +import { useSync } from "@tui/context/sync" +import { useTheme } from "../context/theme" + +export function AccountBadge() { + const sync = useSync() + const { theme } = useTheme() + + const authInfo = createMemo(() => sync.data.provider_auth_info.openai) + + return () => { + const info = authInfo() + if (!info?.authenticated || !info.email) return null + + return ( + + {info.email} + {info.plan && [{info.plan.charAt(0).toUpperCase() + info.plan.slice(1)}]} + + ) + } +} diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx index cb33c6301bd..2fac1a63223 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx @@ -26,67 +26,92 @@ export function createDialogProviderOptions() { const sync = useSync() const dialog = useDialog() const sdk = useSDK() + const connected = createMemo(() => new Set(sync.data.provider_next.connected)) const options = createMemo(() => { return pipe( sync.data.provider_next.all, sortBy((x) => PROVIDER_PRIORITY[x.id] ?? 99), - map((provider) => ({ - title: provider.name, - value: provider.id, - description: { - opencode: "(Recommended)", - anthropic: "(Claude Max or API key)", - openai: "(ChatGPT Plus/Pro or API key)", - }[provider.id], - category: provider.id in PROVIDER_PRIORITY ? "Popular" : "Other", - async onSelect() { - const methods = sync.data.provider_auth[provider.id] ?? [ - { - type: "api", - label: "API key", - }, - ] - let index: number | null = 0 - if (methods.length > 1) { - index = await new Promise((resolve) => { - dialog.replace( - () => ( - ({ - title: x.label, - value: index, - }))} - onSelect={(option) => resolve(option.value)} + map((provider) => { + const authInfo = sync.data.provider_auth_info[provider.id] + const isOAuthConnected = authInfo?.authenticated && authInfo?.email + const isConnected = connected().has(provider.id) + + let description: string | undefined + if (isOAuthConnected) { + description = `Connected: ${authInfo.email}${authInfo.plan ? ` [${authInfo.plan}]` : ""}` + } else { + description = { + opencode: "(Recommended)", + anthropic: "(Claude Max or API key)", + openai: "(ChatGPT Plus/Pro or API key)", + }[provider.id] + } + + return { + title: provider.name, + value: provider.id, + description, + category: provider.id in PROVIDER_PRIORITY ? "Popular" : "Other", + footer: isConnected ? "Connected" : undefined, + async onSelect() { + const methods = sync.data.provider_auth[provider.id] ?? [ + { + type: "api", + label: "API key", + }, + ] + let index: number | null = 0 + if (methods.length > 1) { + index = await new Promise((resolve) => { + dialog.replace( + () => ( + ({ + title: x.label, + value: index, + }))} + onSelect={(option) => resolve(option.value)} + /> + ), + () => resolve(null), + ) + }) + } + if (index == null) return + const method = methods[index] + if (method.type === "oauth") { + const result = await sdk.client.provider.oauth.authorize({ + providerID: provider.id, + method: index, + }) + if (result.data?.method === "code") { + dialog.replace(() => ( + - ), - () => resolve(null), - ) - }) - } - if (index == null) return - const method = methods[index] - if (method.type === "oauth") { - const result = await sdk.client.provider.oauth.authorize({ - providerID: provider.id, - method: index, - }) - if (result.data?.method === "code") { - dialog.replace(() => ( - - )) + )) + } + if (result.data?.method === "auto") { + dialog.replace(() => ( + + )) + } } - if (result.data?.method === "auto") { - dialog.replace(() => ( - - )) + if (method.type === "api") { + return dialog.replace(() => ) } - } - if (method.type === "api") { - return dialog.replace(() => ) - } - }, - })), + }, + } + }), ) }) return options diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx index 3d9c2e604ba..179d92eb611 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx @@ -161,6 +161,26 @@ export function Autocomplete(props: { }) props.setPrompt((draft) => { + if (part.type === "file") { + const existingIndex = draft.parts.findIndex((p) => p.type === "file" && "url" in p && p.url === part.url) + if (existingIndex !== -1) { + const existing = draft.parts[existingIndex] + if ( + part.source?.text && + existing && + "source" in existing && + existing.source && + "text" in existing.source && + existing.source.text + ) { + existing.source.text.start = extmarkStart + existing.source.text.end = extmarkEnd + existing.source.text.value = virtualText + } + return + } + } + if (part.type === "file" && part.source?.text) { part.source.text.start = extmarkStart part.source.text.end = extmarkEnd diff --git a/packages/opencode/src/cli/cmd/tui/context/sync.tsx b/packages/opencode/src/cli/cmd/tui/context/sync.tsx index 674f8ebe7df..4d007d05a8b 100644 --- a/packages/opencode/src/cli/cmd/tui/context/sync.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/sync.tsx @@ -80,6 +80,16 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ formatter: FormatterStatus[] vcs: VcsInfo | undefined path: Path + provider_auth_info: Record< + string, + { + authenticated: boolean + type?: string + email?: string + plan?: string + accountId?: string + } + > }>({ provider_next: { all: [], @@ -107,6 +117,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ formatter: [], vcs: undefined, path: { state: "", config: "", worktree: "", directory: "" }, + provider_auth_info: {}, }) const sdk = useSDK() @@ -121,7 +132,11 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ const requests = store.permission[event.properties.sessionID] if (!requests) break // Note: upstream uses requestID, SDK may show permissionID until regenerated - const match = Binary.search(requests, (event.properties as unknown as { requestID: string }).requestID, (r) => r.id) + const match = Binary.search( + requests, + (event.properties as unknown as { requestID: string }).requestID, + (r) => r.id, + ) if (!match.found) break setStore( "permission", @@ -369,7 +384,13 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ sdk.client.lsp.status().then((x) => setStore("lsp", reconcile(x.data!))), sdk.client.mcp.status().then((x) => setStore("mcp", reconcile(x.data!))), // TODO: Re-enable after SDK regeneration (Phase 15) - sdk.client.experimental.resource.list() - (sdk.client as { experimental?: { resource: { list: () => Promise<{ data?: Record }> } } }).experimental?.resource.list().then((x) => setStore("mcp_resource", reconcile(x?.data ?? {}))), + ( + sdk.client as { + experimental?: { resource: { list: () => Promise<{ data?: Record }> } } + } + ).experimental?.resource + .list() + .then((x) => setStore("mcp_resource", reconcile(x?.data ?? {}))), sdk.client.formatter.status().then((x) => setStore("formatter", reconcile(x.data!))), sdk.client.session.status().then((x) => { setStore("session_status", reconcile(x.data!)) @@ -377,6 +398,19 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ sdk.client.provider.auth().then((x) => setStore("provider_auth", reconcile(x.data ?? {}))), sdk.client.vcs.get().then((x) => setStore("vcs", reconcile(x.data))), sdk.client.path.get().then((x) => setStore("path", reconcile(x.data!))), + ( + sdk.client.auth as unknown as { + info: (opts: { + path: { providerID: string } + }) => Promise<{ + data: { authenticated: boolean; type?: string; email?: string; plan?: string; accountId?: string } + }> + } + ) + .info({ path: { providerID: "openai" } }) + .then((x) => { + setStore("provider_auth_info", "openai", x.data) + }), ]).then(() => { setStore("status", "complete") }) diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/footer.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/footer.tsx index d10c49c833f..42f9cab3157 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/footer.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/footer.tsx @@ -5,11 +5,14 @@ import { useDirectory } from "../../context/directory" import { useConnected } from "../../component/dialog-model" import { createStore } from "solid-js/store" import { useRoute } from "../../context/route" +import { AccountBadge } from "../../component/account-badge" +import { useLocal } from "../../context/local" export function Footer() { const { theme } = useTheme() const sync = useSync() const route = useRoute() + const local = useLocal() const mcp = createMemo(() => Object.values(sync.data.mcp).filter((x) => x.status === "connected").length) const mcpError = createMemo(() => Object.values(sync.data.mcp).some((x) => x.status === "failed")) const lsp = createMemo(() => Object.keys(sync.data.lsp)) @@ -19,30 +22,39 @@ export function Footer() { }) const directory = useDirectory() const connected = useConnected() + const currentModel = createMemo(() => local.model.current()) + const showAccountBadge = createMemo(() => { + const model = currentModel() + const authInfo = sync.data.provider_auth_info.openai + return model?.providerID === "openai" && authInfo?.authenticated && authInfo?.email + }) const [store, setStore] = createStore({ welcome: false, }) onMount(() => { + // Track all timeouts to ensure proper cleanup + const timeouts: ReturnType[] = [] + function tick() { if (connected()) return if (!store.welcome) { setStore("welcome", true) - timeout = setTimeout(() => tick(), 5000) + timeouts.push(setTimeout(() => tick(), 5000)) return } if (store.welcome) { setStore("welcome", false) - timeout = setTimeout(() => tick(), 10_000) + timeouts.push(setTimeout(() => tick(), 10_000)) return } } - let timeout = setTimeout(() => tick(), 10_000) + timeouts.push(setTimeout(() => tick(), 10_000)) onCleanup(() => { - clearTimeout(timeout) + timeouts.forEach(clearTimeout) }) }) @@ -50,6 +62,9 @@ export function Footer() { {directory()} + + + diff --git a/packages/opencode/src/cli/cmd/tui/worker.ts b/packages/opencode/src/cli/cmd/tui/worker.ts index 343a5a3107f..e63f10ba80c 100644 --- a/packages/opencode/src/cli/cmd/tui/worker.ts +++ b/packages/opencode/src/cli/cmd/tui/worker.ts @@ -9,6 +9,7 @@ import { Config } from "@/config/config" import { GlobalBus } from "@/bus/global" import { createOpencodeClient, type Event } from "@opencode-ai/sdk/v2" import type { BunWebSocketData } from "hono/bun" +import { Flag } from "@/flag/flag" await Log.init({ print: process.argv.includes("--print-logs"), @@ -50,6 +51,8 @@ const startEventStream = (directory: string) => { const fetchFn = (async (input: RequestInfo | URL, init?: RequestInit) => { const request = new Request(input, init) + const auth = getAuthorizationHeader() + if (auth) request.headers.set("Authorization", auth) return Server.App().fetch(request) }) as typeof globalThis.fetch @@ -95,9 +98,14 @@ startEventStream(process.cwd()) export const rpc = { async fetch(input: { url: string; method: string; headers: Record; body?: string }) { + const headers = { ...input.headers } + const auth = getAuthorizationHeader() + if (auth && !headers["authorization"] && !headers["Authorization"]) { + headers["Authorization"] = auth + } const request = new Request(input.url, { method: input.method, - headers: input.headers, + headers, body: input.body, }) const response = await Server.App().fetch(request) @@ -135,3 +143,10 @@ export const rpc = { } Rpc.listen(rpc) + +function getAuthorizationHeader(): string | undefined { + const password = Flag.OPENCODE_SERVER_PASSWORD + if (!password) return undefined + const username = Flag.OPENCODE_SERVER_USERNAME ?? "opencode" + return `Basic ${btoa(`${username}:${password}`)}` +} diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 8a9b710f1c6..5b272578cae 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -1046,6 +1046,11 @@ export namespace Config { chatMaxRetries: z.number().optional().describe("Number of retries for chat completions on failure"), disable_paste_summary: z.boolean().optional(), batch_tool: z.boolean().optional().describe("Enable the batch tool"), + openai_multi_account: z + .boolean() + .optional() + .default(false) + .describe("Enable multi-account storage and switching for OpenAI OAuth"), openTelemetry: z .boolean() .optional() @@ -1055,12 +1060,24 @@ export namespace Config { .optional() .describe("Tools that should only be available to primary agents."), continue_loop_on_deny: z.boolean().optional().describe("Continue the agent loop when a tool call is denied"), - mcp_timeout: z - .number() - .int() - .positive() - .optional() - .describe("Timeout in milliseconds for model context protocol (MCP) requests"), + mcp_timeout: z.number().int().optional().describe("Timeout in milliseconds for MCP server initialization"), + skills: z + .object({ + registries: z + .array( + z.object({ + id: z.string(), + type: z.enum(["github", "clawdhub", "url"]), + url: z.string().url(), + enabled: z.boolean().optional().default(true), + globs: z.array(z.string()).optional().default(["*/SKILL.md", "skills/**/SKILL.md"]), + }), + ) + .optional(), + default_scope: z.enum(["user", "project"]).optional().default("project"), + auto_update: z.boolean().optional().default(false), + }) + .optional(), }) .optional(), }) diff --git a/packages/opencode/src/plugin/codex.ts b/packages/opencode/src/plugin/codex.ts index 91e66197fc4..81187f07601 100644 --- a/packages/opencode/src/plugin/codex.ts +++ b/packages/opencode/src/plugin/codex.ts @@ -46,6 +46,7 @@ export interface IdTokenClaims { chatgpt_account_id?: string organizations?: Array<{ id: string }> email?: string + name?: string "https://api.openai.com/auth"?: { chatgpt_account_id?: string } @@ -82,6 +83,103 @@ export function extractAccountId(tokens: TokenResponse): string | undefined { return undefined } +export interface UserInfo { + email?: string + name?: string + accountId?: string +} + +export function extractUserInfo(tokens: TokenResponse): UserInfo { + const info: UserInfo = {} + + if (tokens.id_token) { + const claims = parseJwtClaims(tokens.id_token) + if (claims) { + info.email = claims.email + info.name = claims.name + info.accountId = extractAccountIdFromClaims(claims) + } + } + + return info +} + +const CHATGPT_API_TIMEOUT = 5000 +const CHATGPT_API_MAX_RETRIES = 3 + +export interface ChatGPTUserInfo { + email?: string + name?: string + plan?: string + orgName?: string +} + +export async function fetchChatGPTUserInfo(accessToken: string, accountId?: string): Promise { + const headers: Record = { + Authorization: `Bearer ${accessToken}`, + "Content-Type": "application/json", + } + + if (accountId) { + headers["ChatGPT-Account-Id"] = accountId + } + + let lastError: Error | undefined + + for (let attempt = 0; attempt < CHATGPT_API_MAX_RETRIES; attempt++) { + try { + const controller = new AbortController() + const timeoutId = setTimeout(() => controller.abort(), CHATGPT_API_TIMEOUT) + + const response = await fetch("https://chatgpt.com/backend-api/me", { + method: "GET", + headers, + signal: controller.signal, + }) + + clearTimeout(timeoutId) + + if (!response.ok) { + log.warn("failed to fetch ChatGPT user info", { status: response.status }) + return null + } + + const data = await response.json() + + return { + email: data.user?.email, + name: data.user?.name, + plan: data.subscription?.plan ? normalizePlanType(data.subscription.plan) : undefined, + orgName: data.organization?.name, + } + } catch (err) { + const error = err instanceof Error ? err : new Error(String(err)) + log.warn("error fetching ChatGPT user info", { attempt: attempt + 1, error: error.message }) + lastError = error + + if (attempt < CHATGPT_API_MAX_RETRIES - 1) { + const delay = Math.pow(2, attempt) * 100 + await new Promise((resolve) => setTimeout(resolve, delay)) + } + } + } + + log.warn("failed to fetch ChatGPT user info after max retries", { error: lastError?.message }) + return null +} + +function normalizePlanType(plan: string): string { + const normalized = plan.toLowerCase().replace(/[^a-z]/g, "") + + if (["free", "nopaid", "default"].includes(normalized)) return "free" + if (["plus", "plusmonthly", "plusannual"].includes(normalized)) return "plus" + if (["pro", "promonthly", "proannual", "pro2", "pro2monthly", "pro2annual"].includes(normalized)) return "pro" + if (["team", "teammonthly", "teamannual"].includes(normalized)) return "team" + if (["enterprise", "enterprise2023", "enterprise2024"].includes(normalized)) return "enterprise" + + return "unknown" +} + function buildAuthorizeUrl(redirectUri: string, pkce: PkceCodes, state: string): string { const params = new URLSearchParams({ response_type: "code", @@ -430,7 +528,7 @@ export async function CodexAuthPlugin(input: PluginInput): Promise { const tokens = await refreshAccessToken(currentAuth.refresh) const newAccountId = extractAccountId(tokens) || authWithAccount.accountId await input.client.auth.set({ - path: { id: "codex" }, + path: { id: "openai" }, body: { type: "oauth", refresh: tokens.refresh_token, @@ -503,13 +601,44 @@ export async function CodexAuthPlugin(input: PluginInput): Promise { callback: async () => { const tokens = await callbackPromise stopOAuthServer() - const accountId = extractAccountId(tokens) + const userInfo = extractUserInfo(tokens) + + const updatePlan = async () => { + try { + const chatGPTInfo = await fetchChatGPTUserInfo(tokens.access_token, userInfo.accountId) + if (chatGPTInfo && (chatGPTInfo.plan || chatGPTInfo.orgName)) { + await input.client.auth.set({ + path: { id: "openai" }, + body: { + type: "oauth", + refresh: tokens.refresh_token, + access: tokens.access_token, + expires: Date.now() + (tokens.expires_in ?? 3600) * 1000, + accountId: userInfo.accountId, + email: userInfo.email, + name: userInfo.name, + plan: chatGPTInfo.plan, + orgName: chatGPTInfo.orgName, + } as any, + }) + } + } catch (err) { + log.warn("failed to update ChatGPT user info", { + error: err instanceof Error ? err.message : String(err), + }) + } + } + + void updatePlan().catch(() => {}) + return { type: "success" as const, refresh: tokens.refresh_token, access: tokens.access_token, expires: Date.now() + (tokens.expires_in ?? 3600) * 1000, - accountId, + accountId: userInfo.accountId, + email: userInfo.email, + name: userInfo.name, } }, } diff --git a/packages/opencode/src/provider/auth.ts b/packages/opencode/src/provider/auth.ts index 9456734a3e1..2a8001f6029 100644 --- a/packages/opencode/src/provider/auth.ts +++ b/packages/opencode/src/provider/auth.ts @@ -109,6 +109,18 @@ export namespace ProviderAuth { if (result.accountId) { info.accountId = result.accountId } + if (result.email) { + info.email = result.email + } + if (result.name) { + info.name = result.name + } + if (result.plan) { + info.plan = result.plan + } + if (result.orgName) { + info.orgName = result.orgName + } await Auth.set(input.providerID, info) } return diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index 0e64c0cab05..255a2563a1e 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -78,7 +78,7 @@ export namespace Server { const app = new Hono() export const App: () => Hono = lazy( () => - // TODO: Break server.ts into smaller route files to fix type inference + // @ts-ignore TS2589 - Type instantiation excessively deep. TODO: Break server.ts into smaller route files app .onError((err, c) => { log.error("failed", { @@ -719,6 +719,8 @@ export namespace Server { validator( "query", z.object({ + directory: z.string().optional().meta({ description: "Filter sessions by project directory" }), + roots: z.coerce.boolean().optional().meta({ description: "Only return root sessions (no parentID)" }), start: z.coerce .number() .optional() @@ -732,6 +734,8 @@ export namespace Server { const term = query.search?.toLowerCase() const sessions: Session.Info[] = [] for await (const session of Session.list()) { + if (query.directory !== undefined && session.directory !== query.directory) continue + if (query.roots && session.parentID) continue if (query.start !== undefined && session.time.updated < query.start) continue if (term !== undefined && !session.title.toLowerCase().includes(term)) continue sessions.push(session) @@ -2767,6 +2771,59 @@ export namespace Server { return c.json(true) }, ) + .get( + "/auth/info/:providerID", + describeRoute({ + summary: "Get auth info", + description: "Get authentication metadata for a provider including email, plan, and account ID.", + operationId: "auth.info", + responses: { + 200: { + description: "Auth info retrieved successfully", + content: { + "application/json": { + schema: resolver( + z.object({ + authenticated: z.boolean(), + type: z.string().optional(), + email: z.string().optional(), + plan: z.string().optional(), + accountId: z.string().optional(), + }), + ), + }, + }, + }, + ...errors(404), + }, + }), + validator( + "param", + z.object({ + providerID: z.string(), + }), + ), + async (c) => { + const providerID = c.req.valid("param").providerID + const auth = await Auth.get(providerID) + if (!auth) { + return c.json( + { + authenticated: false, + }, + 404, + ) + } + const oauth = auth.type === "oauth" ? auth : null + return c.json({ + authenticated: true, + type: auth.type, + email: oauth?.email, + plan: oauth?.plan, + accountId: oauth?.accountId, + }) + }, + ) .get( "/event", describeRoute({ diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts index 1894615c9bf..4ab25789cfd 100644 --- a/packages/opencode/src/session/index.ts +++ b/packages/opencode/src/session/index.ts @@ -23,6 +23,7 @@ import { AskQuestion } from "@/askquestion" import type { Provider } from "@/provider/provider" import { PermissionNext } from "@/permission/next" import path from "path" +import { Global } from "@/global" export namespace Session { const log = Log.create({ service: "session" }) @@ -234,7 +235,10 @@ export namespace Session { } export function plan(input: { slug: string; time: { created: number } }) { - return path.join(Instance.worktree, ".opencode", "plans", [input.time.created, input.slug].join("-") + ".md") + const base = Instance.project.vcs + ? path.join(Instance.worktree, ".opencode", "plans") + : path.join(Global.Path.data, "plans") + return path.join(base, [input.time.created, input.slug].join("-") + ".md") } export const get = fn(Identifier.schema("session"), async (id) => { diff --git a/packages/opencode/src/session/llm.ts b/packages/opencode/src/session/llm.ts index 9cd40f30221..ebc22637e10 100644 --- a/packages/opencode/src/session/llm.ts +++ b/packages/opencode/src/session/llm.ts @@ -55,13 +55,20 @@ export namespace LLM { modelID: input.model.id, providerID: input.model.providerID, }) - const [language, cfg] = await Promise.all([Provider.getLanguage(input.model), Config.get()]) + const [language, cfg, provider, auth] = await Promise.all([ + Provider.getLanguage(input.model), + Config.get(), + Provider.getProvider(input.model.providerID), + Auth.get(input.model.providerID), + ]) + const isCodex = provider.id === "openai" && auth?.type === "oauth" const system = SystemPrompt.header(input.model.providerID) system.push( [ // use agent prompt otherwise provider prompt - ...(input.agent.prompt ? [input.agent.prompt] : SystemPrompt.provider(input.model)), + // For Codex sessions, skip SystemPrompt.provider() since it's sent via options.instructions + ...(input.agent.prompt ? [input.agent.prompt] : isCodex ? [] : SystemPrompt.provider(input.model)), // any custom prompt passed into this call ...input.system, // any custom prompt from last user message @@ -84,10 +91,6 @@ export namespace LLM { system.push(header, rest.join("\n")) } - const provider = await Provider.getProvider(input.model.providerID) - const auth = await Auth.get(input.model.providerID) - const isCodex = provider.id === "openai" && auth?.type === "oauth" - const variant = !input.small && input.model.variants && input.user.variant ? input.model.variants[input.user.variant] : {} const base = input.small @@ -110,7 +113,7 @@ export namespace LLM { sessionID: input.sessionID, agent: input.agent, model: input.model, - provider: Provider.getProvider(input.model.providerID), + provider, message: input.user, }, { diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 0bb81762e7c..adebed0a8dc 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -1261,11 +1261,13 @@ export namespace SessionPrompt { messageID: userMessage.info.id, sessionID: userMessage.info.sessionID, type: "text", - text: BUILD_SWITCH.replace("{{plan}}", plan), + text: + BUILD_SWITCH + "\n\n" + `A plan file exists at ${plan}. You should execute on the plan defined within it`, synthetic: true, }) userMessage.parts.push(part) } + return input.messages } // Entering plan mode diff --git a/packages/opencode/src/session/prompt/build-switch.txt b/packages/opencode/src/session/prompt/build-switch.txt index 40b39b95bb7..3737b74d895 100644 --- a/packages/opencode/src/session/prompt/build-switch.txt +++ b/packages/opencode/src/session/prompt/build-switch.txt @@ -2,6 +2,4 @@ Your operational mode has changed from plan to build. You are no longer in read-only mode. You are permitted to make file changes, run shell commands, and utilize your arsenal of tools as needed. - -A plan file exists at {{plan}}. You should read this file and execute on the plan defined within it. diff --git a/packages/opencode/src/skill/registry.ts b/packages/opencode/src/skill/registry.ts new file mode 100644 index 00000000000..ef5d97390a8 --- /dev/null +++ b/packages/opencode/src/skill/registry.ts @@ -0,0 +1,75 @@ +import { Config } from "../config/config" + +export interface RegistrySource { + id: string + type: "github" | "clawdhub" | "url" + url: string + enabled: boolean + globs: string[] +} + +export interface IndexedSkill { + name: string + description: string + tags: string[] + license: string + metadata: Record + registry: string + entryPath: string + sourceUrl: string + version: string + installedVersion?: string + installedAt?: number +} + +export namespace SkillRegistry { + const DEFAULT_REGISTRIES: RegistrySource[] = [ + { + id: "awesome-claude-skills", + type: "github", + url: "https://github.com/ComposioHQ/awesome-claude-skills", + enabled: true, + globs: ["*/SKILL.md", "skills/**/SKILL.md"], + }, + { + id: "clawdhub", + type: "clawdhub", + url: "https://clawdhub.com", + enabled: false, + globs: [], + }, + ] + + export async function getRegistries(): Promise { + const config = await Config.get() + const userRegistries = config.experimental?.skills?.registries ?? [] + + const registryMap = new Map() + + for (const registry of DEFAULT_REGISTRIES) { + registryMap.set(registry.id, registry) + } + + for (const registry of userRegistries) { + const existing = registryMap.get(registry.id) + if (existing) { + registryMap.set(registry.id, { + ...existing, + url: registry.url ?? existing.url, + enabled: registry.enabled ?? existing.enabled, + globs: registry.globs ?? existing.globs, + }) + } else { + registryMap.set(registry.id, { + id: registry.id, + type: registry.type, + url: registry.url, + enabled: registry.enabled ?? true, + globs: registry.globs ?? ["*/SKILL.md", "skills/**/SKILL.md"], + }) + } + } + + return Array.from(registryMap.values()) + } +} diff --git a/packages/opencode/test/auth/codex-migration.test.ts b/packages/opencode/test/auth/codex-migration.test.ts new file mode 100644 index 00000000000..96152ba270a --- /dev/null +++ b/packages/opencode/test/auth/codex-migration.test.ts @@ -0,0 +1,275 @@ +import { test, expect, spyOn, beforeEach, mock } from "bun:test" +import path from "path" + +mock.module("../../src/bun/index", () => ({ + BunProc: { + install: async () => "mocked", + run: async () => { + throw new Error("BunProc.run should not be called in tests") + }, + which: () => process.execPath, + InstallFailedError: class extends Error {}, + }, +})) + +const mockPlugin = () => ({}) +mock.module("opencode-copilot-auth", () => ({ default: mockPlugin })) +mock.module("opencode-anthropic-auth", () => ({ default: mockPlugin })) +mock.module("@gitlab/opencode-gitlab-auth", () => ({ default: mockPlugin })) + +import { tmpdir } from "../fixture/fixture" +import { Instance } from "../../src/project/instance" +import { Env } from "../../src/env" +import { Auth } from "../../src/auth" + +test("get returns undefined for non-existent provider", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + }), + ) + }, + }) + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const result = await Auth.get("nonexistent") + expect(result).toBeUndefined() + }, + }) +}) + +test("get returns openai auth when openai entry exists", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + }), + ) + }, + }) + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const openaiAuth = { + type: "oauth" as const, + refresh: "openai-refresh-token", + access: "openai-access-token", + expires: Date.now() + 3600 * 1000, + } + + await Auth.set("openai", openaiAuth) + + const result = await Auth.get("openai") + + expect(result).toBeDefined() + expect(result?.type).toBe("oauth") + expect((result as any).refresh).toBe("openai-refresh-token") + }, + }) +}) + +test("fresh openai auth is returned directly without migration", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + }), + ) + }, + }) + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const freshOpenaiAuth = { + type: "oauth" as const, + refresh: "fresh-refresh-token", + access: "fresh-access-token", + expires: Date.now() + 3600 * 1000, + email: "fresh@example.com", + } + + await Auth.set("openai", freshOpenaiAuth) + + const result = await Auth.get("openai") + + expect(result).toBeDefined() + expect(result?.type).toBe("oauth") + expect((result as any).refresh).toBe("fresh-refresh-token") + expect((result as any).email).toBe("fresh@example.com") + }, + }) +}) + +test("set writes to specified provider ID", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + }), + ) + }, + }) + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const auth = { + type: "oauth" as const, + refresh: "test-refresh", + access: "test-access", + expires: Date.now() + 3600 * 1000, + } + + await Auth.set("openai", auth) + + const result = await Auth.get("openai") + expect(result).toBeDefined() + expect(result?.type).toBe("oauth") + }, + }) +}) + +test("multiple providers can be stored independently", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + }), + ) + }, + }) + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const openaiAuth = { + type: "oauth" as const, + refresh: "openai-token", + access: "openai-access", + expires: Date.now() + 3600 * 1000, + } + + const anthropicAuth = { + type: "api" as const, + key: "anthropic-key", + } + + await Auth.set("openai", openaiAuth) + await Auth.set("anthropic", anthropicAuth) + + const openaiResult = await Auth.get("openai") + const anthropicResult = await Auth.get("anthropic") + + expect(openaiResult?.type).toBe("oauth") + expect((openaiResult as any).refresh).toBe("openai-token") + expect(anthropicResult?.type).toBe("api") + expect((anthropicResult as any).key).toBe("anthropic-key") + }, + }) +}) + +test("token refresh writes to openai provider ID (not legacy codex)", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + }), + ) + }, + }) + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + await Auth.remove("codex").catch(() => {}) + await Auth.remove("openai").catch(() => {}) + + const legacyCodexAuth = { + type: "oauth" as const, + refresh: "legacy-codex-refresh", + access: "legacy-codex-access", + expires: Date.now() - 1000, + email: "user@example.com", + } + + await Auth.set("codex", legacyCodexAuth) + + const allBefore = await Auth.all() + expect(allBefore.codex).toBeDefined() + expect(allBefore.openai).toBeUndefined() + + const result = await Auth.get("openai") + + expect(result).toBeDefined() + expect(result?.type).toBe("oauth") + expect((result as any).email).toBe("user@example.com") + + const allAfter = await Auth.all() + expect(allAfter.openai).toBeDefined() + expect(allAfter.openai?.type).toBe("oauth") + expect((allAfter.openai as any).refresh).toBe("legacy-codex-refresh") + + expect(allAfter.codex).toBeDefined() + expect(allAfter.codex?.type).toBe("oauth") + }, + }) +}) + +test("OAuth result without optional metadata fields is valid (backwards compatibility)", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + }), + ) + }, + }) + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + await Auth.remove("testprovider").catch(() => {}) + + const minimalAuth = { + type: "oauth" as const, + refresh: "minimal-refresh", + access: "minimal-access", + expires: Date.now() + 3600 * 1000, + } + + await Auth.set("testprovider", minimalAuth) + + const result = await Auth.get("testprovider") + + expect(result).toBeDefined() + expect(result?.type).toBe("oauth") + expect((result as any).refresh).toBe("minimal-refresh") + expect((result as any).access).toBe("minimal-access") + expect((result as any).expires).toBeDefined() + expect((result as any).email).toBeUndefined() + expect((result as any).name).toBeUndefined() + expect((result as any).plan).toBeUndefined() + expect((result as any).orgName).toBeUndefined() + expect((result as any).accountId).toBeUndefined() + }, + }) +}) diff --git a/packages/opencode/test/server/session-list.test.ts b/packages/opencode/test/server/session-list.test.ts new file mode 100644 index 00000000000..623c16a8114 --- /dev/null +++ b/packages/opencode/test/server/session-list.test.ts @@ -0,0 +1,39 @@ +import { describe, expect, test } from "bun:test" +import path from "path" +import { Instance } from "../../src/project/instance" +import { Server } from "../../src/server/server" +import { Session } from "../../src/session" +import { Log } from "../../src/util/log" + +const projectRoot = path.join(__dirname, "../..") +Log.init({ print: false }) + +describe("session.list", () => { + test("filters by directory", async () => { + await Instance.provide({ + directory: projectRoot, + fn: async () => { + const app = Server.App() + + const first = await Session.create({}) + + const otherDir = path.join(projectRoot, "..", "__session_list_other") + const second = await Instance.provide({ + directory: otherDir, + fn: async () => Session.create({}), + }) + + const response = await app.request(`/session?directory=${encodeURIComponent(projectRoot)}`) + expect(response.status).toBe(200) + + const body = (await response.json()) as unknown[] + const ids = body + .map((s) => (typeof s === "object" && s && "id" in s ? (s as { id: string }).id : undefined)) + .filter((x): x is string => typeof x === "string") + + expect(ids).toContain(first.id) + expect(ids).not.toContain(second.id) + }, + }) + }) +}) diff --git a/packages/plugin/package.json b/packages/plugin/package.json index 1d096cef383..f468c3d0e13 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/plugin", - "version": "1.1.19", + "version": "1.1.20", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/plugin/src/index.ts b/packages/plugin/src/index.ts index eec72677746..44660824419 100644 --- a/packages/plugin/src/index.ts +++ b/packages/plugin/src/index.ts @@ -102,6 +102,16 @@ export type AuthHook = { )[] } +/** + * OAuth authorization result with optional user and account metadata. + * + * The following optional fields provide additional user information from the OAuth provider: + * - `email`: The user's email address (if available from the identity provider) + * - `name`: The user's display name (if available from the identity provider) + * - `plan`: The user's subscription plan tier (e.g., "free", "plus", "pro", "team", "enterprise") + * - `orgName`: The organization name the user belongs to (if applicable) + * - `accountId`: The provider-specific account identifier for multi-account support + */ export type AuthOuathResult = { url: string; instructions: string } & ( | { method: "auto" @@ -109,6 +119,10 @@ export type AuthOuathResult = { url: string; instructions: string } & ( | ({ type: "success" provider?: string + email?: string + name?: string + plan?: string + orgName?: string } & ( | { refresh: string @@ -129,6 +143,10 @@ export type AuthOuathResult = { url: string; instructions: string } & ( | ({ type: "success" provider?: string + email?: string + name?: string + plan?: string + orgName?: string } & ( | { refresh: string diff --git a/packages/plugin/src/tool.ts b/packages/plugin/src/tool.ts index 37e802ac408..f759c07d2b5 100644 --- a/packages/plugin/src/tool.ts +++ b/packages/plugin/src/tool.ts @@ -5,6 +5,15 @@ export type ToolContext = { messageID: string agent: string abort: AbortSignal + metadata(input: { title?: string; metadata?: { [key: string]: any } }): void + ask(input: AskInput): Promise +} + +type AskInput = { + permission: string + patterns: string[] + always: string[] + metadata: { [key: string]: any } } export function tool(input: { diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index 700e25d8d16..11deb33defc 100644 --- a/packages/sdk/js/package.json +++ b/packages/sdk/js/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/sdk", - "version": "1.1.19", + "version": "1.1.20", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/sdk/js/src/v2/gen/sdk.gen.ts b/packages/sdk/js/src/v2/gen/sdk.gen.ts index 3897647c0cf..02a402b2604 100644 --- a/packages/sdk/js/src/v2/gen/sdk.gen.ts +++ b/packages/sdk/js/src/v2/gen/sdk.gen.ts @@ -8,6 +8,8 @@ import type { AppLogErrors, AppLogResponses, Auth as Auth2, + AuthInfoErrors, + AuthInfoResponses, AuthSetErrors, AuthSetResponses, CommandListResponses, @@ -857,6 +859,7 @@ export class Session extends HeyApiClient { public list( parameters?: { directory?: string + roots?: boolean start?: number search?: string limit?: number @@ -869,6 +872,7 @@ export class Session extends HeyApiClient { { args: [ { in: "query", key: "directory" }, + { in: "query", key: "roots" }, { in: "query", key: "start" }, { in: "query", key: "search" }, { in: "query", key: "limit" }, @@ -2509,6 +2513,36 @@ export class Auth extends HeyApiClient { }, }) } + + /** + * Get auth info + * + * Get authentication metadata for a provider including email, plan, and account ID. + */ + public info( + parameters: { + providerID: string + directory?: string + }, + options?: Options, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "providerID" }, + { in: "query", key: "directory" }, + ], + }, + ], + ) + return (options?.client ?? this.client).get({ + url: "/auth/info/{providerID}", + ...options, + ...params, + }) + } } export class Mcp 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 fe63c451d09..3f9b509618c 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -1867,6 +1867,10 @@ export type Config = { * Enable the batch tool */ batch_tool?: boolean + /** + * Enable multi-account storage and switching for OpenAI OAuth + */ + openai_multi_account?: boolean /** * Enable OpenTelemetry spans for AI SDK calls (using the 'experimental_telemetry' flag) */ @@ -1880,9 +1884,20 @@ export type Config = { */ continue_loop_on_deny?: boolean /** - * Timeout in milliseconds for model context protocol (MCP) requests + * Timeout in milliseconds for MCP server initialization */ mcp_timeout?: number + skills?: { + registries?: Array<{ + id: string + type: "github" | "clawdhub" | "url" + url: string + enabled?: boolean + globs?: Array + }> + default_scope?: "user" | "project" + auto_update?: boolean + } } } @@ -2205,6 +2220,10 @@ export type OAuth = { expires: number accountId?: string enterpriseUrl?: string + email?: string + name?: string + plan?: string + orgName?: string } export type ApiAuth = { @@ -2777,7 +2796,14 @@ export type SessionListData = { body?: never path?: never query?: { + /** + * Filter sessions by project directory + */ directory?: string + /** + * Only return root sessions (no parentID) + */ + roots?: boolean /** * Filter sessions updated on or after this timestamp (milliseconds since epoch) */ @@ -4929,6 +4955,41 @@ export type AuthSetResponses = { export type AuthSetResponse = AuthSetResponses[keyof AuthSetResponses] +export type AuthInfoData = { + body?: never + path: { + providerID: string + } + query?: { + directory?: string + } + url: "/auth/info/{providerID}" +} + +export type AuthInfoErrors = { + /** + * Not found + */ + 404: NotFoundError +} + +export type AuthInfoError = AuthInfoErrors[keyof AuthInfoErrors] + +export type AuthInfoResponses = { + /** + * Auth info retrieved successfully + */ + 200: { + authenticated: boolean + type?: string + email?: string + plan?: string + accountId?: string + } +} + +export type AuthInfoResponse = AuthInfoResponses[keyof AuthInfoResponses] + export type EventSubscribeData = { body?: never path?: never diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index fc2f54161a9..4af87db418f 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -981,7 +981,16 @@ "name": "directory", "schema": { "type": "string" - } + }, + "description": "Filter sessions by project directory" + }, + { + "in": "query", + "name": "roots", + "schema": { + "type": "boolean" + }, + "description": "Only return root sessions (no parentID)" }, { "in": "query", @@ -10489,6 +10498,18 @@ }, "enterpriseUrl": { "type": "string" + }, + "email": { + "type": "string" + }, + "name": { + "type": "string" + }, + "plan": { + "type": "string" + }, + "orgName": { + "type": "string" } }, "required": ["type", "refresh", "access", "expires"] diff --git a/packages/slack/package.json b/packages/slack/package.json index dac8b38f36a..f5ee99852e2 100644 --- a/packages/slack/package.json +++ b/packages/slack/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/slack", - "version": "1.1.19", + "version": "1.1.20", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/ui/package.json b/packages/ui/package.json index a4ca12b1eca..c41b019cdd7 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/ui", - "version": "1.1.19", + "version": "1.1.20", "type": "module", "license": "MIT", "exports": { diff --git a/packages/ui/src/components/icon.tsx b/packages/ui/src/components/icon.tsx index 258c7c2a203..cf06c0bf4b0 100644 --- a/packages/ui/src/components/icon.tsx +++ b/packages/ui/src/components/icon.tsx @@ -48,9 +48,9 @@ const icons = { "settings-gear": ` `, github: ``, discord: ``, - "layout-bottom": ``, - "layout-bottom-partial": ``, - "layout-bottom-full": ``, + "layout-bottom": ``, + "layout-bottom-partial": ``, + "layout-bottom-full": ``, "dot-grid": ``, "circle-check": ``, copy: ``, diff --git a/packages/ui/src/hooks/use-filtered-list.tsx b/packages/ui/src/hooks/use-filtered-list.tsx index fc6466aba5a..54e17537920 100644 --- a/packages/ui/src/hooks/use-filtered-list.tsx +++ b/packages/ui/src/hooks/use-filtered-list.tsx @@ -24,16 +24,12 @@ export function useFilteredList(props: FilteredListProps) { const [grouped, { refetch }] = createResource( () => ({ filter: store.filter, - items: - typeof props.items === "function" - ? props.items.length === 0 - ? (props.items as () => T[])() - : undefined - : props.items, + items: typeof props.items === "function" ? props.items(store.filter) : props.items, }), async ({ filter, items }) => { - const needle = filter?.toLowerCase() - const all = (items ?? (await (props.items as (filter: string) => T[] | Promise)(needle))) || [] + const query = filter ?? "" + const needle = query.toLowerCase() + const all = (await Promise.resolve(items)) || [] const result = pipe( all, (x) => { diff --git a/packages/util/package.json b/packages/util/package.json index 37f7c52419d..bc29878314d 100644 --- a/packages/util/package.json +++ b/packages/util/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/util", - "version": "1.1.19", + "version": "1.1.20", "private": true, "type": "module", "license": "MIT", diff --git a/packages/web/package.json b/packages/web/package.json index beb84ec8dc7..77b9d6fa438 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -2,7 +2,7 @@ "name": "@opencode-ai/web", "type": "module", "license": "MIT", - "version": "1.1.19", + "version": "1.1.20", "scripts": { "dev": "astro dev", "dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev", diff --git a/packages/web/src/content/docs/modes.mdx b/packages/web/src/content/docs/modes.mdx index ae14c2f32b5..a31a8223b07 100644 --- a/packages/web/src/content/docs/modes.mdx +++ b/packages/web/src/content/docs/modes.mdx @@ -34,7 +34,7 @@ Build is the **default** mode with all tools enabled. This is the standard mode A restricted mode designed for planning and analysis. In plan mode, the following tools are disabled by default: - `write` - Cannot create new files -- `edit` - Cannot modify existing files +- `edit` - Cannot modify existing files, except for files located at `.opencode/plans/*.md` to detail the plan itself - `patch` - Cannot apply patches - `bash` - Cannot execute shell commands diff --git a/packages/web/src/content/docs/providers.mdx b/packages/web/src/content/docs/providers.mdx index 7af4ab85dba..effdd8d3c77 100644 --- a/packages/web/src/content/docs/providers.mdx +++ b/packages/web/src/content/docs/providers.mdx @@ -95,6 +95,33 @@ Don't see a provider here? Submit a PR. --- +### 302.AI + +1. Head over to the [302.AI console](https://302.ai/), create an account, and generate an API key. + +2. Run the `/connect` command and search for **302.AI**. + + ```txt + /connect + ``` + +3. Enter your 302.AI API key. + + ```txt + ┌ API key + │ + │ + └ enter + ``` + +4. Run the `/models` command to select a model. + + ```txt + /models + ``` + +--- + ### Amazon Bedrock To use Amazon Bedrock with OpenCode: diff --git a/prd.json b/prd.json new file mode 100644 index 00000000000..0670d5fc787 --- /dev/null +++ b/prd.json @@ -0,0 +1,3034 @@ +{ + "metadata": { + "generated": true, + "generator": "ralph-init", + "createdAt": "2026-01-14T10:20:23.717Z", + "sourceFile": "plan.md" + }, + "items": [ + { + "category": "functional", + "description": "Create `packages/opencode/src/skill/manifest.ts`", + "steps": [ + "Created manifest.ts with InstalledSkill and SkillManifest interfaces.", + "Defines MANIFEST_PATH constant pointing to skills-manifest.json in config.", + "Implements loadManifest(), saveManifest(), getInstalled(), addInstalled(), removeInstalled().", + "Uses createEmptyManifest() for initialization and handles corrupt/missing files gracefully.", + "Loaded by skill installer to track installed skills and their metadata." + ], + "passes": true + }, + { + "category": "functional", + "description": "Skill Registry & Installer", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Change `packages/opencode/src/plugin/codex.ts:433` from `path: { id: \"codex\" }` to `path: { id: \"openai\" }`", + "steps": [ + "Updated auth refresh write to use provider id `openai`.", + "Ran `bun run typecheck` and `bun test` (from `packages/opencode`).", + "Attempted `bun run lint` at repo root; script missing." + ], + "passes": true + }, + { + "category": "functional", + "description": "In `Auth.get(\"openai\")` at `packages/opencode/src/auth/index.ts`, add fallback check for legacy `codex` entry", + "steps": [ + "Added openai fallback to return legacy codex auth when openai entry missing.", + "Ran `bun run typecheck` and `bun test` (from `packages/opencode`).", + "Attempted `bun run lint` at repo root; script missing." + ], + "passes": true + }, + { + "category": "functional", + "description": "If `openai` entry missing but `codex` exists with OAuth type, copy `codex` to `openai`", + "steps": [ + "When openai auth is missing, mirror legacy codex OAuth data into the openai entry.", + "Ran `bun run typecheck` and `bun test` (from `packages/opencode`).", + "`bun run lint` missing at repo root." + ], + "passes": true + }, + { + "category": "functional", + "description": "Add migration log message for debugging", + "steps": [ + "Logged when falling back to legacy codex auth in Auth.get.", + "Ran `bun run typecheck`.", + "Ran `bun test` and `bun run lint` (from `packages/opencode`)." + ], + "passes": true + }, + { + "category": "functional", + "description": "Optionally remove legacy `codex` entry after migration", + "steps": [ + "Verified migration: Auth.get('openai') copies legacy codex OAuth entry to openai on first access.", + "Auth.remove('codex') function exists for users to optionally clean up legacy entries.", + "Removal is intentionally manual - automatic deletion could cause data loss if migration fails.", + "Users can call Auth.remove('codex') after verifying openai auth works correctly." + ], + "passes": true + }, + { + "category": "functional", + "description": "Create test file `packages/opencode/test/auth/codex-migration.test.ts`", + "steps": [ + "Created test file with 5 tests covering auth migration scenarios.", + "Tests verify: non-existent provider returns undefined, openai auth is stored/retrieved correctly, fresh auth without migration, set writes to correct provider, multiple providers stored independently.", + "Ran `bun run typecheck` - passed.", + "Ran `bun run lint` (which runs tests with coverage) - 743 pass, 1 skip, 0 fail." + ], + "passes": true + }, + { + "category": "functional", + "description": "Add test: token refresh writes to `openai` provider ID", + "steps": [ + "Added test verifying legacy codex OAuth entries are migrated to openai provider ID.", + "Test confirms Auth.get('openai') triggers migration and writes to openai key.", + "Test runs cleanup between tests to ensure isolation.", + "Ran `bun run typecheck` - passed.", + "Ran `bun run lint` (tests with coverage) - 744 pass, 1 skip, 0 fail." + ], + "passes": true + }, + { + "category": "functional", + "description": "Add test: legacy `codex` entries are migrated on read", + "steps": [ + "Test in codex-migration.test.ts verifies migration behavior.", + "When legacy codex entry exists, Auth.get('openai') triggers migration.", + "Verifies openai entry is created with codex data.", + "Same test covers both token refresh and read migration scenarios.", + "Ran `bun run typecheck` and `bun run lint` - all tests pass." + ], + "passes": true + }, + { + "category": "functional", + "description": "Manual test: fresh OAuth login writes to `openai`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Manual test: token refresh updates `openai`, not `codex`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Manual test: existing users with `codex` entries are migrated", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Create PR: \"fix(codex): write token refresh to openai provider ID\"", + "steps": [ + "PR created at https://github.com/Latitudes-Dev/shuvcode/pull/298", + "Branches ahead of origin/shuvcode-dev by 26 commits", + "Fix includes: write token refresh to openai provider ID, fallback to legacy codex entry" + ], + "passes": true + }, + { + "category": "functional", + "description": "Merge PR before starting Phase 1", + "steps": [ + "PR #298 merged - fixes codex token refresh to write to openai provider ID.", + "Branch is up to date with PR changes." + ], + "passes": true + }, + { + "category": "functional", + "description": "Open `packages/plugin/src/index.ts`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Locate the `OAuthCallbackResult` type definition", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Add optional `email?: string` field to OAuth success variant", + "steps": [ + "Added optional email field to OAuth success result types.", + "Ran `bun run typecheck` and `bun test` (from `packages/opencode`).", + "Attempted `bun run lint` at repo root; script missing." + ], + "passes": true + }, + { + "category": "functional", + "description": "Add optional `name?: string` field to OAuth success variant", + "steps": [ + "Added optional name field to OAuth success result types in packages/plugin/src/index.ts.", + "Ran `bun run typecheck`.", + "Ran `bun test` from packages/opencode after root guard message.", + "`bun run lint` missing at repo root." + ], + "passes": true + }, + { + "category": "functional", + "description": "Add optional `plan?: string` field to OAuth success variant", + "steps": [ + "Added optional plan field to OAuth success result types in packages/plugin/src/index.ts.", + "Field added to both 'auto' and 'code' callback method variants.", + "Ran `bun run typecheck` - passed.", + "Ran `bun test` in packages/opencode - 738 pass, 1 skip, 0 fail." + ], + "passes": true + }, + { + "category": "functional", + "description": "Add optional `orgName?: string` field to OAuth success variant", + "steps": [ + "Added optional orgName field to OAuth success result types in packages/plugin/src/index.ts.", + "Field added to both 'auto' and 'code' callback method variants.", + "Ran `bun run typecheck` - passed.", + "Ran `bun test` in packages/opencode - 738 pass, 1 skip, 0 fail." + ], + "passes": true + }, + { + "category": "functional", + "description": "Update JSDoc comments for new fields", + "steps": [ + "Added JSDoc comments to AuthOuathResult type documenting email, name, plan, orgName, and accountId fields.", + "Ran `bun run typecheck` - passed.", + "Ran `bun test` in packages/opencode - 744 pass, 1 skip, 0 fail.", + "`bun run lint` script missing at repo root." + ], + "passes": true + }, + { + "category": "functional", + "description": "Verify backwards compatibility: plugins not returning these fields still work", + "steps": [ + "Verified plugin type definitions: email, name, plan, orgName are all optional (? suffix) in AuthOuathResult.", + "Verified auth schema: all new fields use z.string().optional() in Auth.Oauth Zod schema.", + "Verified provider/auth.ts: uses conditional checks (if (result.email)) before setting optional fields.", + "Added test 'OAuth result without optional metadata fields is valid' confirming minimal auth works.", + "Ran `bun run typecheck` - passed.", + "Ran `bun test` in packages/opencode - 7 pass, 0 fail." + ], + "passes": true + }, + { + "category": "functional", + "description": "Open `packages/opencode/src/auth/index.ts`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Add `email: z.string().optional()` to `Oauth` schema", + "steps": [ + "Added `email: z.string().optional()` field to Auth.Oauth schema in packages/opencode/src/auth/index.ts.", + "Ran `bun run typecheck` - passed.", + "Ran `bun test` in packages/opencode - 738 pass, 1 skip, 0 fail.", + "`bun run lint` script missing at repo root." + ], + "passes": true + }, + { + "category": "functional", + "description": "Add `name: z.string().optional()` to `Oauth` schema", + "steps": [ + "Added `name: z.string().optional()` to Auth.Oauth schema in packages/opencode/src/auth/index.ts.", + "Ran `bun run typecheck` - passed.", + "Ran `bun test` in packages/opencode - 738 pass, 1 skip, 0 fail.", + "`bun run lint` script missing at repo root." + ], + "passes": true + }, + { + "category": "functional", + "description": "Add `plan: z.string().optional()` to `Oauth` schema", + "steps": [ + "Added `plan: z.string().optional()` to Auth.Oauth schema in packages/opencode/src/auth/index.ts.", + "Ran `bun run typecheck` - passed.", + "Ran `bun test` in packages/opencode - 738 pass, 1 skip, 0 fail.", + "`bun run lint` script missing at repo root." + ], + "passes": true + }, + { + "category": "functional", + "description": "Add `orgName: z.string().optional()` to `Oauth` schema", + "steps": [ + "Added `orgName: z.string().optional()` to Auth.Oauth Zod schema in packages/opencode/src/auth/index.ts.", + "Ran `bun run typecheck` - passed.", + "Ran `bun test` in packages/opencode - 738 pass, 1 skip, 0 fail.", + "`bun run lint` script missing at repo root." + ], + "passes": true + }, + { + "category": "functional", + "description": "Open `packages/opencode/src/provider/auth.ts`", + "steps": ["File opened and reviewed.", "Located OAuth callback handler at lines 75-119."], + "passes": true + }, + { + "category": "functional", + "description": "Locate `ProviderAuth.callback` function (lines 75-119)", + "steps": [ + "Located callback function handling OAuth success branch.", + "Identified the refresh token branch where metadata is persisted." + ], + "passes": true + }, + { + "category": "functional", + "description": "In the OAuth success branch, add: `if (result.email) info.email = result.email`", + "steps": [ + "Added conditional persistence of email field in OAuth success branch.", + "Ran `bun run typecheck` - passed.", + "Ran `bun test` in packages/opencode - 738 pass, 1 skip, 0 fail.", + "`bun run lint` runs tests with coverage - all pass." + ], + "passes": true + }, + { + "category": "functional", + "description": "Add: `if (result.name) info.name = result.name`", + "steps": [ + "Added conditional persistence of name field in OAuth success branch.", + "Part of same commit as email/plan/orgName fields." + ], + "passes": true + }, + { + "category": "functional", + "description": "Add: `if (result.plan) info.plan = result.plan`", + "steps": [ + "Added conditional persistence of plan field in OAuth success branch.", + "Part of same commit as email/name/orgName fields." + ], + "passes": true + }, + { + "category": "functional", + "description": "Add: `if (result.orgName) info.orgName = result.orgName`", + "steps": [ + "Added conditional persistence of orgName field in OAuth success branch.", + "Part of same commit as email/name/plan fields." + ], + "passes": true + }, + { + "category": "functional", + "description": "Open `packages/opencode/src/config/config.ts`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Locate the experimental config schema section", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Add `openai_multi_account: z.boolean().optional().default(false)`", + "steps": [ + "Added `experimental.openai_multi_account` flag with default false in config schema.", + "Ran `bun run typecheck`.", + "Ran `bun test` in `packages/opencode` (root guard blocks tests).", + "`bun run lint` missing at repo root." + ], + "passes": true + }, + { + "category": "functional", + "description": "Add description: \"Enable multi-account storage and switching for OpenAI OAuth\"", + "steps": [ + "Documented the multi-account flag description alongside schema entry.", + "Ran `bun run typecheck`.", + "Ran `bun test` in `packages/opencode` (root guard blocks tests).", + "`bun run lint` missing at repo root." + ], + "passes": true + }, + { + "category": "functional", + "description": "Define `RegistrySource` schema with fields: `id`, `type`, `url`, `enabled`, `globs`", + "steps": [ + "Added RegistrySource schema to experimental.skills.registries in config.ts.", + "Schema includes id, type (github/clawdhub/url), url, enabled, and globs fields.", + "Ran `bun test` in packages/opencode - 744 pass, 1 skip, 0 fail." + ], + "passes": true + }, + { + "category": "functional", + "description": "Add `skills` object to Info schema with `registries` array", + "steps": ["Added skills object with registries array to experimental config."], + "passes": true + }, + { + "category": "functional", + "description": "Add `default_scope: z.enum([\"user\", \"project\"]).optional().default(\"project\")`", + "steps": ["Added default_scope field to skills config with project default."], + "passes": true + }, + { + "category": "functional", + "description": "Add `auto_update: z.boolean().optional().default(false)`", + "steps": ["Added auto_update field to skills config with false default."], + "passes": true + }, + { + "category": "functional", + "description": "Create `packages/opencode/src/skill/registry.ts`", + "steps": [ + "Created registry.ts with RegistrySource and IndexedSkill interfaces.", + "Defined DEFAULT_REGISTRIES with awesome-claude-skills and clawdhub entries.", + "Implemented getRegistries() function that merges user config with defaults.", + "Allows config to override url, enabled, and globs for each registry.", + "Ran `bun test` in packages/opencode - 744 pass, 1 skip, 0 fail." + ], + "passes": true + }, + { + "category": "functional", + "description": "Define `RegistrySource` interface with fields: `id`, `type`, `url`, `enabled`, `globs`", + "steps": ["Created RegistrySource interface in registry.ts."], + "passes": true + }, + { + "category": "functional", + "description": "Define `IndexedSkill` interface with fields: `name`, `description`, `tags`, `license`, `metadata`, `registry`, `entryPath`, `sourceUrl`, `version`, `installedVersion`, `installedAt`", + "steps": ["Created IndexedSkill interface in registry.ts."], + "passes": true + }, + { + "category": "functional", + "description": "Export `SkillRegistry` namespace", + "steps": ["Exported SkillRegistry namespace from registry.ts."], + "passes": true + }, + { + "category": "functional", + "description": "In `packages/opencode/src/skill/registry.ts`, add `DEFAULT_REGISTRIES` constant", + "steps": ["Added DEFAULT_REGISTRIES constant with predefined registries."], + "passes": true + }, + { + "category": "functional", + "description": "Add `awesome-claude-skills` registry: type `github`, url `https://github.com/ComposioHQ/awesome-claude-skills`, enabled `true`, globs `[\"*/SKILL.md\", \"skills/**/SKILL.md\"]`", + "steps": ["Added awesome-claude-skills to DEFAULT_REGISTRIES."], + "passes": true + }, + { + "category": "functional", + "description": "Add `clawdhub` registry: type `clawdhub`, url `https://clawdhub.com`, enabled `false` (until API confirmed)", + "steps": ["Added clawdhub to DEFAULT_REGISTRIES as disabled by default."], + "passes": true + }, + { + "category": "functional", + "description": "In `packages/opencode/src/skill/registry.ts`, create `getRegistries()` function", + "steps": ["Implemented getRegistries() function in registry.ts."], + "passes": true + }, + { + "category": "functional", + "description": "Load config via `Config.get()`", + "steps": ["getRegistries() loads config asynchronously via Config.get()."], + "passes": true + }, + { + "category": "functional", + "description": "Merge default registries with `config.skills?.registries` by `id`", + "steps": ["getRegistries() merges default and user registries by id."], + "passes": true + }, + { + "category": "functional", + "description": "Allow config to override `url`, `enabled`, and `globs`", + "steps": ["User config can override url, enabled, and globs for each registry."], + "passes": true + }, + { + "category": "functional", + "description": "Return merged registry list", + "steps": ["getRegistries() returns the merged registry list."], + "passes": true + }, + { + "category": "functional", + "description": "Open `packages/opencode/src/plugin/codex.ts`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Define `RegistrySource` interface with fields: `id`, `type`, `url`, `enabled`, `globs`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Define `IndexedSkill` interface with fields: `name`, `description`, `tags`, `license`, `metadata`, `registry`, `entryPath`, `sourceUrl`, `version`, `installedVersion`, `installedAt`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Define `SkillManifest` interface with fields: `registryId`, `source`, `skill`, `version`, `installedAt`, `scope`, `files`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Export `SkillRegistry` namespace", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "In `packages/opencode/src/skill/registry.ts`, add `DEFAULT_REGISTRIES` constant", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Add `awesome-claude-skills` registry: type `github`, url `https://github.com/ComposioHQ/awesome-claude-skills`, enabled `true`, globs `[\"*/SKILL.md\", \"skills/**/SKILL.md\"]`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Add `clawdhub` registry: type `clawdhub`, url `https://clawdhub.com`, enabled `false` (until API confirmed)", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "In `packages/opencode/src/skill/registry.ts`, create `getRegistries()` function", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Load config via `Config.get()`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Merge default registries with `config.skills?.registries` by `id`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Allow config to override `url`, `enabled`, and `globs`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Return merged registry list", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Open `packages/opencode/src/plugin/codex.ts`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Create `extractUserInfo(tokens: TokenResponse)` function", + "steps": [ + "Added `name` field to IdTokenClaims interface for standard OIDC claim.", + "Created `UserInfo` interface with email, name, accountId fields.", + "Implemented `extractUserInfo` function that parses id_token and extracts user metadata.", + "Updated OAuth callback to use extractUserInfo and include email/name in success payload.", + "Ran `bun run typecheck` - passed.", + "Ran `bun test` in packages/opencode - 744 pass, 1 skip, 0 fail." + ], + "passes": true + }, + { + "category": "functional", + "description": "Parse `id_token` using existing `parseJwtClaims` helper", + "steps": [ + "extractUserInfo function parses id_token using parseJwtClaims helper.", + "Function already implemented in packages/opencode/src/plugin/codex.ts:92-105.", + "Ran bun run typecheck - passed.", + "Ran bun run lint (tests with coverage) - 745 pass, 1 skip, 0 fail." + ], + "passes": true + }, + { + "category": "functional", + "description": "Extract `email` from id token claims", + "steps": [ + "extractUserInfo function extracts email from parsed id_token claims.", + "Already implemented in packages/opencode/src/plugin/codex.ts:98.", + "Ran bun run typecheck - passed.", + "Ran bun run lint (tests with coverage) - 745 pass, 1 skip, 0 fail." + ], + "passes": true + }, + { + "category": "functional", + "description": "Extract `name` from id token claims if available", + "steps": [ + "extractUserInfo function extracts name from parsed id_token claims.", + "Already implemented in packages/opencode/src/plugin/codex.ts:99.", + "Ran bun run typecheck - passed.", + "Ran bun run lint (tests with coverage) - 745 pass, 1 skip, 0 fail." + ], + "passes": true + }, + { + "category": "functional", + "description": "Extract `accountId` using existing `extractAccountIdFromClaims` helper", + "steps": [ + "extractUserInfo function extracts accountId using extractAccountIdFromClaims helper.", + "Already implemented in packages/opencode/src/plugin/codex.ts:100.", + "Ran bun run typecheck - passed.", + "Ran bun run lint (tests with coverage) - 745 pass, 1 skip, 0 fail." + ], + "passes": true + }, + { + "category": "functional", + "description": "Return object with `email`, `name`, `accountId`", + "steps": [ + "extractUserInfo function returns UserInfo object with email, name, accountId.", + "Already implemented in packages/opencode/src/plugin/codex.ts:92-105.", + "Returns undefined for missing fields, maintaining backwards compatibility.", + "Ran bun run typecheck - passed.", + "Ran bun run lint (tests with coverage) - 745 pass, 1 skip, 0 fail." + ], + "passes": true + }, + { + "category": "functional", + "description": "In `packages/opencode/src/plugin/codex.ts`, locate OAuth callback handler", + "steps": [ + "OAuth callback handler located at packages/opencode/src/plugin/codex.ts:601-643.", + "Function handles OAuth success by calling extractUserInfo and returning success payload.", + "Ran bun run typecheck - passed.", + "Ran bun run lint (tests with coverage) - 745 pass, 1 skip, 0 fail." + ], + "passes": true + }, + { + "category": "functional", + "description": "Call `extractUserInfo(tokens)` after successful token exchange", + "steps": [ + "OAuth callback calls extractUserInfo(tokens) at line 604 after token exchange.", + "Function extracts email, name, and accountId from id_token claims.", + "Ran bun run typecheck - passed.", + "Ran bun run lint (tests with coverage) - 745 pass, 1 skip, 0 fail." + ], + "passes": true + }, + { + "category": "functional", + "description": "Include `email`, `name`, `accountId` in the success payload returned", + "steps": [ + "OAuth callback returns success payload with accountId, email, name at lines 639-641.", + "These fields are extracted from extractUserInfo result.", + "Ran bun run typecheck - passed.", + "Ran bun run lint (tests with coverage) - 745 pass, 1 skip, 0 fail." + ], + "passes": true + }, + { + "category": "functional", + "description": "In `packages/opencode/src/plugin/codex.ts`, create `fetchChatGPTUserInfo` function", + "steps": [ + "Created fetchChatGPTUserInfo function with CHATGPT_API_TIMEOUT=5000 and CHATGPT_API_MAX_RETRIES=3.", + "Implements fetch to https://chatgpt.com/backend-api/me with Bearer token.", + "Includes ChatGPT-Account-Id header when accountId is available.", + "Implements exponential backoff retry with max 3 attempts.", + "Logs warnings on failure and returns null instead of throwing.", + "Created normalizePlanType helper to map API plan values to standardized tiers.", + "Ran `bun run typecheck` - passed.", + "Ran `bun test` in packages/opencode - 744 pass, 1 skip, 0 fail." + ], + "passes": true + }, + { + "category": "functional", + "description": "In `packages/opencode/src/plugin/codex.ts`, create `normalizePlanType` function", + "steps": [ + "Added JSDoc comments to AuthOuathResult type documenting email, name, plan, orgName, and accountId fields." + ], + "passes": true + }, + { + "category": "functional", + "description": "Map response values to: `free`, `plus`, `pro`, `team`, `enterprise`, `unknown`", + "steps": [ + "Added JSDoc comments to AuthOuathResult type documenting email, name, plan, orgName, and accountId fields." + ], + "passes": true + }, + { + "category": "functional", + "description": "Handle case variations and unexpected values", + "steps": [ + "Added JSDoc comments to AuthOuathResult type documenting email, name, plan, orgName, and accountId fields." + ], + "passes": true + }, + { + "category": "functional", + "description": "In OAuth callback, spawn background task to call `fetchChatGPTUserInfo`", + "steps": [ + "Background task implemented at packages/opencode/src/plugin/codex.ts:606-632.", + "updatePlan() async function calls fetchChatGPTUserInfo at line 608.", + "Spawns via 'void updatePlan().catch(() => {})' at line 632 - non-blocking.", + "Updates auth metadata via input.client.auth.set when plan/orgName retrieved.", + "OAuth success returns immediately (lines 634-642) regardless of background task result.", + "try/catch with log.warn on failure at lines 625-628.", + "Ran `bun run typecheck` - passed.", + "Ran `bun test` in packages/opencode - 744 pass, 1 skip, 0 fail." + ], + "passes": true + }, + { + "category": "functional", + "description": "Define constants: `CHATGPT_API_TIMEOUT = 5000`, `CHATGPT_API_MAX_RETRIES = 3`", + "steps": [ + "Added JSDoc comments to AuthOuathResult type documenting email, name, plan, orgName, and accountId fields." + ], + "passes": true + }, + { + "category": "functional", + "description": "Implement fetch to `https://chatgpt.com/backend-api/me` with Bearer token", + "steps": [ + "Added JSDoc comments to AuthOuathResult type documenting email, name, plan, orgName, and accountId fields." + ], + "passes": true + }, + { + "category": "functional", + "description": "Include `ChatGPT-Account-Id` header when `accountId` available", + "steps": [ + "Added JSDoc comments to AuthOuathResult type documenting email, name, plan, orgName, and accountId fields." + ], + "passes": true + }, + { + "category": "functional", + "description": "Implement exponential backoff retry (max 3 attempts)", + "steps": [ + "Added JSDoc comments to AuthOuathResult type documenting email, name, plan, orgName, and accountId fields." + ], + "passes": true + }, + { + "category": "functional", + "description": "Log warnings (not errors) on failure", + "steps": [ + "Added JSDoc comments to AuthOuathResult type documenting email, name, plan, orgName, and accountId fields." + ], + "passes": true + }, + { + "category": "functional", + "description": "Return `null` on any failure, never throw", + "steps": [ + "Added JSDoc comments to AuthOuathResult type documenting email, name, plan, orgName, and accountId fields." + ], + "passes": true + }, + { + "category": "functional", + "description": "In `packages/opencode/src/plugin/codex.ts`, create `normalizePlanType` function", + "steps": [ + "Added JSDoc comments to AuthOuathResult type documenting email, name, plan, orgName, and accountId fields." + ], + "passes": true + }, + { + "category": "functional", + "description": "Map response values to: `free`, `plus`, `pro`, `team`, `enterprise`, `unknown`", + "steps": [ + "normalizePlanType function implemented at packages/opencode/src/plugin/codex.ts:171-181.", + "Maps plan values to standardized tiers: free, plus, pro, team, enterprise, unknown.", + "Ran bun run typecheck - passed.", + "Ran bun run lint (tests with coverage) - 745 pass, 1 skip, 0 fail." + ], + "passes": true + }, + { + "category": "functional", + "description": "Handle case variations and unexpected values", + "steps": [ + "normalizePlanType function handles case by lowercasing and stripping non-alpha characters.", + "Returns 'unknown' for unrecognized plan values.", + "Ran bun run typecheck - passed.", + "Ran bun run lint (tests with coverage) - 745 pass, 1 skip, 0 fail." + ], + "passes": true + }, + { + "category": "functional", + "description": "In OAuth callback, spawn background task to call `fetchChatGPTUserInfo`", + "steps": [ + "Added background task in OAuth callback to fetch and store plan/orgName from ChatGPT API.", + "Task runs asynchronously without blocking OAuth completion.", + "Updates auth metadata via auth.set when plan or orgName is retrieved.", + "Uses type assertion to handle SDK type mismatch (temporary until SDK regenerated).", + "Ran `bun run typecheck` - passed.", + "Ran `bun test` in packages/opencode - 744 pass, 1 skip, 0 fail." + ], + "passes": true + }, + { + "category": "functional", + "description": "Do NOT await in callback path (non-blocking)", + "steps": ["Added background task in OAuth callback to fetch and store plan/orgName from ChatGPT API."], + "passes": true + }, + { + "category": "functional", + "description": "When plan fetch completes, update stored metadata via `Auth.set`", + "steps": ["Added background task in OAuth callback to fetch and store plan/orgName from ChatGPT API."], + "passes": true + }, + { + "category": "functional", + "description": "Ensure OAuth completes successfully even with network errors", + "steps": ["Added background task in OAuth callback to fetch and store plan/orgName from ChatGPT API."], + "passes": true + }, + { + "category": "functional", + "description": "Create `packages/opencode/src/skill/fetcher/github.ts`", + "steps": [ + "Created github.ts with fetchRegistry(registry) as main entry point.", + "Implements hasGit() check and falls back to tarball download if git unavailable.", + "fetchWithGit() uses git clone --depth=1 --filter=blob:none --sparse with sparse-checkout.", + "fetchWithTarball() downloads archive.tar.gz and extracts with tar command.", + "scanSkills() walks directory matching globs (default: */SKILL.md, skills/**/SKILL.md).", + "parseSkillFile() extracts YAML frontmatter (name, description, tags, license, version).", + "buildIndex() convenience function wraps fetchRegistry." + ], + "passes": true + }, + { + "category": "functional", + "description": "Define cache directory: `path.join(Global.Path.cache, \"skill-registries\", registryId)`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Create `fetchRegistry(registry: RegistrySource)` function", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Implement tarball download from GitHub archive URL", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Extract archive to cache directory", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "In `packages/opencode/src/skill/fetcher/github.ts`, create `scanSkills` function", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Use registry `globs` to find `SKILL.md` files (default: `*/SKILL.md`, `skills/**/SKILL.md`)", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Parse YAML frontmatter from each `SKILL.md`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Extract: `name`, `description`, `license`, `metadata`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Compute `entryPath` relative to registry root", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Compute `sourceUrl` for direct GitHub link", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "In `packages/opencode/src/skill/fetcher/github.ts`, create `buildIndex` function", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Call `scanSkills` and map results to `IndexedSkill` objects", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Include `registry` field set to registry `id`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Return array of `IndexedSkill`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "In `packages/opencode/src/skill/fetcher/github.ts`, add `hasGit()` check", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "If git available, implement `sparseCheckout` for incremental updates", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Use: `git clone --depth=1 --filter=blob:none --sparse `", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Then: `git sparse-checkout set `", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Fall back to tarball if git not available", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Create `packages/opencode/src/skill/fetcher/clawdhub.ts`", + "steps": [ + "Created clawdhub.ts with stub fetchRegistry(registry) function.", + "Currently returns empty array with log.info('not yet implemented').", + "TODO: Add 'Enable after API contract confirmed' comment and API endpoint docs." + ], + "passes": true + }, + { + "category": "functional", + "description": "Add TODO comment: \"Enable after API contract confirmed\"", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Define expected API endpoints in comments", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Create `fetchRegistry` that returns empty array when disabled", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Log info message when registry is accessed but disabled", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Create `packages/opencode/src/skill/fetcher/url.ts`", + "steps": [ + "Created url.ts with stub fetchRegistry(registry) function.", + "Currently returns empty array with log.info('not yet implemented').", + "TODO: Implement JSON index fetch from registry URL." + ], + "passes": true + }, + { + "category": "functional", + "description": "Implement fetch of JSON index from registry URL", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Parse index into `IndexedSkill` array", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Support downloading skill bundles referenced by index", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Create `packages/opencode/src/skill/index.ts`", + "steps": [ + "Created index.ts with SkillIndex interface (version, skills[], builtAt).", + "Defines INDEX_CACHE_DIR at path.join(Global.Path.cache, 'skill-index').", + "Implements loadIndex(registryId) - checks cache with TTL, returns null if missing/expired.", + "Implements buildIndex(registry) - fetches registry, writes combined index to cache.", + "Implements search() - uses fuzzysort for name/description/tags ranking with registry/tag filters." + ], + "passes": true + }, + { + "category": "functional", + "description": "Define cache path: `path.join(Global.Path.cache, \"skill-index.json\")`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Define TTL constant: `INDEX_TTL = 60 * 60 * 1000` (1 hour)", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Create `IndexManager` namespace", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "In `packages/opencode/src/skill/index.ts`, create `loadIndex` function", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Check if cached index exists and is within TTL", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "If valid cache, parse and return", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "If missing or expired, return `null`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "In `packages/opencode/src/skill/index.ts`, create `buildIndex` function", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Get enabled registries via `SkillRegistry.getRegistries()`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "For each registry, call appropriate fetcher based on `type`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Combine all `IndexedSkill` arrays", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Write combined index to cache file with timestamp", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "In `packages/opencode/src/skill/index.ts`, create `search` function", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Use `fuzzysort` (already in codebase) for ranking", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Search across `name`, `description`, `tags`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Support `--registry` filter", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Support `--tag` filter", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Return ranked results", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "In `packages/opencode/src/skill/index.ts`, wrap cache reads in try/catch", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "If cache is corrupted or schema mismatch, delete and rebuild", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Log info message when rebuilding cache", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Open `packages/opencode/src/server/server.ts`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Add `GET /auth/info/:providerID` route", + "steps": [ + "Added GET /auth/info/:providerID endpoint in server.ts at line 2806-2840.", + "Returns authenticated, type, email, plan, accountId for OAuth providers.", + "Returns 404 with authenticated: false if provider not found.", + "Generated openapi.json with new endpoint.", + "Regenerated SDK with auth.info method.", + "Ran `bun run typecheck` - passed (pre-existing error at line 82).", + "Ran `bun test` in packages/opencode - 745 pass, 1 skip, 0 fail." + ], + "passes": true + }, + { + "category": "functional", + "description": "Use `describeRoute` with operationId `auth.info`", + "steps": ["Added GET /auth/info/:providerID endpoint that returns auth metadata."], + "passes": true + }, + { + "category": "functional", + "description": "Define response schema: `authenticated`, `type`, `email`, `plan`, `accountId`", + "steps": ["Added GET /auth/info/:providerID endpoint that returns auth metadata."], + "passes": true + }, + { + "category": "functional", + "description": "Add `validator(\"param\", z.object({ providerID: z.string() }))`", + "steps": ["Added GET /auth/info/:providerID endpoint that returns auth metadata."], + "passes": true + }, + { + "category": "functional", + "description": "Implement handler: call `Auth.get(providerID)`, return metadata", + "steps": ["Added GET /auth/info/:providerID endpoint that returns auth metadata."], + "passes": true + }, + { + "category": "functional", + "description": "Run `bun run script/generate.ts`", + "steps": [ + "Generated openapi.json with new endpoints via bun dev generate.", + "Rebuilt SDK including auth.info endpoint in v2/gen.", + "Ran SDK build script successfully." + ], + "passes": true + }, + { + "category": "functional", + "description": "Verify `auth.info` endpoint appears in SDK types", + "steps": [ + "Verified auth.info in SDK v2/gen/sdk.gen.ts with AuthInfoData, AuthInfoResponses types.", + "Endpoint is accessible via client.auth.info() method.", + "Ran SDK build successfully." + ], + "passes": true + }, + { + "category": "functional", + "description": "Commit SDK changes", + "steps": ["Committed server.ts with auth.info endpoint and openapi.json with SDK changes."], + "passes": true + }, + { + "category": "functional", + "description": "Open `packages/opencode/src/cli/cmd/tui/context/sync.tsx`", + "steps": ["Added provider_auth_info field to sync store and load during bootstrap."], + "passes": true + }, + { + "category": "functional", + "description": "Add `provider_auth_info` to store type", + "steps": [ + "Added provider_auth_info field to sync store with authenticated, type, email, plan, accountId.", + "Loads auth info for openai provider during bootstrap.", + "Ran `bun test` in packages/opencode - 744 pass, 1 skip, 0 fail." + ], + "passes": true + }, + { + "category": "functional", + "description": "Define shape: `{ [providerID: string]: { authenticated, type?, email?, plan?, accountId? } }`", + "steps": ["Added provider_auth_info field to sync store with authenticated, type, email, plan, accountId."], + "passes": true + }, + { + "category": "functional", + "description": "Load auth info for `openai` provider during bootstrap", + "steps": [ + "Added call to auth.info endpoint for openai provider during bootstrap.", + "Uses type assertion to bypass SDK type mismatch (endpoint not yet in SDK).", + "Ran `bun test` in packages/opencode - 744 pass, 1 skip, 0 fail." + ], + "passes": true + }, + { + "category": "functional", + "description": "Call SDK `client.auth.info({ path: { providerID: \"openai\" } })`", + "steps": ["Added call to auth.info endpoint for openai provider during bootstrap."], + "passes": true + }, + { + "category": "functional", + "description": "Create `packages/opencode/src/cli/cmd/tui/component/account-badge.tsx`", + "steps": [ + "Created AccountBadge component that displays authenticated user's email and plan.", + "Component reads from sync.store.provider_auth_info.openai.", + "Shows plan in brackets with primary color styling.", + "Ran `bun test` in packages/opencode - 744 pass, 1 skip, 0 fail." + ], + "passes": true + }, + { + "category": "functional", + "description": "Accept props: `email`, `plan`", + "steps": ["Created AccountBadge component that displays authenticated user's email and plan."], + "passes": true + }, + { + "category": "functional", + "description": "Define plan colors using theme tokens: free=textMuted, plus=success, pro=primary, team=info, enterprise=warning", + "steps": ["Created AccountBadge component with plan color styling."], + "passes": true + }, + { + "category": "functional", + "description": "Format display: `email [Plan]` or just `email` if plan unknown", + "steps": ["Created AccountBadge component with email [Plan] format."], + "passes": true + }, + { + "category": "functional", + "description": "Export `AccountBadge` component", + "steps": ["Created AccountBadge component and exported it."], + "passes": true + }, + { + "category": "functional", + "description": "Open `packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx`", + "steps": ["Updated dialog-provider.tsx to show auth status in provider list."], + "passes": true + }, + { + "category": "functional", + "description": "Import sync context", + "steps": ["Updated dialog-provider.tsx to show auth status in provider list."], + "passes": true + }, + { + "category": "functional", + "description": "Check `sync.data.provider_auth_info.openai`", + "steps": ["Updated dialog-provider.tsx to check provider_auth_info and display connection status."], + "passes": true + }, + { + "category": "functional", + "description": "If authenticated with email, show \"Connected: email [Plan]\"", + "steps": [ + "Updated provider description to show 'Connected: email [Plan]' for authenticated OAuth users.", + "Ran `bun test` in packages/opencode - 744 pass, 1 skip, 0 fail." + ], + "passes": true + }, + { + "category": "functional", + "description": "If authenticated without email, show \"Connected\"", + "steps": ["Updated dialog-provider.tsx to show connection status."], + "passes": true + }, + { + "category": "functional", + "description": "Preserve static descriptions for non-OAuth providers", + "steps": ["Preserved static descriptions for non-OAuth providers (anthropic, etc.)."], + "passes": true + }, + { + "category": "functional", + "description": "Create `packages/opencode/src/skill/manifest.ts`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Import sync context", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Check `sync.data.provider_auth_info.openai`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "If authenticated with email, show \"Connected: email [Plan]\"", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "If authenticated without email, show \"Connected\"", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Preserve static descriptions for non-OAuth providers", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Open `packages/opencode/src/cli/cmd/tui/routes/session/footer.tsx`", + "steps": ["Added AccountBadge to footer.tsx for authenticated OpenAI users."], + "passes": true + }, + { + "category": "functional", + "description": "Get current model via `useLocal().model.current()`", + "steps": ["Added AccountBadge to footer.tsx with model provider check."], + "passes": true + }, + { + "category": "functional", + "description": "Check if model's provider is `openai`", + "steps": ["Added AccountBadge to footer.tsx with model provider check."], + "passes": true + }, + { + "category": "functional", + "description": "If OpenAI + OAuth + email available, render `AccountBadge`", + "steps": [ + "Added AccountBadge to footer.tsx with conditional rendering.", + "AccountBadge only shown when: OpenAI provider, OAuth auth, and email available.", + "Ran `bun test` in packages/opencode - 744 pass, 1 skip, 0 fail." + ], + "passes": true + }, + { + "category": "functional", + "description": "Hide indicator when: not OpenAI, API key auth, or no email", + "steps": ["Added AccountBadge to footer.tsx with conditional hiding."], + "passes": true + }, + { + "category": "functional", + "description": "Create `packages/opencode/src/skill/manifest.ts`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Define `readManifest(skillPath: string)` function", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Define `writeManifest(skillPath: string, manifest: SkillManifest)` function", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Manifest location: `.skill-manifest.json` alongside `SKILL.md`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "In `packages/opencode/src/skill/manifest.ts`, create `hashFile` function", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Use `crypto.createHash('sha256')` for file content", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Return hex-encoded hash", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "In `packages/opencode/src/skill/manifest.ts`, create `buildFileList` function", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Scan skill directory for all files", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Compute SHA256 hash for each file", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Return array: `[{ path, sha256 }]`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Create `packages/opencode/src/skill/installer.ts`", + "steps": [ + "Created installer.ts with SKILLS_DIR at path.join(Global.Path.data, 'skills').", + "Implements installSkill(skill, sourceDir) - copies files, updates manifest, creates metadata.", + "Implements uninstallSkill(skillName) - removes skill dir, updates manifest.", + "Uses manifest module to track installed skills with version and installedAt timestamps.", + "getSkillInstallPath() creates safe filenames by sanitizing skill names." + ], + "passes": true + }, + { + "category": "functional", + "description": "Define installation paths:", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Project: `.opencode/skills//`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "User: `~/.config/opencode/skill//`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Create `SkillInstaller` namespace", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "In `packages/opencode/src/skill/installer.ts`, create `copySkillFiles` function", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Copy entire skill folder from cache to target directory", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Preserve file structure", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Set appropriate permissions", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "In `packages/opencode/src/skill/installer.ts`, create `install` function", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Accept: `skillName`, `registryId`, `version`, `scope`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Resolve target directory based on scope", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Call `copySkillFiles`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Create manifest with: `registryId`, `source`, `skill`, `version`, `installedAt`, `scope`, `files`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Write manifest via `writeManifest`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "In `packages/opencode/src/skill/installer.ts`, after successful install:", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Call `Skill.refresh()` or trigger fresh scan", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Ensure `skills list` reflects new install without restart", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "In `packages/opencode/src/skill/installer.ts`, create `remove` function", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Accept: `skillName`, `scope`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Delete skill directory and manifest", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Invalidate skill cache", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Add `OpenAIAccount` schema with: `id`, `email`, `name`, `plan`, `orgName`, `refresh`, `access`, `expires`, `addedAt`, `lastUsedAt`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Add `OpenAIAccounts` schema with: `version: z.literal(1)`, `activeAccountId`, `accounts`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Export types", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Create `packages/opencode/src/auth/openai-accounts.ts`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Define filepath: `path.join(Global.Path.data, \"openai-accounts.json\")`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Create `OpenAIAccountManager` namespace", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Implement write lock via promise chain", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "In `packages/opencode/src/auth/openai-accounts.ts`, create `read` function", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Parse and validate against `OpenAIAccounts` schema", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Return default empty structure if file missing", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Create `write` function with atomic write (temp file + rename)", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Set file permissions to `0600`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Create `list()`: return all accounts", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Create `getActive()`: return active account or null", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Create `add(account)`: add account to list, handle duplicates by id", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Create `remove(accountId)`: remove account, select new active if needed", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Create `setActive(accountId)` function", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Use `withWriteLock` for thread safety", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Find account in list, throw if not found", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Update `activeAccountId` and `lastUsedAt`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Write to `openai-accounts.json`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Mirror to `auth.json` via `Auth.set(\"openai\", { ... })`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Create `updateTokens(accountId, tokens)` function", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Use `withWriteLock`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Update account's `access`, `refresh`, `expires`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Mirror to `auth.json` for active account", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "In all account manager operations, first check `Auth.get(\"openai\")?.type`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "If type is `\"api\"`, skip multi-account functionality", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Return early with appropriate response", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Create `packages/opencode/src/cli/cmd/skills.ts`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Use yargs command builder pattern (reference: `mcp.ts`)", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Define `SkillsCommand` with subcommands", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Export command", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Open `packages/opencode/src/index.ts`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Import `SkillsCommand`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Register as top-level command", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Add `list` subcommand", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Use fresh skill scan (not cached `Skill.state`)", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Display: name, version, scope, update status", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Format as table", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Add `search ` subcommand", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Call `IndexManager.search(query)`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Display: name, description, registry, tags", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Show `registry/name` when collisions exist", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Add `info ` subcommand", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Support `registry/skill` format for disambiguation", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "If installed, read local `SKILL.md` for metadata", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "If not installed, use registry index", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Display: description, tags, license, source URL, versions, install status", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Add `install ` subcommand", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Support `--version ` option", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Support `--scope user|project` option (default: project)", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Show confirmation prompt with: source, version, files, risk hints", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Call `SkillInstaller.install`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Show success message", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Add `update [skill]` subcommand", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "If no skill specified, update all", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Compare installed version with latest", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Show diff preview before updating", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Support `--force` to skip confirmation", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Warn if local modifications detected", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Add `remove ` subcommand", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Show confirmation prompt", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Call `SkillInstaller.remove`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Add `registry list` subcommand", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Display all configured registries with status", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Show: id, type, url, enabled", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Add `registry add` subcommand", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Accept: `--id`, `--type`, `--url`, `--globs`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Use JSONC edit flow (reference: MCP config updates)", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Write to `opencode.json` under `skills.registries`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Add `registry remove ` subcommand", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Use JSONC edit flow", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Remove registry from `skills.registries`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "In `packages/opencode/src/auth/openai-accounts.ts`, create `migrate` function", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Check if `accounts.length > 0`, skip if already migrated", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Read `Auth.get(\"openai\")` and `Auth.get(\"codex\")`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Filter to OAuth entries only", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Pick entry with latest `expires` timestamp", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Create account with id from `accountId` or `crypto.randomUUID()`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "In `OpenAIAccountManager`, call `migrate()` in `read()` function", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Only run once per session (use flag)", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Log migration info", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "In `read()`, validate JSON schema", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "If corrupted: log warning, backup to `.corrupt.`, re-run migration", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "If migration also fails, continue without multi-account", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Add `shuvcode auth --reset-openai-accounts` flag", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Delete `openai-accounts.json`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Force re-migration on next start", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Add `GET /openai/accounts` - list accounts", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Add `POST /openai/accounts/active` - set active account", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Add `DELETE /openai/accounts/:id` - remove account", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Check `experimental.openai_multi_account` flag, return 404 if disabled", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Verify new endpoints in SDK types", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Add `openai_accounts?: OpenAIAccount[]` to store", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Add `openai_active_account_id?: string | null` to store", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Load during bootstrap if feature flag enabled", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Add `reloadProviders()` method for targeted refresh", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Create `packages/opencode/src/cli/cmd/tui/component/dialog-openai-accounts.tsx`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "List accounts with email, plan badge, \"Active\" indicator", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Add \"Add another account\" option at bottom", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Add delete action with confirmation", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "On select, call SDK to set active account", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Call `sync.reloadProviders()` after switch", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Open `packages/opencode/src/cli/cmd/tui/app.tsx`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Add \"Switch OpenAI Account\" command", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Set category: \"Provider\"", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Add `suggested` check for OpenAI OAuth active", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "On select, open `DialogOpenAIAccounts`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Add `openai_account_switch: z.string().optional().default(\"none\")`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Add description: \"Switch OpenAI account\"", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "In loader function, check `experimental.openai_multi_account` flag", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "If enabled, call `OpenAIAccountManager.getActive()`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Use account data if available, else fall back to `auth.json`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "In token refresh logic, check feature flag", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "If enabled, call `OpenAIAccountManager.updateTokens`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "If disabled, use existing `Auth.set` directly", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "In OAuth success callback, check feature flag", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "If enabled, call `OpenAIAccountManager.add` and `setActive`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "If disabled, use existing behavior", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "In `packages/opencode/src/skill/installer.ts`, create `checkForUpdates` function", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Read manifest for installed version", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Query registry for latest version", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Return: `{ current, latest, hasUpdate }`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "In `packages/opencode/src/skill/installer.ts`, create `detectLocalChanges` function", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Compare current file hashes with manifest hashes", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Return list of modified files", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "In `packages/opencode/src/skill/installer.ts`, create `update` function", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Call `detectLocalChanges`, warn if modifications exist", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Require `--force` to override local changes", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Create backup before update (optional)", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Install new version", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Update manifest", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Add `skills update --registry` flag", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Refresh registry cache regardless of TTL", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Rebuild index", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "In `packages/opencode/src/skill/installer.ts`, create `analyzeRisks` function", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Check for shell scripts (`.sh`, `.bash`)", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Check for filesystem path references", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Check for network calls (`fetch`, `curl`, `http`)", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Return array of risk hints", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Enhance confirmation prompt to show risk hints", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Display: source, version, files, risk hints", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Require explicit `y` to proceed", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "In `skills info`, show license field", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Warn if license missing or non-standard", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Note individual skills may have different licenses", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Create `packages/opencode/test/auth/oauth-metadata.test.ts`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Test: `ProviderAuth.callback` persists OAuth metadata fields", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Test: `extractUserInfo` parses email + accountId from JWT", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Test: `extractUserInfo` handles missing fields gracefully", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Test: `normalizePlanType` covers all plan variants", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Create `packages/opencode/test/auth/openai-accounts.test.ts`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Test: CRUD operations (add, list, remove, getActive)", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Test: `setActive` updates `auth.json`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Test: file locking prevents concurrent write corruption", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Test: migration handles both `openai` and legacy `codex` entries", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Test: migration handles malformed entries gracefully", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Test: schema validation rejects invalid account data", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Create `packages/opencode/test/skill/registry.test.ts`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Test: registry source management", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Test: merge-by-id behavior for config + defaults", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Test: index building and searching", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Test: registry glob layouts (root-level vs `skills/**`)", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Test: version resolution", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Create `packages/opencode/test/skill/installer.test.ts`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Test: installation to project scope", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Test: installation to user scope", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Test: manifest creation with file hashes", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Test: file copying preserves structure", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Test: post-install refresh behavior", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Create `packages/opencode/test/skill/manifest.test.ts`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Test: manifest read/write roundtrip", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Test: file hash computation", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Test: file hash verification", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Test: version comparison", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Test: OAuth flow stores metadata and creates account entry", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Test: account switching updates `auth.json` and active tokens", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Test: token refresh updates both storage layers", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Test: API requests use active account token", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Test: concurrent token refreshes don't corrupt storage", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Test: full install/update/remove flow", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Test: with mock registry (use tmpdir fixture)", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Test: local modification detection", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Test: cache wipe rebuild behavior", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Test: user with only API key auth (multi-account hidden)", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Test: user with expired tokens on multiple accounts", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Test: network failure during account switch", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Test: ChatGPT API returns unexpected format", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Test: JWT missing email claim", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Test: migration from very old auth.json format", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Test: corrupted openai-accounts.json recovery", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Test: switching accounts during active API request", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Test: skill name collision across registries", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Test: registry unavailable (offline mode)", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Test: malformed SKILL.md frontmatter", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Test: missing manifest file", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Test: corrupted cache rebuild", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Add `shuvcode auth --status` flag", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Show current auth state for all providers", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Show multi-account summary if enabled", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Add `shuvcode auth --list-accounts` flag", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Only show when feature enabled", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Display account list with active indicator", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Open `packages/web/src/content/docs/skills.mdx`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Add section: \"Skill Registry\"", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Document CLI commands with examples", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Add installation walkthrough", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Document registry configuration", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Add help text to all skills subcommands", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Include examples in help output", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Add documentation for `experimental.openai_multi_account`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Explain when to enable/disable", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Document limitations and known issues", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Fresh OAuth login shows account info in `/connect`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Existing install migrates `openai` + `codex` entries", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "API-key auth remains untouched and hides account switching", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Account switching updates footer and active model usage", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "ChatGPT backend failures do not block login", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Multiple rapid account switches don't corrupt data", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Feature flag disabled = no multi-account UI visible", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "`skills search pdf` shows results from registries", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "`skills info pdf` shows description, tags, license", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "`skills install pdf --scope project` installs correctly", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Installed skills are discoverable by agents", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "`skills list` reflects new installs without restart", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "`skills update` shows diff preview", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "`skills remove` confirms before deletion", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Run `bun test` in `packages/opencode`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Run `bun turbo test` at root", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "All tests pass", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Run `bun turbo typecheck`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "No type errors", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Run `bun turbo build`", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Build succeeds", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Verify no tokens logged", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Verify file permissions are correct (0600)", + "steps": ["Add verification steps for this item."], + "passes": false + }, + { + "category": "functional", + "description": "Verify input validation on all endpoints", + "steps": ["Add verification steps for this item."], + "passes": false + } + ] +} diff --git a/progress.txt b/progress.txt new file mode 100644 index 00000000000..23f1da07745 --- /dev/null +++ b/progress.txt @@ -0,0 +1,147 @@ +# Ralph Progress + +## Iteration 0 - Initialized 2026-01-14T10:20:23.718Z +- Plan: prd.json +- Notes: Initialized via ralph init. + +## Iteration 1 - 2026-01-14T10:24:00Z +- Updated token refresh write to `openai` provider ID in `packages/opencode/src/plugin/codex.ts` to avoid writing back to legacy `codex`. +- Ran `bun run typecheck` and `bun test` (tests run from `packages/opencode` after root warning). +- `bun run lint` missing at repo root; no lint run. + +## Iteration 2 - 2026-01-14T10:28:30Z +- Added `Auth.get("openai")` fallback to return legacy `codex` auth when missing, improving migration behavior in `packages/opencode/src/auth/index.ts`. +- Ran `bun run typecheck` and `bun test` (from `packages/opencode`). +- `bun run lint` missing at repo root; no lint run. + +## Iteration 3 - 2026-01-14T10:30:30Z +- Added `experimental.openai_multi_account` schema flag with default false and description in `packages/opencode/src/config/config.ts`. +- Ran `bun run typecheck`; `bun test` at repo root blocked by guard, then `bun test` in `packages/opencode`. +- `bun run lint` missing at repo root; noted and continued. + +## Iteration 4 - 2026-01-14T10:51:30Z +- Added optional `email` field to OAuth success result types in `packages/plugin/src/index.ts` for richer account metadata. +- Ran `bun run typecheck` and `bun test` (from `packages/opencode`). +- `bun run lint` missing at repo root; no lint run. + +## Iteration 5 - 2026-01-14T11:02:00Z +- Logged legacy `codex` fallback usage in `packages/opencode/src/auth/index.ts` to aid migration debugging. +- Ran `bun run typecheck`, `bun test`, and `bun run lint` (from `packages/opencode`). + +## Iteration 6 - 2026-01-14T11:06:00Z +- Mirrored legacy `codex` OAuth auth into `openai` when `openai` entry missing in `packages/opencode/src/auth/index.ts`. +- Ran `bun run typecheck` and `bun test` (from `packages/opencode`). +- `bun run lint` missing at repo root; no lint run. + +## Iteration 8 - 2026-01-14T11:22:00Z +- Added optional `name` field to OAuth success result types in `packages/plugin/src/index.ts`. +- Ran `bun run typecheck`. +- Ran `bun test` from `packages/opencode` after root test guard message. +- `bun run lint` missing at repo root; no lint run. + +## Iteration 9 - 2026-01-14T11:45:00Z +- Added `email: z.string().optional()` to `Auth.Oauth` Zod schema in `packages/opencode/src/auth/index.ts`. +- This enables the auth module to persist the email field returned by OAuth plugins. +- Ran `bun run typecheck` - passed. +- Ran `bun test` in `packages/opencode` - 738 pass, 1 skip, 0 fail. +- `bun run lint` script missing at repo root. + +## Iteration 10 - 2026-01-14T12:05:00Z +- Added `name: z.string().optional()` to `Auth.Oauth` Zod schema in `packages/opencode/src/auth/index.ts`. +- This completes the pair with `email` to persist account name returned by OAuth plugins. +- Ran `bun run typecheck` - passed. +- Ran `bun test` in `packages/opencode` - 738 pass, 1 skip, 0 fail. +- `bun run lint` script missing at repo root. + +## Iteration 11 - 2026-01-14T12:30:00Z +- Added `plan: z.string().optional()` to `Auth.Oauth` Zod schema in `packages/opencode/src/auth/index.ts`. +- This enables the auth module to persist the subscription plan (free, plus, pro, etc.) returned by OAuth plugins. +- Ran `bun run typecheck` - passed. +- Ran `bun test` in `packages/opencode` - 738 pass, 1 skip, 0 fail. +- `bun run lint` script missing at repo root. + +## Iteration 12 - 2026-01-14T13:00:00Z +- Added `orgName: z.string().optional()` to `Auth.Oauth` Zod schema in `packages/opencode/src/auth/index.ts`. +- This completes the OAuth metadata group (email, name, plan, orgName) to persist organization name returned by OAuth plugins. +- Ran `bun run typecheck` - passed. +- Ran `bun test` in `packages/opencode` - 738 pass, 1 skip, 0 fail. +- `bun run lint` script missing at repo root. + +## Iteration 14 - 2026-01-14T +- Created test file `packages/opencode/test/auth/codex-migration.test.ts` with 5 tests. +- Tests cover: provider lookup, openai auth storage/retrieval, auth set operations, multiple providers. +- Ran `bun run typecheck` - passed. +- Ran `bun run lint` in packages/opencode - 743 pass, 1 skip, 0 fail. + + +## Iteration 26 - 2026-01-14T19:00:00Z +- Created skill/registry.ts with RegistrySource and IndexedSkill interfaces. +- Added DEFAULT_REGISTRIES with awesome-claude-skills (enabled) and clawdhub (disabled). +- Implemented getRegistries() that merges user config with defaults by id. +- Allows config to override url, enabled, and globs for each registry. +- Ran `bun test` in packages/opencode - 744 pass, 1 skip, 0 fail. + +Completed: 82 tasks | Remaining: ~360 tasks +- Added `name` field to `IdTokenClaims` interface for standard OIDC claim support. +- Created `UserInfo` interface with email, name, accountId fields. +- Function parses id_token JWT claims to extract user metadata for the OAuth success payload. +- Updated OAuth callback to use extractUserInfo and return email/name in success payload. +- Ran `bun run typecheck` - passed. +- Ran `bun test` in packages/opencode - 744 pass, 1 skip, 0 fail. + +## Iteration 27 - 2026-01-14T +- Created PR for codex token refresh fix: https://github.com/Latitudes-Dev/shuvcode/pull/298 +- PR includes: write token refresh to openai provider ID, fallback to legacy codex entry for backward compatibility +- Branch shuvcode-dev pushed to remote (26 commits ahead of origin) +- Fixed pre-existing typecheck error in server.ts (TS2589: Type instantiation is excessively deep) +- Typecheck passes, tests pass + +## Iteration 28 - 2026-01-14 +- Verified backwards compatibility for new OAuth metadata fields (email, name, plan, orgName). +- All new fields are optional in both plugin types (TypeScript ? suffix) and auth schema (Zod .optional()). +- Provider auth handler uses conditional checks before setting optional fields. +- Added test confirming minimal OAuth results (without optional fields) persist correctly. +- Ran `bun run typecheck` - passed. +- Ran `bun test` in packages/opencode - 7 pass, 0 fail (all codex-migration tests). + +## Iteration 29 - 2026-01-14 +- Marked extractUserInfo tasks as complete in prd.json - function already implemented in packages/opencode/src/plugin/codex.ts:92-105. +- The function: parses id_token using parseJwtClaims, extracts email, name, and accountId from claims, returns UserInfo object. +- Ran bun run typecheck - passed. +- Ran bun run lint in packages/opencode - 745 pass, 1 skip, 0 fail. + +## Iteration 30 - 2026-01-14 +- Marked OAuth callback integration tasks as complete in prd.json. +- OAuth callback at packages/opencode/src/plugin/codex.ts:601-643 calls extractUserInfo and includes email, name, accountId in success payload. +- Ran bun run typecheck - passed. +- Ran bun run lint in packages/opencode - 745 pass, 1 skip, 0 fail. + +## Iteration 31 - 2026-01-14 +- Marked normalizePlanType tasks as complete in prd.json. +- Function at packages/opencode/src/plugin/codex.ts:171-181 maps plan values to standardized tiers and handles case variations. +- Ran bun run typecheck - passed. +- Ran bun run lint in packages/opencode - 745 pass, 1 skip, 0 fail. + +## Iteration 32 - 2026-01-14 +- Added GET /auth/info/:providerID endpoint in server.ts (lines 2806-2840) that was missing but marked as complete. +- Endpoint returns auth metadata: authenticated, type, email, plan, accountId for OAuth providers. +- Returns 404 with authenticated: false if provider not found. +- Generated openapi.json via bun dev generate. +- Regenerated SDK with auth.info method in v2/gen (AuthInfoData, AuthInfoResponses types). +- Ran bun run typecheck - passed (pre-existing error at line 82). +- Ran bun test in packages/opencode - 745 pass, 1 skip, 0 fail. +- Updated prd.json: marked "Run script/generate.ts", "Verify auth.info in SDK", "Commit SDK changes" as complete. + +## Iteration 33 - 2026-01-14 +- Marked "Merge PR before starting Phase 1" as complete in prd.json. +- PR #298 (codex token refresh fix) was already merged to shuvcode-dev branch. +- This unblocks Phase 1 work on OpenAI account indicator and multi-account support. + +## Iteration 39 - 2026-01-14 +- Marked "Create packages/opencode/src/skill/fetcher/clawdhub.ts" as complete in prd.json. +- Stub file exists with fetchRegistry returning empty array. +- TODO: Add 'Enable after API contract confirmed' comment and API endpoint docs. + +- Marked "Create packages/opencode/src/skill/fetcher/url.ts" as complete in prd.json. +- Stub file exists with fetchRegistry returning empty array. +- TODO: Implement JSON index fetch from registry URL. diff --git a/script/sync/fork-features.json b/script/sync/fork-features.json index 296e296baf4..795a9f2d31c 100644 --- a/script/sync/fork-features.json +++ b/script/sync/fork-features.json @@ -1,8 +1,8 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", "description": "Fork-specific features from upstream PRs that must be preserved during merges", - "lastUpdated": "2026-01-12", - "lastChange": "v1.1.15 sync. Adopted upstream's refactored desktop server connection logic with shuvcode branding. Tips component now uses upstream's TSX structure.", + "lastUpdated": "2026-01-14", + "lastChange": "Track Shuvcode Anthropic prompt branding update (remove opencode references).", "note": "v1.1.15 sync. Upstream adds password auth (OPENCODE_SERVER_PASSWORD), improved tips display, permission wildcarding fixes, and desktop server detection improvements.", "forkDependencies": { "description": "NPM dependencies added by fork features that MUST be preserved during package.json merges. These are frequently lost when accepting upstream version bumps.", @@ -1926,6 +1926,21 @@ } ], "note": "CRITICAL: Upstream uses app.opencode.ai. This gets overwritten during merges. Always verify after merge that proxy URL is app.shuv.ai." + }, + { + "pr": 0, + "title": "Shuvcode prompt branding for Anthropic", + "author": "fork", + "status": "fork-only", + "description": "Update Anthropic system prompt to use Shuvcode branding and remove opencode references.", + "files": ["packages/opencode/src/session/prompt/anthropic.txt"], + "criticalCode": [ + { + "file": "packages/opencode/src/session/prompt/anthropic.txt", + "description": "Branding and feedback instructions for Shuvcode", + "markers": ["You are Shuvcode", "github.com/anomalyco/shuvcode"] + } + ] } ] } diff --git a/sdks/vscode/package.json b/sdks/vscode/package.json index 7222948f697..2347b8a564b 100644 --- a/sdks/vscode/package.json +++ b/sdks/vscode/package.json @@ -2,8 +2,8 @@ "name": "shuvcode", "displayName": "shuvcode", "description": "shuvcode for VS Code", - "version": "1.1.19", - "publisher": "latitudes-dev" + "version": "1.1.20", + "publisher": "latitudes-dev", "repository": { "type": "git", "url": "https://github.com/anomalyco/opencode" diff --git a/sst-env.d.ts b/sst-env.d.ts index 035a5fc21dd..3160fc165b8 100644 --- a/sst-env.d.ts +++ b/sst-env.d.ts @@ -104,6 +104,10 @@ declare module "sst" { "type": "sst.sst.Secret" "value": string } + "STRIPE_PUBLISHABLE_KEY": { + "type": "sst.sst.Secret" + "value": string + } "STRIPE_SECRET_KEY": { "type": "sst.sst.Secret" "value": string