jun is Jun’s command-line tool. It does two separate jobs:
- Agent as code: talk to your deployed desk with an API token:
login <url>,init,pull,push,eval. - Local LLM tools for developing Jun itself:
login chatgpt,logout,whoami,models,ask,chat.
Install and run
The CLI isn’t published to npm. It’s part of the Jun repository (packages/cli, AGPL-3.0) and runs straight from the source on Node.js 22.18 or later, with no build step.
npm install links the jun command into node_modules/.bin, which is why npx jun works inside the checkout. With npm run, put -- before the CLI’s own arguments so npm passes them through. Folder paths are relative to the directory you run from; npm run always runs from the repository root.
Run jun with no command, or with -h / --help, to print the built-in help.
Agent as code
API tokens
The desk commands authenticate with a personal API token.- Create one: MCP → API tokens, enter a name (
laptop,GitHub Actions) and press Create token. The token starts withjun_and is shown once. - What it can do: it acts as you, in this workspace only, and only on the agent config (
/api/workspaces/<id>/agent…) and the CLI’s login check. It can’t read conversations, change settings, or read or change action keys. A token from one workspace is refused by another (wrong_workspace). - Role: any member can
pullandeval.pushneeds an owner or admin, the same as saving on the Agent page. - Revoke: the same card lists your tokens with when each was created and last used. Revoke deletes it at once.
jun login <desk-url>
Connects the CLI to a desk.
string
required
The desk’s address, starting with
https:// (or http:// for a local desk).string
The API token. Without it, the CLI uses
JUN_DESK_TOKEN if set, or asks: API token (MCP → API tokens):.Logged in to <workspace> at <url> as <you>. It saves the URL, token, workspace and your name in ~/.jun/desk.json, readable only by you (mode 600). Logging in again replaces it; one desk at a time.
A token that doesn’t start with jun_ is refused before anything is sent.
jun init [dir]
Writes a starter config into an empty folder (default support-agent).
.example files aren’t part of the config until you rename them (drop .example); push and eval skip them. init refuses a folder that isn’t empty. It doesn’t contact the desk, so it works before login.
jun pull [dir]
Downloads the desk’s live config into the folder (default support-agent).
- Writes every config file, creating folders as needed.
- Deletes config files in the folder that no longer exist on the desk. Files that aren’t config files (your own notes,
.examplefiles) are left alone. - Writes the version to
.jun-version, whichpushuses to detect desk edits made since. Commit it. - Writes the guide files
AGENTS.md,CLAUDE.mdandREADME.md(see Edit with your own AI). They’re generated, rewritten on every pull, and never pushed. A file of one of those names that you wrote yourself is kept, with a note.
instructions.md and the message says built-in default config.
A folder from before October 2026 kept the instructions in AGENTS.md. pull moves them to instructions.md and puts the guide in AGENTS.md.
You also get desk.yaml, the desk’s settings. If no version has it yet, the desk writes it from the current settings. See desk.yaml.
jun push [dir]
Validates the folder and makes it the live config, as a new version.
string
A note for the version, shown in History on the Agent page. At most 500 characters; a longer note is refused.
boolean
Push even if someone saved on the Agent page since your last pull. Their edits are discarded from the live config (they remain in History).
desk.yaml changes the desk’s settings: the keys whose value changed since the previous version go live. Deleting desk.yaml changes no setting. The guide files (AGENTS.md, CLAUDE.md, README.md) are never sent. Other files are listed as (skipped <path>: not a config file) and not sent. instructions.md must exist; in a folder from before October 2026 without one, AGENTS.md is sent as instructions.md.
On success:
.jun-version, the push stops and tells you to pull, merge with git, and push again (or use --force). Changes made only on the settings pages (in desk.yaml) don’t stop it, as long as your folder didn’t change desk.yaml too. See Agent as code.
jun eval [dir]
Tests the folder’s config on the desk without making it live.
- Test cases: every case in
tests/*.yaml, run against your files. Each passes or fails on itsexpect. - Replay: the first customer message of recent conversations the AI took part in, answered by the live config and by yours. A judge model compares the two and reports which answers would change in substance.
number
default:"20"
How many recent conversations to replay, from 0 to 50.
0 skips the replay.boolean
Actions return their
mock: response instead of calling your API. An action without mock: returns an error to the AI.boolean
Skip
tests/*.yaml, replay only.boolean
Print one JSON event per line instead of the readable report. See Run evals in CI for the events. With
npm run, add -s (npm run -s jun -- eval … --json) so npm’s own banner lines don’t end up in the output.boolean
Exit with code 1 when any replayed answer changes.
= is a replayed answer that stays the same in substance; ≠ one that changes, with the live and new reply side by side. ! is an error (a case or conversation that couldn’t run).
Without
--mock-tools, actions call your real API, including POST requests. Use mocks, or point test runs at a staging API, for actions that change things.eval prints Eval didn't run: Fix the config errors first. and the problems. The desk’s AI provider must be usable (for example, OPENAI_API_KEY set when the provider is OpenAI); otherwise the run stops with that error.
Exit codes
push also exits with 1 when the config has errors or the push is refused; every command exits with 1 on an error.
Use the CLI in CI
Set two environment variables instead of runningjun login:
When both are set, the desk commands use them and ignore
~/.jun/desk.json. See Run evals in CI for a complete GitHub Actions workflow.
Local LLM tools
These commands are for working on Jun itself, for example to run the end-to-end tests with real AI. They don’t touch your desk.jun login chatgpt
Signs in with ChatGPT using OpenAI’s loopback flow.
boolean
Print the sign-in link instead of opening a browser.
http://127.0.0.1:1455 (or any free port if that’s taken), opens the sign-in page, and waits up to 10 minutes. If the browser ends on “127.0.0.1 refused to connect”, copy the address from its address bar and paste it into the terminal. Credentials go to ~/.jun/chatgpt.json, readable only by you.
jun logout
Revokes the ChatGPT tokens and forgets them. It doesn’t log out of your desk; to forget the desk login, delete ~/.jun/desk.json.
jun whoami
Shows the signed-in ChatGPT account, when its access token expires, and whether plan usage was granted.
jun models
Lists the models your ChatGPT account can use.
jun ask "<question>"
Asks once and streams the answer to the terminal.
jun chat
A multi-turn chat in the terminal. An empty line or Ctrl+C ends it.
ask and chat take:
chatgpt | openai
default:"chatgpt"
Which provider to use. Defaults to
$JUN_LLM_PROVIDER, else chatgpt. openai needs OPENAI_API_KEY.string
A model slug. Defaults to
$JUN_MODEL, else the provider’s default.string
System guidance for the assistant.
Environment variables
Files
File format
Every key in
instructions.md, tasks, actions, widgets and tests.Evals in CI
A GitHub Actions workflow for pull requests and merges.