Skip to content

feat(cli): embeddable CLI library with CliHost - #73

Merged
chaliy merged 3 commits into
mainfrom
claude/project-thread-solgcd
Nov 1, 2026
Merged

chaliy merged 3 commits into
mainfrom
claude/project-thread-solgcd

Conversation

@chaliy

@chaliy chaliy commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Михайло · project thread

What changed

Prep so bashkit can ship a markkit builtin with the exact same CLI as the standalone binary.

  • CLI is now a library. markkit_cli::run(args, &impl CliHost) -> i32; the markkit binary is a thin wrapper with NativeHost. The host owns stdout/stderr, stdin, file reads (--file, --rules), URL extraction and MCP stdio. run never exits the process or touches the host filesystem.
  • URL extraction is a host method that gets the configured Markkit. NativeHost keeps calling extract_url_document (markkit's SSRF policy). A sandbox fetches through its own egress, sets base_url to the final URL and calls extract_document.
  • native-http feature (default) carries markkit/http_client for NativeHost. Without it the CLI library links no reqwest/TLS.
  • Hosts can refuse --ocr (host Tesseract binary, host model files). It still parses and shows in help; exit 1 with a one-line error before any input is read. mcp is refused by any host that doesn't implement serve_mcp.
  • clap without color/suggestions, matching bashkit's clap set (features unify).
  • Errors keep anyhow's Error: … / Caused by: layout but no longer append a backtrace when RUST_BACKTRACE is set (output must not depend on env; embedders must not leak internals). A closed stdout pipe now exits 0 instead of panicking in print!.

Why

Design: markkit + fetchkit "preinstalled" in bashkit sessions with an identical CLI. Sharing the code (not copying clap structs) keeps the interface from drifting. Companion: everruns/fetchkit#189.

Before / After

Binary output unchanged apart from the backtrace/broken-pipe notes above. New tests/host.rs:

  • --help, extract --help, mcp --help, --version: run() stdout == binary stdout, byte for byte
  • usage errors (unknown flag, no subcommand, --layout without --json): same stderr as binary, exit 2
  • runtime error (extract with no source): same stderr as binary, exit 1
  • stdin (--stdin, -), --file + --rules read from an in-memory host fs
  • URL via host: relative links resolve against the host-reported final URL
  • refused --ocr: exit 1, no fetch; mcp refused by default host; broken pipe exits 0
cargo tree -p markkit-cli --no-default-features --features all_formats -e normal -i reqwest
error: package ID specification `reqwest` did not match any packages

just pre-pr: fmt, clippy, test, check-okf, script tests, examples pass locally. cargo deny is not installed here, so CI runs it (no new dependencies).

Risk

  • Low
  • Breaking: markkit-cli with default-features = false must enable native-http for URL sources (CHANGELOG).
  • CLI help/errors lose ANSI color on terminals.

Move the markkit CLI into `markkit_cli::run(args, &impl CliHost)`; the
binary is a thin wrapper. Hosts own stdio, file reads, URL extraction and
MCP, and may refuse --ocr. URL support in NativeHost is the default
native-http feature, so embedders link no reqwest. clap default features
off to match bashkit. Lets bashkit ship a `markkit` builtin with the
identical interface.
@chaliy
chaliy force-pushed the claude/project-thread-solgcd branch from bacdc2b to 2aa8b40 Compare November 1, 2026 02:37
@chaliy chaliy self-assigned this Nov 1, 2026
@chaliy
chaliy merged commit 36e7385 into main Nov 1, 2026
12 checks passed
chaliy added a commit to everruns/fetchkit that referenced this pull request Nov 1, 2026
<!-- claude-projects-attribution: {"github_login":"chaliy"} -->
_Requested by **Михайло** · [project
thread](https://claude.ai/code/project/chan_019YEkBJuZMHJdXrjom86cGX?thread=cmsg_019YEkBJuZMHJdXrjom86cGXFSAmywce8BSJgNQ4YNsrks)_

## What changed
Prep so bashkit can ship a `fetchkit` builtin with the exact same CLI as
the standalone binary.

- **CLI is now a library.** `fetchkit_cli::run(args, &dyn CliHost) ->
i32`. The `fetchkit` binary is a 6-line wrapper over it with
`NativeHost`. The host owns stdout/stderr, env lookups (`CLOUDFLARE_*`),
MCP stdio, and egress policy (`configure_tool`, e.g. a custom
`HttpTransport`). `run` never calls `process::exit`, prints directly, or
reads `std::env`.
- **Hosts can refuse host-only flags** (`--allow-env-proxy`,
`--render-rakers`, `--bot-auth-key/--bot-auth-agent`,
`--allow-remote-conversion`; `mcp` is refused by any host that doesn't
implement `serve_mcp`). Flags still parse and show in `--help`; the
command fails before any request with `Error: <flag> <reason>`, exit 1.
- **reqwest default features off.** New default `reqwest-tls` feature
carries TLS, HTTP/2, proxy discovery and gzip/brotli/deflate for
`ReqwestTransport`. With `default-features = false` fetchkit links no
TLS stack and no aws-lc-sys (bashkit is ring-only because aws-lc-sys
breaks its aarch64 manylinux cross builds).
- **clap without color/suggestions**, matching bashkit's clap set
(features unify; otherwise output would differ between the binary and
the builtin).
- **Markkit 0.2** (was 0.1.1): one markkit copy when bashkit links both.
Small API updates (`limits.*`, typed warning codes).
- MCP server reads stdin per line instead of holding `StdinLock` across
`.await` (needed for a `Send` future; same behavior).

## Why
Design: markkit + fetchkit "preinstalled" in bashkit sessions with an
identical CLI. Sharing the code (not copying clap structs) is the only
way to keep the interface from drifting. Companion: everruns/markkit#73.

## Before / After
Binary behavior unchanged. New `tests/host.rs` proves parity and the
host contract:

- `--help`, `fetch --help`, `mcp --help`, `--version`: `run()` output ==
binary stdout, byte for byte, exit 0
- usage error: same stderr as binary, exit 2
- `fetch` through a host transport: frontmatter markdown + JSON output,
every request (page and agent-discovery probes) went through the host
- each refused flag: exit 1, one-line error, zero transport calls
- broken stdout pipe: exit 0

```
cargo tree -p fetchkit --no-default-features -e normal -i aws-lc-sys
error: package ID specification `aws-lc-sys` did not match any packages
```

## Risk
- Low/Medium
- **Breaking** for `default-features = false` users who rely on HTTPS
through `ReqwestTransport`: enable `reqwest-tls`. CHANGELOG marks it.
- CLI help/errors lose ANSI color on terminals.

### Checklist
- [x] Unit tests are passed (`cargo test --workspace`, clippy `-D
warnings`, fmt, docs `-D warnings`, release build)
- [x] Smoke tests are passed
- [x] Documentation is updated (CHANGELOG)
- [x] Knowledge is up to date and not in conflict (tool-contract,
fetchers, log; OKF check passes)
chaliy added a commit that referenced this pull request Nov 1, 2026
<!-- claude-projects-attribution: {"github_login":"chaliy"} -->
_Requested by **Михайло** · [project
thread](https://claude.ai/code/project/chan_019YEkBJuZMHJdXrjom86cGX?thread=cmsg_019YEkBJuZMHJdXrjom86cGXFSAmywce8BSJgNQ4YNsrks)_

## What changed
Release v0.3.0: version bump (Cargo, markkit-js, markkit-wasm),
CHANGELOG Unreleased moved under `[0.3.0] - 2026-11-01`, knowledge log
entry.

Ships #73: the `markkit_cli` library (`run(args, &impl CliHost)`) that
bashkit's `markkit` builtin calls.

## Why
Minor bump: `markkit-cli` URL sources moved behind the default
`native-http` feature (breaking for `default-features = false` builds).

## Before / After
No behavior change in this PR beyond the version.

## Risk
- Low. Merging runs `release.yml` (tag, GitHub Release, crates.io
publish of `markkit` then `markkit-cli`).

---
_Generated by [Claude
Code](https://claude.ai/code/session_01Wy7MByPXsW4jHxfLeTfGEc)_
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant