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
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,20 @@ file is the human-readable summary of what changed and why.
longer needs any of them.
([#306](https://github.com/quickbeard/codev-code/pull/306))

### Skills

- **Manage SkillHub skills without the hub CLI** — `codev skill` ports the
whole `codevhub skill` workflow into the standalone CLI: `search` queries
the public SkillHub registry, `pull` installs a skill into the
`.claude/skills` + `.agents/skills` folders (current directory or global,
one real copy plus links, with a picker for which agents to serve — CoDev
Code always included), `push` publishes a skill directory or `.zip` with a
preview, confirmation, and an inline SSO sign-in, and `office` downloads
the CoDev Office offline bundle and runs its installer. Sessions and the
office download folder are shared with `codevhub`, so a hub login or an
already-downloaded bundle is reused as-is.
([#330](https://github.com/quickbeard/codev-code/pull/330))

### Install

- **Installing downloads about half as much** — the published platform packages
Expand Down
2 changes: 1 addition & 1 deletion DIVERGENCES.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ from the squash message first, or use a plain merge.
| File | Change | Why | On conflict |
| --- | --- | --- | --- |
| `packages/core/src/global.ts`, `packages/core/test/global.test.ts`, `packages/opencode/test/cli/mcp-add.test.ts` | `const app = "opencode"` → `"codev"`; test expectations `tmpdir()/opencode` → `tmpdir()/codev` and `.config/opencode/` → `.config/codev/` | Config/data/cache/state dirs under `~/.config/codev` etc.; upstream tests assert the literal dir name. | Keep ours (the one constant + expectations); take upstream's surrounding changes. |
| `packages/opencode/src/index.ts`, `packages/opencode/test/cli/error.test.ts`, `packages/opencode/test/cli/help/help-snapshots.test.ts`, `packages/opencode/test/cli/help/__snapshots__/help-snapshots.test.ts.snap` | `scriptName("opencode")` → `"codev"`; `show()` prefix check `"opencode "` → `"codev "`; registered the fork-only `ConfigureCommand` (cli/cmd/configure.ts, new file), `CodegraphCommand` (cli/cmd/codegraph.ts, new file), and `UploadCommand` (cli/cmd/upload.ts, new file — `codev upload`, the standalone `codevhub upload`: exports sessions and ships them to the analysis backend, `--force` to re-upload unchanged, `--logs` to print the last run's diagnostic trail) and added `configure` + `codegraph` + `upload` to the help-snapshot TOP_LEVEL list; test expectation `` `opencode models` `` → `` `codev models` ``; help snapshots regenerated so usage lines read `codev …` | Help/usage text shows the real user-facing command; `codev configure` is the standalone gateway-setup entry point, and `codev codegraph` is the standalone CodeGraph MCP wiring for installs not managed by CoDev Hub. The `show()` check must always match the scriptName; upstream's error-format test and help snapshots assert the literal command. | Keep our strings, command registration + expectation; take upstream's surrounding changes. Regenerate the snapshot file with `bun test test/cli/help --update-snapshots` instead of hand-merging. |
| `packages/opencode/src/index.ts`, `packages/opencode/test/cli/error.test.ts`, `packages/opencode/test/cli/help/help-snapshots.test.ts`, `packages/opencode/test/cli/help/__snapshots__/help-snapshots.test.ts.snap` | `scriptName("opencode")` → `"codev"`; `show()` prefix check `"opencode "` → `"codev "`; registered the fork-only `ConfigureCommand` (cli/cmd/configure.ts, new file), `CodegraphCommand` (cli/cmd/codegraph.ts, new file), `UploadCommand` (cli/cmd/upload.ts, new file — `codev upload`, the standalone `codevhub upload`: exports sessions and ships them to the analysis backend, `--force` to re-upload unchanged, `--logs` to print the last run's diagnostic trail; its inline SSO prompt now lives in cli/cmd/sso-prompt.ts, new file, shared with `codev skill push`), and `SkillHubCommand` (cli/cmd/skill.ts, new file — `codev skill search|pull|push|office`, the standalone `codevhub skill`: searches/installs/publishes SkillHub skills and fetches the CoDev Office offline bundle, backed by the fork-only `packages/codev-skillhub` workspace package with SSO via `@codev/gateway`) and added `configure` + `codegraph` + `upload` + `skill` to the help-snapshot TOP_LEVEL list (and the four `skill` subcommands to SUBCOMMANDS); test expectation `` `opencode models` `` → `` `codev models` ``; help snapshots regenerated so usage lines read `codev …` | Help/usage text shows the real user-facing command; `codev configure` is the standalone gateway-setup entry point, and `codev codegraph` is the standalone CodeGraph MCP wiring for installs not managed by CoDev Hub. The `show()` check must always match the scriptName; upstream's error-format test and help snapshots assert the literal command. | Keep our strings, command registration + expectation; take upstream's surrounding changes. Regenerate the snapshot file with `bun test test/cli/help --update-snapshots` instead of hand-merging. |
| `package.json` (root) | `name` `opencode` → `codev-code`; `repository.url` → `github.com/quickbeard/codev-code`; added `release` script delegating to packages/opencode; added `bump` script pointing at the fork-only `script/bump-version.ts`; removed the `sso`, `dev:stats`, and `random` scripts; added the `lint-staged` devDependency and a `lint-staged` config block (`"*": "prettier --write --ignore-unknown"`) driving the fork-only `.husky/pre-commit` | The root manifest carries the fork's name (nothing reads it — root is private and unpublished; `packages/script` reads only `packageManager` — so this is cosmetic and also removes the name collision with the `packages/opencode` workspace member, whose name MUST stay `opencode`: build.ts derives bun compile targets and dist dir names from it). `bun run release` and `bun run bump` work from the repo root. `sso` logs into upstream's AWS org (`--sso-session=opencode`) and `dev:stats` runs inside an sst shell against upstream's production stage — neither works without anomalyco credentials; `random` is inert cruft (`echo 'Random script'`). `lint-staged` keeps staged files at Prettier's fixed point on commit, so the formatting drift PR #261 had to clean up cannot silently reaccumulate; `--ignore-unknown` skips files Prettier has no parser for (`bun.lock`, `.husky/*`, binaries) and `.prettierignore` still exempts `DIVERGENCES.md` and the docs merge base. | Keep our name + release/bump lines and the removals (drop any new upstream-infra-only scripts too); take upstream's other fields. |
| `packages/opencode/package.json` | bin key `opencode` → `codev` (shim path `./bin/opencode` unchanged); added `release` script; added dependencies `"@codev/brand": "workspace:*"`, `"@codev/gateway": "workspace:*"` (the latter for the gateway-key migration's shared-auth-file read, see the config.ts migration row), and `"@codev/upload": "workspace:*"` (the session uploader — see the session-upload wiring row) | Installed command is `codev` — matches the CLI's `scriptName` and help text (the hub's command is `codevhub`; it relinquished the `codev` bin in codev-ai ≥ 0.4.0). `bun run release` publishes the version in this file (upstream-synced; script/release.ts is fork-only). | Keep our bin key, release line, and dependency lines; take upstream's other fields including `version`. |
| Session-upload wiring: `packages/opencode/src/cli/cmd/tui.ts`, `packages/opencode/src/cli/cmd/run.ts`, `packages/opencode/src/cli/cmd/debug/index.ts` | tui.ts + run.ts: `spawnUploadDaemon()` (from the fork-only `@codev/upload` package) fire-and-forgotten at handler start — spawns a detached, unref'd, stdio-ignored `codev debug upload` child. debug/index.ts: registered the fork-only `UploadCommand` (debug/upload.ts, new file), the entry that child runs. | Standalone parity with the hub's launch-time upload daemon: every agent launch ships prior codev sessions to the analysis backend in the background. All gating (opt-out env `CODEV_DISABLE_AUTO_UPLOAD`, signed-in check, the hub-shared lockfile) lives inside `@codev/upload` — the wiring is one guarded call per entry point that can never block or fail the launch. | Keep the import + `spawnUploadDaemon()` line in both handlers and the debug registration; take upstream's surrounding changes. |
Expand Down
21 changes: 21 additions & 0 deletions bun.lock

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

6 changes: 6 additions & 0 deletions packages/codev-gateway/src/auth-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,12 @@ export function loadApiKey(): ApiKeyCreds | null {
}
}

// Hub-only credential (written by `codevhub login --admin`); read here so
// skill commands can ride an existing admin session, never written.
export function loadSkillhubCookie(): string | null {
return readAuthFile()?.skillhub_cookie ?? null
}

export function saveCodevConfig(config: CodevConfig): void {
writeAuthFile({
...(readAuthFile() ?? {}),
Expand Down
1 change: 1 addition & 0 deletions packages/codev-gateway/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export {
loadAuth,
loadCodevConfig,
loadGatewayUrl,
loadSkillhubCookie,
saveApiKey,
saveCodevConfig,
type ApiKeyCreds,
Expand Down
13 changes: 13 additions & 0 deletions packages/codev-gateway/test/auth-file.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,19 @@ describe("auth-file", () => {
expect(raw.api_key).toBe("sk-2")
})

test("loadSkillhubCookie reads the hub cookie and survives our own saves", async () => {
const { loadSkillhubCookie, saveApiKey, authFilePath } = await import("../src/auth-file")
const { mkdirSync, writeFileSync } = await import("node:fs")
expect(loadSkillhubCookie()).toBeNull()

mkdirSync(join(tempDir, ".codev"), { recursive: true })
writeFileSync(authFilePath(), JSON.stringify({ skillhub_cookie: "skill-hub-session=abc" }))
expect(loadSkillhubCookie()).toBe("skill-hub-session=abc")

saveApiKey({ apiKey: "sk-1" })
expect(loadSkillhubCookie()).toBe("skill-hub-session=abc")
})

test.skipIf(process.platform === "win32")("writes the file 0600", async () => {
const { saveApiKey, authFilePath } = await import("../src/auth-file")
saveApiKey({ apiKey: "sk-3" })
Expand Down
23 changes: 23 additions & 0 deletions packages/codev-skillhub/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"$schema": "https://json.schemastore.org/package",
"name": "@codev/skillhub",
"version": "0.0.1",
"private": true,
"type": "module",
"scripts": {
"typecheck": "tsc --noEmit",
"test": "bun test"
},
"dependencies": {
"@codev/gateway": "workspace:*",
"adm-zip": "0.5.16"
},
"devDependencies": {
"@tsconfig/bun": "catalog:",
"@types/adm-zip": "0.5.7",
"@types/bun": "catalog:"
},
"exports": {
".": "./src/index.ts"
}
}
48 changes: 48 additions & 0 deletions packages/codev-skillhub/src/agents.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { existsSync } from "node:fs"
import { delimiter, join } from "node:path"
import { home } from "./paths"
import { SKILL_AGENTS, type SkillAgent } from "./skill-dirs"

// The hub derives its default agent set from its own configure/backup markers
// (`detectCodevTools`), which only exist on machines the hub has configured.
// This standalone CLI instead treats "the agent is present on this machine" as
// the signal: its CLI resolves on PATH, or its config directory exists.
const AGENT_BINARIES: Record<Exclude<SkillAgent, "codev">, string> = {
claude: "claude",
codex: "codex",
opencode: "opencode",
}

function configDirs(agent: Exclude<SkillAgent, "codev">): string[] {
if (agent === "claude") return [join(home(), ".claude")]
if (agent === "codex") return [join(home(), ".codex")]
return [join(home(), ".config", "opencode")]
}

// Object indirection so tests can stub detection without module mocks.
export const detectApi = {
which(binary: string): boolean {
const pathValue = process.env.PATH ?? ""
// Windows resolves executables through PATHEXT (claude.cmd, codex.exe, …).
const extensions = process.platform === "win32" ? (process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";") : [""]
return pathValue
.split(delimiter)
.filter((dir) => dir !== "")
.some((dir) => extensions.some((ext) => existsSync(join(dir, binary + ext.toLowerCase()))))
},
dirExists(path: string): boolean {
return existsSync(path)
},
}

// The set installed for when the user names none: CoDev Code always (the
// flagship agent is never opted out of), plus every other agent present on
// this machine. Used as both the prompt's pre-check and the non-interactive
// default, so CI never has to spell out --agent.
export function defaultAgents(): SkillAgent[] {
return SKILL_AGENTS.filter((agent) => {
// Literal comparison (not ALWAYS_AGENT) so TypeScript narrows the union.
if (agent === "codev") return true
return detectApi.which(AGENT_BINARIES[agent]) || configDirs(agent).some((dir) => detectApi.dirExists(dir))
})
}
Loading
Loading