A minimal LLM harness for your terminal.
haze 0.9.0 closes the security and correctness findings from the 0.8.0 code review: private state, truthful status, bounded work, and robust boundaries.
- Private home-state storage. Settings, sessions, logs, and history now use
0700directories and0600files on POSIX, with opportunistic tightening of pre-existing overly-broad modes. - Truthful turn and tool status. Turn and tool status is now authoritative across the UI, events, logs, sessions, and headless exit codes — a turn that ends without a substantive answer, after an unresolved tool failure, or at a hard step/tool budget reports
failedeven when the provider returned normally. - Bounded work, not just bounded output. Bash, grep, and LSP output, file reads, edits, and stored handles enforce collection-time byte limits; bash timeout and abort terminate the whole process tree, not just the shell.
- Stronger filesystem and integration boundaries.
greprejects directly named ignored files, skills and LSP confine to real workspace/root paths (symlink-safe), MCP discovery is bounded and abort-aware, and credentialed remote plaintext HTTP is rejected while loopback HTTP stays supported. - Ordered, flushable persistence. Sessions and debug logs preserve write order and flush at turn end and shutdown, surfacing persistence failures instead of swallowing them.
Previous releases:
- 0.8.0 — AI SDK v7 runtime, live busy status with model/elapsed/tool labels, bounded bash output processing, and standardized lowercase
hazenaming. - 0.7.0 — Headless
-p/--output json/--output stream-json, pinned-connectionfetchsafety, scoped instruction refresh during turns, smaller durable sessions, and startup context visibility. - 0.6.0 — AI SDK-native ToolLoopAgent core, optional LSP semantic navigation, MCP server support, unified config pickers, cleaner transcripts,
/context, and startup update checks. - 0.5.0 —
fetchtool for public URLs (Markdown/JSON/text, SSRF-protected), removed all provider env vars (config via/provider//model//settings), debug-only LLM logs, command-aware output reduction, Markdown rendering in the CLI, scoped nested context files, and auto-clearing completed tasks. - 0.4.0 — 3-step skill wizard, language-agnostic skill intent extraction, model-managed tasks, leaner command surface, docs site additions.
- 0.3.0 — Docs site redesign, task bar moves above the activity spinner, tasks auto-clear between sessions.
- 0.2.0 — Reliability release: stronger continuation after failed edits and validation, structured bash classification, parsed validation summaries, multi-line chat input with vertical cursor movement.
- 0.1.0 — Bundled ripgrep, subagent delegation, inline diff display.
- 0.0.3 — Durable sessions, context compaction, provider management.
- 0.0.2 — Markdown skills, autocomplete, listFiles pagination.
- 0.0.1 — Initial release.
haze works with OpenAI-compatible providers, including OpenRouter and local endpoints. Use /provider to choose or add one, then /model to select a model.
_
| |
| |__ __ _ _______
| '_ \ / _` |_ / _ \
| | | | (_| |/ / __/
|_| |_|\__,_/___\___|haze keeps guardrails light. The LLM can work from the terminal with freedoms close to yours, while trying to stay scoped to the current project. It is aimed at developers who want an expert-oriented tool, not a permission dialog factory. Watch the tool calls. Keep your hands near the wheel. Progress.
Install haze:
npm install -g @denizokcu/hazeOpen haze from your project:
$ hazeOn first run, create or choose a provider, then choose your first model:
/provider
/model/provider opens provider setup for any OpenAI-compatible endpoint — e.g. OpenRouter, OpenAI, LM Studio, Ollama, or a proxy. haze will ask for a provider name, base URL, optional API key, and model names.
/model selects the model haze should use. You can also set one directly:
/model anthropic/claude-sonnet-4.6
/model local:llama3.1Use /mcp to connect Model Context Protocol servers and give the agent extra tools. It opens an interactive picker (like /provider): choose a server to enable, disable, remove, or set an API key for it, or choose add server to add one from a preset (Context7 ships built-in for up-to-date library docs) or enter custom details.
/mcp # opens the server picker
# add server -> context7 (preset)
# add server -> custom -> name -> http -> url (custom remote)
# add server -> custom -> name -> stdio -> cmd (custom local)API keys for HTTP/SSE servers are entered in a masked prompt and sent as Authorization: Bearer <value>. Stdio authentication belongs in the command or wrapper; haze does not attach HTTP headers to stdio. Servers persist in ~/.haze/settings.json under mcpServers. Discovery and cleanup have bounded deadlines, failures are isolated, and MCP tools never shadow built-ins.
Saved settings live in ~/.haze/settings.json. Provider keys and MCP headers require HTTPS unless the endpoint is loopback HTTP (localhost, *.localhost, 127/8, or ::1); keyless HTTP remains available. Local OpenAI-compatible providers can be configured without a key. Malformed settings are shown as an actionable startup error rather than treated as empty. Use /provider, /model, and /settings to configure everything from inside haze — there are no environment variables to set.
haze is intentionally minimal: chat, local tools, context files, sessions, and Markdown skills. Any workflow beyond the core is meant to be grown with the LLM through /skills (an interactive picker: generate a custom skill from a description, then enable/disable, validate, or remove it). If you want reviews, release prep, deploy checks, debugging rituals, or your team's strange checklist, ask haze to create a skill and then refine the Markdown.
Open a project and ask for work:
create a calculator in calc-app in ruby with add subtract multiply dividehaze will inspect, search, write files, fetch public URLs, run commands, and show compact tool activity inline. Small file edits include a colorized line diff with one context line before and after the change; large diffs stay summarized so the transcript does not become a wall of noise. Bash output is bounded while the process runs, then reduced by command-aware filters for validation, git, search, JSON, diffs, and noisy logs; failures point at relevant diagnostics. Handles retain raw output only within explicit per-entry and aggregate memory budgets, and report omitted bytes when collection overflow is dropped. Sessions are saved by default so you can resume the latest workspace conversation with haze --continue or /resume.
Use / to discover commands and skills. Tab completes the top suggestion.
Useful starters:
/init
/skills # then add skill: name + a description like:
# "review my current branch against main like a senior engineer"
# "prepare clean git commits from my uncommitted changes"
# "implement small features with tests and a concise summary"/init creates or updates AGENTS.md so future sessions understand the project.
Skills are Markdown workflows that haze creates with /skills and stores in ~/.haze/skills so you can inspect or refine them later.
If you do something for the second time, build a skill for it:
/skills
# Picker → add skill
# Name: branch-diff-review
# Description: review the diff between my current branch and main, focusing on bugs, tests, DRY and KISShaze uses the model to create the skill file for you:
~/.haze/skills/<skill-name>/SKILL.mdA skill is just Markdown with frontmatter, a role, a focused prompt, and a small output template:
---
name: code-review-diff-main
description: Use when the user asks for a code review of the current branch against main.
---
# Role
You are a focused code reviewer.
# Focused prompt
Review the actual change and return useful, evidence-based feedback.
# Procedure
Inspect branch state, changed files, staged and unstaged diffs, then review incrementally.
# Output template
## Summary
- <scope and result>
## Findings
- <prioritized findings, or "No issues found">
## Evidence inspected
- <commands/files used>Installed skills appear as slash commands like:
/code-review-diff-mainThey are also available through one skill catalog tool. haze loads one workflow body first and fetches large references only when needed. Skills provide instructions; they do not execute code.
This is the trick: do normal work, notice friction, create a skill, keep going. Your workflow adapts instead of asking you to adapt to the tool. Rude, but in a good way.
/help
/provider
/model
/model <name-or-provider:name>
/model list
/settings
/settings open
/logs [id]
/lsp
/mcp
/init
/context
/session
/resume
/new
/compact [instructions]
/clear
/exit
/skillsSkill management is a single interactive picker, mirroring /provider, /lsp, and /mcp: generate a custom skill from a description, then show info, enable/disable, validate, or remove. Disabled skills drop out of the model catalog and the /<name> command list until re-enabled.
CLI flags:
haze --debug # show model/tool debug logs and write detailed JSONL logs to ~/.haze/logs
haze --continue # resume the latest saved session for this workspace
haze --no-session # run without durable session storageNon-interactive / print mode:
haze -p "refactor utils.ts to remove the unused export"
haze -p "summarize this repo" --model openai:gpt-4o-mini
haze -p "list the top 3 bugs in src/api.ts" --output json
haze -p "audit src/auth.ts" --output stream-json # live NDJSON events, then the result envelope
echo "what does this project do?" | haze-p / --prompt runs a single agentic turn (with the full tool set and guardrails) and prints the final assistant text. --model overrides the active model for that one run without changing ~/.haze/settings.json — accepts a bare model name or provider:name. The selected model must already be registered under a provider's models (add it once via /provider); an unknown or ambiguous selector exits non-zero with a precise error on stderr. When -p is omitted and stdin is piped, the prompt is read from stdin. A one-shot run never starts or resumes a durable session; --continue is ignored in this mode. Headless runs do not auto-compact on context overflow, so very large CI prompts may fail rather than recover — keep prompts within the model's window. Add --debug to also write a detailed JSONL log under ~/.haze/logs/. --output selects how the run is reported: text (default), json (a single final envelope), or stream-json (a live NDJSON event stream ending in that envelope) — see below.
--output json prints a single-line envelope instead of plain text:
{
"type": "result",
"status": "complete", // "complete" (exit 0) | "aborted" | "failed" (both exit non-zero)
"result": "the final assistant text",
"usage": {
"inputTokens": 0,
"outputTokens": 0,
"cacheReadTokens": 0,
"cacheWriteTokens": 0,
"reasoningTokens": 0
}
}The status field is authoritative (driven by the agent's terminal state, not by parsing result), and the exit code mirrors it: 0 only for complete. A turn that ends without a substantive final answer, after an unresolved final tool failure, or at a hard step/tool budget is failed even if the provider returned normally.
--output stream-json streams the run as it happens instead of staying silent until the end. Each public progress event is written to stdout as one newline-delimited JSON object (NDJSON), and the last line is the exact same { type: "result", status, result, usage } envelope as --output json — so a consumer can render live progress and still parse the final line identically:
Every line is standalone valid JSON, so the stream pipes cleanly through jq -c .. The event types are turn_start, message_start / message_update / message_end, tool_start / tool_end, retry, context_overflow, and turn_end; each carries an ISO-8601 at timestamp. Tool events intentionally omit raw tool inputs and outputs because stdout is often captured by CI/harness logs; use --debug when detailed local JSONL logs are needed. This mode is intended for harnesses driving haze autonomously: the incremental, ever-changing output lets a supervisor show live progress and run stdout-based stagnation/loop detection, while the trailing result envelope remains the single source of truth for status, text, and usage. text and json outputs are unchanged.
By default, haze does not write the detailed LLM log files under ~/.haze/logs/ (they capture full prompts, messages, and tool I/O). File logging is only enabled with haze --debug, which also turns on the on-screen debug panel. Use the /logs command to review past log files once logging has been enabled.
haze exposes a deliberately small toolset:
listFiles— structured discovery, recursive with cursor pagination when needed.readFile— read numbered UTF-8 lines in bounded pages, withnextOffsetwhen more remain.grep— structured ripgrep search with a true global result cap and compacted long lines/results; directly named ignored roots are rejected unlessincludeIgnoredis explicitly set.editFile— unique text replacements, with line-number-prefix tolerance for common model mistakes.replaceLines— line-range edits when exact replacements are awkward; slightly-too-large EOF ranges are clamped.writeFile— create files and parent directories.bash— run tests, builds, git/gh commands, inspections, scripts, installs, and other shell workflows with command-aware output reduction (git, gh, search, JSON, diffs, logs) and compact validation output.readToolOutput— page through retained raw output omitted from an oversized or reduced tool result (subject to bounded in-memory budgets).fetch— read a publichttp(s)URL and return readable content (Markdown for docs, pretty JSON, or text). Private/loopback/metadata hosts and non-http(s)schemes are blocked; output is bounded and retrievable viareadToolOutput.writeTasks— replace the task list at meaningful phase changes; completed lists auto-clear on the next user turn.skill— load one installed Markdown workflow or one of its references.lspWorkspaceSymbols,lspSymbols,lspDefinition,lspReferences— optional read-only semantic navigation through user-configured language servers. These tools are exposed only when an enabled LSP command is installed.
Tool calls are grouped in the transcript so you can see what happened without reading a novella. Successful targeted file edits show a compact diff with colored additions/removals and one context line around the change when the diff is small; larger diffs are summarized with a pointer to git diff. File-tool failures return structured reason codes and recovery hints. Large bash/search/fetch output is kept behind an in-memory handle so later model calls carry only reduced validation, git, search, diff, JSON, log, or head/tail summaries.
haze can use user-configured stdio Language Server Protocol servers for semantic code navigation. Configure them with /lsp (an interactive picker, like /provider and /mcp): add a preset or a custom command, then enable/disable/remove servers. Presets currently include TypeScript, Rust, Python, Go, and PHP. haze does not install language servers for you, and it hides LSP tools from the model unless an enabled server command exists on PATH, so projects without LSP still use grep, listFiles, and readFile normally.
Example TypeScript setup:
npm install -g typescript typescript-language-server/lsp
# -> add server -> typescriptSubagents are a delegation feature, not another file operation. When a request clearly splits into independent parallel work, haze can spin up focused agents with fresh context, let them inspect or act with their own capped tool loop, then fold their concise summaries back into the main conversation.
Use them for parallel investigation across separate areas of a codebase. Do not use them for single sequential tasks where the main agent already has the best context.
haze saves durable workspace sessions in ~/.haze/sessions. Settings, history, sessions, and debug logs use private POSIX directory/file permissions (0700/0600) and ordered, flushable writes. Use /session to see the current file, /new to start fresh, /resume to restore the latest session, and /compact to summarize older model context. Sessions also persist compact structured work state: the active goal, touched files, validation evidence, blockers, and next action.
Session files are optimized for resume and audit, not token-by-token playback: completed user/assistant messages, tool lifecycle events, conversation snapshots, and work-state snapshots are persisted, but streaming message_update events are skipped. Large persisted tool outputs are replaced with previews and byte counts so a resumed model can reread current files instead of carrying stale megabytes forward.
Long turns use bounded tool slices. Older successful tool results are compacted while failures and recent evidence remain verbatim, synthetic haze control nudges are not persisted as user requests, and token-pressure compaction preserves the structured work state.
haze loads project instructions from:
~/.claude/CLAUDE.md~/.haze/AGENTS.mdCLAUDE.md/AGENTS.mdfiles from filesystem root to the current workspace
At the same scope, AGENTS.md overrides CLAUDE.md; global haze guidance in ~/.haze/AGENTS.md overrides global Claude guidance in ~/.claude/CLAUDE.md. Nested CLAUDE.md / AGENTS.md files below the workspace are scoped: haze surfaces them only when file tools operate inside that directory or its subdirectories, injects newly discovered scoped guidance into the next model step, and mutating tools stop once so the model can review it before editing. Scoped context files are tracked by signature, so changed nested guidance can be read again later in the same session.
Use AGENTS.md for project conventions, commands, architecture notes, and things future-you does not want to re-explain. /init is intentionally budget-aware: it does one small discovery pass, preserves useful existing guidance, and asks for a compact file because context files are injected into every request.
- File tools are restricted to the current workspace.
- File tools follow
.gitignoreby default. - Ignored files require an explicit override.
- Bash commands are classified and shown with working-directory metadata, but haze does not use command confirmation gates.
- The
fetchtool reads publichttp(s)URLs only; private, loopback, link-local, and cloud-metadata hosts and non-http(s)schemes are blocked, and each redirect hop is connected to the already validated public IP to avoid DNS-rebinding races. - Mutating and destructive commands can run when they are relevant to the user's request; this is intentional for expert users.
- haze is powerful enough to help and dumb enough to deserve supervision. Ideal software, basically.
npm install
npm run dev
npm run typecheck
npm test
npm run lint
npm run build
npm run context:reportnpm run context:report prints estimated system, project-context, and tool-schema tokens without reading ~/.haze. Pass explicit context-file paths, or use npm run context:report -- --trace tests/fixtures/agent-traces/long-workflow.json for offline trace accounting.
Package check:
npm pack --dry-runThe npm package ships bin, dist, README, license, changelog, and examples.
npm run typecheck
npm test
npm run lint
npm run build
npm pack --dry-run
git tag vX.Y.Z
git push origin main --tags
npm publish --access publicMIT
{"type":"turn_start","request":"audit src/auth.ts","at":"2026-06-27T22:00:00.000Z"} {"type":"message_start","id":"a1","role":"assistant","at":"..."} {"type":"message_update","id":"a1","text":"Reading the auth module…","at":"..."} {"type":"tool_start","id":"t1","name":"readFile","at":"..."} {"type":"tool_end","id":"t1","name":"readFile","success":true,"durationMs":12,"at":"..."} {"type":"message_end","id":"a1","text":"Here are the findings…","at":"..."} {"type":"turn_end","request":"audit src/auth.ts","status":"complete","at":"..."} {"type":"result","status":"complete","result":"Here are the findings…","usage":{"inputTokens":0,"outputTokens":0,"cacheReadTokens":0,"cacheWriteTokens":0,"reasoningTokens":0}}