{
  "markdown": "<div align=\"center\">\n\n<img src=\"site/assets/logo.svg\" alt=\"heliograph, by DBHQ\" width=\"120\">\n\n# heliograph\n\n**Remote, captured, auditable execution on a machine you cannot log into**\n\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)\n[![Docs](https://img.shields.io/badge/docs-heliograph.dbhq.uk-4E7FB3)](https://heliograph.dbhq.uk)\n\nA free, open-source tool by [DBHQ](https://dbhq.uk)\n\n</div>\n\n---\n\nSomeone can reach the machine. You cannot, and you are the one who knows what\nto ask it. heliograph runs that gap as a loop rather than a relay: you publish\na step, it runs on the far side, and the whole run comes back as a log with\nevery line timestamped in UTC, whether it passed or failed.\n\n```\nyou        heliograph send net-probe ────────────────▶ transport\nstation    picks it up within seconds, runs it\n           pushes status, then the log ──────────────▶ transport\nyou        heliograph logs --last --gaps ◀────────────\n```\n\n## Does this sound familiar\n\n- You have **no SSH access to production**, and you are not going to be given any.\n- The environment is **air-gapped**, or behind a bastion, a jump host or a VPN you are not on.\n- It is a **client-owned or customer-managed estate**. Only their staff can log in.\n- Access is blocked by **policy, not capability**: regulated, restricted, change-controlled.\n- You are on the fourth round of **\"can you run this and paste the output\"**, and what came back was a screenshot of half a terminal.\n- You are an **AI coding agent** driving an investigation, and you need the evidence rather than somebody's summary of it.\n\nIf you can just SSH in, you do not need this.\n\n## Three roles, one boundary\n\nThe boundary is the gap, and the layout states it once:\n\n| | |\n|---|---|\n| **control** | your machine: the `heliograph` CLI, `heliograph mcp`, and the skill that drives them. Go, and whatever the near side can afford |\n| **transport** | the channel: git, relay, file share, bundle, object store - all behind one interface, so the read-only gates live in one place and cannot drift per transport |\n| **station** | the far side: [`station/bash/`](station/), planted into a private transport repo. Bash 4+, git and GNU coreutils. No packages, no credentials, no tunnel |\n\n**Nothing is ever installed on the far side.** The station is plain text you\ncan read before you run - bash 4+, or PowerShell 5.1 for a Windows estate that\nhas no bash and will not be given any - and no Go will ever appear under\n`station/` beyond the one file that lets the CLI carry the payload. CI\nenforces it. That constraint is the entire proposition on a locked-down\nbox where installing anything is its own change request.\n\n## Install\n\n```bash\n# Linux and macOS, from a release\ncurl -sSL https://github.com/dbhq-uk/heliograph/releases/latest/download/heliograph-linux-amd64 \\\n  -o /usr/local/bin/heliograph && chmod +x /usr/local/bin/heliograph\n\n# or from source\ngo install github.com/dbhq-uk/heliograph/cmd/heliograph@latest\n```\n\nA single static binary, no runtime. Checksums are published with each\nrelease, and the binary carries the station payload it was built with.\n\n**The agent skill** - the same loop, driven from Claude Code, Codex, Cursor\nand friends:\n\n```\n/plugin marketplace add dbhq-uk/marketplace\n/plugin install heliograph@dbhq         # Claude Code\n./install-codex.sh                      # Codex, from a clone\n./install.sh                            # Claude Code, from a clone\nnpx skills add dbhq-uk/heliograph       # any agent, via skills.sh\n```\n\n## Use\n\n```bash\nheliograph bootstrap ~/transport/payments             # plant the station payload\nheliograph init payments --dir ~/transport/payments   # git, the default\nheliograph plant                                      # what to send the operator\nheliograph send net-probe HOSTS=\"sql01 sql02\"         # publish a request\nheliograph watch                                      # follow it\nheliograph logs --last                                # read the whole log\nheliograph logs --last --gaps                         # where it stalled\nheliograph doctor                                     # will this work from here\nheliograph mcp                                        # serve all of the above as tools\n```\n\nThe operator's whole job is what `plant` prints: clone the transport repo,\nrun `./start.sh`, walk away. The loop is **read-only unless the operator said\notherwise**: every step declares itself (`# heliograph-mode: read-only` or\n`action`), one that declares neither does not run, and the station refuses an\naction unless it was started with `--allow-actions`. It will not run as root\neither.\n\nFor an agent, `heliograph mcp` is the same CLI as typed MCP tools:\n\n```bash\nclaude mcp add heliograph -- heliograph mcp\n```\n\nThe gates do not move. A tool call publishes a request; the station still\ndecides whether to run it.\n\n`--gaps` is the one worth knowing about. *\"Scan the timestamp column for gaps\nbefore reading the content\"* is the most valuable instruction in the method,\nand it is arithmetic:\n\n```\n$ heliograph logs --last --gaps\ndemo-20260906T183628Z.txt\n5 captured lines\n\n1 interval(s) of 10s or more, longest first.\nEach is attributed to the line BEFORE it, which is what was running.\n\n   3m12s  after  09:14:02 | Refreshing state...\n```\n\nThe gap belongs to the line **before** it: the stamp on a line is when that\nline was produced, so a long interval means the operation named on the\npreceding line is what took the time. A log where every line carries the same\ntimestamp is reported as an **error**, not as \"no gaps\".\n\n## Status\n\n| | |\n|---|---|\n| control CLI over git | works, tested end to end against a stock station |\n| `heliograph bootstrap` | works: the binary plants the station it was built with |\n| `--gaps` | works |\n| MCP server (`heliograph mcp`) | works |\n| bash station | in use over git: the loop, the gates, the capture, Azure hosts, Kubernetes, the Windows launcher |\n| relay | **half a transport.** The station side is written and complete - it fetches requests, publishes status and delivers the finished log - and the [relay server](https://github.com/dbhq-uk/heliograph-relay) is deployed. No CLI command can select it |\n| file share, bundle, object store | **control side only.** The CLI implements all three; the station has no transport for any of them |\n| Azure Blob | works end to end, through `drop.sh` in the station payload rather than the CLI. It is what the Azure Function host uses |\n| PowerShell station | planned: [A8](docs/specs/2026-09-08-powershell-station-and-full-documentation-design.md) |\n| documentation site | [heliograph.dbhq.uk](https://heliograph.dbhq.uk): the CLI, the transports, and the far side - the station, the runner, steps, hosts, Azure, Windows, containers, services, secrets, security and the capture contract |\n\nA transport that works on one side of the gap is not a transport, so this\ntable names both sides. Git is the one the CLI drives end to end; what the\nothers still need, and in what order, is\n[the roadmap](docs/plans/2026-09-08-powershell-and-docs-roadmap.md).\n\n## The relay\n\nBoth sides dial out over ordinary HTTPS, so an estate needs no git host, no\nstorage account and no VNet. Hosted, and self-hostable from the same binary.\n\n**Not yet usable end to end.** The station side is complete and the server is\ndeployed; no CLI command can select it, so the near side is the missing half.\n\n**The relay cannot read your logs, and cannot make a station run anything.**\nThat second half is the one that matters: a relay able to forge a request\nwould be code execution inside every estate at once. Content is end-to-end\nencrypted with keys the relay never holds, and every message is signed.\nNothing bespoke - [age](https://age-encryption.org/v1) primitives plus\nEd25519. The full account, including what DBHQ can and cannot honestly claim,\nis in\n[`docs/specs/2026-09-06-relay-encryption-design.md`](docs/specs/2026-09-06-relay-encryption-design.md).\nThe relay server is its own repository,\n[dbhq-uk/heliograph-relay](https://github.com/dbhq-uk/heliograph-relay),\nbecause it holds no keys and must be publicly, obviously incapable of reading\nanything it carries.\n\n## What it will not do\n\nGive you access you do not have. It does not tunnel, proxy or hold a\nconnection open to a host you control, and there is nothing here to punch\nthrough a firewall with. A raw TCP transport was considered and **dropped**\nfor exactly that reason: a persistent reverse connection is a C2 channel by\nany blue team's definition, and that sentence is a large part of why this\nclass of tool is permitted in regulated estates.\n\nEvery command runs on the far side because someone with legitimate access\nchose to run it.\n\n## Layout\n\n```\ncmd/heliograph/         the control CLI, and `heliograph mcp`\ncmd/heliograph-seal/    key generation for the relay transport\ncmd/heliograph-site/    the static site generator\ninternal/transport/     git | relay | share | bundle | objstore\ninternal/bootstrap/     `heliograph bootstrap`: plants the embedded station\ninternal/wire/          the request and status documents that cross the gap\ninternal/seal/          sign-then-encrypt, for the relay\ninternal/logfile/       gap analysis\ninternal/mcp/           JSON-RPC over stdio, no dependencies\ninternal/estate/        which transport a name refers to\ninternal/plant/         what to send the operator\nstation/bash/           the bash station: everything that runs on the far side\nstation/bootstrap.sh    the no-CLI bootstrap: clone this repo, run it by hand\nskills/heliograph/      the agent skill: drives the CLI, and nothing else\ntests/                  the station's own suite, conformance contract included\nsite/content/           the documentation, one source, three renderings\ninfra/                  terraform: DNS, Pages, R2 state\ndocs/specs/             the designs, written before the code\n```\n\nThe two halves used to be separate repositories, split along Go-versus-bash\nrather than along the gap, and every reader had to work out which half they\nwere looking at. `dbhq-uk/heliograph-skill` was merged in on 2026-09-08 with\nits full history; the reasoning is in\n[`docs/specs/2026-09-08-station-and-skill-merge.md`](docs/specs/2026-09-08-station-and-skill-merge.md).\n\n## Development\n\n[`PLAN.md`](PLAN.md) is where the work stands: what has landed, what is next,\nand which defects are known and unfixed.\n[`CONTRIBUTING.md`](CONTRIBUTING.md) covers working on it and\n[`AGENTS.md`](AGENTS.md) is for an AI agent doing so. The skill is\n[`skills/heliograph/SKILL.md`](skills/heliograph/SKILL.md);\n[`docs/dev-setup.md`](docs/dev-setup.md) sets it up from source with live\nedits.\n\n## Licence\n\n[MIT](LICENSE) (c) 2026 DBHQ Consulting Ltd\n",
  "bytes": 10714,
  "sha": "ac242fd66e8fe0497eeeb4979d59217bf3a4891750ad948d266104a09ee47d4e",
  "repo_slug": "dbhq-uk/heliograph",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_dbhq_uk_heliograph_70aba370/readme"
}