From 307043c303dce6e041a0230e2a026398d62e064f Mon Sep 17 00:00:00 2001 From: sonwr Date: Tue, 3 Mar 2026 11:56:32 +0900 Subject: [PATCH] docs(cli): clarify hidden .kilo install artifact behavior --- README.md | 10 +++ .../test/cli/install-artifact.test.ts | 64 +++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 packages/opencode/test/cli/install-artifact.test.ts diff --git a/README.md b/README.md index d393f1ff3c0..97a3a4c2832 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,16 @@ npx @kilocode/cli Then run `kilo` in any project directory to start. + +### npm Install Note: Hidden `.kilo` File + +On some systems and npm versions, installing `@kilocode/cli` can create a hidden `.kilo` file near the installed `kilo` command (for example in a global npm bin directory). This file is an npm-generated launcher helper, not project data. + +- Why it exists: npm may create helper artifacts while wiring CLI executables. +- Size caveat: size can vary by platform, npm version, and install mode (symlink vs copied launcher), so a strict fixed size is not guaranteed. +- Safety: it is safe to leave in place. Do not edit it manually. Use your package manager's uninstall (`npm uninstall -g @kilocode/cli`) to remove install artifacts cleanly. + + ### Autonomous Mode (CI/CD) Use the `--auto` flag with `kilo run` to enable fully autonomous operation without user interaction. This is ideal for CI/CD pipelines and automated workflows: diff --git a/packages/opencode/test/cli/install-artifact.test.ts b/packages/opencode/test/cli/install-artifact.test.ts new file mode 100644 index 00000000000..11aeedfc1f9 --- /dev/null +++ b/packages/opencode/test/cli/install-artifact.test.ts @@ -0,0 +1,64 @@ +// kilocode_change - new file +import { describe, expect, test } from "bun:test" +import { $ } from "bun" +import fs from "fs/promises" +import os from "os" +import path from "path" + +const root = path.join(import.meta.dir, "..", "..") +const wrapper = path.join(root, "bin", "kilo") + +describe("npm install artifact behavior", () => { + test("keeps the CLI wrapper contract", async () => { + const text = await fs.readFile(wrapper, "utf8") + expect(text.startsWith("#!/usr/bin/env node")).toBe(true) + expect(text).toContain("const envPath = process.env.KILO_BIN_PATH") + expect(text).toContain("const base = \"@kilocode/cli-\" + platform + \"-\" + arch") + expect(text).toContain("function findBinary(startDir)") + }) + + test("links npm bin commands to the wrapper during local install", async () => { + const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "kilo-install-artifact-")) + const pkg = path.join(tmp, "pkg") + const bin = path.join(pkg, "bin") + const prefix = path.join(tmp, "prefix") + await fs.mkdir(bin, { recursive: true }) + await fs.mkdir(prefix, { recursive: true }) + await fs.copyFile(wrapper, path.join(bin, "kilo")) + await Bun.write( + path.join(pkg, "package.json"), + JSON.stringify( + { + name: "kilo-install-artifact-repro", + version: "1.0.0", + bin: { + kilo: "./bin/kilo", + kilocode: "./bin/kilo", + }, + }, + null, + 2, + ), + ) + + await $`npm install --prefix ${prefix} ${pkg} --no-package-lock --ignore-scripts --no-audit --no-fund`.quiet() + + const commands = ["kilo", "kilocode"] + for (const name of commands) { + const link = path.join(prefix, "node_modules", ".bin", name) + const stat = await fs.lstat(link) + expect(stat.isSymbolicLink() || stat.isFile()).toBe(true) + } + + const hidden = path.join(prefix, "node_modules", ".bin", ".kilo") + const exists = await fs + .access(hidden) + .then(() => true) + .catch(() => false) + if (!exists) return + + const stat = await fs.lstat(hidden) + expect(stat.isFile() || stat.isSymbolicLink()).toBe(true) + if (!stat.isSymbolicLink()) expect(stat.size).toBeGreaterThan(0) + }) +})