Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ file is the human-readable summary of what changed and why.
globally with the same agent picker the CLI uses — CoDev Code always
included, one real copy plus links for the other agents. Re-installing
offers a safe overwrite, and everything rides the same shared sign-in as
`codev skill` and `codevhub`.
([#331](https://github.com/quickbeard/codev-code/pull/331))
`codev skill` and `codevhub` — when the hub asks for a signed-in session,
the install dialog offers the browser sign-in right in the app.
([#331](https://github.com/quickbeard/codev-code/pull/331),
[#335](https://github.com/quickbeard/codev-code/pull/335))

### Language

Expand Down
1 change: 1 addition & 0 deletions DIVERGENCES.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ from the squash message first, or use a plain merge.
| `packages/desktop/src/renderer/index.tsx` | Calls `installConsoleFormatting()` (fork-only `./console-format`) before anything else logs. | `spyRendererConsole` receives Chromium's flattened console-message string, so any renderer `console.*` with an object argument reached the terminal and the exported debug logs as `[object Object]`; the wrapper serializes structured arguments (Error-aware, circular-safe, truncated) at the source. | Keep the import and call; take upstream's surrounding entry changes. |
| Desktop gateway SSO wiring: `packages/desktop/src/main/index.ts`, `packages/desktop/src/preload/{index,types}.ts`, `packages/desktop/src/renderer/index.tsx` | Main instantiates the fork-only `src/main/gateway-sso/` controller (wrapping `@codev/gateway` `login`/`silentSso` — node-only loopback OAuth — with `shell.openExternal` and a `net.fetch` wrapper) and registers its `gateway-sso-*` IPC channels next to the sandbox ones, passing an `onConfigured` callback that restarts the running sandboxes (`SandboxController.restartRunning`) because a guest is handed its codev config once at boot and cannot see credentials that appear afterwards; preload exposes the `gatewaySso` namespace (subscribe fan-out, sandbox pattern) with types re-exported from `@opencode-ai/app/gateway/types`; the renderer platform maps `gatewaySso: window.api.gatewaySso`. | SSO login for the desktop configure gate must run in the main process (`node:http` loopback server, `~/.codev/auth.json` writes); `net.fetch` so enterprise proxies/PAC apply (egress precedent). | Keep the wiring; take upstream's surrounding changes. Keep channel names `gateway-sso-*` stable. |
| Desktop Skill Hub wiring: `packages/desktop/src/main/index.ts`, `packages/desktop/src/preload/{index,types}.ts`, `packages/desktop/src/renderer/index.tsx`, `packages/desktop/package.json`, `packages/app/src/app.tsx`, `packages/app/src/i18n/{en,es}.ts` | Main registers the fork-only `src/main/skillhub/` controller (over the fork-only `@codev/skillhub` package) and its `skillhub-*` IPC channels; preload exposes the `skillhub` namespace (types aliased from `@opencode-ai/app/skillhub/types`); renderer maps `skillhub: window.api.skillhub` into the platform; `@codev/skillhub` added under desktop devDependencies (electron-vite bundles devDependency workspace packages); app.tsx registers lazy `/skills` + `/skills/:id` routes inside the new-layout `<Show>`; i18n adds `sidebar.skillhub` + the `skillhub.*` family (vi.ts is fork-only). | The Skill Hub page is native (fork-only `packages/app/src/{skillhub/**,pages/skill-hub*.tsx,components/skill-hub-nav.tsx}`); all hub HTTP must originate in the Electron main process because the hub serves no ACAO on /api/v1 and 403s cross-origin writes, so the renderer reaches it only over IPC. | Keep the registration/preload/renderer/package.json lines, the two route lines, and the i18n keys; take upstream's surrounding changes. |
| `packages/app/vite.js` | Added `optimizeDeps.entries`: the default `index.html` plus session-ui's `markdown.worker.ts` (absolute path derived from the plugin file, forward-slashed for fast-glob), so the dev-time dep scanner crawls the markdown worker at startup. | Vite's dep scanner does not crawl `?worker&url` entries, so the worker's deps (`@shikijs/stream`, `katex`, `marked`, `marked-shiki`, `remend`, `shiki`) were first discovered when the first markdown rendered; that mid-session re-optimize full-page reloads the dev app — e.g. a Skill Hub search reset itself the moment results rendered. A bare `optimizeDeps.include` list cannot express this fix: with bun's isolated linker the deps live only in the workspace packages' own `node_modules`, and no single include chain resolves from both consumers' roots (`packages/app` and the desktop renderer's `src/renderer`). Dev-only; production builds ignore `optimizeDeps`. | Keep the `optimizeDeps` block and keep `index.html` listed in it — setting `entries` replaces Vite's default entry inference, so dropping it unscans the app itself. If upstream moves or renames the worker file, update the path: a stale pattern matches nothing, logs nothing, and silently brings the reload back. |
| `packages/app/package.json` | Added `"./gateway/types": "./src/gateway/types.ts"`, `"./skillhub/types": "./src/skillhub/types.ts"`, and `"./i18n/*": "./src/i18n/*.ts"` to `exports`. | The desktop preload aliases the app-owned gateway SSO wire types (sandbox/types pattern); the desktop main process imports the locale dicts to translate the macOS menu bar (`./i18n/*` mirrors how `@opencode-ai/ui` already exports its own — see the localized desktop menu row). | Keep both exports; take upstream's other manifest changes. |
| `packages/app/src/i18n/{en,es}.ts` | Added the `gateway.configure.*` key family (configure-gate dialog) and reworded `sidebar.gettingStarted.line1` — the "includes free models" pitch no longer holds with the hosted providers hidden. | Strings for the fork's configure gate; the getting-started card must not advertise models the fork does not offer. | Keep our keys and copy; take upstream's surrounding i18n changes. |
| `packages/desktop/src/main/index.ts` | Deleted upstream's `app.commandLine.appendSwitch("proxy-bypass-list", "<-loopback>")`. | Chromium's `<-loopback>` bypass rule means the opposite of what it reads as: it *subtracts* the implicit rules that keep loopback off the proxy, i.e. it opts localhost traffic *into* proxying (`net/docs/proxy.md`). Today it is inert — Electron reads `--proxy-bypass-list` only together with `--proxy-server` (`shell/browser/browser_process_impl.cc`), which the app never passes — but it becomes actively harmful the moment anything launches the app with `--proxy-server`: the renderer's sidecar traffic would be routed into the proxy. Chromium already bypasses loopback by default, so nothing replaces it. | Keep deleted. If upstream adds new proxy switches, check them against Chromium's implicit-bypass semantics before taking them. |
Expand Down
1 change: 1 addition & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions packages/app/src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1076,6 +1076,10 @@ export const dict = {
"skillhub.install.cancel": "Cancel",
"skillhub.install.confirm": "Install",
"skillhub.install.installing": "Installing…",
"skillhub.install.error.auth": "Your SkillHub session has expired. Sign in to continue.",
"skillhub.install.signin": "Sign in",
"skillhub.install.signin.waiting": "Finish signing in in your browser…",
"skillhub.install.error.failed": "The Skill Hub could not deliver this skill.",
"skillhub.install.success.title": "Installed {{name}}",
"skillhub.install.done": "Done",
"skillhub.desktopOnly.title": "Skill Hub is available in the desktop app",
Expand Down
4 changes: 4 additions & 0 deletions packages/app/src/i18n/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1069,6 +1069,10 @@ export const dict = {
"skillhub.install.cancel": "Cancelar",
"skillhub.install.confirm": "Instalar",
"skillhub.install.installing": "Instalando…",
"skillhub.install.error.auth": "Tu sesión de SkillHub ha caducado. Inicia sesión para continuar.",
"skillhub.install.signin": "Iniciar sesión",
"skillhub.install.signin.waiting": "Termina de iniciar sesión en tu navegador…",
"skillhub.install.error.failed": "El Skill Hub no pudo entregar esta habilidad.",
"skillhub.install.success.title": "{{name}} instalado",
"skillhub.install.done": "Hecho",
"skillhub.desktopOnly.title": "Skill Hub está disponible en la aplicación de escritorio",
Expand Down
4 changes: 4 additions & 0 deletions packages/app/src/i18n/vi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1034,6 +1034,10 @@ export const dict = {
"skillhub.install.cancel": "Hủy",
"skillhub.install.confirm": "Cài đặt",
"skillhub.install.installing": "Đang cài đặt…",
"skillhub.install.error.auth": "Phiên đăng nhập SkillHub đã hết hạn. Hãy đăng nhập để tiếp tục.",
"skillhub.install.signin": "Đăng nhập",
"skillhub.install.signin.waiting": "Hoàn tất đăng nhập trong trình duyệt…",
"skillhub.install.error.failed": "Skill Hub không thể tải kỹ năng này về.",
"skillhub.install.success.title": "Đã cài đặt {{name}}",
"skillhub.install.done": "Xong",
"skillhub.desktopOnly.title": "Skill Hub có sẵn trong ứng dụng desktop",
Expand Down
70 changes: 62 additions & 8 deletions packages/app/src/skillhub/dialog-install.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2"
import { CheckboxV2 } from "@opencode-ai/ui/v2/checkbox-v2"
import { LoaderV2 } from "@opencode-ai/ui/v2/loader-v2"
import { Dialog, DialogBody, DialogFooter, DialogHeader, DialogTitle } from "@opencode-ai/ui/v2/dialog-v2"
import { DividerV2 } from "@opencode-ai/ui/v2/divider-v2"
import { RadioGroupV2, RadioItemV2 } from "@opencode-ai/ui/v2/radio-v2"
Expand Down Expand Up @@ -55,7 +56,8 @@ export function DialogSkillInstall(props: { id: string; name: string; version?:
scope: (preselected() ? "project" : "global") as "project" | "global",
directory: preselected()?.worktree as string | undefined,
agents: rememberedAgents(),
phase: "form" as "form" | "installing" | "overwrite" | "done",
phase: "form" as "form" | "installing" | "signing-in" | "overwrite" | "done",
needsAuth: false,
error: undefined as string | undefined,
existingPath: undefined as string | undefined,
outcome: undefined as SkillHubInstallOutcome | undefined,
Expand All @@ -77,13 +79,15 @@ export function DialogSkillInstall(props: { id: string; name: string; version?:
state.scope === "project" && state.directory
? { kind: "project", directory: state.directory }
: { kind: "global" }
setState({ phase: "installing", error: undefined })
setState({ phase: "installing", needsAuth: false, error: undefined })
try {
localStorage.setItem(AGENT_CHOICE_KEY, JSON.stringify(state.agents))
} catch {
// Remember-last-choice only; a blocked localStorage changes nothing.
}
const result = await skillhub.install({ id: props.id, scope, agents: state.agents, force })
// Copy the agents out of the store: ipcRenderer.invoke structured-clones
// its arguments, and a store's reactive Proxy is not cloneable.
const result = await skillhub.install({ id: props.id, scope, agents: [...state.agents], force })
if (result.ok) {
setState({ phase: "done", outcome: result.value })
showToast(language.t("skillhub.install.success.title", { name: props.name }))
Expand All @@ -94,10 +98,41 @@ export function DialogSkillInstall(props: { id: string; name: string; version?:
setState({ phase: "overwrite", existingPath: match?.[1] ?? result.error.message })
return
}
const key = result.error.code === "network" ? "skillhub.error.network" : "skillhub.error.unknown"
if (result.error.code === "auth") {
// The hub wants a signed-in session — the dialog can run the same SSO
// flow the configure gate uses, so offer that instead of the raw
// message's run-a-CLI hint.
setState({ phase: "form", needsAuth: true, error: language.t("skillhub.install.error.auth") })
return
}
const key = result.error.code === "network" ? "skillhub.error.network" : "skillhub.install.error.failed"
setState({ phase: "form", error: `${language.t(key)} (${result.error.message})` })
}

const signIn = async () => {
const sso = platform.gatewaySso
if (!sso) return
setState({ phase: "signing-in", error: undefined })
const failure = await sso.startLogin().then(
() => undefined,
(error: unknown) => (error instanceof Error ? error.message : String(error)),
)
// A cancelled login is the user pressing Cancel below, not a failure —
// cancelSignIn already put the form back.
if (failure?.includes("cancelled")) return
if (failure !== undefined) {
setState({ phase: "form", error: failure })
return
}
setState("needsAuth", false)
await install(false)
}

const cancelSignIn = async () => {
await platform.gatewaySso?.cancelLogin().catch(() => {})
setState({ phase: "form" })
}

const [selectedProject, setSelectedProject] = createSignal(preselected())

return (
Expand Down Expand Up @@ -163,6 +198,13 @@ export function DialogSkillInstall(props: { id: string; name: string; version?:
</Show>
</Show>

<Show when={state.phase === "signing-in"}>
<div class="flex flex-col items-center gap-3 py-6">
<LoaderV2 class="size-5" />
<div class="text-[13px] text-v2-text-text-muted">{language.t("skillhub.install.signin.waiting")}</div>
</div>
</Show>

<Show when={state.phase === "overwrite"}>
<div class="flex flex-col gap-2">
<div class="text-[13px] font-[530] text-v2-text-text-base">
Expand Down Expand Up @@ -197,10 +239,22 @@ export function DialogSkillInstall(props: { id: string; name: string; version?:
<ButtonV2 variant="ghost" onClick={() => dialog.close()}>
{language.t("skillhub.install.cancel")}
</ButtonV2>
<ButtonV2 disabled={state.phase === "installing"} onClick={() => void install(false)}>
{state.phase === "installing"
? language.t("skillhub.install.installing")
: language.t("skillhub.install.confirm")}
<Show
when={state.needsAuth && platform.gatewaySso}
fallback={
<ButtonV2 disabled={state.phase === "installing"} onClick={() => void install(false)}>
{state.phase === "installing"
? language.t("skillhub.install.installing")
: language.t("skillhub.install.confirm")}
</ButtonV2>
}
>
<ButtonV2 onClick={() => void signIn()}>{language.t("skillhub.install.signin")}</ButtonV2>
</Show>
</Show>
<Show when={state.phase === "signing-in"}>
<ButtonV2 variant="ghost" onClick={() => void cancelSignIn()}>
{language.t("skillhub.install.cancel")}
</ButtonV2>
</Show>
<Show when={state.phase === "overwrite"}>
Expand Down
17 changes: 17 additions & 0 deletions packages/app/vite.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ import { fileURLToPath } from "url"

const theme = fileURLToPath(new URL("./public/oc-theme-preload.js", import.meta.url))

// Absolute so it resolves from any consumer's root (the desktop renderer's is
// src/renderer); forward slashes because optimizeDeps.entries is a fast-glob
// pattern, which rejects backslashes.
const markdownWorker = fileURLToPath(
new URL("../session-ui/src/components/markdown.worker.ts", import.meta.url),
).replaceAll("\\", "/")

const channel = (() => {
const raw = process.env.CODEV_CHANNEL
if (raw === "dev" || raw === "beta" || raw === "prod") return raw
Expand All @@ -31,6 +38,16 @@ export default [
worker: {
format: "es",
},
optimizeDeps: {
// Vite's startup dep scanner does not crawl `?worker&url` entries,
// so the markdown worker's deps (@shikijs/stream, katex, marked,
// marked-shiki, remend) were only discovered on the first markdown
// render — a mid-session re-optimize that full-page reloads the app
// in dev (e.g. resetting a Skill Hub search). Scanning the worker as
// an extra entry pre-bundles them at startup. "index.html" restates
// the default entry, which setting `entries` would otherwise drop.
entries: ["index.html", markdownWorker],
},
}
},
},
Expand Down
1 change: 1 addition & 0 deletions packages/tui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"dependencies": {
"@codev/brand": "workspace:*",
"@codev/gateway": "workspace:*",
"@codev/upload": "workspace:*",
"@opencode-ai/core": "workspace:*",
"@opencode-ai/plugin": "workspace:*",
"@opencode-ai/sdk": "workspace:*",
Expand Down
Loading