diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..0d29535 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,78 @@ +# Contributing + +Thanks for taking a look. Posel is pre-1.0 and moving quickly — issues and PRs are welcome. + +## Build & run + +Requires a current stable Rust toolchain. GPUI uses recently-stabilized std features; if a build +fails with `E0658`, run `rustup update stable`. + +```bash +cargo run -p posel-app # the desktop app (binary: posel) +cargo run -p posel-cli -- # headless engine driver +cargo test --workspace +cargo clippy --workspace --all-targets -- --deny warnings +cargo fmt --all --check # CI enforces this +``` + +A workspace can be opened by passing a path as the first argument to the app, or via the +`POSEL_WORKSPACE` environment variable: + +```bash +cargo run -p posel-app -- examples/github-api +``` + +### Linux build prerequisites + +GPUI links against system libraries that need their development packages present. Without them the +build fails inside `yeslogic-fontconfig-sys` with *"The pkg-config command could not be found"*, +which reads like a toolchain problem but isn't: + +```bash +# Debian / Ubuntu +sudo apt install -y pkg-config libfontconfig-dev libfreetype-dev \ + libxkbcommon-dev libxkbcommon-x11-dev libwayland-dev libxcb1-dev libx11-dev +``` + +## Layout + +The domain logic is headless and unit-tested; GPUI is confined to the edge (`ui`/`app` only). + +| Crate | Responsibility | +|-------|----------------| +| `core` | Protocol-tagged request/collection model (the source of truth) | +| `template` | Variable / secret / dynamic-value resolution | +| `engine` | tokio runtime, the `Transport` trait + HTTP transport, request resolution | +| `schema` | GraphQL brains: introspection → queryable schema, completion, formatting, validation | +| `storage` | git-friendly `.toml` format, secret backends, history, file-watching | +| `ui` | GPUI widgets: editors, viewers, sidebar, palette (protocol-aware via enum dispatch) | +| `app` | `posel` desktop binary | +| `cli` | `posel-cli` headless engine driver | + +`core`, `template`, `engine`, `schema`, and `storage` carry no UI dependency and are tested in +isolation. A new protocol is a new `Transport` impl plus a registered editor/viewer; the UI stays +protocol-agnostic. See [docs/ARCHITECTURE.md](./docs/ARCHITECTURE.md). + +## A note on the GPUI dependency + +GPUI ships only as a git dependency (no crates.io release), and Posel carries a handful of small, +upstreamable editor enhancements to `gpui-component` — a completion trigger, bracket-aware +auto-indent, and bracket/quote auto-pairing among them. Those live on a public fork, +[`starvy/gpui-component`](https://github.com/starvy/gpui-component), pinned here to an immutable tag +so the whole graph resolves to a single, reproducible `gpui`. No local checkout is needed — +`cargo build` fetches everything. + +Because the fork pins `gpui` itself, the two must move in lockstep: bumping one without the other +gives you two incompatible copies of `gpui` in the graph. + +## Conventions + +- **Conventional Commits** — `type(scope): subject`, scope is the crate (`ui`, `engine`, `storage`, …). +- **GPUI at the edge.** Only `ui` and `app` may depend on `gpui`/`gpui-component`. +- **Never block the GPUI thread.** IO runs on the engine's tokio runtime and is marshaled back + via channels. +- `thiserror` in libraries, `anyhow` at binary edges. No `unwrap`/`expect` on fallible library paths. +- Tests track behavior, not lines — cover engine/storage/template logic and tricky edge cases. + +Before pushing, run the same gate CI does: `cargo fmt --all --check`, `cargo clippy --workspace +--all-targets -- --deny warnings`, `cargo test --workspace`. diff --git a/README.md b/README.md index 427028e..c139e8d 100644 --- a/README.md +++ b/README.md @@ -1,186 +1,127 @@ # Posel -A fast, **native** API client for developers — REST and GraphQL first, multi-protocol by -design. No webview, no account, no cloud. Your collections are plain files on disk you can -commit to git. +**A native API client for REST, GraphQL, and tRPC. Your requests are files.** -Built in Rust on [GPUI](https://www.gpui.rs/) (the framework behind the Zed editor) and the -[gpui-component](https://github.com/longbridge/gpui-component) widget library. +[![CI](https://github.com/starvy/posel/actions/workflows/ci.yml/badge.svg)](https://github.com/starvy/posel/actions/workflows/ci.yml) +[![Release](https://img.shields.io/github/v/release/starvy/posel)](https://github.com/starvy/posel/releases/latest) +[![License](https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue)](#license) +[![Built with Rust](https://img.shields.io/badge/built%20with-Rust-dea584?logo=rust&logoColor=white)](https://www.rust-lang.org) -![Posel — sending REST and GraphQL requests, switching environments, and inspecting response timing](docs/assets/posel-demo.gif) +Posel stores your requests as plain `.toml` files in a folder you own. + +Diff them, review them, branch them — collaboration is just git. -Opening a saved request, sending it with Ctrl+Enter, resolving -`{{variables}}` against an environment, inspecting headers and the timing waterfall, running a -GraphQL query, and the command palette. ([higher-quality MP4](docs/assets/posel-demo.mp4)) +No account, no sync server, no telemetry. Not now, not later. -## Why another API client? +![Posel — sending REST and GraphQL requests, switching environments, and inspecting response timing](docs/assets/posel-demo.gif) -Existing clients are mostly either cloud-first and heavy, or built on a web runtime. Posel's bet -is the opposite corner of the design space: +[Download](https://github.com/starvy/posel/releases) · [Docs](./docs) · [Architecture](./docs/ARCHITECTURE.md) · [Contributing](./CONTRIBUTING.md) -- **Native speed & huge payloads** — responses stream on a tokio runtime and render - incrementally; no webview to choke on large bodies. -- **Multi-protocol depth** — REST + GraphQL today, behind a clean *Protocol vs Transport* - boundary so gRPC and WebSocket/SSE slot in additively rather than as bolt-ons. -- **Git-native & local-first** — collections are human-readable `.toml`; collaboration is just - git. Secrets stay out of the repo — in your OS keychain by default, or another backend you - pick ([Secrets](#secrets)). -- **Keyboard-driven / Zed-like** — command palette, quick-open, env switcher, multi-tab; a - keyboard-first flow with minimal chrome. +## Your requests are files -## Status +This is a whole request — variables, and tests that run on every send: + +```toml +name = "Get a user" +method = "GET" +url = "{{base_url}}/users/{{username}}" + +[[assertions]] +target = "status" +op = "equals" +value = "200" + +[[assertions]] +target = "json" +path = "login" +op = "equals" +value = "{{username}}" +``` -Working, in active development. Runs on **macOS, Linux, and Windows**. +Commit it. Review it in a PR. Run it in CI with `posel-cli test`. -- **REST** — request editor (method, query/headers/body), streaming response panel, - environments + variables, pluggable secret storage, request history. -- **GraphQL** — schema introspection from the endpoint, schema-aware autocomplete, query - formatting, inline validation, and a dedicated data/errors viewer. -- **Workspace** — git-friendly `.toml` collections, live reflection of external file edits, - full collection CRUD, multi-tab requests, command palette. -- **Headless CLI** — `posel-cli` drives the same engine without the UI (REST send, GraphQL - introspect/query). +## Features -See [docs/PLAN.md](./docs/PLAN.md) for the phased roadmap and what's next. +- **REST, GraphQL, and tRPC** — GraphQL gets schema introspection, schema-aware autocomplete, + formatting, and inline validation. +- **Native, not a webview** — responses stream on a tokio runtime and render incrementally, so a + 100 MB body opens and scrolls without jank. +- **Environments & variables** — `{{base_url}}` per environment, with a switcher. +- **Secrets stay out of the repo** — only the `{{secret:name}}` reference is written to disk. +- **Assertions & a headless CLI** — `posel-cli test` runs the same requests in CI. +- **Import & export** — bring in OpenAPI 3.x specs or a `curl` command; copy any request back out + as curl or JS fetch. +- **Keyboard-first** — command palette, quick-open, env switcher, multi-tab. Ctrl+Enter sends. ## Install -Grab the latest build for your platform from the -[Releases](https://github.com/starvy/posel/releases) page: +Grab a build from the [Releases](https://github.com/starvy/posel/releases) page: -- **macOS** — `.dmg` (universal: Apple Silicon + Intel) -- **Linux** — `.AppImage` (portable) or `.deb` (Debian/Ubuntu) -- **Windows** — `.msi` installer +| Platform | Download | +|----------|----------| +| macOS | `.dmg` (universal — Apple Silicon + Intel) | +| Linux | `.AppImage` (portable) or `.deb` | +| Windows | `.msi` | -> **Unsigned builds.** Releases aren't code-signed yet, so the OS warns on first launch: -> - **macOS** — right-click the app → **Open**, or `xattr -dr com.apple.quarantine /Applications/Posel.app`. -> - **Windows** — SmartScreen → **More info** → **Run anyway**. -> -> This goes away once signing/notarization is set up. +
+First launch on macOS or Windows -## Architecture +Releases aren't code-signed yet, so the OS warns the first time: -The domain logic is headless and unit-tested; GPUI is confined to the edge (`ui`/`app` only). +- **macOS** — right-click the app → **Open**, or + `xattr -dr com.apple.quarantine /Applications/Posel.app` +- **Windows** — SmartScreen → **More info** → **Run anyway** -| Crate | Responsibility | -|-------|----------------| -| `core` | Protocol-tagged request/collection model (the source of truth) | -| `template` | Variable / secret / dynamic-value resolution | -| `engine` | tokio runtime, the `Transport` trait + HTTP transport, REST & GraphQL resolution | -| `schema` | GraphQL brains: introspection → queryable schema, completion, formatting, validation | -| `storage` | git-friendly `.toml` format, pluggable secret backends, history, file-watching | -| `ui` | GPUI widgets: editors, viewers, sidebar, palette (protocol-aware via enum dispatch) | -| `app` | `posel` desktop binary | -| `cli` | `posel-cli` headless engine driver | +This goes away once signing and notarization are set up. -`core`, `template`, `engine`, `schema`, and `storage` carry no UI dependency and are tested in -isolation. New protocols are a new `Transport` impl plus a registered editor/viewer; the UI -stays protocol-agnostic. See [docs/ARCHITECTURE.md](./docs/ARCHITECTURE.md). +
-## Build & run +Or build it yourself — see [CONTRIBUTING.md](./CONTRIBUTING.md). -Requires a current stable Rust toolchain (GPUI uses recently-stabilized std features; run -`rustup update stable` if a build fails with `E0658`). Builds on macOS, Linux, and Windows; -secrets default to the platform keychain (macOS Keychain, Windows Credential Manager, Linux -Secret Service) and can be pointed elsewhere — see [Secrets](#secrets). +## Quick start -On **Linux**, GPUI links against system libraries that need their development packages present, -or the build fails in `yeslogic-fontconfig-sys` with *"The pkg-config command could not be -found"*: +There's a ready-made workspace in [`examples/github-api`](./examples/github-api): 36 requests across +seven collections, REST and GraphQL, two environments — and a `Meta` collection that needs no token. ```bash -# Debian / Ubuntu -sudo apt install -y pkg-config libfontconfig-dev libfreetype-dev \ - libxkbcommon-dev libxkbcommon-x11-dev libwayland-dev libxcb1-dev libx11-dev +cargo run -p posel-app -- examples/github-api # or File ▸ Open Workspace… ``` +Pick **Meta ▸ Get zen** and hit Ctrl+Enter. Same thing headless: + ```bash -cargo run -p posel-app # the desktop app (binary: posel) -cargo run -p posel-cli -- # headless engine driver -cargo test --workspace -cargo clippy --workspace -- --deny warnings +posel-cli send examples/github-api Meta/get-zen.toml +posel-cli test examples/github-api Meta/get-zen.toml # run its assertions ``` -A workspace can be opened by passing a path as the first argument to the app, or via the -`POSEL_WORKSPACE` environment variable. - ## Secrets -A `{{secret:name}}` reference is resolved at send time; the workspace `.toml` only ever holds the -*reference*, never the value, so collections stay safe to commit. Where the value comes from is -your choice: - -| Backend | Writable from Posel | Where values live | -|---------|---------------------|-------------------| -| `keychain` *(default)* | yes | macOS Keychain, Windows Credential Manager, Linux Secret Service | -| `file` | yes | a `0600` TOML file in your user data directory | -| `env` | no | the process environment (`{{secret:github_token}}` → `$POSEL_SECRET_GITHUB_TOKEN`) | -| `command` | no | stdout of a helper command you configure (`pass`, `op`, `gh`, …) | -| `none` | — | nowhere; every `{{secret:…}}` fails | - -The keychain is the default and the recommended choice — but not every machine has one. A headless -Linux box, a container, or a CI runner has no Secret Service, which is where -`org.freedesktop.DBus.Error.ServiceUnknown: The name org.freedesktop.secrets was not provided by -any .service files` comes from. Pick one of the other backends there. - -Configure it under `[secrets]` in `settings.toml` in your user data directory -(`~/.local/share/posel` on Linux, `~/Library/Application Support/posel` on macOS, -`%LOCALAPPDATA%\posel\data` on Windows): - -```toml -[secrets] -backend = "file" # keychain | file | env | command | none +Secret *values* never enter your workspace — only the `{{secret:name}}` reference does, so the +folder stays safe to commit. Values come from your OS keychain by default; a local file, +environment variables, or a shell command (`pass`, `1Password`, `gh auth token`) all work too. +See [docs/SECRETS.md](./docs/SECRETS.md). -env_prefix = "POSEL_SECRET_" # env backend: name → $POSEL_SECRET_ -command = ["pass", "show", "posel/{name}"] # command backend: stdout is the value -``` +## Why shouldn't I use Posel? -`command` runs the given argv once per lookup and takes its stdout (trailing newline trimmed) as -the value; `{name}` is substituted with the secret name in every argument — e.g. -`["op", "read", "op://vault/{name}/token"]` or `["gh", "auth", "token"]`. A non-zero exit surfaces -the helper's stderr. +- **It's pre-1.0.** It works and it's a usable daily driver, but expect breaking changes. +- **Builds aren't signed yet**, so first launch takes an extra click on macOS and Windows. +- **No Postman/Insomnia collection import yet** — OpenAPI and curl are supported, those aren't. +- **If you want cloud sync or a team workspace server, this isn't it** — and won't be. -**About the `file` backend.** It stores values in plaintext at rest — the same posture as -`~/.aws/credentials`, `~/.docker/config.json`, or `gh`'s `hosts.yml`. On Unix the file is *created* -`0600` (owner-only) before any value is written to it, never chmod'ed afterwards, so the plaintext -is not readable by anyone else even momentarily; on Windows it inherits the user-scoped ACL of -`%LOCALAPPDATA%`. Updates go through a temp file and an atomic rename, entries are namespaced per -workspace, and it lives in your user data directory — **never inside a workspace**, so it can't be -committed by accident. Use the keychain where you have one; use this where you don't. +## Docs -Either way can be overridden for a single run with `POSEL_SECRET_BACKEND`, which wins over -`settings.toml` (an unrecognized value is ignored and the configured backend stands): +- [SPEC.md](./docs/SPEC.md) — what Posel is and the product decisions behind it +- [ARCHITECTURE.md](./docs/ARCHITECTURE.md) — how it's put together +- [SECRETS.md](./docs/SECRETS.md) — secret backends and how to pick one +- [PLAN.md](./docs/PLAN.md) — the phased roadmap +- [CONTRIBUTING.md](./CONTRIBUTING.md) — build it, run the tests, send a PR -```bash -POSEL_SECRET_BACKEND=env cargo run -p posel-app -``` - -`posel-cli` takes the same choice as a flag on `send`, `login`, `test`, and `codegen`: - -```bash -posel-cli send ./ws users/list.toml --secret-backend env # this run only -posel-cli send ./ws users/list.toml --secret token=abc # inline; no store is read -posel-cli send ./ws users/list.toml --keychain # pin the OS keychain -``` - -Given none of those, the CLI resolves through whatever `settings.toml` configures. If several are -given, `--keychain` wins, then `--secret`, then `--secret-backend`. `--secret-backend` swaps only -the backend — the configured `env_prefix` and `command` still apply. - -## A note on the GPUI dependency +## Status -GPUI ships only as a git dependency (no crates.io release), and Posel carries three small, -upstreamable editor enhancements to `gpui-component` (a completion trigger, bracket-aware -auto-indent, and bracket/quote auto-pairing). Those live on a public fork, -[`starvy/gpui-component`](https://github.com/starvy/gpui-component), pinned here to an immutable -tag so the whole graph resolves to a single, reproducible `gpui`. No local checkout is needed — -`cargo build` fetches everything. +Public beta on macOS, Linux, and Windows. Built in Rust on [GPUI](https://www.gpui.rs/), the +framework behind the [Zed](https://zed.dev) editor. ## License -Dual-licensed under either of - -- Apache License, Version 2.0 ([LICENSE-APACHE](./LICENSE-APACHE)) -- MIT license ([LICENSE-MIT](./LICENSE-MIT)) - -at your option. +Dual-licensed under either of [Apache-2.0](./LICENSE-APACHE) or [MIT](./LICENSE-MIT), at your +option. diff --git a/docs/SECRETS.md b/docs/SECRETS.md new file mode 100644 index 0000000..0185c69 --- /dev/null +++ b/docs/SECRETS.md @@ -0,0 +1,62 @@ +# Secrets + +A `{{secret:name}}` reference is resolved at send time; the workspace `.toml` only ever holds the +*reference*, never the value, so collections stay safe to commit. Where the value comes from is +your choice: + +| Backend | Writable from Posel | Where values live | +|---------|---------------------|-------------------| +| `keychain` *(default)* | yes | macOS Keychain, Windows Credential Manager, Linux Secret Service | +| `file` | yes | a `0600` TOML file in your user data directory | +| `env` | no | the process environment (`{{secret:github_token}}` → `$POSEL_SECRET_GITHUB_TOKEN`) | +| `command` | no | stdout of a helper command you configure (`pass`, `op`, `gh`, …) | +| `none` | — | nowhere; every `{{secret:…}}` fails | + +The keychain is the default and the recommended choice — but not every machine has one. A headless +Linux box, a container, or a CI runner has no Secret Service, which is where +`org.freedesktop.DBus.Error.ServiceUnknown: The name org.freedesktop.secrets was not provided by +any .service files` comes from. Pick one of the other backends there. + +Configure it under `[secrets]` in `settings.toml` in your user data directory +(`~/.local/share/posel` on Linux, `~/Library/Application Support/posel` on macOS, +`%LOCALAPPDATA%\posel\data` on Windows): + +```toml +[secrets] +backend = "file" # keychain | file | env | command | none + +env_prefix = "POSEL_SECRET_" # env backend: name → $POSEL_SECRET_ +command = ["pass", "show", "posel/{name}"] # command backend: stdout is the value +``` + +`command` runs the given argv once per lookup and takes its stdout (trailing newline trimmed) as +the value; `{name}` is substituted with the secret name in every argument — e.g. +`["op", "read", "op://vault/{name}/token"]` or `["gh", "auth", "token"]`. A non-zero exit surfaces +the helper's stderr. + +**About the `file` backend.** It stores values in plaintext at rest — the same posture as +`~/.aws/credentials`, `~/.docker/config.json`, or `gh`'s `hosts.yml`. On Unix the file is *created* +`0600` (owner-only) before any value is written to it, never chmod'ed afterwards, so the plaintext +is not readable by anyone else even momentarily; on Windows it inherits the user-scoped ACL of +`%LOCALAPPDATA%`. Updates go through a temp file and an atomic rename, entries are namespaced per +workspace, and it lives in your user data directory — **never inside a workspace**, so it can't be +committed by accident. Use the keychain where you have one; use this where you don't. + +Any of these can be overridden for a single run with `POSEL_SECRET_BACKEND`, which wins over +`settings.toml` (an unrecognized value is ignored and the configured backend stands): + +```bash +POSEL_SECRET_BACKEND=env posel +``` + +`posel-cli` takes the same choice as a flag on `send`, `login`, `test`, and `codegen`: + +```bash +posel-cli send ./ws users/list.toml --secret-backend env # this run only +posel-cli send ./ws users/list.toml --secret token=abc # inline; no store is read +posel-cli send ./ws users/list.toml --keychain # pin the OS keychain +``` + +Given none of those, the CLI resolves through whatever `settings.toml` configures. If several are +given, `--keychain` wins, then `--secret`, then `--secret-backend`. `--secret-backend` swaps only +the backend — the configured `env_prefix` and `command` still apply.