Skill: inertia186/junie (version 1.0.0)

--- name: junie description: Install, update, authenticate, configure, and direct JetBrains Junie CLI in Junie-native ways on macOS or Linux shells. Use when a host agent should steer Junie iteratively toward an overall goal, especially when the host agent has broader context than Junie, Junie should carry out focused implementation/review work, or the task may benefit from Junie-accessible models not available to the host agent. Also use when asked to set up Junie, verify an existing install, create or adjust ~/.junie/config.json or project .junie/config.json, bootstrap a repo’s .junie layout, wire in skills/guidelines/MCP/model locations, prepare Junie for CI headless usage, or decide whether interactive Junie flows truly require headless-terminal-style PTY control. Trigger on limited host-agent command phrases such as /junie help, /junie status, /junie model, /junie usage, /junie bootstrap, and /junie dry-run. --- # Junie Use this skill to set up Junie CLI without guessing and without fighting Junie’s own conventions. Also use it when the host agent should act as the planner/reviewer while Junie acts as a focused implementation or review agent. Prefer Junie’s documented non-interactive surfaces first: installer script, CLI flags, environment variables, and `config.json`. Preserve Junie’s standard `.junie/` layout unless the user explicitly wants something custom. Reach for interactive TUI driving only when the task genuinely requires the welcome screen or `/account` flow. ## Junie ecosystem guardrails - Prefer project-shared configuration in `<project>/.junie/`. - Prefer personal defaults in `~/.junie/`. - Prefer `.junie/AGENTS.md` for reusable project guidance instead of inventing a custom location. - Prefer `.junie/skills/`, `.junie/commands/`, `.junie/agents/`, `.junie/models/`, `.junie/mcp/`, and `.junie/rules/` when bootstrapping a repo. - Treat `~/.junie/settings.json` as Junie-owned runtime state; do not edit it unless the user explicitly asks. - Ask before persisting secrets to durable config. Environment variables are usually better for CI and temporary setup. ## Primary use cases Use Junie as an iterative execution partner when the host agent should direct a body of work but Junie should carry out focused repo-local steps. In this mode: - The host agent owns the overall goal, context synthesis, task shaping, risk management, and acceptance checks. - Junie owns the focused implementation, review, or repo operation it has been briefed to perform. - Expect some back and forth: the host agent briefs Junie, inspects the result, then either accepts it, asks a targeted follow-up, or corrects small issues directly. - Prefer this mode when the host agent has more relevant context than Junie, especially prior discussion, cross-system evidence, user preferences, project doctrine, or sensitive constraints that Junie would not infer from the repo alone. - Consider this mode when Junie can use models or model/tool combinations that the host agent cannot access directly. When model choice matters, recommend the best fit for the task, but treat the user as the final authority. Stronger Junie models may imply heavy quota or billing utilization, so do not silently choose an expensive model for a substantial run unless the user has already approved that posture. ## Workflow 1. Inspect the current state. 2. Install or update Junie if needed. 3. Choose an authentication path. 4. Decide whether this is setup/config work or iterative Junie-directed execution. 5. Write or merge configuration conservatively. 6. Brief Junie sharply when using it as an execution partner. 7. Verify with the smallest meaningful check. 8. Use `headless-terminal` only if the interactive UI becomes the blocker. ## Limited host-agent slash commands Treat these as host-agent-facing trigger phrases for this skill, not as Junie’s own interactive slash commands. Keep them conservative and predictable: - `/junie help` — list these commands and ask for the missing target/path only if needed. - `/junie status` — inspect install/config/runtime posture without launching a new Junie task; report install state, relevant config locations, current/default model if known, and latest usage availability. - `/junie model` — report the observed current/default Junie model, provider, and effort sources; do not change config. - `/junie usage` — summarize latest observable Junie session usage with `python3 scripts/summarize_junie_usage.py --limit 1`; do not launch Junie. - `/junie bootstrap` — bootstrap or refresh the repo’s Junie-native `.junie/` layout using `scripts/bootstrap_junie_project.py`; preserve existing `.junie/AGENTS.md` unless the user explicitly asks for `--force-agents`. - `/junie dry-run` — prepare or run a read-only Junie reconnaissance task. Default to `Do not modify files`; use `Open files only; do not run shell commands.` when command execution itself is unwanted. If a user gives extra words after the command, treat them as the target/scope. If the command is ambiguous or would mutate files/config externally, ask one concise clarifying question before acting. ## 1) Inspect the current state Check these first: ```bash command -v junie || true junie --version || true printf '%s\n' "$SHELL" ls -la ~/.junie 2>/dev/null || true ls -la ./.junie 2>/dev/null || true ls -la ./.junie/skills 2>/dev/null || true ``` Look for: - whether Junie is already installed - whether `~/.local/bin` is on PATH - whether user-scoped or project-scoped `.junie` config already exists - whether the repo already has Junie-native directories such as `.junie/skills` or `.junie/AGENTS.md` - whether the request is local interactive setup or CI/headless setup Also identify Junie’s model and usage posture and call it out to the user: - explicit CLI choice you plan to pass (`--model`, `--provider`, `--effort`) - project/user config model or provider, if present in `.junie/config.json` or `~/.junie/config.json` - Junie’s current launch model from read-only runtime state, usually `~/.junie/settings.json` keys such as `modelForLaunch` and `effortPerModel` - after a run, confirm from Junie logs when available; log lines containing `AgentParameters(modelParameters=ModelParameters(model=..., effort=...))` are a useful observed source - after a run, summarize observable usage when available: model(s), estimated cost, input/output tokens, cache read/write tokens, and any notable helper-model usage Use the bundled helper from the skill directory for a quick best-effort usage summary: ```bash python3 scripts/summarize_junie_usage.py --limit 1 ``` Do not edit `~/.junie/settings.json` unless explicitly requested. Redact credentials if inspecting config. If the effective model or usage cannot be determined confidently, say `Junie model: unknown` or `Junie usage: unavailable` rather than guessing. ## 2) Install or update Junie ### Preferred install path Use the bundled helper that matches the shell you are in. Security/trust posture for publishing: - Prefer package-manager installs (`brew`, `winget`, `npm`) when they satisfy the request. - Treat remote installer scripts as higher-trust operations because they download code and execute it locally. - If you use the official installer path, say so plainly and give the user a chance to review the source URL first when that trust decision matters. macOS / Linux: ```bash bash scripts/install_junie.sh ``` Useful variants: ```bash bash scripts/install_junie.sh --reinstall bash scripts/install_junie.sh --version 888.169 bash scripts/install_junie.sh --method brew bash scripts/install_junie.sh --method npm ``` Windows PowerShell: ```powershell powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\install_junie.ps1 ``` Useful variants: ```powershell powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\install_junie.ps1 -Reinstall powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\install_junie.ps1 -Version 888.169 powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\install_junie.ps1 -Method winget powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\install_junie.ps1 -Method npm ``` ### Verification After install, verify with the platform-appropriate shell. POSIX shells: ```bash export PATH="$HOME/.local/bin:$PATH" junie --version junie --help ``` PowerShell: ```powershell junie --version junie --help ``` Do not claim success until `junie --version` returns cleanly. ## 3) Choose an authentication path Pick the least interactive path that fits the request. ### Account readiness and first-run failures When a Junie command fails with account/auth readiness instead of a repo or config result, do not summarize it as a generic blocker. Tell the user what to do next. Common cases: - `Authenticated successfully` followed by `Insufficient Account Balance`: Junie is installed and signed in, but the JetBrains Junie account has no usable token balance/credits for CLI runs. Tell the user to open `https://junie.jetbrains.com/cli`, sign in with the intended JetBrains account, check/add CLI credits or generate a fresh CLI token, then rerun with `--auth=<token>` or set `JUNIE_API_KEY`. - Login/token required, expired, or missing: for normal local desktop usage, first suggest the simplest path: `cd <project-root>` and run `junie` so the interactive first-run/sign-in flow can complete in the project context. For headless/automation, tell the user to visit `https://junie.jetbrains.com/cli`, sign in, generate a CLI token, then either export it temporarily (`export JUNIE_API_KEY='...'`) or pass it once with `junie --auth="$JUNIE_API_KEY" ...`. If the user is still confused or the interactive first-run screen is itself the blocker, offer to drive the `junie` TUI from the project root using `headless-terminal`/PTY control when available; read `references/headless-terminal-fit.md` before doing that. - BYOK preferred: tell the user to provide/export the relevant provider key (`JUNIE_OPENAI_API_KEY`, `JUNIE_ANTHROPIC_API_KEY`, etc.) and choose the matching `--model`. If this happens while verifying project guidance, report the partial result clearly: installation/config inspection may be complete, but Junie itself did not execute the assertion because account readiness needs user action. ### A. Headless / CI / automation Prefer token or provider-key auth. Junie token: ```bash export JUNIE_API_KEY='...' junie --auth="$JUNIE_API_KEY" "summarize this repository" ``` BYOK examples: ```bash export JUNIE_OPENAI_API_KEY='...' junie --model gpt-5 "review the latest changes" export JUNIE_ANTHROPIC_API_KEY='...' junie --model sonnet "review the latest changes" ``` Use this path when the user asks for CI, scripting, or reliable non-interactive execution. ### B. Interactive local usage If the user wants normal desktop Junie with browser sign-in or welcome-screen account selection, install Junie first and then let the human complete OAuth unless they explicitly ask you to drive the UI. Useful command: ```bash junie ``` ### C. Config-file auth defaults If the user wants persistent defaults, write them into `~/.junie/config.json` or project `.junie/config.json` instead of repeating flags. Put team-shared, non-secret defaults in project config; keep secrets in user config or environment variables unless the user explicitly wants otherwise. Use the merge helper: ```bash python3 scripts/merge_junie_config.py ~/.junie/config.json '{"model":"sonnet","auto-update":true}' ``` For BYOK defaults: ```bash python3 scripts/merge_junie_config.py ~/.junie/config.json '{"provider":"anthropic","byok":{"anthropic":"sk-ant-..."}}' ``` Be careful with secrets: - prefer environment variables for ephemeral automation - ask before writing API keys into durable config files - never overwrite an existing config blindly ## 4) Configure Junie Junie loads configuration from: - `~/.junie/config.json` - `<project>/.junie/config.json` When bootstrapping a project, prefer the bundled helper so the same conservative layout is created every time: ```bash python3 scripts/bootstrap_junie_project.py . python3 scripts/bootstrap_junie_project.py . --model sonnet python3 scripts/bootstrap_junie_project.py . --config '{"skill-locations":["./skills"]}' ``` The helper creates missing directories, preserves an existing `.junie/AGENTS.md` unless `--force-agents` is passed, and merges `.junie/config.json` instead of replacing it. Baseline layout: ```text .junie/ ├── AGENTS.md ├── config.json ├── skills/ ├── commands/ ├── agents/ ├── models/ ├── mcp/ └── rules/ ``` Higher-priority sources override lower-priority ones: 1. CLI flags 2. `~/.junie/settings.json` 3. project `.junie/config.json` 4. user `~/.junie/config.json` ### Common configuration patterns #### Shared project defaults Create `<project>/.junie/config.json` when the whole repo should share behavior. Keep paths relative to `.junie/config.json` when possible so the repo stays portable: ```json { "model": "sonnet", "skill-locations": ["./skills"], "skill-default-locations": true, "guidelines-location": "./AGENTS.md", "auto-update": true } ``` #### Personal machine defaults Create `~/.junie/config.json` for user-specific defaults: ```json { "model": "sonnet", "brave": false, "auto-update": true, "time-limit": 3600 } ``` #### Add external skills or shared folders Junie already auto-discovers `.junie/skills/`. Add extra paths only when you truly have shared skills outside the standard layout. ```json { "skill-locations": [ "./skills", "/opt/shared/junie-skills" ], "skill-default-locations": true } ``` #### Point at a custom guidelines file ```json { "guidelines-location": "./AGENTS.md" } ``` Only set `guidelines-location` when you need to override discovery. Otherwise let Junie find guidelines naturally. Current documented order includes: 1. custom guidelines filename from `--guidelines-filename` / `JUNIE_GUIDELINES_FILENAME` 2. `.junie/AGENTS.md` 3. `AGENTS.md` 4. `.junie/rules/*.md` 5. legacy `.junie/guidelines.md` ## 5) Steer Junie task quality Junie is most valuable when it is treated as a dev agent in a managed loop, not merely as a one-shot file generator. For simple one-off tasks, the host agent or Junie can often do the work directly. For higher-value usage, the host agent should act like the project manager/reviewer and Junie like the implementation agent: - shape the task before handing it off: scope, constraints, acceptance criteria, context commands, and done definition - keep Junie focused on implementation and local repo operations while the host agent tracks goals, risk, sensitive paths, and verification - inspect Junie’s result independently, then either accept it, ask a targeted follow-up, or apply small corrections directly - prefer back-and-forth for exploratory implementation, bug fixing, refactors, or ambiguous repo work; avoid over-orchestrating trivial one-shot artifacts ### Joining an existing host-agent workstream When Junie joins work that the host agent already understands from prior chat, project notes, an Obsidian-style knowledge base, an issue thread, or another local context index, do not make Junie rediscover all of that context from scratch. Give Junie a compact workstream handoff: - current goal and why Junie is being brought in - known state in 5-10 concrete bullets - open decisions or hypotheses to test - exact files/directories Junie should inspect first - whether the first pass is read-only, plan-only, or allowed to edit - output shape the host agent needs for review - model and usage posture: observed current/default Junie model when known, any explicit `--model`/`--provider`/`--effort` override, stronger model recommendation if relevant, and post-run observable usage/cost if available For read-only reconnaissance, say `Do not modify files` and also constrain shell posture if needed. Junie may still use harmless read-only shell commands unless explicitly told not to; if command execution itself is unwanted, say `Open files only; do not run shell commands.` Critical steering rule: do not assume Junie inherits the host agent’s understanding of the project just because that context exists elsewhere. If the task depends on project doctrine, local vocabulary, current constraints, or medium-specific expectations, restate that context explicitly in the Junie task. In practice, front-load the brief with the context Junie actually needs to succeed: - what the project is, in plain language - what file(s) or subsystem(s) matter - what truths are canonical versus metaphorical - what is explicitly out of scope - what failure would look like - what a successful result should change, and what must stay untouched Be especially explicit when the work carries non-obvious project doctrine or domain priors that Junie may otherwise fill in incorrectly. Examples: - domain-specific vocabulary (`customer` vs `tenant`, `case` vs `ticket`, `workspace` vs `project`) - platform or framework constraints (supported runtime, deployment target, storage model, permissions model) - project-specific invariants, safety rules, or compatibility promises - naming that looks familiar but means something narrower in this repo than in generic software usage When a task is likely to trigger generic priors, say so directly and pin Junie to the local truth. Good steering often sounds like: - `Do not assume the generic framework defaults apply; this repo wraps request handling through the project-specific adapter in <file>.` - `Treat the current public API as the compatibility boundary; prefer internal refactors over changing exported behavior unless explicitly requested.` - `The host agent knows the broader project history, but you should rely only on the context explicitly given here plus the listed files.` Also steer explicitly around write posture. If Junie is not in Brave Mode, or if the task touches important existing files, do not assume it will confidently mutate them just because the user asked the host agent. Say what it is allowed to edit and, when helpful, whether a derived output file is preferred. - name the exact file(s) Junie may change - say whether it should modify in place or create a sibling/derived file first - if conservatism is desirable, prefer `create exactly one new file` over an ambiguous in-place rewrite - if in-place mutation is required, say so plainly instead of implying it Treat Junie runs as expensive. Model quota, patience, and review attention are all finite. Prefer fewer, sharper runs over exploratory churn. - front-load the real scope and doctrine into one brief rather than spending multiple paid runs rediscovering context - collapse reconnaissance when the host agent already has the answer; pass Junie the relevant conclusions and file list - prefer one decisive implementation pass plus one follow-up correction over a long chain of vague attempts - when a run is drifting into re-reading and re-deriving, tighten the task or stop and retry with a narrower brief - if a cheaper artifact would de-risk the real task (for example a derived file or a doctrine-alignment doc pass), use that deliberately rather than by accident Do not assume Junie’s own suggested tasks will produce good output with default prompting/model settings. For quality-sensitive work, especially synthesis from repo history or source context: - call out which Junie model/effort is currently in use or planned before reporting/starting meaningful runs; include the source when useful, e.g. `Junie model observed: claude-opus-4-7, effort low (from ~/.junie/settings.json / latest Junie log)` - after each meaningful run, call out observable Junie usage when available; keep it compact, e.g. `Junie usage observed: $0.027318; claude-opus-4-7 + helper models; 1,931 input / 430 output / 16,890 cache-read / 1,330 cache-write tokens` - choose or recommend a stronger model/effort before blaming the task itself - explain model tradeoffs plainly when they matter: quality, speed, availability, and likely quota/billing weight - for quick dry-runs, use the current/default Junie model unless the user already chose a model or the task clearly needs escalation - treat the user as the final decision-maker for model selection, especially for substantial runs on expensive or scarce models - give explicit acceptance criteria, desired depth, and output shape - provide the exact context command when possible, e.g. `git log -5 --stat --summary` instead of vague “last 5 commits” - verify the artifact yourself before reporting success; if output is shallow, say so and either retry with better steering/model or fix it directly ## 6) Verify configuration Use the lightest check that proves the requested state. ### Install only ```bash junie --version junie --help ``` ### Headless auth If a token is available, run a tiny non-destructive prompt in a throwaway project or harmless directory: ```bash junie --auth="$JUNIE_API_KEY" --task "Say hello and report the current working directory" --output-format text ``` ### Config merge or project bootstrap Read back `.junie/config.json` or `~/.junie/config.json` and confirm only the intended keys changed. Do not silently replace unrelated discovery paths, provider defaults, or BYOK entries. For project bootstrap, also confirm the expected directories exist and that an existing `.junie/AGENTS.md` was preserved unless overwrite was explicitly requested. ## 7) Decide whether `headless-terminal` is needed Usually: no. Treat Junie CLI/TUI ↔ IDE integration as aspirational unless the current environment proves otherwise. The CLI exposes hints such as `--acp` for IDE integrations, `--ide-guidelines`, and `~/.junie/settings.json` may contain `connectToIde`, but do not assume the IDE side is actually active, visible, or useful. Prefer standalone CLI behavior and repo-local files as the reliable integration surface. Junie exposes enough non-interactive control for installation and configuration: - installer script - `--auth` - provider API key flags - environment variables - `config.json` Use `headless-terminal` only when all of these are true: - the user explicitly wants the interactive Junie UI driven by the agent, or a setup step exists only in the TUI - plain `exec`/`process` control is too brittle because the UI redraws, uses alternate screen buffers, or waits for keystroke-driven menus - there is no equivalent flag, env var, or config-file route Observed practical rule from local Junie safari: - plain `exec`/`process` PTY control is good enough when Junie behaves like a transcript-emitting interactive CLI and you only need progress text or a simple prompt/response loop - `headless-terminal` becomes worthwhile when you need the rendered screen itself to be legible and stable, especially command menus, `/usage`, `/model`, help overlays, or other palette-like UI states Typical `ht`-worthy cases: - navigating the welcome screen - driving `/account` or `/model` inside Junie interactively - inspecting `/usage` or other slash-command surfaces where screen layout matters - capturing a stable snapshot of the Junie TUI for debugging Non-`ht` cases: - running the installer - writing config files - passing `--auth` or env vars - headless CI usage - ordinary Junie task execution where the transcript already reports opened files, edits, and task results If you do need `ht`, read `references/headless-terminal-fit.md` and use the separate `headless-terminal` skill. ## References - `references/junie-doc-notes.md`: distilled install, auth, config, directory-layout, and verification notes from Junie docs - `references/headless-terminal-fit.md`: decision rule for when interactive PTY tooling is worth it - `scripts/install_junie.sh`: POSIX-shell wrapper around official install methods - `scripts/install_junie.ps1`: PowerShell wrapper around official Windows install methods - `scripts/merge_junie_config.py`: conservative JSON merge helper for Junie config files - `scripts/bootstrap_junie_project.py`: conservative `.junie/` project-layout bootstrap helper

path: CHANGELOG.md
content: # Changelog

## 1.0.0

Initial public release.

### Added
- Junie-native install, update, auth, and config guidance
- Repo bootstrap support for `.junie/AGENTS.md`, `.junie/skills/`, `.junie/commands/`, `.junie/agents/`, `.junie/models/`, `.junie/mcp/`, and `.junie/rules/`
- Host-agent to Junie handoff guidance for iterative execution and review work
- Model posture reporting guidance for current/default model, provider, and effort
- Usage reporting guidance plus `scripts/summarize_junie_usage.py`
- Limited host-agent trigger phrases such as `/junie status`, `/junie model`, `/junie usage`, `/junie bootstrap`, and `/junie dry-run`

### Changed
- Prefer non-interactive/config-driven Junie setup before TUI driving
- Prefer package-manager installs when they fit; treat remote installer scripts as higher-trust operations
- Treat Junie CLI/TUI to IDE integration as aspirational unless the environment proves otherwise
- Use plain PTY/process control for normal Junie execution; escalate to `headless-terminal` only when rendered TUI state matters

### Hardening
- Added clearer trust boundaries around install/auth flows and durable secret storage
- Removed private/local project assumptions and optional local-context dependencies
- Generalized orchestration wording to `host agent` for better portability


path: references/headless-terminal-fit.md
content: # When Junie setup needs headless-terminal

Short answer: usually it does not.

## Why `ht` is usually unnecessary

Junie already has first-class non-interactive control surfaces for the work that matters during setup:
- installation via installer script, Homebrew, or npm
- authentication via `--auth` or environment variables
- persistent defaults via `~/.junie/config.json` or project `.junie/config.json`
- runtime targeting via flags like `--model`, `--project`, and config discovery flags

That means most install/configure tasks are ordinary shell and file-editing work, not TUI automation.

## Use `ht` only when the UI itself is the task

Reach for `headless-terminal` if:
- the user explicitly wants Junie's interactive welcome screen or in-app account flow driven by the agent
- the step cannot be expressed with documented flags, env vars, or config files
- the program redraws aggressively enough that plain PTY handling becomes unreliable
- you need a stable screen snapshot while debugging the Junie TUI

Examples:
- navigate welcome-screen auth choices
- drive `/account` interactively to add a provider key
- capture the current Junie screen for documentation or debugging

## Do not use `ht` for these

- downloading and running the installer
- checking `junie --version`
- writing `config.json`
- exporting `JUNIE_API_KEY`
- running headless CI prompts

## If `ht` is needed

Switch to the `headless-terminal` skill and use a named session. Prefer deterministic waits such as `--wait-text`, `--wait-idle`, or `--wait-exit` instead of blind sleeps.

Suggested first-run probe:
1. Start Junie from the project root: `cd <project-root> && junie`.
2. If it opens to `What shall we build today?`, the local interactive account flow is already satisfied; it may not ask for sign-in.
3. To verify repo guidance, prompt Junie to read only the project guidance and confirm the relevant rule. Example: `Read only the project Junie guidance/guidelines needed to understand your operating rules for this repo. Confirm whether you are allowed to inspect <path>. Do not inspect <path> itself.`
4. If Junie asks to run a narrow read-only command such as `ls -R .junie` or to open `.junie/AGENTS.md`, allowing once is reasonable when the task is explicitly guidance verification. Do not allow broad repo scans when investigating sensitive denylisted paths.
5. Capture the result and exit the TUI. If Ctrl-C only focuses the prompt or asks for confirmation, send Ctrl-C again or terminate the session.


path: references/junie-doc-notes.md
content: # Junie doc notes

This file distills the Junie documentation into the pieces most useful when an agent is asked to install or configure Junie.

## Install

Documented install methods:
- macOS/Linux: `curl -fsSL https://junie.jetbrains.com/install.sh | bash`
- Windows PowerShell: `powershell -NoProfile -ExecutionPolicy Bypass -Command "iex (irm 'https://junie.jetbrains.com/install.ps1')"`
- macOS Homebrew: `brew tap jetbrains/junie && brew update && brew install junie`
- npm: `npm install -g @jetbrains/junie`

The bundled helpers in this skill wrap those documented install paths:
- `scripts/install_junie.sh` for POSIX shells
- `scripts/install_junie.ps1` for PowerShell

The official shell installer:
- detects OS and arch
- installs the shim to `~/.local/bin/junie`
- stores versions under `~/.local/share/junie/versions/`
- maintains `~/.local/share/junie/current`
- attempts to add `~/.local/bin` to shell startup files
- supports `JUNIE_VERSION=<version>` for a pinned install

## Quick verification

```bash
export PATH="$HOME/.local/bin:$PATH"
junie --version
junie --help
```

## Authentication

### Junie token
- CLI flag: `--auth`
- env var: `JUNIE_API_KEY`

Example:

```bash
junie --auth="$JUNIE_API_KEY" "review this repository"
```

### BYOK provider keys
Junie documents provider-specific flags and env vars for:
- Anthropic
- OpenAI
- Google
- xAI / Grok
- OpenRouter

Examples:

```bash
export JUNIE_ANTHROPIC_API_KEY='...'
export JUNIE_OPENAI_API_KEY='...'
```

Interactive local auth can also happen from:
- the welcome screen
- `/account`

## Headless mode

Headless mode is the preferred automation path. Junie documents:

```bash
junie --auth="$JUNIE_API_KEY" "Review and fix any code quality issues in the latest commit"
```

This is the main reason installation/configuration usually does not require terminal-UI driving.

## Config files

Default config locations:
- user: `~/.junie/config.json`
- project: `<project>/.junie/config.json`

Junie-native project layout typically centers on a `.junie/` directory. A sensible bootstrap shape is:

```text
.junie/
├── AGENTS.md
├── config.json
├── skills/
├── commands/
├── agents/
├── models/
├── mcp/
└── rules/
```

Precedence documented by Junie:
1. CLI flags
2. `~/.junie/settings.json`
3. project `.junie/config.json`
4. user `~/.junie/config.json`

Common config fields documented by Junie:
- `model`
- `provider`
- `brave`
- `flags`
- `mcp-locations`
- `mcp-default-locations`
- `skill-locations`
- `skill-default-locations`
- `command-locations`
- `command-default-locations`
- `agent-locations`
- `agent-default-location`
- `model-locations`
- `model-default-locations`
- `auto-update`
- `guidelines-location`
- `time-limit`
- `byok`
- `proxies`

Relative paths in config files are resolved relative to the config file's directory.

Guidelines discovery order documented by Junie:
1. custom filename from `--guidelines-filename` / `JUNIE_GUIDELINES_FILENAME` (inside project `.junie/`)
2. `.junie/AGENTS.md`
3. root `AGENTS.md`
4. `.junie/rules/*.md`
5. legacy `.junie/guidelines.md`

## Skills and guidelines

Junie skills live under:
- project: `<project>/.junie/skills/`
- user: `~/.junie/skills/`

Related Junie-native discovery roots are commonly:
- `<project>/.junie/commands/`
- `<project>/.junie/agents/`
- `<project>/.junie/models/`
- `<project>/.junie/mcp/`
- `<project>/.junie/rules/`
- `~/.junie/commands/`, `~/.junie/agents/`, `~/.junie/models/`, `~/.junie/mcp/`

Practical rule: prefer `.junie/AGENTS.md` for repo-specific guidance and `.junie/rules/*.md` for split-out domain rules. Avoid setting `guidelines-location` unless you are intentionally overriding discovery.

Junie can also be pointed at additional skills/guidelines via config or environment variables.

## Practical recommendations

- Prefer headless auth and config files over interactive setup when possible.
- Ask before persisting secrets in `config.json`.
- Merge existing config; do not replace it unless explicitly asked.
- Preserve Junie-owned runtime state such as `~/.junie/settings.json` unless the user explicitly wants it edited.
- Use the interactive UI only when a human specifically wants that experience or when there is no documented non-interactive equivalent.


path: scripts/bootstrap_junie_project.py
content: #!/usr/bin/env python3
"""Conservatively bootstrap a Junie-native .junie project layout."""
import argparse
import json
from pathlib import Path

DEFAULT_CONFIG = {
    "skill-default-locations": True,
    "command-default-locations": True,
    "agent-default-location": True,
    "model-default-locations": True,
    "mcp-default-locations": True,
    "auto-update": True,
}

AGENTS_TEMPLATE = """# Junie project guidance\n\nUse this file for persistent, project-specific instructions that Junie CLI should apply to tasks in this repo.\n\n- Prefer small, reviewable changes.\n- Explain any command that may mutate files before running it.\n- Keep secrets out of committed config.\n"""


def merge(base, patch):
    if isinstance(base, dict) and isinstance(patch, dict):
        result = dict(base)
        for key, value in patch.items():
            result[key] = merge(result[key], value) if key in result else value
        return result
    return patch


def read_object(path: Path) -> dict:
    if not path.exists():
        return {}
    data = json.loads(path.read_text(encoding="utf-8"))
    if not isinstance(data, dict):
        raise ValueError(f"{path} must contain a JSON object")
    return data


def main() -> int:
    parser = argparse.ArgumentParser(description="Bootstrap a Junie-native .junie layout without overwriting user content.")
    parser.add_argument("project", nargs="?", default=".", help="Project root to bootstrap. Defaults to current directory.")
    parser.add_argument("--model", help="Optional default model to add to .junie/config.json.")
    parser.add_argument("--guidelines-location", help="Optional guidelines-location value for .junie/config.json.")
    parser.add_argument("--config", help="Optional JSON object to merge into .junie/config.json.")
    parser.add_argument("--force-agents", action="store_true", help="Overwrite .junie/AGENTS.md with the default template.")
    args = parser.parse_args()

    root = Path(args.project).expanduser().resolve()
    junie_dir = root / ".junie"
    junie_dir.mkdir(parents=True, exist_ok=True)

    for name in ["skills", "commands", "agents", "models", "mcp", "rules"]:
        (junie_dir / name).mkdir(exist_ok=True)

    agents_path = junie_dir / "AGENTS.md"
    if args.force_agents or not agents_path.exists():
        agents_path.write_text(AGENTS_TEMPLATE, encoding="utf-8")

    patch = dict(DEFAULT_CONFIG)
    if args.model:
        patch["model"] = args.model
    if args.guidelines_location:
        patch["guidelines-location"] = args.guidelines_location
    if args.config:
        extra = json.loads(args.config)
        if not isinstance(extra, dict):
            raise ValueError("--config must be a JSON object")
        patch = merge(patch, extra)

    config_path = junie_dir / "config.json"
    existing = read_object(config_path)
    merged = merge(existing, patch)
    config_path.write_text(json.dumps(merged, indent=2, sort_keys=True) + "\n", encoding="utf-8")

    print(junie_dir)
    return 0


if __name__ == "__main__":
    raise SystemExit(main())


path: scripts/install_junie.sh
content: #!/usr/bin/env bash
set -euo pipefail

METHOD="auto"
REINSTALL="false"
VERSION=""

usage() {
  cat <<'EOF'
Usage: install_junie.sh [--method auto|script|brew|npm] [--version VERSION] [--reinstall]

Installs or reinstalls JetBrains Junie CLI using a documented method.

Options:
  --method      Install method. Default: auto
  --version     Pin a Junie version for the official installer path
  --reinstall   Reinstall even if junie is already present on PATH
  -h, --help    Show this help
EOF
}

while [[ $# -gt 0 ]]; do
  case "$1" in
    --method)
      METHOD="$2"
      shift 2
      ;;
    --version)
      VERSION="$2"
      shift 2
      ;;
    --reinstall)
      REINSTALL="true"
      shift
      ;;
    -h|--help)
      usage
      exit 0
      ;;
    *)
      echo "Unknown argument: $1" >&2
      usage >&2
      exit 2
      ;;
  esac
done

log() {
  printf '[junie-install] %s\n' "$*"
}

fail() {
  printf '[junie-install] ERROR: %s\n' "$*" >&2
  exit 1
}

have() {
  command -v "$1" >/dev/null 2>&1
}

os_name() {
  case "$(uname -s)" in
    Darwin) echo "macos" ;;
    Linux) echo "linux" ;;
    *) echo "other" ;;
  esac
}

ensure_path_for_current_shell() {
  case ":$PATH:" in
    *":$HOME/.local/bin:"*) ;;
    *) export PATH="$HOME/.local/bin:$PATH" ;;
  esac
}

if [[ "$REINSTALL" != "true" ]] && have junie; then
  log "Junie already exists at $(command -v junie)"
  junie --version || true
  exit 0
fi

if [[ "$METHOD" == "auto" ]]; then
  if [[ "$(os_name)" == "macos" ]] && have brew; then
    METHOD="brew"
  elif have npm; then
    METHOD="npm"
  else
    METHOD="script"
  fi
fi

case "$METHOD" in
  script)
    have curl || fail "curl is required for script install"
    local_tmp="$(mktemp)"
    trap 'rm -f "$local_tmp"' EXIT
    log "Downloading official Junie installer from https://junie.jetbrains.com/install.sh"
    log "Trust note: this method downloads a shell script and executes it locally. Prefer --method brew or --method npm when that is a better fit."
    curl -fsSL https://junie.jetbrains.com/install.sh -o "$local_tmp"
    if [[ -n "$VERSION" ]]; then
      log "Running installer with JUNIE_VERSION=$VERSION"
      JUNIE_VERSION="$VERSION" bash "$local_tmp"
    else
      log "Running installer"
      bash "$local_tmp"
    fi
    ;;
  brew)
    [[ "$(os_name)" == "macos" ]] || fail "Homebrew install is only supported here on macOS"
    have brew || fail "brew is required for --method brew"
    log "Installing Junie via Homebrew"
    brew tap jetbrains/junie
    if [[ "$REINSTALL" == "true" ]]; then
      brew reinstall junie
    else
      brew install junie || brew upgrade junie
    fi
    ;;
  npm)
    have npm || fail "npm is required for --method npm"
    log "Installing Junie via npm"
    npm install -g @jetbrains/junie
    ;;
  *)
    fail "Unsupported method: $METHOD"
    ;;
esac

ensure_path_for_current_shell

have junie || fail "Junie is still not on PATH after install. Try: export PATH=\"$HOME/.local/bin:\$PATH\""

log "Installed $(command -v junie)"
junie --version


path: scripts/merge_junie_config.py
content: #!/usr/bin/env python3
import json
import os
import sys
from pathlib import Path


def usage() -> None:
    print(
        "Usage: merge_junie_config.py <config-path> <json-patch>\n"
        "\n"
        "Example:\n"
        "  merge_junie_config.py ~/.junie/config.json '{\"model\":\"sonnet\",\"auto-update\":true}'\n"
    )


def merge(base, patch):
    if isinstance(base, dict) and isinstance(patch, dict):
        result = dict(base)
        for key, value in patch.items():
            if key in result:
                result[key] = merge(result[key], value)
            else:
                result[key] = value
        return result
    return patch


def main() -> int:
    if len(sys.argv) != 3:
        usage()
        return 2

    raw_path = os.path.expanduser(sys.argv[1])
    config_path = Path(raw_path)

    try:
        patch = json.loads(sys.argv[2])
    except json.JSONDecodeError as exc:
        print(f"Invalid JSON patch: {exc}", file=sys.stderr)
        return 2

    if not isinstance(patch, dict):
        print("Patch must be a JSON object", file=sys.stderr)
        return 2

    existing = {}
    if config_path.exists():
        try:
            existing = json.loads(config_path.read_text(encoding="utf-8"))
        except json.JSONDecodeError as exc:
            print(f"Existing config is not valid JSON: {exc}", file=sys.stderr)
            return 1
        if not isinstance(existing, dict):
            print("Existing config must be a JSON object", file=sys.stderr)
            return 1

    merged = merge(existing, patch)

    config_path.parent.mkdir(parents=True, exist_ok=True)
    config_path.write_text(json.dumps(merged, indent=2, sort_keys=True) + "\n", encoding="utf-8")
    print(str(config_path))
    return 0


if __name__ == "__main__":
    raise SystemExit(main())


path: scripts/summarize_junie_usage.py
content: #!/usr/bin/env python3
"""Summarize observable Junie usage from local session event logs.

Best-effort only: Junie log formats are owned by JetBrains and may change.
This script reads ~/.junie/sessions and prints aggregate model/cost/token usage.
It does not read credentials.
"""
from __future__ import annotations

import argparse
import json
from collections import defaultdict
from pathlib import Path
from typing import Any

TOKEN_KEYS = (
    "inputTokens",
    "outputTokens",
    "cacheInputTokens",
    "cacheCreateTokens",
    "cacheCreateInputTokens",
    "reasoningTokens",
    "webSearchCount",
)


def empty_totals() -> dict[str, float]:
    totals: dict[str, float] = {key: 0 for key in TOKEN_KEYS}
    totals["cost"] = 0.0
    totals["records"] = 0
    return totals


def normalize_usage(record: dict[str, Any]) -> dict[str, float]:
    out = empty_totals()
    out["records"] = 1
    for key in TOKEN_KEYS:
        value = record.get(key, 0)
        if isinstance(value, (int, float)):
            out[key] += value
    value = record.get("cost", 0)
    if isinstance(value, (int, float)):
        out["cost"] += value
    return out


def add_totals(target: dict[str, float], source: dict[str, float]) -> None:
    for key, value in source.items():
        target[key] += value


def find_model_usage_records(value: Any):
    """Yield Junie's LlmResponseMetadataEvent modelUsage records."""
    if isinstance(value, dict):
        records = value.get("modelUsage")
        if isinstance(records, list):
            for record in records:
                if isinstance(record, dict):
                    yield record
        # Some state.json files embed an escaped AgentState JSON string containing
        # assistantRequest.usage records. Parse those as fallback usage records.
        blob = value.get("blob")
        if isinstance(blob, str) and "usage" in blob:
            try:
                yield from find_assistant_usage_records(json.loads(blob))
            except json.JSONDecodeError:
                pass
        for child in value.values():
            yield from find_model_usage_records(child)
    elif isinstance(value, list):
        for child in value:
            yield from find_model_usage_records(child)


def find_assistant_usage_records(value: Any):
    """Yield generic assistantRequest.usage records from embedded state blobs."""
    if isinstance(value, dict):
        usage = value.get("usage")
        if isinstance(usage, dict) and any(key in usage for key in TOKEN_KEYS):
            record = dict(usage)
            record.setdefault("model", "unknown")
            yield record
        for child in value.values():
            yield from find_assistant_usage_records(child)
    elif isinstance(value, list):
        for child in value:
            yield from find_assistant_usage_records(child)


def session_dirs(root: Path) -> list[Path]:
    if not root.exists():
        return []
    return sorted(
        (path for path in root.glob("session-*") if path.is_dir()),
        key=lambda path: path.stat().st_mtime,
        reverse=True,
    )


def summarize_jsonl(path: Path) -> dict[str, dict[str, float]]:
    by_model: dict[str, dict[str, float]] = defaultdict(empty_totals)
    with path.open("r", encoding="utf-8", errors="replace") as handle:
        for line in handle:
            line = line.strip()
            if not line:
                continue
            try:
                event = json.loads(line)
            except json.JSONDecodeError:
                continue
            for record in find_model_usage_records(event):
                model = str(record.get("model") or "unknown")
                add_totals(by_model[model], normalize_usage(record))
    return dict(by_model)


def summarize_session(session_dir: Path) -> dict[str, dict[str, float]]:
    events_path = session_dir / "events.jsonl"
    if events_path.exists():
        by_model = summarize_jsonl(events_path)
        # Prefer events.jsonl because state.json may repeat a partial snapshot of
        # the same conversation and can double-count usage.
        if by_model:
            return by_model

    state_path = session_dir / "state.json"
    by_model: dict[str, dict[str, float]] = defaultdict(empty_totals)
    if not state_path.exists():
        return {}
    try:
        data = json.loads(state_path.read_text(encoding="utf-8", errors="replace"))
    except json.JSONDecodeError:
        return {}
    for record in find_model_usage_records(data):
        model = str(record.get("model") or "unknown")
        add_totals(by_model[model], normalize_usage(record))
    return dict(by_model)


def print_totals(by_model: dict[str, dict[str, float]]) -> bool:
    if not by_model:
        print("  usage: unavailable (no usage records found)")
        return False
    grand = empty_totals()
    for totals in by_model.values():
        add_totals(grand, totals)
    print(f"  usage records: {int(grand['records'])}")
    print(f"  estimated cost: ${grand['cost']:.6f}")
    print(f"  input tokens: {int(grand['inputTokens'])}")
    print(f"  output tokens: {int(grand['outputTokens'])}")
    print(f"  cache read tokens: {int(grand['cacheInputTokens'])}")
    cache_write = grand["cacheCreateTokens"] + grand["cacheCreateInputTokens"]
    print(f"  cache write tokens: {int(cache_write)}")
    if grand["reasoningTokens"]:
        print(f"  reasoning tokens: {int(grand['reasoningTokens'])}")
    if grand["webSearchCount"]:
        print(f"  web searches: {int(grand['webSearchCount'])}")
    print("  by model:")
    for model, totals in sorted(by_model.items(), key=lambda item: item[1]["cost"], reverse=True):
        cache_write = totals["cacheCreateTokens"] + totals["cacheCreateInputTokens"]
        print(
            "    - "
            f"{model}: cost=${totals['cost']:.6f}, "
            f"in={int(totals['inputTokens'])}, out={int(totals['outputTokens'])}, "
            f"cache_read={int(totals['cacheInputTokens'])}, cache_write={int(cache_write)}"
        )
    return True


def main() -> int:
    parser = argparse.ArgumentParser(description="Summarize observable Junie usage from ~/.junie/sessions")
    parser.add_argument("--session", help="Specific Junie session directory name or path. Defaults to latest session.")
    parser.add_argument("--limit", type=int, default=1, help="Number of newest sessions to summarize when --session is omitted.")
    parser.add_argument("--sessions-root", default=str(Path.home() / ".junie" / "sessions"), help="Junie sessions root")
    args = parser.parse_args()

    root = Path(args.sessions_root).expanduser()
    if args.session:
        candidate = Path(args.session).expanduser()
        targets = [candidate if candidate.exists() else root / args.session]
    else:
        targets = session_dirs(root)[: max(1, args.limit)]

    if not targets:
        print(f"No Junie sessions found under {root}")
        return 1

    any_usage = False
    for session_dir in targets:
        print(f"Junie session: {session_dir.name}")
        any_usage = print_totals(summarize_session(session_dir)) or any_usage
    return 0 if any_usage else 2


if __name__ == "__main__":
    raise SystemExit(main())


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (MEDIUM)