Moonshot AI has released a method for running Kimi CLI as a non-interactive coding agent. The process involves installing the tool via uv in a Python 3.13 environment, configuring API keys through a TOML file, and using a Python wrapper to execute commands without human input. The workflow then inspects a codebase, spots risks, edits source files, writes tests, and loops until the project passes validation.
In this article
Environment setup and installation
The script begins by importing standard Python modules and defining a helper function to run shell commands safely. It installs uv, adds the binary directory to the system path, and provisions the Kimi CLI tool.
import os, subprocess, textwrap, json, getpass, pathlib, shutil
HOME = pathlib.Path.home()
def sh(cmd, check=True, env=None, cwd=None):
"""Run a shell command, stream its output, return CompletedProcess."""
print(f"\n$ {cmd}")
e = {**os.environ, **(env or {})}
r = subprocess.run(cmd, shell=True, env=e, cwd=cwd,
capture_output=True, text=True)
if r.stdout: print(r.stdout)
if r.stderr: print(r.stderr[-2000:])
if check and r.returncode != 0:
raise RuntimeError(f"Command failed ({r.returncode}): {cmd}")
return r
print("=" * 70, "\nPART 1: Installing uv + Kimi CLI\n", "=" * 70)
sh("curl -LsSf https://astral.sh/uv/install.sh | sh")
UV_BIN = str(HOME / ".local" / "bin")
os.environ["PATH"] = f"{UV_BIN}:{os.environ['PATH']}"
sh("uv tool install --python 3.13 kimi-cli")
sh("kimi --version")
This sequence verifies the installation by querying the version number.
Configuring API authentication
The code retrieves the Moonshot API key from Google Colab Secrets or asks for it via a hidden prompt. It defines the base URL and the specific model name, then creates a configuration directory at `~/.kimi`.
print("=" * 70, "\nPART 2: Configuring API access\n", "=" * 70)
try:
from google.colab import userdata
API_KEY = userdata.get("MOONSHOT_API_KEY")
print("Loaded key from Colab Secrets.")
except Exception:
API_KEY = getpass.getpass("Paste your Moonshot API key (hidden): ")
BASE_URL = "https://api.moonshot.ai/v1"
MODEL_NAME = "kimi-k2-0711-preview"
kimi_dir = HOME / ".kimi"
kimi_dir.mkdir(exist_ok=True)
(kimi_dir / "config.toml").write_text(textwrap.dedent(f"""\
default_model = "kimi-k2"
[providers.moonshot]
type = "kimi"
base_url = "{BASE_URL}"
api_key = "{API_KEY}"
[models.kimi-k2]
provider = "moonshot"
model = "{MODEL_NAME}"
max_context_size = 131072
"""))
print("Wrote ~/.kimi/config.toml")
The resulting `config.toml` file stores provider details, the context window size, and the default model for non-interactive sessions.
Building the execution wrapper
A Python function named `kimi` is defined to run CLI prompts programmatically. It assembles command-line flags for quiet output, JSON streaming, autonomous tool approval, and working directory isolation. The function captures the output, prints the response or error details, and returns the result string.
def kimi(prompt, work_dir=".", yolo=False, cont=False, quiet=True,
stream_json=False, extra="", timeout=600):
"""Run one headless Kimi CLI turn and return its stdout."""
flags = []
if stream_json:
flags.append("--print --output-format stream-json")
elif quiet:
flags.append("--quiet")
else:
flags.append("--print")
if yolo: flags.append("--yolo")
if cont: flags.append("--continue")
flags.append(f'-w "{work_dir}"')
if extra: flags.append(extra)
cmd = f'kimi {" ".join(flags)} -p "{prompt}"'
print(f"\n$ {cmd}\n" + "-" * 60)
r = subprocess.run(cmd, shell=True, capture_output=True,
text=True, timeout=timeout)
out = r.stdout.strip()
print(out if out else r.stderr[-1500:])
return out
Creating and analysing a sample project
The tutorial creates a small inventory management project containing a Python module, a script, and a README file. The `inventory.py` file includes intentional defects, such as allowing negative stock and raising incorrect errors for missing items. A prompt is then sent to the agent to summarise the project structure and list identified bugs.
print("=" * 70, "\nPART 4: Demo A — codebase Q&A\n", "=" * 70)
proj = pathlib.Path("/content/demo_project")
if proj.exists(): shutil.rmtree(proj)
(proj / "app").mkdir(parents=True)
(proj / "app" / "inventory.py").write_text(textwrap.dedent("""\
class Inventory:
def __init__(self):
self.items = {}
def add(self, name, qty):
self.items[name] = self.items.get(name, 0) + qty
def remove(self, name, qty):
# BUG: allows negative stock and KeyError on missing items
self.items[name] = self.items[name] - qty
def total(self):
return sum(self.items.values())
"""))
(proj / "app" / "main.py").write_text(textwrap.dedent("""\
from inventory import Inventory
inv = Inventory()
inv.add("widget", 10)
inv.remove("widget", 3)
print("Total stock:", inv.total())
"""))
(proj / "README.md").write_text("# Demo: a tiny inventory service\n")
kimi("Summarize this project's structure and purpose in under 120 words, "
"then list any bugs or design risks you can spot in inventory.py.",
work_dir=str(proj))
Automating code repair and validation
The agent is instructed to fix the logic in `inventory.py` so that `remove()` raises a `ValueError` for unknown items and prevents negative stock. It must then generate unit tests at the project root, run them with `unittest`, and repeat the process until all tests pass. The code displays the modified files and executes the test suite to verify the changes.
print("=" * 70, "\nPART 5: Demo B — Kimi fixes the bug & adds tests\n", "=" * 70)
kimi("Fix the bugs in app/inventory.py: remove() must raise KeyError->ValueError "
"for unknown items and never allow negative stock. Then create tests.py at "
"the project root using unittest covering add/remove/total and edge cases, "
"run it with 'python -m unittest tests -v', and iterate until all tests pass. "
"Finally print the test results.",
work_dir=str(proj), yolo=True, extra="--max-steps-per-turn 30")
print("\n--- Files after Kimi's edits ---")
for f in sorted(proj.rglob("*.py")):
print(f"\n### {f.relative_to(proj)} ###\n{f.read_text()}")
sh("python -m unittest tests -v", cwd=str(proj), check=False)
Structured JSONL and advanced features
The final section covers structured JSONL event streams, persistent multi-turn sessions, plan mode, model selection, Ralph loops, MCP integrations, session export, and web-based access. These tools provide a foundation for embedding the CLI into automated development pipelines.
What it means
Developers can now run Kimi as a background agent rather than a chat interface. The tool executes commands, edits files, and runs tests without waiting for a user to press enter. This allows scripts to handle complex tasks like fixing bugs and writing tests in a single automated run.




