Merge pull request #12895 from Kilo-Org/rapid-empress

feat(jetbrains): visible CLI download/bundled mode + skill to pin/unpin/update
This commit is contained in:
Kirill Kalishev
2026-08-05 12:20:46 -04:00
committed by GitHub
36 changed files with 343 additions and 9 deletions
@@ -0,0 +1,5 @@
---
"@kilocode/kilo-jetbrains": minor
---
Log whether the JetBrains plugin downloads Core or uses the bundled/cached version, and mark the Core version shown in the popup as "Bundled" when it wasn't downloaded.
+92
View File
@@ -0,0 +1,92 @@
---
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` -> remove the repo-CLI Bun path hint -> 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` -> write the repo-CLI Bun path hint -> `:backend:buildRepoCli` (fresh CLI) -> `:backend:stageRepoCli` -> assert staged `kilo-cli.zip` -> verify with `gradlew typecheck`. |
| `regen` | Fast dev loop while unpinned: refresh the repo-CLI Bun path hint -> `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.
## Bun Path Hint
In repo CLI mode, Gradle's `generateOpenApiSpec` task runs the local CLI source through
`bun run --conditions=browser ./src/index.ts generate`. IDE-launched Gradle runs can have
a stripped `PATH` where `bun` isn't resolvable. The `unpin`/`regen` commands write an ignored, worktree-local hint:
```text
packages/kilo-jetbrains/.gradle/kilo-cli-pin.properties
```
The file contains `bun.path=<absolute path>` and is consumed by `backend/build.gradle.kts`
for repo CLI tasks. `pin` removes it because pinned mode should not depend on local Bun.
## 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,114 @@
#!/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 hint = `${jb}/.gradle/kilo-cli-pin.properties`
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}`))
}
function bunPath() {
return Bun.which("bun") ?? process.execPath
}
async function writeBunHint() {
const path = bunPath()
await $`mkdir -p ${jb}/.gradle`
await Bun.write(hint, `# Generated by jetbrains-cli-pin so IDE-launched Gradle can find Bun in repo CLI mode.\nbun.path=${path}\n`)
console.log(`Wrote Bun path hint for repo CLI mode: ${path}`)
}
async function removeBunHint() {
await $`rm -f ${hint}`.nothrow()
}
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)
await removeBunHint()
// 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)
await writeBunHint()
// 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 writeBunHint()
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 |
@@ -26,6 +26,13 @@ val repoCli = pinned.map { !it }
val bundled = providers.gradleProperty("kilo.cli.bundled").map { it.trim().toBoolean() }.orElse(false)
val downloadsCli = repoCli.zip(bundled) { repo, bundle -> !repo && !bundle }
val repoRootDir = rootProject.layout.projectDirectory.dir("../opencode")
val local = rootProject.layout.projectDirectory.file(".gradle/kilo-cli-pin.properties")
val bunPathProvider = providers.fileContents(local).asText.map { text ->
text.lineSequence().firstNotNullOfOrNull { line ->
val pair = line.split("=", limit = 2)
if (pair.getOrNull(0)?.trim() == "bun.path") pair.getOrNull(1)?.trim()?.takeIf { it.isNotEmpty() } else null
} ?: "bun"
}.orElse("bun")
val pinnedCliVersion = providers.fileContents(rootProject.layout.projectDirectory.file("package.json")).asText.map { text ->
Regex("\"version\"\\s*:\\s*\"([^\"]+)\"").find(text)?.groupValues?.get(1)
@@ -64,12 +71,13 @@ val generateOpenApiSpec by tasks.registering(GenerateOpenApiSpecTask::class) {
)
cacheDir.set(layout.buildDirectory.dir("cli-cache"))
spec.set(rawSpec)
bunPath.set(bunPathProvider)
}
val buildRepoCli by tasks.registering(Exec::class) {
description = "Build the local repo CLI for the current platform"
workingDir = repoRootDir.asFile
commandLine("bun", "run", "script/build.ts", "--single", "--skip-install")
commandLine(bunPathProvider.get(), "run", "script/build.ts", "--single", "--skip-install")
}
fun platform(): String {
@@ -126,14 +126,18 @@ class KiloBackendCliManager(
private suspend fun resolveCli(onProgress: (CliDownload) -> Unit): File {
val force = forceExtract
forceExtract = false
val version = KiloProps.cliVersion()
val platform = KiloCliPlatform.current()
if (KiloRepoCli.available()) {
if (force) log.info("Force re-extracting bundled CLI ${KiloProps.cliVersion()}")
if (force) log.info("Force re-extracting bundled CLI $version")
log.info("Kilo CLI mode: BUNDLED — using CLI $version ($platform) shipped in the plugin; no download needed")
val cli = KiloRepoCli.extract(force)
onProgress(CliDownload(100, KiloProps.cliVersion(), KiloCliPlatform.current()))
onProgress(CliDownload(100, version, platform))
return cli
}
if (force) log.info("Force re-downloading CLI ${KiloProps.cliVersion()}")
return KiloCliDownloader(log = log).resolve(KiloProps.cliVersion(), force, onProgress)
if (force) log.info("Force re-downloading CLI $version")
log.info("Kilo CLI mode: DOWNLOAD — resolving CLI $version ($platform) from the GitHub release")
return KiloCliDownloader(log = log).resolve(version, force, onProgress)
}
// Must be called from a background thread — devStorageEnv() performs blocking I/O (mkdirs).
@@ -112,7 +112,7 @@ class KiloCliDownloader(
"completeExists=${done.isFile} digestValid=$valid exe=${exe.absolutePath} complete=${done.absolutePath}"
)
if (!exe.isFile || !valid) return null
log.info("Using cached Kilo CLI $version for $platform at ${exe.absolutePath}")
log.info("Kilo CLI $version ($platform) already cached at ${exe.absolutePath}; skipping download and extraction")
if (!SystemInfo.isWindows) exe.setExecutable(true)
prune(version)
return exe
@@ -36,10 +36,12 @@ object KiloRepoCli {
val exe = File(root, "$platform/bin/${KiloCliPlatform.exe()}")
val done = File(root, ".complete")
if (!force && done.isFile && exe.isFile) {
log.info("Bundled Kilo CLI ${KiloProps.cliVersion()} ($platform) already extracted at ${exe.absolutePath}; skipping extraction")
if (!SystemInfo.isWindows) exe.setExecutable(true)
if (cleanup) prune(root)
return@withContext exe
}
log.info("Extracting bundled Kilo CLI ${KiloProps.cliVersion()} ($platform) into ${root.absolutePath}")
if (root.exists() && !root.deleteRecursively()) {
throw IllegalStateException("Failed to delete local repo CLI under ${root.absolutePath}")
@@ -11,6 +11,7 @@ import ai.kilocode.backend.app.LoadProgress
import ai.kilocode.backend.app.ProfileResult
import ai.kilocode.backend.cli.KiloCliPlatform
import ai.kilocode.backend.cli.KiloProps
import ai.kilocode.backend.cli.KiloRepoCli
import ai.kilocode.jetbrains.api.model.KiloProfile200Response
import ai.kilocode.rpc.dto.ConfigPatchDto
import ai.kilocode.rpc.KiloAppRpcApi
@@ -57,6 +58,8 @@ class KiloAppRpcApiImpl : KiloAppRpcApi {
override suspend fun cliPlatform(): String = KiloCliPlatform.current()
override suspend fun cliBundled(): Boolean = KiloRepoCli.available()
override suspend fun retry() = app.retry()
override suspend fun restart() = app.restart()
@@ -70,7 +70,7 @@ class KiloCliDownloaderTest {
assertEquals(cli.absolutePath, cached.absolutePath)
assertEquals(1, server.requestCount)
assertTrue(cachedProgress.isEmpty())
assertContains(log.messages, "INFO: Using cached Kilo CLI 1.2.3 for ${KiloCliPlatform.current()} at ${cli.absolutePath}")
assertContains(log.messages, "INFO: Kilo CLI 1.2.3 (${KiloCliPlatform.current()}) already cached at ${cli.absolutePath}; skipping download and extraction")
File(cli.parentFile.parentFile, ".complete").writeText("ok\n")
server.enqueue(MockResponse().setResponseCode(200).setBody(Buffer().write(bytes)))
@@ -49,6 +49,9 @@ abstract class GenerateOpenApiSpecTask : DefaultTask() {
@get:Internal
abstract val cacheDir: DirectoryProperty
@get:Internal
abstract val bunPath: Property<String>
@get:OutputFile
abstract val spec: RegularFileProperty
@@ -76,7 +79,7 @@ abstract class GenerateOpenApiSpecTask : DefaultTask() {
val err = ByteArrayOutputStream()
val result = exec.exec {
workingDir = root
commandLine("bun", "run", "--conditions=browser", "./src/index.ts", "generate")
commandLine(bunPath.get(), "run", "--conditions=browser", "./src/index.ts", "generate")
standardOutput = out
errorOutput = err
isIgnoreExitValue = true
@@ -15,8 +15,10 @@ class CoreInfoAction : AnAction(), DumbAware {
val app = service<KiloAppService>()
val info = app.core
if (info == null) app.fetchCoreInfoAsync()
app.fetchBundledAsync()
val key = if (app.bundled == true) "action.Kilo.CoreInfo.bundled" else "action.Kilo.CoreInfo.text"
e.presentation.text = info?.let {
KiloBundle.message("action.Kilo.CoreInfo.text", it.version, it.platform)
KiloBundle.message(key, it.version, it.platform)
} ?: KiloBundle.message("action.Kilo.CoreInfo.loading")
e.presentation.description = KiloBundle.message("action.Kilo.CoreInfo.description")
e.presentation.isEnabled = false
@@ -58,6 +58,18 @@ class KiloAppService internal constructor(
val version: String? get() = info?.version
/**
* Whether the running Core is bundled in the plugin (true) or downloaded (false).
* Null until fetched. This is a static property of the plugin build, so it is
* fetched once via RPC independently of the download-progress state.
*/
@Volatile
private var bundledFlag: Boolean? = null
private val bundledLock = Any()
private var bundledJob: Job? = null
val bundled: Boolean? get() = bundledFlag
/**
* App-lifetime scope for fire-and-forget work that must outlive transient UIs such as the
* settings dialog (whose own scope is cancelled the moment it closes on OK).
@@ -152,6 +164,7 @@ class KiloAppService internal constructor(
platform = call { cliPlatform() },
)
info = next
bundledFlag = call { cliBundled() }
next
} catch (e: Exception) {
LOG.warn("core info failed", e)
@@ -200,6 +213,26 @@ class KiloAppService internal constructor(
fetchCoreInfoAsync { done(it?.version) }
}
/** Fetch whether the running Core is bundled and cache it. Deduped and fetched once. */
fun fetchBundledAsync() {
if (bundledFlag != null) return
synchronized(bundledLock) {
if (bundledFlag != null || bundledJob != null) return
bundledJob = cs.launch {
val value = try {
call { cliBundled() }
} catch (e: Exception) {
LOG.warn("core bundled check failed", e)
null
}
synchronized(bundledLock) {
if (value != null) bundledFlag = value
bundledJob = null
}
}
}
}
fun refreshModelFavoritesAsync() {
cs.launch {
try {
@@ -731,6 +731,7 @@ action.Kilo.Reinstall.text=Reinstall
action.Kilo.Reinstall.cli.text=Reinstall Core
action.Kilo.Reinstall.description=Download a fresh Core binary and restart
action.Kilo.CoreInfo.text=Core v{0} • Architecture: {1}
action.Kilo.CoreInfo.bundled=Bundled Core v{0} • Architecture: {1}
action.Kilo.CoreInfo.loading=Core details loading...
action.Kilo.CoreInfo.description=Kilo Core version and architecture
action.Kilo.Session.Open.text=Open
@@ -155,6 +155,7 @@ action.Kilo.Restart.description=إنهاء عملية Core وإعادة تشغي
action.Kilo.Reinstall.text=إعادة تثبيت Kilo
action.Kilo.Reinstall.cli.text=إعادة تثبيت Core
action.Kilo.Reinstall.description=Download a fresh Core binary and restart
action.Kilo.CoreInfo.bundled=‏Core المُضمّن v{0} • البنية: {1}
action.Kilo.Session.Open.text=فتح
action.Kilo.Session.Open.description=فتح الجلسة المحددة
action.Kilo.Session.Rename.text=إعادة تسمية
@@ -155,6 +155,7 @@ action.Kilo.Restart.description=Ugasite i ponovo pokrenite Core proces
action.Kilo.Reinstall.text=Ponovo instalirajte Kilo
action.Kilo.Reinstall.cli.text=Ponovo instalirajte Core
action.Kilo.Reinstall.description=Download a fresh Core binary and restart
action.Kilo.CoreInfo.bundled=Ugrađeni Core v{0} • Arhitektura: {1}
action.Kilo.Session.Open.text=Otvori
action.Kilo.Session.Open.description=Otvorite odabranu sesiju
action.Kilo.Session.Rename.text=Preimenuj
@@ -155,6 +155,7 @@ action.Kilo.Restart.description=Afslut og genstart Core-processen
action.Kilo.Reinstall.text=Geninstaller Kilo
action.Kilo.Reinstall.cli.text=Geninstaller Core
action.Kilo.Reinstall.description=Download a fresh Core binary and restart
action.Kilo.CoreInfo.bundled=Medfølgende Core v{0} • Arkitektur: {1}
action.Kilo.Session.Open.text=Åbn
action.Kilo.Session.Open.description=Åbn den valgte session
action.Kilo.Session.Rename.text=Omdøb
@@ -155,6 +155,7 @@ action.Kilo.Restart.description=Core-Prozess beenden und neu starten
action.Kilo.Reinstall.text=Kilo neu installieren
action.Kilo.Reinstall.cli.text=Core neu installieren
action.Kilo.Reinstall.description=Download a fresh Core binary and restart
action.Kilo.CoreInfo.bundled=Gebündelter Core v{0} • Architektur: {1}
action.Kilo.Session.Open.text=Öffnen
action.Kilo.Session.Open.description=Ausgewählte Sitzung öffnen
action.Kilo.Session.Rename.text=Umbenennen
@@ -155,6 +155,7 @@ action.Kilo.Restart.description=Terminar y reiniciar el proceso Core
action.Kilo.Reinstall.text=Reinstalar Kilo
action.Kilo.Reinstall.cli.text=Reinstalar Core
action.Kilo.Reinstall.description=Download a fresh Core binary and restart
action.Kilo.CoreInfo.bundled=Core incluido v{0} • Arquitectura: {1}
action.Kilo.Session.Open.text=Abrir
action.Kilo.Session.Open.description=Abrir la sesión seleccionada
action.Kilo.Session.Rename.text=Renombrar
@@ -155,6 +155,7 @@ action.Kilo.Restart.description=Tuer et redémarrer le processus Core
action.Kilo.Reinstall.text=Réinstaller Kilo
action.Kilo.Reinstall.cli.text=Réinstaller Core
action.Kilo.Reinstall.description=Download a fresh Core binary and restart
action.Kilo.CoreInfo.bundled=Core intégré v{0} • Architecture : {1}
action.Kilo.Session.Open.text=Ouvrir
action.Kilo.Session.Open.description=Ouvrir la session sélectionnée
action.Kilo.Session.Rename.text=Renommer
@@ -155,6 +155,7 @@ action.Kilo.Restart.description=Coreプロセスを終了して再起動
action.Kilo.Reinstall.text=Kiloを再インストール
action.Kilo.Reinstall.cli.text=Coreを再インストール
action.Kilo.Reinstall.description=Download a fresh Core binary and restart
action.Kilo.CoreInfo.bundled=バンドルされた Core v{0} • アーキテクチャ: {1}
action.Kilo.Session.Open.text=開く
action.Kilo.Session.Open.description=選択したセッションを開く
action.Kilo.Session.Rename.text=変名
@@ -155,6 +155,7 @@ action.Kilo.Restart.description=Core 프로세스 종료 후 재시작
action.Kilo.Reinstall.text=Kilo 재설치
action.Kilo.Reinstall.cli.text=Core 재설치
action.Kilo.Reinstall.description=Download a fresh Core binary and restart
action.Kilo.CoreInfo.bundled=번들 Core v{0} • 아키텍처: {1}
action.Kilo.Session.Open.text=열기
action.Kilo.Session.Open.description=선택한 세션 열기
action.Kilo.Session.Rename.text=이름 바꾸기
@@ -155,6 +155,7 @@ action.Kilo.Restart.description=Core-proces beëindigen en herstarten
action.Kilo.Reinstall.text=Kilo herinstalleren
action.Kilo.Reinstall.cli.text=Core herinstalleren
action.Kilo.Reinstall.description=Download a fresh Core binary and restart
action.Kilo.CoreInfo.bundled=Gebundelde Core v{0} • Architectuur: {1}
action.Kilo.Session.Open.text=Openen
action.Kilo.Session.Open.description=Geselecteerde sessie openen
action.Kilo.Session.Rename.text=Hernoemen
@@ -160,6 +160,7 @@ action.Kilo.Restart.description=Avslutt og start Core-prosessen på nytt
action.Kilo.Reinstall.text=Installer Kilo på nytt
action.Kilo.Reinstall.cli.text=Installer Core på nytt
action.Kilo.Reinstall.description=Download a fresh Core binary and restart
action.Kilo.CoreInfo.bundled=Innebygd Core v{0} • Arkitektur: {1}
action.Kilo.Session.Open.text=Åpne
action.Kilo.Session.Open.description=Åpne valgt økt
action.Kilo.Session.Rename.text=Gi nytt navn
@@ -160,6 +160,7 @@ action.Kilo.Restart.description=Zakończ i uruchom ponownie proces Core
action.Kilo.Reinstall.text=Zainstaluj ponownie Kilo
action.Kilo.Reinstall.cli.text=Zainstaluj ponownie Core
action.Kilo.Reinstall.description=Download a fresh Core binary and restart
action.Kilo.CoreInfo.bundled=Wbudowany Core v{0} • Architektura: {1}
action.Kilo.Session.Open.text=Otwórz
action.Kilo.Session.Open.description=Otwórz wybraną sesję
action.Kilo.Session.Rename.text=Zmień nazwę
@@ -160,6 +160,7 @@ action.Kilo.Restart.description=Encerrar e reiniciar o processo Core
action.Kilo.Reinstall.text=Reinstalar Kilo
action.Kilo.Reinstall.cli.text=Reinstalar Core
action.Kilo.Reinstall.description=Download a fresh Core binary and restart
action.Kilo.CoreInfo.bundled=Core incluído v{0} • Arquitetura: {1}
action.Kilo.Session.Open.text=Abrir
action.Kilo.Session.Open.description=Abrir a sessão selecionada
action.Kilo.Session.Rename.text=Renomear
@@ -160,6 +160,7 @@ action.Kilo.Restart.description=Завершить и перезапустить
action.Kilo.Reinstall.text=Переустановить Kilo
action.Kilo.Reinstall.cli.text=Переустановить Core
action.Kilo.Reinstall.description=Download a fresh Core binary and restart
action.Kilo.CoreInfo.bundled=Встроенный Core v{0} • Архитектура: {1}
action.Kilo.Session.Open.text=Открыть
action.Kilo.Session.Open.description=Открыть выбранную сессию
action.Kilo.Session.Rename.text=Переименовать
@@ -160,6 +160,7 @@ action.Kilo.Restart.description=สิ้นสุดและรีสตาร
action.Kilo.Reinstall.text=ติดตั้ง Kilo ใหม่
action.Kilo.Reinstall.cli.text=ติดตั้ง Core ใหม่
action.Kilo.Reinstall.description=Download a fresh Core binary and restart
action.Kilo.CoreInfo.bundled=Core แบบรวมมาในตัว v{0} • สถาปัตยกรรม: {1}
action.Kilo.Session.Open.text=เปิด
action.Kilo.Session.Open.description=เปิดเซสชันที่เลือก
action.Kilo.Session.Rename.text=เปลี่ยนชื่อ
@@ -160,6 +160,7 @@ action.Kilo.Restart.description=Core sürecini sonlandır ve yeniden başlat
action.Kilo.Reinstall.text=Kilo’yu yeniden yükle
action.Kilo.Reinstall.cli.text=Core’yi yeniden yükle
action.Kilo.Reinstall.description=Download a fresh Core binary and restart
action.Kilo.CoreInfo.bundled=Paketlenmiş Core v{0} • Mimari: {1}
action.Kilo.Session.Open.text=Aç
action.Kilo.Session.Open.description=Seçilen oturumu aç
action.Kilo.Session.Rename.text=Yeniden adlandır
@@ -155,6 +155,7 @@ action.Kilo.Restart.description=Завершити і перезапустити
action.Kilo.Reinstall.text=Перевстановити Kilo
action.Kilo.Reinstall.cli.text=Перевстановити Core
action.Kilo.Reinstall.description=Download a fresh Core binary and restart
action.Kilo.CoreInfo.bundled=Вбудований Core v{0} • Архітектура: {1}
action.Kilo.Session.Open.text=Відкрити
action.Kilo.Session.Open.description=Відкрити вибрану сесію
action.Kilo.Session.Rename.text=Перейменувати
@@ -155,6 +155,7 @@ action.Kilo.Restart.description=终止并重启 Core 进程
action.Kilo.Reinstall.text=重新安装 Kilo
action.Kilo.Reinstall.cli.text=重新安装 Core
action.Kilo.Reinstall.description=Download a fresh Core binary and restart
action.Kilo.CoreInfo.bundled=捆绑的 Core v{0} • 架构:{1}
action.Kilo.Session.Open.text=打开
action.Kilo.Session.Open.description=打开所选会话
action.Kilo.Session.Rename.text=重命名
@@ -155,6 +155,7 @@ action.Kilo.Restart.description=終止並重啟 Core 程序
action.Kilo.Reinstall.text=重新安裝 Kilo
action.Kilo.Reinstall.cli.text=重新安裝 Core
action.Kilo.Reinstall.description=Download a fresh Core binary and restart
action.Kilo.CoreInfo.bundled=捆綁的 Core v{0} • 架構:{1}
action.Kilo.Session.Open.text=開啟
action.Kilo.Session.Open.description=開啟所選工作階段
action.Kilo.Session.Rename.text=重命名
@@ -126,6 +126,21 @@ class KiloRecoveryActionsTest : BasePlatformTestCase() {
assertEquals("Core v1.2.3 • Architecture: darwin-arm64", event.presentation.text)
}
fun `test core info action marks bundled core`() {
appRpc.cliVersion = "1.2.3"
appRpc.cliPlatform = "darwin-arm64"
appRpc.cliBundled = true
ApplicationManager.getApplication().executeOnPooledThread {
runBlocking { app().coreInfo() }
}.get()
val action = CoreInfoAction()
val event = event(action)
update(action, event)
assertEquals("Bundled Core v1.2.3 • Architecture: darwin-arm64", event.presentation.text)
}
fun `test local config action says open when target exists`() {
rpc.localConfigPath = "/test/.kilo/kilo.jsonc"
rpc.localConfigDisplayPath = "~/.kilo/kilo.jsonc"
@@ -37,6 +37,7 @@ class FakeAppRpcApi : KiloAppRpcApi {
var health = HealthDto(healthy = true, version = "1.0.0")
var cliVersion = "1.0.0"
var cliPlatform = "darwin-arm64"
var cliBundled = false
var cliInfoGate: CompletableDeferred<Unit>? = null
var cliInfoError: Exception? = null
var cliVersionCalls = 0
@@ -94,6 +95,11 @@ class FakeAppRpcApi : KiloAppRpcApi {
return cliPlatform
}
override suspend fun cliBundled(): Boolean {
assertNotEdt("cliBundled")
return cliBundled
}
override suspend fun retry() {
assertNotEdt("retry")
retries += 1
@@ -45,6 +45,9 @@ interface KiloAppRpcApi : RemoteApi<Unit> {
/** Core platform downloaded by the backend process. */
suspend fun cliPlatform(): String
/** Whether the running Core is bundled in the plugin (true) or downloaded (false). */
suspend fun cliBundled(): Boolean
/** Retry app connection or loading after a failure. */
suspend fun retry()