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
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,15 @@ key_refused`; against an older backend the previous behaviour stands.

### Skills

- **Skill Stacks in the desktop app** — the Skill Hub now has two tabs,
Skills and Skill Stacks. The Stacks tab lists the hub's stacks with their
owner, skill and member counts and a preview of what's inside; Install
asks where (this project or global) and for which agents, then installs
the whole set through the server the app is connected to — so it lands on
the machine running the agent, and works against a remote server too.
Skills already installed are named and you choose to skip or overwrite
them; the result lists what was installed, skipped, or failed, and the
new skills are visible to the model straight away.
- **Skill Stacks in the TUI** — `/stacks` (also the `prompt_stacks`
keybind, unbound by default) opens a browser for the CoDev SkillHub's Skill
Stacks right in the chat: pick one, choose this project or global and which
Expand Down
2 changes: 1 addition & 1 deletion DIVERGENCES.md
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ from the squash message first, or use a plain merge.
| `packages/app/src/components/prompt-input-v2.tsx` | `onUnpaidClick` opens `DialogConnectProvider` (lazy import) instead of the free-models "unpaid" dialogs; their now-unused imports dropped. (The legacy `prompt-input.tsx` carried the same swap until its deletion — see the legacy prompt-input removal row.) | With the hosted providers hidden, "unpaid" means nothing is connected, and the free-models dialogs would render empty lists (this path is reachable on the web deployment; on desktop the gate covers it). | Keep the handler swap; take upstream's surrounding prompt-input changes. |
| `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, passing a `net.fetch` wrapper as the hub transport (`setHubTransport`) and a `warmProxy` hook — `session.defaultSession.resolveProxy(SKILLHUB_URL)` — so hub traffic honors enterprise proxies/PAC like the gateway SSO calls do and Chromium's proxy initialization is waited out before the first hub request; 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. |
| 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, passing a `net.fetch` wrapper as the hub transport (`setHubTransport`) and a `warmProxy` hook — `session.defaultSession.resolveProxy(SKILLHUB_URL)` — so hub traffic honors enterprise proxies/PAC like the gateway SSO calls do and Chromium's proxy initialization is waited out before the first hub request; 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/stacks` + `/skills/:id` routes inside the new-layout `<Show>` (`/skills/stacks` before `/skills/:id`; it is the fork-only Skill Stacks page, `src/pages/skill-stacks.tsx`, served by the server's `v2.skillhub` routes rather than the Electron bridge); i18n adds `sidebar.skillhub` + the `skillhub.*` and `skillstack.*` families (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. |
Expand Down
2 changes: 2 additions & 0 deletions packages/app/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ const NewSession = lazy(() => import("@/pages/new-session"))

const SkillHubPage = lazy(() => import("@/pages/skill-hub"))
const SkillHubDetailPage = lazy(() => import("@/pages/skill-hub-detail"))
const SkillStacksPage = lazy(() => import("@/pages/skill-stacks"))

const SessionRoute = () => {
const settings = useSettings()
Expand Down Expand Up @@ -708,6 +709,7 @@ function Routes(props: { serverScoped?: JSX.Element }) {
<Show when={settings.general.newLayoutDesigns()}>
<Route path="/" component={NewHome} />
<Route path="/skills" component={SkillHubPage} />
<Route path="/skills/stacks" component={SkillStacksPage} />
<Route path="/skills/:id" component={SkillHubDetailPage} />
<Route path="/:dir/session/:id" component={NewLayoutLegacySessionRedirect} />
<Route path="/server/:serverKey/session/:id" component={TargetSessionRoute} />
Expand Down
34 changes: 34 additions & 0 deletions packages/app/src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1098,6 +1098,40 @@ export const dict = {
"skillhub.install.error.missingDirectory": "That project folder is not on this computer.",
"skillhub.install.success.title": "Installed {{name}}",
"skillhub.install.done": "Done",
"skillhub.tabs.skills": "Skills",
"skillhub.tabs.stacks": "Skill Stacks",
"skillstack.title": "Skill Stacks",
"skillstack.subtitle": "A team's whole skill set, installed in one step.",
"skillstack.search.placeholder": "Search stacks…",
"skillstack.empty": "No stacks match your search.",
"skillstack.card.skills": "{{count}} skills",
"skillstack.card.members": "{{count}} members",
"skillstack.card.more": "+{{count}} more",
"skillstack.card.install": "Install",
"skillstack.install.title": "Install {{name}}",
"skillstack.install.scope.label": "Install to",
"skillstack.install.scope.project": "This project",
"skillstack.install.scope.global": "Global (all projects)",
"skillstack.install.agents.label": "For which agents?",
"skillstack.install.agents.locked": "Always included",
"skillstack.install.confirm": "Install",
"skillstack.install.cancel": "Cancel",
"skillstack.install.done": "Done",
"skillstack.install.preparing": "Downloading the stack…",
"skillstack.install.installing": "Installing…",
"skillstack.install.conflicts.title": "{{count}} already installed",
"skillstack.install.conflicts.description":
"These skills are already in place: {{names}}. Skip them, or overwrite them and lose any local edits?",
"skillstack.install.conflicts.skip": "Skip them, install the rest",
"skillstack.install.conflicts.overwrite": "Overwrite them",
"skillstack.install.success.title": "Installed {{installed}} of {{total}} skills from {{name}}",
"skillstack.install.result.installed": "Installed",
"skillstack.install.result.skipped": "Skipped",
"skillstack.install.result.failed": "Failed",
"skillstack.error.auth": "Sign in to the Skill Hub first — run `codev skill push` in a terminal.",
"skillstack.error.notFound": "That stack is gone, or has no public skills.",
"skillstack.error.expired": "That download expired before it was installed. Try again.",
"skillstack.error.unknown": "The Skill Hub could not deliver this stack.",
"skillhub.desktopOnly.title": "Skill Hub is available in the desktop app",
"skillhub.desktopOnly.description":
"Browse and install skills from the CoDev desktop app, or use `codev skill` in the terminal.",
Expand Down
34 changes: 34 additions & 0 deletions packages/app/src/i18n/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1092,6 +1092,40 @@ export const dict = {
"skillhub.install.error.missingDirectory": "Esa carpeta de proyecto no está en este equipo.",
"skillhub.install.success.title": "{{name}} instalado",
"skillhub.install.done": "Hecho",
"skillhub.tabs.skills": "Habilidades",
"skillhub.tabs.stacks": "Skill Stacks",
"skillstack.title": "Skill Stacks",
"skillstack.subtitle": "Todas las habilidades de un equipo, instaladas en un solo paso.",
"skillstack.search.placeholder": "Buscar stacks…",
"skillstack.empty": "Ningún stack coincide con tu búsqueda.",
"skillstack.card.skills": "{{count}} habilidades",
"skillstack.card.members": "{{count}} miembros",
"skillstack.card.more": "+{{count}} más",
"skillstack.card.install": "Instalar",
"skillstack.install.title": "Instalar {{name}}",
"skillstack.install.scope.label": "Instalar en",
"skillstack.install.scope.project": "Este proyecto",
"skillstack.install.scope.global": "Global (todos los proyectos)",
"skillstack.install.agents.label": "¿Para qué agentes?",
"skillstack.install.agents.locked": "Siempre incluido",
"skillstack.install.confirm": "Instalar",
"skillstack.install.cancel": "Cancelar",
"skillstack.install.done": "Hecho",
"skillstack.install.preparing": "Descargando el stack…",
"skillstack.install.installing": "Instalando…",
"skillstack.install.conflicts.title": "{{count}} ya instaladas",
"skillstack.install.conflicts.description":
"Estas habilidades ya están instaladas: {{names}}. ¿Omitirlas, o sobrescribirlas y perder los cambios locales?",
"skillstack.install.conflicts.skip": "Omitirlas e instalar el resto",
"skillstack.install.conflicts.overwrite": "Sobrescribirlas",
"skillstack.install.success.title": "Instaladas {{installed}} de {{total}} habilidades de {{name}}",
"skillstack.install.result.installed": "Instaladas",
"skillstack.install.result.skipped": "Omitidas",
"skillstack.install.result.failed": "Fallidas",
"skillstack.error.auth": "Primero inicia sesión en el Skill Hub: ejecuta `codev skill push` en una terminal.",
"skillstack.error.notFound": "Ese stack ya no existe o no tiene habilidades públicas.",
"skillstack.error.expired": "Esa descarga caducó antes de instalarse. Inténtalo de nuevo.",
"skillstack.error.unknown": "El Skill Hub no pudo entregar este stack.",
"skillhub.desktopOnly.title": "Skill Hub está disponible en la aplicación de escritorio",
"skillhub.desktopOnly.description":
"Explora e instala skills desde la aplicación de escritorio de CoDev, o usa `codev skill` en la terminal.",
Expand Down
34 changes: 34 additions & 0 deletions packages/app/src/i18n/vi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1055,6 +1055,40 @@ export const dict = {
"skillhub.install.error.missingDirectory": "Thư mục dự án đó không có trên máy tính này.",
"skillhub.install.success.title": "Đã cài đặt {{name}}",
"skillhub.install.done": "Xong",
"skillhub.tabs.skills": "Kỹ năng",
"skillhub.tabs.stacks": "Skill Stack",
"skillstack.title": "Skill Stack",
"skillstack.subtitle": "Trọn bộ kỹ năng của một nhóm, cài đặt trong một bước.",
"skillstack.search.placeholder": "Tìm kiếm stack…",
"skillstack.empty": "Không có stack nào khớp với tìm kiếm.",
"skillstack.card.skills": "{{count}} kỹ năng",
"skillstack.card.members": "{{count}} thành viên",
"skillstack.card.more": "+{{count}} nữa",
"skillstack.card.install": "Cài đặt",
"skillstack.install.title": "Cài đặt {{name}}",
"skillstack.install.scope.label": "Cài đặt vào",
"skillstack.install.scope.project": "Dự án này",
"skillstack.install.scope.global": "Toàn cục (mọi dự án)",
"skillstack.install.agents.label": "Cho agent nào?",
"skillstack.install.agents.locked": "Luôn được bao gồm",
"skillstack.install.confirm": "Cài đặt",
"skillstack.install.cancel": "Hủy",
"skillstack.install.done": "Xong",
"skillstack.install.preparing": "Đang tải stack…",
"skillstack.install.installing": "Đang cài đặt…",
"skillstack.install.conflicts.title": "{{count}} kỹ năng đã được cài",
"skillstack.install.conflicts.description":
"Các kỹ năng này đã có sẵn: {{names}}. Bỏ qua chúng, hay ghi đè và mất các chỉnh sửa cục bộ?",
"skillstack.install.conflicts.skip": "Bỏ qua, cài phần còn lại",
"skillstack.install.conflicts.overwrite": "Ghi đè",
"skillstack.install.success.title": "Đã cài {{installed}}/{{total}} kỹ năng từ {{name}}",
"skillstack.install.result.installed": "Đã cài",
"skillstack.install.result.skipped": "Đã bỏ qua",
"skillstack.install.result.failed": "Thất bại",
"skillstack.error.auth": "Hãy đăng nhập Skill Hub trước — chạy `codev skill push` trong terminal.",
"skillstack.error.notFound": "Stack này không còn tồn tại hoặc không có kỹ năng công khai.",
"skillstack.error.expired": "Bản tải này đã hết hạn trước khi được cài. Hãy thử lại.",
"skillstack.error.unknown": "Skill Hub không thể tải stack này về.",
"skillhub.desktopOnly.title": "Skill Hub có sẵn trong ứng dụng desktop",
"skillhub.desktopOnly.description":
"Duyệt và cài skill từ ứng dụng desktop CoDev, hoặc dùng `codev skill` trong terminal.",
Expand Down
2 changes: 2 additions & 0 deletions packages/app/src/pages/skill-hub.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { usePlatform } from "@/context/platform"
import { browseModel } from "@/skillhub/browse-model"
import { SkillCard } from "@/skillhub/skill-card"
import { SkillHubDesktopOnly } from "@/skillhub/desktop-only"
import { SkillHubTabs } from "@/skillhub/tabs"
import type { SkillHubDomain, SkillHubSort } from "@/skillhub/types"

const SORTS: SkillHubSort[] = ["featured", "newest", "downloads", "rating"]
Expand Down Expand Up @@ -38,6 +39,7 @@ export default function SkillHubPage() {
return (
<ScrollView class="min-h-0 w-full flex-1">
<div class="flex w-full flex-col gap-4 px-4 pb-8 pt-6 lg:px-8 lg:pt-10">
<SkillHubTabs />
<div class="flex flex-wrap items-center justify-between gap-3">
<h1 class="text-[18px] font-[560] text-v2-text-text-base">{language.t("skillhub.title")}</h1>
<div class="flex items-center gap-2">
Expand Down
Loading
Loading