feat(jetbrains): add jetbrains-cli-pin skill

Add a skill that pins the JetBrains plugin to the latest released CLI,
unpins to the local repo CLI, or fresh-regenerates the local CLI. Every
command first cleans all leftover CLI binaries and build artifacts in the
current worktree so each run starts from a fresh, artifact-free state.

Reuses the release-jetbrains set-pin/pin-common helpers for validated
version bumps and cross-links the skill from the JetBrains AGENTS.md.
This commit is contained in:
kirillk
2026-08-04 19:28:55 -04:00
parent 974f03203a
commit 6a8302881d
4 changed files with 199 additions and 0 deletions
+79
View File
@@ -0,0 +1,79 @@
---
name: jetbrains-cli-pin
description: Use when pinning or unpinning the CLI version the Kilo JetBrains plugin uses, or fresh-regenerating the local repo CLI. Cleans all leftover CLI binaries and build artifacts in the current worktree so every operation starts from a fresh, artifact-free state.
---
# JetBrains CLI Pin
Pin the Kilo JetBrains plugin to the latest released CLI, unpin it to use the local
repo CLI, or fresh-regenerate the local CLI while unpinned. Every command first cleans
all CLI/pin build artifacts and binaries in the current worktree so the result never
carries state from a previous run.
Run all commands from the repository root of the worktree you want to affect. Paths are
relative, so they resolve to the current worktree, not the main checkout.
## Two Controls
The plugin's CLI behavior is governed by two independent values:
| Control | Location | Meaning |
|---|---|---|
| Pin mode | `packages/kilo-jetbrains/gradle.properties` -> `kilo.cli.pinned` | `true` = download the released CLI at build/connect time. `false` = build and bundle the local repo CLI. |
| Pinned version | `packages/kilo-jetbrains/package.json` -> `version` | Which GitHub CLI release is downloaded and generated from when `pinned=true`. |
"Pin to latest" means `kilo.cli.pinned=true` **and** `package.json` set to the latest
stable CLI release. "Unpin" means `kilo.cli.pinned=false` with a freshly built local CLI
bundled.
## Commands
```bash
bun .kilo/skills/jetbrains-cli-pin/script/cli-pin.ts <command> [--no-verify]
```
| Command | Steps |
|---|---|
| `pin` | Clean -> set `kilo.cli.pinned=true` -> bump `package.json` to latest release (via `set-pin.ts --latest`, which validates release assets) -> verify with a cold `gradlew clean typecheck`. |
| `unpin` | Clean -> set `kilo.cli.pinned=false` -> `:backend:buildRepoCli` (fresh CLI) -> `:backend:stageRepoCli` -> assert staged `kilo-cli.zip` -> verify with `gradlew typecheck`. |
| `regen` | Fast dev loop while unpinned: `rm -rf dist` -> `buildRepoCli` -> `stageRepoCli`. Refuses to run unless `kilo.cli.pinned=false`. |
| `clean` | Run the shared artifact clean only. |
`--no-verify` skips the gradle verification build (rewrites + clean only). Use it when
offline or without Java 21.
## Cleaned Artifacts
`clean()` runs `./gradlew clean` plus targeted deletes. All paths are gitignored, so
tracked files are never touched. The clean removes the stale artifacts that otherwise
leak across a pin/unpin flip:
| Artifact | Path |
|---|---|
| Repo CLI binaries | `packages/opencode/dist/` |
| Staged CLI archive | `packages/kilo-jetbrains/backend/build/generated/kilo-cli-res/kilo-cli.zip` |
| Generated props / checksums / OpenAPI client | `packages/kilo-jetbrains/backend/build/generated/` |
| Compiled resources (bundled zip on classpath) | `packages/kilo-jetbrains/backend/build/resources/` |
| CLI download cache | `packages/kilo-jetbrains/backend/build/cli-cache/` |
The staged `kilo-cli.zip` is the nastiest leak: once it lands in `backend/build/resources/main/`
from an unpinned build, runtime prefers the bundled zip over downloading. A full clean is
the only reliable reset.
## Notes
- Verification builds pass `--no-configuration-cache` so the changed `kilo.cli.pinned`
value is re-read instead of served from the on-disk Gradle configuration cache.
- The `pin` verification is a cold build: it downloads the pinned CLI release via
`generateOpenApiSpec` and needs network access plus Java 21. Use `--no-verify` offline.
- `kilo.cli.pinned=false` is dev-only and not releasable. Production Gradle builds,
`script/build-version.sh`, and the release scripts hard-fail on `false` -- run `pin`
before releasing.
## Related
- Version-bump and release-gating logic lives in the `release-jetbrains` skill
(`.kilo/skills/release-jetbrains/SKILL.md`); this skill reuses its `set-pin.ts` and
`pin-common.ts` helpers.
- Background on the build wiring: the "CLI Pinning, Unpinning, and Bumping" and "CLI
Integration" sections of `packages/kilo-jetbrains/AGENTS.md`.
@@ -0,0 +1,23 @@
import { $ } from "bun"
// Single source of truth for every CLI/pin artifact that can leak across a mode
// flip in the current worktree. Everything here is gitignored (dist, backend/build,
// .gradle), so cleaning never touches tracked files.
//
// The build's conditional sourceSets/dependsOn wiring in backend/build.gradle.kts only
// produces a correct package from a clean build/ directory. Incremental builds are what
// let a stale kilo-cli.zip survive a pin<->unpin flip, and runtime prefers a bundled
// zip over downloading -- so a full gradle clean is the reliable reset.
export async function clean(jb = "packages/kilo-jetbrains") {
// gradle clean wipes each project's build directory (including backend/build).
await $`./gradlew clean --quiet`.cwd(jb).nothrow()
// Stale per-platform CLI binaries. build.ts only rm -rf dist for the platforms it
// builds, so old platform dirs can survive; wipe the whole tree.
await $`rm -rf packages/opencode/dist`
// Belt-and-suspenders in case gradle clean was skipped or ran offline.
await $`rm -rf ${jb}/backend/build/generated`.nothrow()
await $`rm -rf ${jb}/backend/build/resources`.nothrow()
await $`rm -rf ${jb}/backend/build/cli-cache`.nothrow()
}
@@ -0,0 +1,95 @@
#!/usr/bin/env bun
import { $ } from "bun"
import { parseArgs } from "util"
import { clean } from "./clean"
const jb = "packages/kilo-jetbrains"
const props = `${jb}/gradle.properties`
const pkg = `${jb}/package.json`
const zip = `${jb}/backend/build/generated/kilo-cli-res/kilo-cli.zip`
const arg = Bun.argv[2]
const cmd = arg && !arg.startsWith("-") ? arg : undefined
const { values } = parseArgs({
args: cmd ? Bun.argv.slice(3) : Bun.argv.slice(2),
options: {
"no-verify": { type: "boolean", default: false },
help: { type: "boolean", short: "h", default: false },
},
})
if (values.help || !cmd) {
console.log(`
Usage: bun .kilo/skills/jetbrains-cli-pin/script/cli-pin.ts <command> [--no-verify]
Commands:
pin Pin the JetBrains plugin to the latest released CLI. Cleans artifacts,
sets kilo.cli.pinned=true, bumps package.json to the latest release,
then verifies with a cold gradle build (needs network + Java 21).
unpin Use the local repo CLI. Cleans artifacts, sets kilo.cli.pinned=false,
fresh-builds and stages the repo CLI, then verifies with typecheck.
regen Fast dev loop: rebuild + restage the local repo CLI (requires unpinned).
clean Remove all CLI/pin build artifacts and binaries in the current worktree.
Options:
--no-verify Skip the gradle verification build (rewrites + clean only).
Run from the repository root of the worktree you want to affect.
`)
process.exit(values.help ? 0 : 1)
}
async function pinned() {
const text = await Bun.file(props).text()
const line = text.split(/\r?\n/).find((l) => l.startsWith("kilo.cli.pinned="))
return (line?.split("=", 2)[1]?.trim().toLowerCase() ?? "true") === "true"
}
async function setPinned(value: boolean) {
const text = await Bun.file(props).text()
if (!/^kilo\.cli\.pinned=.*$/m.test(text)) throw new Error(`kilo.cli.pinned not found in ${props}`)
await Bun.write(props, text.replace(/^kilo\.cli\.pinned=.*$/m, `kilo.cli.pinned=${value}`))
}
async function report() {
const version = (await Bun.file(pkg).json()).version
console.log(`\nState: kilo.cli.pinned=${await pinned()}, package.json version=${version}`)
}
if (cmd === "pin") {
await clean()
await setPinned(true)
// set-pin.ts bumps package.json to the latest release and refuses versions with
// missing runtime assets, so we do not reimplement release/asset validation.
await $`bun .kilo/skills/release-jetbrains/script/set-pin.ts --latest`
if (!values["no-verify"]) {
// Cold pinned build downloads the pinned CLI release via generateOpenApiSpec.
await $`./gradlew clean typecheck --no-configuration-cache`.cwd(jb)
}
await report()
} else if (cmd === "unpin") {
await clean()
await setPinned(false)
// build.ts does rm -rf dist internally, producing a fresh single-platform binary.
await $`./gradlew :backend:buildRepoCli --no-configuration-cache`.cwd(jb)
// stageRepoCli has upToDateWhen{false}; force it so the staged zip matches this build.
await $`./gradlew :backend:stageRepoCli --no-configuration-cache`.cwd(jb)
if (!(await Bun.file(zip).exists())) throw new Error(`Expected staged CLI at ${zip} after unpin`)
if (!values["no-verify"]) {
await $`./gradlew typecheck --no-configuration-cache`.cwd(jb)
}
await report()
} else if (cmd === "regen") {
if (await pinned()) throw new Error("regen requires the unpinned state; run 'unpin' first")
await $`rm -rf packages/opencode/dist`
await $`./gradlew :backend:buildRepoCli --no-configuration-cache`.cwd(jb)
await $`./gradlew :backend:stageRepoCli --no-configuration-cache`.cwd(jb)
if (!(await Bun.file(zip).exists())) throw new Error(`Expected staged CLI at ${zip} after regen`)
await report()
} else if (cmd === "clean") {
await clean()
await report()
} else {
throw new Error(`Unknown command '${cmd}'. Run with --help for usage.`)
}
+2
View File
@@ -170,6 +170,8 @@ For blocking I/O in coroutines, move the dispatcher switch inside the callee usi
The JetBrains plugin has two independent CLI controls. Use the commands below directly when asked to change either one; do not hand-edit versions by guesswork.
For a one-shot pin/unpin/regen that also cleans every leftover CLI binary and build artifact in the current worktree, use the `jetbrains-cli-pin` skill (`.kilo/skills/jetbrains-cli-pin/SKILL.md`): `bun .kilo/skills/jetbrains-cli-pin/script/cli-pin.ts <pin|unpin|regen|clean>`.
**Pin mode** (`kilo.cli.pinned` in `packages/kilo-jetbrains/gradle.properties`) controls release CLI vs local repo CLI.
| Ask | Do |