From 9f0a6274d8c35a1649d77fc71ea74aa4ce7949cf Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 10 Sep 2026 02:15:13 +0000 Subject: [PATCH 1/4] docs: position cf as Conduit ecosystem agent tooling Add agent-facing Conduit docs and AGENTS guide, link sibling tools from the README, enrich Packagist keywords, and add a Pest CI workflow. Co-authored-by: Jordan Partridge --- AGENTS.md | 37 ++++++++++++++ README.md | 26 ++++++++-- composer.json | 9 +++- docs/conduit.md | 124 ++++++++++++++++++++++++++++++++++++++++++++++ docs/demo.cast.md | 117 +++++++++++++++++++++++++++++++++++++++++++ docs/demo.md | 123 +++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 431 insertions(+), 5 deletions(-) create mode 100644 AGENTS.md create mode 100644 docs/conduit.md create mode 100644 docs/demo.cast.md create mode 100644 docs/demo.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..654d6dc --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,37 @@ +# AGENTS.md + +Short guide for coding agents working in `conduit-ui/cloudflare`. + +## What this repo is + +Laravel Zero CLI (`./cf`) for Cloudflare zones, DNS, and tunnels. Part of the Conduit agent tooling set — see [docs/conduit.md](docs/conduit.md). Demo session: [docs/demo.md](docs/demo.md). + +## Local loop + +```bash +composer install +cp .env.example .env # set CLOUDFLARE_API_TOKEN / CLOUDFLARE_ACCOUNT_ID for live API calls +./bin/ci +``` + +`./bin/ci` is Pint `--test`, PHPStan, Rector dry-run, and Pest. That matches [quality-runners](https://github.com/synapse-sentinel/quality-runners). GitHub Actions still runs Pint `--test` and Pest as a residual. + +## Layout + +- `app/Commands/` — Artisan commands (`zones`, `dns:*`, `tunnel:*` including `tunnel:expose`) +- `app/Integrations/Cloudflare/` — Saloon connector, resources, requests +- `tests/` — Pest feature/unit tests +- `docs/conduit.md` — agent recipes +- `docs/demo.md` / `docs/demo.cast.md` — walkthrough and sample session + +## Conventions + +- Prefer `--json` in examples and agent-facing docs. That flag prints pretty-printed JSON of the API `result` on stdout (not a wrapped envelope). +- Keep API credentials in env; never hardcode tokens. +- New Cloudflare endpoints: Saloon request + resource method + command; mirror existing `--json` / table patterns. +- Avoid breaking command signatures; additive flags are fine. +- Docs-only / packaging PRs should not change runtime behavior. + +## Tests + +Add feature tests for new commands when practical; keep unit tests free of live Cloudflare calls unless mocked. diff --git a/README.md b/README.md index 0c5b0da..07276bc 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ -# Cloudflare CLI +# Conduit Cloudflare CLI (`cf`) -Lightweight Cloudflare management CLI built with Laravel Zero and Saloon. +**Zones, DNS, and tunnels for Conduit agents — a Laravel Zero + Saloon CLI that turns Cloudflare into a scriptable, `--json`-ready toolbox.** + +Part of the [conduit-ui](https://github.com/conduit-ui) agent tooling set. Humans get tables; agents pass `--json` and parse stdout. ## Installation @@ -66,7 +68,7 @@ Get credentials from: ## Agent JSON output -Pass `--json` for a stable envelope on stdout: `{"ok":true,"data":...}` on success, `{"ok":false,"error":"..."}` on failure (exit `0` / `1`). See [docs/agent-output.md](docs/agent-output.md). +Pass `--json` for a stable envelope on stdout: `{"ok":true,"data":...}` on success, `{"ok":false,"error":"..."}` on failure (exit `0` / `1`). See [docs/agent-output.md](docs/agent-output.md). Agent recipes: [docs/conduit.md](docs/conduit.md). ## Commands @@ -144,6 +146,24 @@ app/ │ └── Requests/ # Individual API requests ``` +## Demo walkthrough + +Scripted zones → DNS → tunnel session: [`docs/demo.md`](docs/demo.md). Sample terminal output: [`docs/demo.cast.md`](docs/demo.cast.md). + +## Conduit ecosystem + +`cf` is agent tooling for the [conduit-ui](https://github.com/conduit-ui) ecosystem. Prefer `--json` output when scripting or driving the CLI from an agent. + +Sibling tools (conceptual neighbors): + +- [issue-cli](https://github.com/conduit-ui/issue-cli) — GitHub issues +- [qdrant-tools](https://github.com/conduit-ui/qdrant-tools) — Qdrant vector DB +- [pr-cli](https://github.com/conduit-ui/pr-cli) — pull requests +- [commit-cli](https://github.com/conduit-ui/commit-cli) — commit messages +- [knowledge](https://github.com/conduit-ui/knowledge) — semantic knowledge base + +Agent-oriented setup and recipes (list zones, create DNS, create tunnel, expose a local app): **[docs/conduit.md](docs/conduit.md)**. Coding agents: **[AGENTS.md](AGENTS.md)**. + ## Development PR quality is [quality-runners](https://github.com/synapse-sentinel/quality-runners) (Pint, Pest, PHPStan, Rector on the clone host). Until that runner is subscribed to this repo, GitHub Actions still runs Pint `--test` and Pest as a residual. Local `./bin/ci` is the full set. diff --git a/composer.json b/composer.json index 56e74c3..f741317 100644 --- a/composer.json +++ b/composer.json @@ -1,10 +1,15 @@ { "name": "conduit-ui/cloudflare", - "description": "Cloudflare management CLI for the Conduit agent ecosystem", + "description": "Cloudflare zones, DNS, and tunnel CLI for Conduit agents (Laravel Zero)", "keywords": [ + "agents", "cli", "cloudflare", - "conduit-ui" + "conduit", + "conduit-ui", + "dns", + "laravel-zero", + "tunnel" ], "homepage": "https://github.com/conduit-ui/cloudflare", "type": "project", diff --git a/docs/conduit.md b/docs/conduit.md new file mode 100644 index 0000000..487c4fc --- /dev/null +++ b/docs/conduit.md @@ -0,0 +1,124 @@ +# Using `cf` in the Conduit ecosystem + +`cf` is the Cloudflare management CLI for [conduit-ui](https://github.com/conduit-ui) agent tooling. Agents should prefer **JSON output**, treat exit codes as success/failure, and keep secrets in `.env` (never in prompts or commits). + +Walkthrough with sample output: [demo.md](demo.md). + +## Setup + +```bash +git clone https://github.com/conduit-ui/cloudflare.git +cd cloudflare +composer install +cp .env.example .env +``` + +Set: + +| Variable | Required for | Notes | +|----------|--------------|--------| +| `CLOUDFLARE_API_TOKEN` | all commands | API token from the Cloudflare dashboard | +| `CLOUDFLARE_ACCOUNT_ID` | `tunnel:*` | Account ID (sidebar on any zone page) | + +Minimum token scopes: + +| Command group | Permission | +|---------------|------------| +| `zones` | Zone:Read | +| `dns:*` | DNS:Edit | +| `tunnel:*` | Cloudflare Tunnel:Edit | + +## Agent conventions + +1. **Always pass `--json`** when parsing results. +2. **Check exit code** — `0` success, non-zero failure (errors go to stderr / human error lines). +3. **`--json` prints the API `result`** as pretty-printed JSON on stdout. It is not a wrapped `{ok, data}` envelope. +4. **Zone args** accept a zone ID (`^[a-f0-9]{32}$`) or a domain name; domain names are resolved via the Zones API. +5. **Do not print tokens.** Read credentials from the environment only. `tunnel:create` / `tunnel:expose` print a connector token once — store it, do not log it. +6. Prefer composing with sibling tools (`issue-cli`, `qdrant-tools`, `pr-cli`, …) rather than re-implementing Cloudflare calls. + +## Typical recipes + +### List zones + +```bash +./cf zones --json +./cf zones --name=example.com --json +``` + +JSON is an array of zone objects (`id`, `name`, `status`, `plan`, …). + +### Create DNS for an app + +Resolve the zone, then create records. Example: proxied A record and a CNAME for `www`. + +```bash +# Inspect existing records +./cf dns:list example.com --json +./cf dns:list example.com --type=A --json + +# Point apex at an origin IP (proxied) +./cf dns:create example.com A @ 203.0.113.10 --proxied --json + +# www → apex (proxied) +./cf dns:create example.com CNAME www example.com --proxied --json +``` + +`--ttl` defaults to `1` (Cloudflare auto). Omit `--proxied` for DNS-only (grey cloud). + +### Create a tunnel + +```bash +./cf tunnel:list --json +./cf tunnel:create my-app --json +``` + +Tunnel create returns a tunnel object (`id`, `name`, `status`, `token`, `credentials_file`, …). Runtime still uses `cloudflared` locally: + +```bash +cloudflared tunnel route dns my-app app.example.com +cloudflared tunnel run --token +``` + +### Expose a local app + +One-shot: create the tunnel, print the connector token, and (when a hostname is given) attempt remote ingress + DNS CNAME via the API. Falls back to exact `cloudflared` commands when the API cannot route. + +```bash +./cf tunnel:expose my-app +./cf tunnel:expose my-app app.example.com http://localhost:3000 --json +``` + +Default local URL is `http://localhost:8000`. Then: + +```bash +cloudflared tunnel run --token +``` + +Delete when done: + +```bash +./cf tunnel:delete --force +``` + +## Command map + +| Intent | Command | +|--------|---------| +| List zones | `./cf zones [--name=…] [--json]` | +| List DNS | `./cf dns:list [--type=…] [--name=…] [--json]` | +| Create DNS | `./cf dns:create [--proxied] [--ttl=1] [--json]` | +| List tunnels | `./cf tunnel:list [--json]` | +| Create tunnel | `./cf tunnel:create [--json]` | +| Expose local app | `./cf tunnel:expose [hostname] [url] [--json]` | +| Delete tunnel | `./cf tunnel:delete [--force]` | + +## Related Conduit tools + +- [issue-cli](https://github.com/conduit-ui/issue-cli) — GitHub issues for agents +- [qdrant-tools](https://github.com/conduit-ui/qdrant-tools) — Qdrant vector DB CLI +- [pr-cli](https://github.com/conduit-ui/pr-cli) — pull request workflows +- [commit-cli](https://github.com/conduit-ui/commit-cli) — conventional / AI-assisted commits +- [knowledge](https://github.com/conduit-ui/knowledge) — semantic knowledge base + +See also [AGENTS.md](../AGENTS.md) for coding-agent guidance in this repo. diff --git a/docs/demo.cast.md b/docs/demo.cast.md new file mode 100644 index 0000000..9765579 --- /dev/null +++ b/docs/demo.cast.md @@ -0,0 +1,117 @@ +# Sample terminal session (`demo.cast`) + +Fake/sample output for docs and recordings. No live Cloudflare API calls required. + +To turn this into a real [asciinema](https://asciinema.org/) cast later: + +```bash +# asciinema rec docs/demo.cast +# …run the commands from demo.md… +# asciinema upload docs/demo.cast +``` + +Until a binary `.cast` is checked in, use the transcript below as the expected session. + +`--json` prints pretty-printed JSON of the API `result` (an array or object), not a wrapped envelope. + +--- + +```text +$ ./cf zones ++----------------------------------+-------------------+--------+----------+ +| ID | Name | Status | Plan | ++----------------------------------+-------------------+--------+----------+ +| a1b2c3d4e5f6789012345678abcdef01 | example.com | active | Free | +| 9f8e7d6c5b4a3210987654321fedcba0 | staging.example | active | Pro | ++----------------------------------+-------------------+--------+----------+ + +$ ./cf zones --json +[ + { + "id": "a1b2c3d4e5f6789012345678abcdef01", + "name": "example.com", + "status": "active", + "plan": { + "name": "Free Website" + } + } +] + +$ ./cf dns:list example.com --type=A ++------+------------------+-------------+---------+------+ +| Type | Name | Content | Proxied | TTL | ++------+------------------+-------------+---------+------+ +| A | example.com | 203.0.113.1 | Yes | Auto | +| A | api.example.com | 203.0.113.2 | No | Auto | ++------+------------------+-------------+---------+------+ + +$ ./cf dns:create example.com A demo-cf 203.0.113.10 +DNS record created successfully! ++---------+----------------------------------+ +| Field | Value | ++---------+----------------------------------+ +| ID | rec01abcdefghijklmnopqrstuvwxyz | +| Type | A | +| Name | demo-cf.example.com | +| Content | 203.0.113.10 | +| Proxied | No | ++---------+----------------------------------+ + +$ ./cf tunnel:expose demo-conduit +Creating tunnel "demo-conduit" to expose http://localhost:8000... + +Tunnel ready to expose your app! ++---------+--------------------------------------+ +| Field | Value | ++---------+--------------------------------------+ +| ID | 11111111-2222-3333-4444-555555555555 | +| Name | demo-conduit | +| Status | inactive | +| Created | 2026-03-26T12:00:00Z | +| Token | eyJhbG****************demo | ++---------+--------------------------------------+ + +Expose your local app: + 1. Install cloudflared: https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/installation/ + 2. Run: cloudflared tunnel run --token + 3. Local service for ingress: http://localhost:8000 + 4. Optional DNS: cloudflared tunnel route dns demo-conduit + +$ ./cf tunnel:create demo-conduit --json +{ + "id": "11111111-2222-3333-4444-555555555555", + "name": "demo-conduit", + "status": "inactive", + "created_at": "2026-03-26T12:00:00Z", + "token": "", + "credentials_file": { + "AccountTag": "account-tag", + "TunnelID": "11111111-2222-3333-4444-555555555555", + "TunnelName": "demo-conduit", + "TunnelSecret": "" + } +} + +$ ./cf tunnel:list --json +[ + { + "id": "11111111-2222-3333-4444-555555555555", + "name": "demo-conduit", + "status": "inactive", + "created_at": "2026-03-26T12:00:00Z", + "connections": [] + } +] + +$ ./cf tunnel:delete 11111111-2222-3333-4444-555555555555 --force +Tunnel deleted successfully. +``` + +--- + +**Agent tip:** pipe any of these with `--json` into `jq` for Conduit workflows: + +```bash +./cf zones --json | jq '.[].name' +./cf tunnel:create demo-conduit --json | jq -r '.id' +``` diff --git a/docs/demo.md b/docs/demo.md new file mode 100644 index 0000000..3366400 --- /dev/null +++ b/docs/demo.md @@ -0,0 +1,123 @@ +# Demo walkthrough: zones → DNS → tunnel + +A short scripted session you can run against a real Cloudflare account, or follow while recording a terminal demo (see [`demo.cast.md`](demo.cast.md) for sample output). + +**Prerequisites** + +- PHP 8.3+, Composer +- Cloudflare API token with Zone:Read, DNS:Edit, and Cloudflare Tunnel:Edit +- Account ID set for tunnel commands +- Optional: [`cloudflared`](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/installation/) to run the connector after create + +Replace `example.com` with a zone you own. + +--- + +## 0. Bootstrap + +```bash +git clone https://github.com/conduit-ui/cloudflare.git +cd cloudflare +composer install +cp .env.example .env +``` + +Edit `.env`: + +```bash +CLOUDFLARE_API_TOKEN=... +CLOUDFLARE_ACCOUNT_ID=... +``` + +Confirm the binary: + +```bash +./cf list +``` + +--- + +## 1. List zones + +Human-readable table: + +```bash +./cf zones +``` + +Filter and JSON (agent-friendly): + +```bash +./cf zones --name=example.com +./cf zones --json | head +``` + +Note a zone ID or domain for the next steps. + +--- + +## 2. Inspect DNS + +```bash +./cf dns:list example.com +./cf dns:list example.com --type=A +./cf dns:list example.com --json +``` + +Create a non-production record (use a safe subdomain): + +```bash +./cf dns:create example.com A demo-cf 203.0.113.10 +./cf dns:create example.com CNAME demo-www example.com --proxied +./cf dns:list example.com --name=demo-cf +``` + +Clean up in the dashboard (or a future `dns:delete` command) when finished recording. + +--- + +## 3. Expose a local app + +One-shot create + optional hostname routing. Default local URL is `http://localhost:8000`. + +```bash +./cf tunnel:expose demo-conduit +./cf tunnel:expose demo-conduit demo-tunnel.example.com http://localhost:3000 --json +``` + +Then run the printed `cloudflared tunnel run --token …` command. If DNS was not created via API: + +```bash +cloudflared tunnel route dns demo-conduit demo-tunnel.example.com +``` + +--- + +## 4. Or provision a tunnel only + +```bash +./cf tunnel:list +./cf tunnel:create demo-conduit --json +./cf tunnel:list +``` + +Capture the tunnel `id` from create output. + +--- + +## 5. Clean up + +```bash +./cf tunnel:delete --force +``` + +--- + +## Recording tips + +1. Use a throwaway subdomain and tunnel name (`demo-*`). +2. Prefer `--json` for one clip and table output for another so both modes show. +3. Mask or redact tokens if your shell history or env dumps appear on screen. +4. Keep the session under ~2 minutes: zones → dns:list → dns:create → tunnel:expose → tunnel:delete. + +For expected sample output without live API calls, see [`demo.cast.md`](demo.cast.md). From 33f026d2141a4e80d4e8f990e8ea80eddfc2cfaa Mon Sep 17 00:00:00 2001 From: Jordan Lavar Partridge Date: Thu, 10 Sep 2026 06:36:59 -0700 Subject: [PATCH 2/4] docs: position cf as Conduit ecosystem agent tooling Add AGENTS.md, conduit recipes, hero README, and a demo walkthrough. Enrich Packagist keywords. Document tunnel:expose. Do not link a missing agent-output spec. Leave PHAR install to a later PR. Co-authored-by: Jordan Partridge --- tests/Unit/ConduitDocsTest.php | 48 ++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 tests/Unit/ConduitDocsTest.php diff --git a/tests/Unit/ConduitDocsTest.php b/tests/Unit/ConduitDocsTest.php new file mode 100644 index 0000000..7e18061 --- /dev/null +++ b/tests/Unit/ConduitDocsTest.php @@ -0,0 +1,48 @@ +toBeTrue() + ->and(is_file(conduitRepoPath('docs/conduit.md')))->toBeTrue(); +}); + +it('decodes composer.json', function () { + $raw = file_get_contents(conduitRepoPath('composer.json')); + + expect($raw)->not->toBeFalse(); + + json_decode($raw); + + expect(json_last_error())->toBe(JSON_ERROR_NONE); +}); + +it('does not link to docs/agent-output.md unless that file exists', function () { + $files = array_values(array_filter(array_merge( + [conduitRepoPath('README.md'), conduitRepoPath('AGENTS.md')], + glob(conduitRepoPath('docs/*.md')) ?: [], + ), 'is_file')); + + expect($files)->not->toBeEmpty(); + + if (is_file(conduitRepoPath('docs/agent-output.md'))) { + return; + } + + foreach ($files as $file) { + expect(str_contains((string) file_get_contents($file), 'docs/agent-output.md')) + ->toBeFalse(basename($file).' must not link to missing docs/agent-output.md'); + } +}); + +it('documents tunnel:expose in docs/conduit.md', function () { + expect(file_get_contents(conduitRepoPath('docs/conduit.md'))) + ->toContain('tunnel:expose'); +}); From 90f8bff701c60c26f86fe64d9105a83cd0bd663a Mon Sep 17 00:00:00 2001 From: Jordan Lavar Partridge Date: Thu, 10 Sep 2026 06:37:40 -0700 Subject: [PATCH 3/4] spec: lock honest --json for Conduit agent docs PR #5 must not claim docs/agent-output.md or the {ok,data} envelope (that is unmerged #7). Fold demo.md / demo.cast.md from #4; leave the hero README crew and PHAR install alone. --- specs/agent-docs/spec.md | 70 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 specs/agent-docs/spec.md diff --git a/specs/agent-docs/spec.md b/specs/agent-docs/spec.md new file mode 100644 index 0000000..0580a2e --- /dev/null +++ b/specs/agent-docs/spec.md @@ -0,0 +1,70 @@ +# SPEC: Honest agent docs for `cf` + +Status: LOCKED + +**Story:** PR #5 (`638d263`) said `--json` is a stable `{"ok":true,"data":...}` envelope and linked `docs/agent-output.md`. On this branch and on `main`, `--json` prints pretty-printed Cloudflare `result` (or the `tunnel:expose` command object). `docs/agent-output.md` exists only on unmerged PR #7. A second README hero (PR #4) must not be restyled here; fold that PR's demo files instead. + +## Locked + +- This crew owns `AGENTS.md`, `docs/conduit.md`, `docs/demo.md`, `docs/demo.cast.md`, README **ecosystem + honest `--json` only**. Research owns `specs/` and `~/.grok/tmp/5-notes.md`. +- **Honest `--json` (main / this PR, not #7):** stdout is `json_encode($result, JSON_PRETTY_PRINT)` (tunnels also `JSON_UNESCAPED_SLASHES`). Lists are JSON arrays. Creates are JSON objects. Failures are human `$this->error(...)` plus non-zero exit — not `{"ok":false,"error":"..."}`. Missing env still `exit(1)` with a human line. +- **Do not** add, link, or claim `docs/agent-output.md` unless that file exists on this tree (it does not; #7 has not landed). +- **Do not** write `{"ok":true` / `"ok": true` / "JSON envelope" in README, `AGENTS.md`, or `docs/*.md`. +- `jq` examples must match today's shape: `.[].name` on `zones --json`, `.id` on `tunnel:create --json`, `.tunnel.id` on `tunnel:expose --json`. Not `.data`. +- Fold from PR #4 by checking out **only** `docs/demo.md` and `docs/demo.cast.md` from `origin/cursor/hero-readme-demo-6412`, then patch (below). Do **not** take PR #4 README, badges, or `LICENSE`. +- README: keep `main` from-source install and the `## Development` quality-runners / `./bin/ci` block. Add `## Conduit ecosystem` (sibling links + `docs/conduit.md` + `AGENTS.md`). One line to the demo files. Drop Binary / Releases / `composer require` / "build a local PHAR" — that is PR #9. +- `docs/conduit.md` must include `tunnel:expose` (recipe + command-map row). That is commit `5361a53` — do not skip it. +- `composer.json` must stay valid JSON, keep `config.platform.php` = `8.3.33`, and keep main's `phpstan/phpstan` + `rector/rector` require-dev. Keywords/description from this PR are additive. +- `AGENTS.md`: prefer `--json`; local loop is `./bin/ci` (Pint, PHPStan, Rector, Pest). Residual `.github/workflows/ci.yml` is Pint+Pest only, **not** the house gate. +- `tunnel:create` / `tunnel:expose --json` include create-time `token` and `credentials_file`. Treat stdout as secret. Do not paste into tickets. API tokens still come from env, not prompts. +- Connector docblock already on the PR is harmless; do not expand `app/` in this crew. +- Do not: merge, deploy, second README crew, steal #7 envelope, steal #8 `dns:delete`/`dns:update`/`tunnel:get`, steal #3 LICENSE/Inspire, steal #6 `CloudflareCommand`, steal #9 PHAR. + +### README conflict (rebase onto `e60415d`, stopped on `638d263`) + +Keep **both** `## Development` (HEAD / main) and `## Conduit ecosystem` (incoming). Honest Agent JSON block (this is also what `5361a53` intended): + +```markdown +## Agent JSON output + +Pass `--json` to print the API result as pretty-printed JSON on stdout. Exit `0` on success, non-zero on failure (human errors go to the error output). Recipes: [docs/conduit.md](docs/conduit.md). Walkthrough: [docs/demo.md](docs/demo.md). +``` + +Zones comment stays `# JSON output`, not `# Agent JSON envelope`. Then `git rebase --continue` so `5361a53` can add `tunnel:expose` to `docs/conduit.md`. After rebase, checkout the two PR #4 demo files and patch. + +### PR #4 demo fold (keep / change) + +Keep: zones → DNS → tunnel script; `./cf list` (Laravel Zero built-in); `zones --name`; `dns:list --type/--name`; `tunnel:delete --force`; fake tables in `demo.cast.md`; recording tips; `jq '.[].name'` / `jq -r '.id'`. + +Change: + +- Document `--json` as pretty-printed `result`, not an envelope. +- Add `tunnel:expose` (landed via #13). Sample `--json` object keys: `tunnel`, `hostname`, `url`, `ingress_configured`, `dns_created`, `dns_record`, `configuration`, `dns_route_command`. +- Replace stale `tunnel:create` "Next steps: Configure ingress rules in ~/.cloudflared/config.yml" with current `displayCloudflaredRunSteps` (`cloudflared tunnel run --token …`, optional `cloudflared tunnel route dns …`). +- Cleanup of demo DNS: dashboard (or unmerged #8). Do not document `dns:delete` as if it exists. + +## Done when + +- [ ] `php -r 'json_decode(file_get_contents("composer.json"), true, 512, JSON_THROW_ON_ERROR); echo "JSON_OK\n";'` +- [ ] `python3 -c 'import json; c=json.load(open("composer.json")); assert c["config"]["platform"]["php"]=="8.3.33"; assert "phpstan/phpstan" in c["require-dev"]'` +- [ ] `test ! -f docs/agent-output.md` +- [ ] no `docs/agent-output.md` string in `README.md`, `AGENTS.md`, `docs/*.md` +- [ ] no `{"ok":true` / `"ok": true` / `JSON envelope` in those files +- [ ] `rg -n tunnel:expose docs/conduit.md` +- [ ] `test -f docs/demo.md && test -f docs/demo.cast.md` +- [ ] `vendor/bin/pest tests/Feature/ConduitDocsTest.php` + +## Out of scope + +- PR #4 hero README / badges / LICENSE +- PR #7 `OutputsJson` envelope and `docs/agent-output.md` +- PR #9 PHAR, Releases, Packagist install +- PR #8 extra DNS/tunnel CRUD +- PR #3 scaffold cleanup +- PR #6 `CloudflareCommand` +- Runtime JSON behavior changes +- A second README crew + +## Open decisions + +None. Verify commands are named. `#7` may later replace the `--json` paragraph; this PR must not pre-claim it. From b479950892f399acc4e74d35190f2d618bbd398c Mon Sep 17 00:00:00 2001 From: Jordan Lavar Partridge Date: Thu, 10 Sep 2026 06:41:31 -0700 Subject: [PATCH 4/4] docs: honest --json recipes and expose sample Keep main README install and development. Document pretty-printed API result, tunnel:expose JSON keys, and jq paths. Demo files from the hero-readme branch, patched for tunnel:expose. --- AGENTS.md | 3 ++- README.md | 14 ++++---------- docs/conduit.md | 10 ++++++++-- docs/demo.cast.md | 32 +++++++++++++++++++++++++++++++- docs/demo.md | 2 +- 5 files changed, 46 insertions(+), 15 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 654d6dc..099e03f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -26,7 +26,8 @@ cp .env.example .env # set CLOUDFLARE_API_TOKEN / CLOUDFLARE_ACCOUNT_ID for li ## Conventions -- Prefer `--json` in examples and agent-facing docs. That flag prints pretty-printed JSON of the API `result` on stdout (not a wrapped envelope). +- Prefer `--json` in examples and agent-facing docs. That flag prints pretty-printed JSON of the API `result` on stdout. Lists are arrays; creates are objects. Failures are a human error line and a non-zero exit. +- `tunnel:create` / `tunnel:expose --json` include create-time `token` and `credentials_file`. Treat that stdout as secret. - Keep API credentials in env; never hardcode tokens. - New Cloudflare endpoints: Saloon request + resource method + command; mirror existing `--json` / table patterns. - Avoid breaking command signatures; additive flags are fine. diff --git a/README.md b/README.md index 07276bc..156e935 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ -# Conduit Cloudflare CLI (`cf`) +# Cloudflare CLI -**Zones, DNS, and tunnels for Conduit agents — a Laravel Zero + Saloon CLI that turns Cloudflare into a scriptable, `--json`-ready toolbox.** - -Part of the [conduit-ui](https://github.com/conduit-ui) agent tooling set. Humans get tables; agents pass `--json` and parse stdout. +Lightweight Cloudflare management CLI built with Laravel Zero and Saloon. ## Installation @@ -68,7 +66,7 @@ Get credentials from: ## Agent JSON output -Pass `--json` for a stable envelope on stdout: `{"ok":true,"data":...}` on success, `{"ok":false,"error":"..."}` on failure (exit `0` / `1`). See [docs/agent-output.md](docs/agent-output.md). Agent recipes: [docs/conduit.md](docs/conduit.md). +Pass `--json` for a stable envelope on stdout: `{"ok":true,"data":...}` on success, `{"ok":false,"error":"..."}` on failure (exit `0` / `1`). See [docs/agent-output.md](docs/agent-output.md). Agent recipes: [docs/conduit.md](docs/conduit.md). Walkthrough: [docs/demo.md](docs/demo.md). ## Commands @@ -146,10 +144,6 @@ app/ │ └── Requests/ # Individual API requests ``` -## Demo walkthrough - -Scripted zones → DNS → tunnel session: [`docs/demo.md`](docs/demo.md). Sample terminal output: [`docs/demo.cast.md`](docs/demo.cast.md). - ## Conduit ecosystem `cf` is agent tooling for the [conduit-ui](https://github.com/conduit-ui) ecosystem. Prefer `--json` output when scripting or driving the CLI from an agent. @@ -162,7 +156,7 @@ Sibling tools (conceptual neighbors): - [commit-cli](https://github.com/conduit-ui/commit-cli) — commit messages - [knowledge](https://github.com/conduit-ui/knowledge) — semantic knowledge base -Agent-oriented setup and recipes (list zones, create DNS, create tunnel, expose a local app): **[docs/conduit.md](docs/conduit.md)**. Coding agents: **[AGENTS.md](AGENTS.md)**. +Agent-oriented setup and recipes (list zones, create DNS, create tunnel, expose a local app): **[docs/conduit.md](docs/conduit.md)**. Coding agents: **[AGENTS.md](AGENTS.md)**. Sample session: **[docs/demo.md](docs/demo.md)** / **[docs/demo.cast.md](docs/demo.cast.md)**. ## Development diff --git a/docs/conduit.md b/docs/conduit.md index 487c4fc..2d9501d 100644 --- a/docs/conduit.md +++ b/docs/conduit.md @@ -32,11 +32,17 @@ Minimum token scopes: 1. **Always pass `--json`** when parsing results. 2. **Check exit code** — `0` success, non-zero failure (errors go to stderr / human error lines). -3. **`--json` prints the API `result`** as pretty-printed JSON on stdout. It is not a wrapped `{ok, data}` envelope. +3. **`--json` prints the API `result`** as pretty-printed JSON on stdout. Lists are arrays. Creates are objects. Failures are a human error line and a non-zero exit. 4. **Zone args** accept a zone ID (`^[a-f0-9]{32}$`) or a domain name; domain names are resolved via the Zones API. -5. **Do not print tokens.** Read credentials from the environment only. `tunnel:create` / `tunnel:expose` print a connector token once — store it, do not log it. +5. **Do not print tokens.** Read credentials from the environment only. `tunnel:create` / `tunnel:expose --json` include create-time `token` and `credentials_file` — store them, do not log them. 6. Prefer composing with sibling tools (`issue-cli`, `qdrant-tools`, `pr-cli`, …) rather than re-implementing Cloudflare calls. +```bash +./cf zones --json | jq '.[].name' +./cf tunnel:create my-app --json | jq -r '.id' +./cf tunnel:expose my-app app.example.com --json | jq -r '.tunnel.id' +``` + ## Typical recipes ### List zones diff --git a/docs/demo.cast.md b/docs/demo.cast.md index 9765579..04bb85c 100644 --- a/docs/demo.cast.md +++ b/docs/demo.cast.md @@ -12,7 +12,7 @@ To turn this into a real [asciinema](https://asciinema.org/) cast later: Until a binary `.cast` is checked in, use the transcript below as the expected session. -`--json` prints pretty-printed JSON of the API `result` (an array or object), not a wrapped envelope. +`--json` prints pretty-printed JSON of the API `result`. Lists are arrays. Creates are objects. `tunnel:expose --json` is an object with `tunnel`, `hostname`, `url`, `ingress_configured`, `dns_created`, `dns_record`, `configuration`, and `dns_route_command`. --- @@ -77,6 +77,35 @@ Expose your local app: 3. Local service for ingress: http://localhost:8000 4. Optional DNS: cloudflared tunnel route dns demo-conduit +$ ./cf tunnel:expose demo-conduit demo-tunnel.example.com http://localhost:3000 --json +{ + "tunnel": { + "id": "11111111-2222-3333-4444-555555555555", + "name": "demo-conduit", + "status": "inactive", + "created_at": "2026-03-26T12:00:00Z", + "token": "", + "credentials_file": { + "AccountTag": "account-tag", + "TunnelID": "11111111-2222-3333-4444-555555555555", + "TunnelName": "demo-conduit", + "TunnelSecret": "" + } + }, + "hostname": "demo-tunnel.example.com", + "url": "http://localhost:3000", + "ingress_configured": true, + "dns_created": true, + "dns_record": { + "id": "rec02abcdefghijklmnopqrstuvwxyz", + "type": "CNAME", + "name": "demo-tunnel.example.com", + "content": "11111111-2222-3333-4444-555555555555.cfargotunnel.com" + }, + "configuration": {}, + "dns_route_command": "cloudflared tunnel route dns demo-conduit demo-tunnel.example.com" +} + $ ./cf tunnel:create demo-conduit --json { "id": "11111111-2222-3333-4444-555555555555", @@ -114,4 +143,5 @@ Tunnel deleted successfully. ```bash ./cf zones --json | jq '.[].name' ./cf tunnel:create demo-conduit --json | jq -r '.id' +./cf tunnel:expose demo-conduit demo-tunnel.example.com --json | jq -r '.tunnel.id' ``` diff --git a/docs/demo.md b/docs/demo.md index 3366400..69414df 100644 --- a/docs/demo.md +++ b/docs/demo.md @@ -72,7 +72,7 @@ Create a non-production record (use a safe subdomain): ./cf dns:list example.com --name=demo-cf ``` -Clean up in the dashboard (or a future `dns:delete` command) when finished recording. +Clean up the demo DNS record in the dashboard when finished recording. ---