From 0e434881b3f8da1db58dba8d103ddc234ad17238 Mon Sep 17 00:00:00 2001 From: Ogulcan Celik Date: Mon, 3 Aug 2026 01:21:30 +0300 Subject: [PATCH] fix(docs): publish only released documentation --- .../references/pre-release-audit.md | 19 +- .github/workflows/preview.yml | 38 +- .github/workflows/release.yml | 17 +- .github/workflows/website.yml | 13 +- .gitignore | 2 + AGENTS.md | 8 +- CONTRIBUTING.md | 4 +- .../website/src/content/docs/integrations.mdx | 6 +- .../src/content/docs/ja/integrations.mdx | 40 +- .../src/content/docs/zh-cn/integrations.mdx | 40 +- docs/preview/README.md | 9 + .../src/content/docs/agent-automation.mdx | 123 ++ .../website/src/content/docs/agent-skill.mdx | 65 + .../website/src/content/docs/agents.mdx | 154 +++ .../src/content/docs/cli-reference.mdx | 487 +++++++ .../website/src/content/docs/concepts.mdx | 85 ++ .../src/content/docs/config-reference.mdx | 21 + .../src/content/docs/configuration.mdx | 470 +++++++ .../website/src/content/docs/how-to-work.mdx | 103 ++ .../website/src/content/docs/index.mdx | 77 ++ .../website/src/content/docs/install.mdx | 153 +++ .../website/src/content/docs/integrations.mdx | 315 +++++ .../src/content/docs/ja/agent-automation.mdx | 123 ++ .../src/content/docs/ja/agent-skill.mdx | 65 + .../website/src/content/docs/ja/agents.mdx | 154 +++ .../src/content/docs/ja/cli-reference.mdx | 444 +++++++ .../website/src/content/docs/ja/concepts.mdx | 85 ++ .../src/content/docs/ja/config-reference.mdx | 27 + .../src/content/docs/ja/configuration.mdx | 466 +++++++ .../src/content/docs/ja/how-to-work.mdx | 103 ++ .../website/src/content/docs/ja/index.mdx | 77 ++ .../website/src/content/docs/ja/install.mdx | 153 +++ .../src/content/docs/ja/integrations.mdx | 299 +++++ .../website/src/content/docs/ja/keyboard.mdx | 112 ++ .../src/content/docs/ja/marketplace.mdx | 53 + .../content/docs/ja/persistence-remote.mdx | 132 ++ .../website/src/content/docs/ja/plugins.mdx | 367 ++++++ .../src/content/docs/ja/quick-start.mdx | 69 + .../src/content/docs/ja/session-state.mdx | 107 ++ .../src/content/docs/ja/socket-api.mdx | 752 +++++++++++ .../src/content/docs/ja/troubleshooting.mdx | 95 ++ .../src/content/docs/ja/windows-beta.mdx | 124 ++ .../website/src/content/docs/keyboard.mdx | 112 ++ .../website/src/content/docs/marketplace.mdx | 53 + .../src/content/docs/persistence-remote.mdx | 165 +++ .../website/src/content/docs/plugins.mdx | 372 ++++++ .../website/src/content/docs/quick-start.mdx | 69 + .../src/content/docs/session-state.mdx | 108 ++ .../website/src/content/docs/socket-api.mdx | 887 +++++++++++++ .../src/content/docs/troubleshooting.mdx | 95 ++ .../website/src/content/docs/windows-beta.mdx | 124 ++ .../content/docs/zh-cn/agent-automation.mdx | 123 ++ .../src/content/docs/zh-cn/agent-skill.mdx | 65 + .../website/src/content/docs/zh-cn/agents.mdx | 154 +++ .../src/content/docs/zh-cn/cli-reference.mdx | 444 +++++++ .../src/content/docs/zh-cn/concepts.mdx | 85 ++ .../content/docs/zh-cn/config-reference.mdx | 26 + .../src/content/docs/zh-cn/configuration.mdx | 466 +++++++ .../src/content/docs/zh-cn/how-to-work.mdx | 103 ++ .../website/src/content/docs/zh-cn/index.mdx | 77 ++ .../src/content/docs/zh-cn/install.mdx | 153 +++ .../src/content/docs/zh-cn/integrations.mdx | 299 +++++ .../src/content/docs/zh-cn/keyboard.mdx | 112 ++ .../src/content/docs/zh-cn/marketplace.mdx | 44 + .../content/docs/zh-cn/persistence-remote.mdx | 132 ++ .../src/content/docs/zh-cn/plugins.mdx | 328 +++++ .../src/content/docs/zh-cn/quick-start.mdx | 69 + .../src/content/docs/zh-cn/session-state.mdx | 107 ++ .../src/content/docs/zh-cn/socket-api.mdx | 748 +++++++++++ .../content/docs/zh-cn/troubleshooting.mdx | 95 ++ .../src/content/docs/zh-cn/windows-beta.mdx | 124 ++ .../website/src/data/config-reference.json | 1121 +++++++++++++++++ docs/versions/README.md | 2 +- docs/versions/manifest.json | 1 + justfile | 3 +- scripts/docs_translation_parity.py | 2 +- scripts/test_preview.py | 7 +- website/.gitignore | 4 +- website/README.md | 27 +- website/astro.config.mjs | 2 +- website/index.html | 2 +- website/package.json | 3 +- website/scripts/check-built-docs.mjs | 4 + .../scripts/docs-preview.integration.test.ts | 90 ++ website/scripts/docs-preview.mjs | 75 ++ website/scripts/docs-snapshot.mjs | 124 ++ .../scripts/docs-versions.integration.test.ts | 54 +- website/scripts/docs-versions.mjs | 247 ++-- website/scripts/prepare-docs.mjs | 163 ++- .../src/components/DocsVersionSelect.astro | 8 +- 90 files changed, 13510 insertions(+), 224 deletions(-) create mode 100644 docs/preview/README.md create mode 100644 docs/preview/website/src/content/docs/agent-automation.mdx create mode 100644 docs/preview/website/src/content/docs/agent-skill.mdx create mode 100644 docs/preview/website/src/content/docs/agents.mdx create mode 100644 docs/preview/website/src/content/docs/cli-reference.mdx create mode 100644 docs/preview/website/src/content/docs/concepts.mdx create mode 100644 docs/preview/website/src/content/docs/config-reference.mdx create mode 100644 docs/preview/website/src/content/docs/configuration.mdx create mode 100644 docs/preview/website/src/content/docs/how-to-work.mdx create mode 100644 docs/preview/website/src/content/docs/index.mdx create mode 100644 docs/preview/website/src/content/docs/install.mdx create mode 100644 docs/preview/website/src/content/docs/integrations.mdx create mode 100644 docs/preview/website/src/content/docs/ja/agent-automation.mdx create mode 100644 docs/preview/website/src/content/docs/ja/agent-skill.mdx create mode 100644 docs/preview/website/src/content/docs/ja/agents.mdx create mode 100644 docs/preview/website/src/content/docs/ja/cli-reference.mdx create mode 100644 docs/preview/website/src/content/docs/ja/concepts.mdx create mode 100644 docs/preview/website/src/content/docs/ja/config-reference.mdx create mode 100644 docs/preview/website/src/content/docs/ja/configuration.mdx create mode 100644 docs/preview/website/src/content/docs/ja/how-to-work.mdx create mode 100644 docs/preview/website/src/content/docs/ja/index.mdx create mode 100644 docs/preview/website/src/content/docs/ja/install.mdx create mode 100644 docs/preview/website/src/content/docs/ja/integrations.mdx create mode 100644 docs/preview/website/src/content/docs/ja/keyboard.mdx create mode 100644 docs/preview/website/src/content/docs/ja/marketplace.mdx create mode 100644 docs/preview/website/src/content/docs/ja/persistence-remote.mdx create mode 100644 docs/preview/website/src/content/docs/ja/plugins.mdx create mode 100644 docs/preview/website/src/content/docs/ja/quick-start.mdx create mode 100644 docs/preview/website/src/content/docs/ja/session-state.mdx create mode 100644 docs/preview/website/src/content/docs/ja/socket-api.mdx create mode 100644 docs/preview/website/src/content/docs/ja/troubleshooting.mdx create mode 100644 docs/preview/website/src/content/docs/ja/windows-beta.mdx create mode 100644 docs/preview/website/src/content/docs/keyboard.mdx create mode 100644 docs/preview/website/src/content/docs/marketplace.mdx create mode 100644 docs/preview/website/src/content/docs/persistence-remote.mdx create mode 100644 docs/preview/website/src/content/docs/plugins.mdx create mode 100644 docs/preview/website/src/content/docs/quick-start.mdx create mode 100644 docs/preview/website/src/content/docs/session-state.mdx create mode 100644 docs/preview/website/src/content/docs/socket-api.mdx create mode 100644 docs/preview/website/src/content/docs/troubleshooting.mdx create mode 100644 docs/preview/website/src/content/docs/windows-beta.mdx create mode 100644 docs/preview/website/src/content/docs/zh-cn/agent-automation.mdx create mode 100644 docs/preview/website/src/content/docs/zh-cn/agent-skill.mdx create mode 100644 docs/preview/website/src/content/docs/zh-cn/agents.mdx create mode 100644 docs/preview/website/src/content/docs/zh-cn/cli-reference.mdx create mode 100644 docs/preview/website/src/content/docs/zh-cn/concepts.mdx create mode 100644 docs/preview/website/src/content/docs/zh-cn/config-reference.mdx create mode 100644 docs/preview/website/src/content/docs/zh-cn/configuration.mdx create mode 100644 docs/preview/website/src/content/docs/zh-cn/how-to-work.mdx create mode 100644 docs/preview/website/src/content/docs/zh-cn/index.mdx create mode 100644 docs/preview/website/src/content/docs/zh-cn/install.mdx create mode 100644 docs/preview/website/src/content/docs/zh-cn/integrations.mdx create mode 100644 docs/preview/website/src/content/docs/zh-cn/keyboard.mdx create mode 100644 docs/preview/website/src/content/docs/zh-cn/marketplace.mdx create mode 100644 docs/preview/website/src/content/docs/zh-cn/persistence-remote.mdx create mode 100644 docs/preview/website/src/content/docs/zh-cn/plugins.mdx create mode 100644 docs/preview/website/src/content/docs/zh-cn/quick-start.mdx create mode 100644 docs/preview/website/src/content/docs/zh-cn/session-state.mdx create mode 100644 docs/preview/website/src/content/docs/zh-cn/socket-api.mdx create mode 100644 docs/preview/website/src/content/docs/zh-cn/troubleshooting.mdx create mode 100644 docs/preview/website/src/content/docs/zh-cn/windows-beta.mdx create mode 100644 docs/preview/website/src/data/config-reference.json create mode 100644 website/scripts/docs-preview.integration.test.ts create mode 100644 website/scripts/docs-preview.mjs create mode 100644 website/scripts/docs-snapshot.mjs diff --git a/.agents/skills/herdr-pre-release-audit/references/pre-release-audit.md b/.agents/skills/herdr-pre-release-audit/references/pre-release-audit.md index 5d2280fc..68f7de19 100644 --- a/.agents/skills/herdr-pre-release-audit/references/pre-release-audit.md +++ b/.agents/skills/herdr-pre-release-audit/references/pre-release-audit.md @@ -61,28 +61,29 @@ Process: - Preserve the existing changelog style and sections: `Added`, `Changed`, `Fixed`, `Removed`, and `Breaking Changes` when applicable. 7. Audit next-release public docs. - - Treat root `README.md` and `website/src/content/docs/` as the latest released public docs. - - Treat `docs/next/README.md` as the next-release root README, and `docs/next/website/src/content/docs/` as the full next-release mirror of website docs. + - Treat root `README.md` and the current stable docs source as the latest released public docs. While `docs/versions/manifest.json` has `stable_source: legacy`, that source is `website/src/content/docs/`; afterward it is the current immutable snapshot under `docs/versions//website/src/content/docs/`. + - Treat `docs/next/README.md` as the next-release root README and `docs/next/website/src/content/docs/` as the complete unpublished website-doc draft. + - Treat `docs/preview/website/` as bot-owned output for the active preview release. Never edit it during release review and never use it as the stable release source. - Compare meaningful user-facing changes in the range against next-release docs first. - Flag missing release docs for new or changed features, commands, config keys, protocol behavior, integrations, defaults, and compatibility notes. - Compare English next-release website docs against `docs/next/website/src/content/docs/ja/` and `docs/next/website/src/content/docs/zh-cn/`. Flag missing localized files, stale localized files, and heading-outline drift where translated docs do not have the same section structure as English. - - Compare `docs/next/README.md` against root `README.md`, and compare the staged website-doc mirror against `website/src/content/docs/`. Flag each difference as intended to ship in this release, stale, or needing user decision. + - Compare `docs/next/README.md` and the next website draft against current stable docs. Flag each difference as intended to ship, stale, or needing user decision. Do not require the draft and stable trees to match before release. - Also audit example config snippets for release readiness. 8. Verify finalization state. - - Before `just release`, approved `docs/next/README.md` must be copied to root `README.md`, approved staged website docs must be copied from `docs/next/website/src/content/docs/` to `website/src/content/docs/`, and the deleted root doc files must stay deleted. - - If the release will change `Cargo.lock` or the package version, check `nix/package.nix` after the release version bump and refresh `cargoHash` before tagging. A stale hash fails both the `Nix` workflow and the release workflow's `flake-check` job with a fixed-output derivation mismatch. Use the `got:` hash printed by `nix flake check --print-build-logs` or CI, then rerun the Nix check when available. + - Before `just release`, approved README changes must be finalized in `docs/next/README.md`; release CI promotes that tagged file after publication. Do not copy draft website docs into `website/src/content/docs/` or `docs/preview/`. + - `nix/package.nix` imports `Cargo.lock` through `cargoLock.lockFile`; normal version and lockfile updates do not require a separate cargo hash refresh. If git dependencies are introduced, verify the required `cargoLock.outputHashes` entries. - Run or recommend: ```bash just release-docs-check ``` - - This check must include root `README.md`, root `CHANGELOG.md`, the removed root doc files, exact 1:1 sync between `docs/next/website/src/content/docs/` and `website/src/content/docs/`, and localized heading-outline parity between English, Japanese, and Simplified Chinese docs. + - This check validates the staged draft, localized heading parity, published preview and stable snapshot provenance, and both production and draft website builds. - Do not run `just release` unless the working tree is clean and the docs check passes. 9. Apply changes only when asked. - Do not edit files during the audit unless the user explicitly asks you to apply fixes. - When asked to apply audit fixes, update `docs/next/CHANGELOG.md`, `docs/next/README.md`, and any required staged website docs under `docs/next/website/src/content/docs/`. - - When asked to finalize release docs, copy approved next-release README and changelog into root, copy approved staged website docs into `website/src/content/docs/`, then run `just release-docs-check`. + - When asked to finalize release docs, finalize only the staged files under `docs/next/`, then run `just release-docs-check`. Preview and stable publication remain CI-owned. Output format: @@ -114,8 +115,8 @@ Accepted/no action: Root docs finalized: YES | NO -Nix cargoHash: OK | NEEDS UPDATE | NOT CHECKED - +Nix Cargo lock integration: OK | NEEDS ATTENTION | NOT CHECKED + Required before release: 1. diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml index 6704505b..158f2462 100644 --- a/.github/workflows/preview.yml +++ b/.github/workflows/preview.yml @@ -57,8 +57,8 @@ jobs: commit="$(python3 scripts/preview.py select-commit --ref origin/master)" fi current_preview="$(python3 scripts/preview.py current-commit --manifest website/preview.json || true)" - if [ "$current_preview" = "$commit" ]; then - echo "Preview already points at $commit; skipping." + if [ "$current_preview" = "$commit" ] && node website/scripts/docs-preview.mjs check; then + echo "Preview and documentation already point at $commit; skipping." echo "should_publish=false" >> "$GITHUB_OUTPUT" exit 0 fi @@ -286,6 +286,9 @@ jobs: needs: [preflight, build] if: needs.preflight.outputs.should_publish == 'true' runs-on: ubuntu-latest + concurrency: + group: docs-publish-master + cancel-in-progress: false permissions: contents: write issues: write @@ -301,6 +304,11 @@ jobs: with: path: artifacts + - name: Install Bun + uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 + with: + bun-version: 1.3.14 + - name: Record previous preview commit id: previous-preview shell: bash @@ -353,8 +361,9 @@ jobs: artifacts/herdr-macos-aarch64/herdr-macos-aarch64 artifacts/herdr-windows-x86_64.zip/herdr-windows-x86_64.zip - - name: Update preview manifest + - name: Update preview documentation and manifest run: | + node website/scripts/docs-preview.mjs snapshot '${{ needs.preflight.outputs.commit }}' python3 scripts/preview.py manifest \ --output website/preview.json \ --tag '${{ needs.preflight.outputs.tag }}' \ @@ -366,18 +375,22 @@ jobs: --notes PREVIEW_NOTES.md \ --sha-file preview-sha256.json \ --retain 30 + (cd website && bun install --frozen-lockfile) - - name: Commit preview manifest + - name: Commit preview publication env: KANGAL_GITHUB_TOKEN: ${{ secrets.KANGAL_GITHUB_TOKEN }} run: | git config user.name "kangal-bot" git config user.email "285672167+kangal-bot@users.noreply.github.com" - git add website/preview.json - git diff --cached --quiet || git commit -m "docs: update preview manifest" + git add -A docs/preview website/preview.json + git diff --cached --quiet || git commit -m "docs: publish preview documentation" remote="https://x-access-token:${KANGAL_GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" for attempt in 1 2 3; do git pull --rebase "$remote" master + node website/scripts/docs-preview.mjs check + node website/scripts/docs-versions.mjs check + (cd website && bun run build) if git push "$remote" HEAD:master; then exit 0 fi @@ -386,9 +399,20 @@ jobs: sleep $((attempt * 2)) fi done - echo "failed to publish preview manifest after 3 attempts" >&2 + echo "failed to publish preview documentation after 3 attempts" >&2 exit 1 + - name: Trigger website deploy + env: + DEPLOY_HOOK: ${{ secrets.CLOUDFLARE_PAGES_DEPLOY_HOOK }} + run: | + if [ -z "$DEPLOY_HOOK" ]; then + echo "CLOUDFLARE_PAGES_DEPLOY_HOOK not set; relying on Cloudflare git auto-deploy" + exit 0 + fi + curl -fsS -X POST "$DEPLOY_HOOK" > /dev/null + echo "Triggered Cloudflare Pages production deploy" + - name: Mark preview-released issues continue-on-error: true shell: bash diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2fe55021..75a2ffed 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -163,11 +163,17 @@ jobs: with: persist-credentials: false + - name: Install Bun + uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 + with: + bun-version: 1.3.14 + - name: Validate release inputs run: | python3 scripts/changelog.py validate-product-announcement test -f docs/next/README.md test -f docs/next/README.zh-CN.md + (cd website && bun install --frozen-lockfile && bun run test && bun run build:draft) release: needs: [build, flake-check, validate-release-inputs] @@ -335,6 +341,9 @@ jobs: update-latest-json: needs: release runs-on: ubuntu-latest + concurrency: + group: docs-publish-master + cancel-in-progress: false permissions: contents: write @@ -363,6 +372,11 @@ jobs: python3 scripts/changelog.py validate-product-announcement --path "$ANNOUNCEMENT_PATH" RELEASE_PROTOCOL=$(git show "${GITHUB_REF_NAME}:src/protocol/wire.rs" | python3 -c 'import re, sys; match = re.search(r"pub const PROTOCOL_VERSION: u32 = (\d+);", sys.stdin.read()); sys.exit(1) if match is None else print(match.group(1))') + DOCS_CURRENT=$(node website/scripts/docs-versions.mjs current) + if [ "$DOCS_CURRENT" != "$VERSION" ]; then + echo "Archived v$VERSION documentation without moving current docs backward from v$DOCS_CURRENT" + exit 0 + fi CURRENT_VERSION=$(python3 -c 'import json; print(json.load(open("website/latest.json")).get("version", ""))') if [ "$CURRENT_VERSION" = "$VERSION" ]; then echo "website/latest.json is already at v$VERSION" @@ -380,11 +394,12 @@ jobs: VERSION="${GITHUB_REF_NAME#v}" git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add README.md README.zh-CN.md docs/versions website/latest.json website/src/content/docs website/src/data/config-reference.json docs/next/product-announcement.json + git add -A README.md README.zh-CN.md docs/versions website/latest.json website/src/content/docs website/src/data/config-reference.json docs/next/product-announcement.json git diff --cached --quiet || git commit -m "docs: update website manifest for v$VERSION" for attempt in 1 2 3; do git pull --rebase origin master node website/scripts/docs-versions.mjs check + node website/scripts/docs-preview.mjs check (cd website && bun install --frozen-lockfile && bun run build) if git push origin master; then exit 0 diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index 4cc922d8..4c1be29c 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -6,6 +6,7 @@ on: - ".github/workflows/website.yml" - "website/**" - "docs/next/website/**" + - "docs/preview/**" - "docs/versions/**" - "scripts/test_preview.py" push: @@ -14,6 +15,7 @@ on: - ".github/workflows/website.yml" - "website/**" - "docs/next/website/**" + - "docs/preview/**" - "docs/versions/**" - "scripts/test_preview.py" @@ -40,8 +42,13 @@ jobs: with: bun-version: 1.3.14 - - name: Validate version snapshots - run: node website/scripts/docs-versions.mjs check + - name: Validate published snapshots + run: | + node website/scripts/docs-versions.mjs check + node website/scripts/docs-preview.mjs check - - name: Build website + - name: Build published website run: cd website && bun install --frozen-lockfile && bun run build + + - name: Validate unpublished draft + run: cd website && bun run build:draft diff --git a/.gitignore b/.gitignore index e35d515f..3f6092c0 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,8 @@ __pycache__/ /docs/* !/docs/next/ !/docs/next/** +!/docs/preview/ +!/docs/preview/** !/docs/versions/ !/docs/versions/** /.local/ diff --git a/AGENTS.md b/AGENTS.md index 8793a653..82f89868 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -159,13 +159,13 @@ When updating libghostty-vt, check every active patch in `vendor/libghostty-vt.p ## Docs -Stable public docs live in `website/src/content/docs/`. They are the currently released herdr.dev docs. Do not document unreleased behavior there during normal feature or fix work. +Unreleased docs live in `docs/next/website/src/content/docs/`. Update those when a user-facing change needs docs before the next release. They are committed drafts but are never production website input. `docs/next/README.md` and `docs/next/CHANGELOG.md` stage root README and changelog changes. -Unreleased docs live in `docs/next/website/src/content/docs/`. Update those when a user-facing change needs docs before the next release. `docs/next/README.md` and `docs/next/CHANGELOG.md` stage root README and changelog changes. +The active preview release docs live in `docs/preview/website/`. Preview CI owns this mutable snapshot and commits it atomically with `website/preview.json`; never edit it manually. Validate it with `node website/scripts/docs-preview.mjs check`. -The website build runs `website/scripts/prepare-docs.mjs`. It keeps stable docs at `/docs/`, generates next docs at `/docs/preview/` from `docs/next/website/src/content/docs/`, and generates immutable release docs from `docs/versions/`. Do not edit generated `website/src/content/docs/preview/` or `website/src/content/docs/_versions/`. +Immutable stable release snapshots live in `docs/versions/`. The website build generates `/docs/preview/` from the active preview snapshot and `/docs//` from stable snapshots. The current stable `/docs/` temporarily uses the tracked legacy `website/src/content/docs/` tree; the next stable release switches it to the current immutable snapshot and removes the legacy copy. Do not edit generated preview, version, or snapshot-backed stable files under `website/src/content/docs/`. -During release review, finalize `docs/next` and run `just release-docs-check`. Do not copy next docs into the stable website manually. After the GitHub Release succeeds, release CI snapshots the tagged next docs, promotes them to stable, updates `latest.json`, and deploys them together. Normal feature/fix work should not edit root `README.md`, root `CHANGELOG.md`, stable website docs, or `website/latest.json` unless explicitly requested. +During release review, finalize `docs/next` and run `just release-docs-check`. Do not copy draft docs into preview or stable manually. Preview CI snapshots the selected commit. After a stable GitHub Release succeeds, release CI snapshots the exact tag, updates `latest.json`, and deploys them together. Normal feature/fix work should not edit root `README.md`, root `CHANGELOG.md`, legacy stable website docs, or `website/latest.json` unless explicitly requested. Put local PRDs, planning notes, and exploratory specs under `.local/prd/`; `.local/` is ignored and locally controlled. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2c7619e5..2553c811 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -65,9 +65,9 @@ If your proposal changes the visual language, interaction model, workflow, persi ## Documentation for unreleased changes -The root `README.md`, root `CHANGELOG.md`, and website docs describe the latest released version of herdr. Do not update root `README.md`, root `CHANGELOG.md`, or `website/src/content/docs/` for normal PRs. +The root `README.md`, root `CHANGELOG.md`, and public website docs describe released Herdr builds. Do not update root `README.md`, root `CHANGELOG.md`, `docs/preview/`, `docs/versions/`, or `website/src/content/docs/` for normal PRs. -If your PR changes user-facing behavior, mention the needed public-doc update in the PR. Update `docs/next/README.md` only when the root README needs to change for the next release. Update the full website-doc mirror under `docs/next/website/src/content/docs/` when website docs need to change for the next release. Release CI promotes the tagged next docs only after the GitHub Release succeeds; contributors and maintainers do not copy them into stable docs manually. +If your PR changes user-facing behavior, mention the needed public-doc update in the PR. Update `docs/next/README.md` only when the root README needs to change for the next stable release. Update the draft under `docs/next/website/src/content/docs/` when website docs need to change. Draft changes stay unpublished until preview CI snapshots a selected commit or stable release CI snapshots a tag; contributors and maintainers do not copy them into public docs manually. You do not need to edit the changelog for normal PRs. Maintainers prepare `docs/next/CHANGELOG.md` during release review. diff --git a/docs/next/website/src/content/docs/integrations.mdx b/docs/next/website/src/content/docs/integrations.mdx index 38cea6a1..748e8340 100644 --- a/docs/next/website/src/content/docs/integrations.mdx +++ b/docs/next/website/src/content/docs/integrations.mdx @@ -1,6 +1,6 @@ --- title: Integrations -description: Install Herdr integrations for Pi, OMP, Claude Code, Codex, GitHub Copilot CLI, Devin CLI, Droid, Kimi Code CLI, OpenCode, Kilo Code CLI, Hermes Agent, Qoder CLI, Cursor Agent CLI, MastraCode, and Grok CLI. +description: Install Herdr integrations for Pi, OMP, Claude Code, Codex, GitHub Copilot CLI, Devin CLI, Droid, Kimi Code CLI, OpenCode, Kilo Code CLI, Hermes Agent, Qoder CLI, Cursor Agent CLI, MastraCode, Antigravity CLI, and Grok CLI. --- Herdr detects supported agents automatically. Official integrations can add native session identity for restore, lifecycle state reports, or both. @@ -26,6 +26,7 @@ herdr integration install hermes herdr integration install qodercli herdr integration install cursor herdr integration install mastracode +herdr integration install antigravity-cli herdr integration install grok ``` @@ -46,6 +47,7 @@ herdr integration uninstall hermes herdr integration uninstall qodercli herdr integration uninstall cursor herdr integration uninstall mastracode +herdr integration uninstall antigravity-cli herdr integration uninstall grok ``` @@ -56,7 +58,7 @@ Herdr uses integrations in two different ways: | Integration type | Agents | Effect | | --- | --- | --- | | Lifecycle authority | Pi, OMP, Kimi Code CLI, OpenCode, Kilo Code CLI, Hermes Agent, MastraCode | When installed and actively reporting for the pane, hook or plugin events author `idle`, `working`, and `blocked`. Herdr does not also use screen manifest fallback for that same lifecycle authority. | -| Session identity | Claude Code, Codex, GitHub Copilot CLI, Devin CLI, Droid, Qoder CLI, Cursor Agent CLI, Grok CLI | The integration reports native session references for restore. State still comes from Herdr's screen manifest detection. | +| Session identity | Claude Code, Codex, GitHub Copilot CLI, Devin CLI, Droid, Qoder CLI, Cursor Agent CLI, Antigravity CLI, Grok CLI | The integration reports native session references for restore. State still comes from Herdr's screen manifest detection. | Custom socket integrations can also report state when they define state that is not visible in the native terminal UI. diff --git a/docs/next/website/src/content/docs/ja/integrations.mdx b/docs/next/website/src/content/docs/ja/integrations.mdx index fe4e41b1..ae2eff35 100644 --- a/docs/next/website/src/content/docs/ja/integrations.mdx +++ b/docs/next/website/src/content/docs/ja/integrations.mdx @@ -1,6 +1,6 @@ --- title: インテグレーション -description: Pi、OMP、Claude Code、Codex、GitHub Copilot CLI、Devin CLI、Droid、Kimi Code CLI、OpenCode、Kilo Code CLI、Hermes Agent、Qoder CLI、Cursor Agent CLI、MastraCode 向けの Herdr インテグレーションをインストールします。 +description: Pi、OMP、Claude Code、Codex、GitHub Copilot CLI、Devin CLI、Droid、Kimi Code CLI、OpenCode、Kilo Code CLI、Hermes Agent、Qoder CLI、Cursor Agent CLI、MastraCode、Antigravity CLI、Grok CLI 向けの Herdr インテグレーションをインストールします。 --- Herdr は対応エージェントを自動的に検出します。公式インテグレーションは、復元のためのネイティブセッション識別、ライフサイクル状態の報告、またはその両方を追加できます。 @@ -26,6 +26,8 @@ herdr integration install hermes herdr integration install mastracode herdr integration install qodercli herdr integration install cursor +herdr integration install antigravity-cli +herdr integration install grok ``` ## インテグレーションをアンインストールする @@ -45,6 +47,8 @@ herdr integration uninstall hermes herdr integration uninstall mastracode herdr integration uninstall qodercli herdr integration uninstall cursor +herdr integration uninstall antigravity-cli +herdr integration uninstall grok ``` ## Herdr がインテグレーションをどう使うか @@ -54,13 +58,13 @@ Herdr はインテグレーションを 2 つの異なる方法で使います: | インテグレーションの種類 | エージェント | 効果 | | --- | --- | --- | | ライフサイクル権威 | Pi、OMP、Kimi Code CLI、OpenCode、Kilo Code CLI、Hermes Agent、MastraCode | インストールされ、そのペインについて能動的に報告している間は、フックまたはプラグインのイベントが `idle`、`working`、`blocked` を決定します。同じライフサイクル権威に対して、Herdr はスクリーンマニフェストのフォールバックを併用しません。 | -| セッション識別 | Claude Code、Codex、GitHub Copilot CLI、Devin CLI、Droid、Qoder CLI、Cursor Agent CLI | インテグレーションは復元用のネイティブセッション参照を報告します。状態は引き続き Herdr のスクリーンマニフェスト検出から得られます。 | +| セッション識別 | Claude Code、Codex、GitHub Copilot CLI、Devin CLI、Droid、Qoder CLI、Cursor Agent CLI、Antigravity CLI、Grok CLI | インテグレーションは復元用のネイティブセッション参照を報告します。状態は引き続き Herdr のスクリーンマニフェスト検出から得られます。 | カスタムソケットインテグレーションも、ネイティブのターミナル UI では見えない状態を定義する場合に状態を報告できます。 -一部のインテグレーションは、エージェントのネイティブセッション参照を報告します。Herdr は公式のセッション参照を使って、`[session] resume_agents_on_restore = false` で無効化されていない限り、Herdr サーバーの再起動後に Claude Code、Codex、Devin CLI、Droid、Kimi Code CLI、Qoder CLI、Cursor Agent CLI、GitHub Copilot CLI、Pi、OMP、Hermes Agent、OpenCode、Kilo Code CLI、MastraCode のペインを resume します。 +一部のインテグレーションは、エージェントのネイティブセッション参照を報告します。Herdr は公式のセッション参照を使って、`[session] resume_agents_on_restore = false` で無効化されていない限り、Herdr サーバーの再起動後に Claude Code、Codex、Devin CLI、Droid、Kimi Code CLI、Qoder CLI、Cursor Agent CLI、Grok CLI、GitHub Copilot CLI、Pi、OMP、Hermes Agent、OpenCode、Kilo Code CLI、MastraCode、Antigravity CLI のペインを resume します。 -エージェントネイティブのセッション復元には最新の Herdr インテグレーションが必要です: Pi インテグレーションはバージョン `2`、OMP は `3`、Claude Code は `6`、Codex は `5`、GitHub Copilot CLI は `2`、Devin CLI は `2`、Droid は `2`、Kimi Code CLI は `3`、Qoder CLI は `2`、Cursor Agent CLI は `1`、OpenCode は `5`、Kilo Code CLI は `1`、Hermes Agent は `2`、MastraCode は `1` です。インストール済みバージョンは `herdr integration status` で確認してください。 +エージェントネイティブのセッション復元には最新の Herdr インテグレーションが必要です: Pi インテグレーションはバージョン `2`、OMP は `3`、Claude Code は `6`、Codex は `5`、GitHub Copilot CLI は `2`、Devin CLI は `2`、Droid は `2`、Kimi Code CLI は `3`、Qoder CLI は `2`、Cursor Agent CLI は `1`、Grok CLI は `1`、OpenCode は `5`、Kilo Code CLI は `1`、Hermes Agent は `2`、MastraCode は `1`、Antigravity CLI は `1` です。インストール済みバージョンは `herdr integration status` で確認してください。 ## Pi @@ -256,6 +260,34 @@ Herdr は `~/.mastracode` を使います。インストールは `hooks/herdr-a Herdr は保存された MastraCode スレッドを `mastracode --thread ` で resume します。 +## Antigravity CLI + +Antigravity CLI フックをインストールします: + +```bash +herdr integration install antigravity-cli +``` + +Herdr はデフォルトで `~/.gemini/config/` を使い、`ANTIGRAVITY_CLI_CONFIG_DIR` が設定されていればそちらを使います。これは Antigravity CLI がグローバルカスタマイズを読み込むディレクトリで、あらかじめ存在している必要があります。インストールは `hooks/herdr-agent-state.sh`(Windows では `herdr-agent-state.ps1`)を書き込み、`hooks.json` に Herdr 所有の `herdr` ブロックを追加します。Antigravity CLI は `hooks.json` をフック名で管理するため、インストールはそのブロックだけを書き換え、ほかの名前付きフックには触れません。アンインストールは `herdr` ブロックとフックスクリプトを削除します。 + +このインテグレーションはセッション専用です。ペインで実行中の会話を報告しますが、エージェント状態は報告しません。そのため、Herdr は Antigravity CLI の画面表示から `working`、`idle`、`blocked` を引き続き判定します。 + +フックは `PreInvocation` で実行されるため、最初のプロンプト送信後に Herdr が会話を認識します。それ以降、Herdr サーバーの再起動後に `agy --conversation ` でペインを resume できます。 + +## Grok CLI + +Grok CLI フックをインストールします: + +```bash +herdr integration install grok +``` + +このフックは、Grok CLI が Herdr のペイン内で動いている間、Grok の `SessionStart` フックを通じてセッション識別を報告します。Grok の状態は Herdr のスクリーンマニフェスト検出から得られます。 + +Herdr はデフォルトで `~/.grok` を使い、`GROK_HOME` が設定されていればそちらを使います。Grok の設定ディレクトリはあらかじめ存在している必要があります。Grok はそのディレクトリ内のすべての `hooks/*.json` ファイルを統合するため、インストールは Herdr の `SessionStart` エントリだけを含む `hooks/herdr.json` と `hooks/herdr-agent-state.sh` を書き込み、ほかのフックファイルには触れません。アンインストールは Herdr 所有のこの 2 ファイルだけを削除します。 + +Grok がセッション開始イベントを発行した後、Herdr は報告されたセッション id を使って `grok --resume ` でペインを resume できます。 + ## カスタムステータスラベル インテグレーションは、ライフサイクル状態を意味的な状態としてのみ報告します。たとえば、ライフサイクル報告に表示フィールドを加えず、エージェントを `working` として報告します。 diff --git a/docs/next/website/src/content/docs/zh-cn/integrations.mdx b/docs/next/website/src/content/docs/zh-cn/integrations.mdx index c49f61db..3ff557c4 100644 --- a/docs/next/website/src/content/docs/zh-cn/integrations.mdx +++ b/docs/next/website/src/content/docs/zh-cn/integrations.mdx @@ -1,6 +1,6 @@ --- title: 集成 -description: 为 Pi、OMP、Claude Code、Codex、GitHub Copilot CLI、Devin CLI、Droid、Kimi Code CLI、OpenCode、Kilo Code CLI、Hermes Agent、Qoder CLI、Cursor Agent CLI 和 MastraCode 安装 Herdr 集成。 +description: 为 Pi、OMP、Claude Code、Codex、GitHub Copilot CLI、Devin CLI、Droid、Kimi Code CLI、OpenCode、Kilo Code CLI、Hermes Agent、Qoder CLI、Cursor Agent CLI、MastraCode、Antigravity CLI 和 Grok CLI 安装 Herdr 集成。 --- Herdr 自动检测受支持的智能体。官方集成可以额外提供用于恢复的原生会话身份、生命周期状态上报,或两者兼有。 @@ -26,6 +26,8 @@ herdr integration install hermes herdr integration install mastracode herdr integration install qodercli herdr integration install cursor +herdr integration install antigravity-cli +herdr integration install grok ``` ## 卸载集成 @@ -45,6 +47,8 @@ herdr integration uninstall hermes herdr integration uninstall mastracode herdr integration uninstall qodercli herdr integration uninstall cursor +herdr integration uninstall antigravity-cli +herdr integration uninstall grok ``` ## Herdr 如何使用集成 @@ -54,13 +58,13 @@ Herdr 以两种不同方式使用集成: | 集成类型 | 智能体 | 效果 | | --- | --- | --- | | 生命周期权威 | Pi、OMP、Kimi Code CLI、OpenCode、Kilo Code CLI、Hermes Agent、MastraCode | 已安装且在为该窗格主动上报时,由钩子或插件事件决定 `idle`、`working` 和 `blocked`。对同一个生命周期权威,Herdr 不再使用屏幕清单兜底。 | -| 会话身份 | Claude Code、Codex、GitHub Copilot CLI、Devin CLI、Droid、Qoder CLI、Cursor Agent CLI | 集成上报用于恢复的原生会话引用。状态仍来自 Herdr 的屏幕清单检测。 | +| 会话身份 | Claude Code、Codex、GitHub Copilot CLI、Devin CLI、Droid、Qoder CLI、Cursor Agent CLI、Antigravity CLI、Grok CLI | 集成上报用于恢复的原生会话引用。状态仍来自 Herdr 的屏幕清单检测。 | 自定义 socket 集成在定义了原生终端 UI 中不可见的状态时,也可以上报状态。 -一些集成会上报智能体的原生会话引用。除非被 `[session] resume_agents_on_restore = false` 禁用,Herdr 会在服务器重启后使用官方会话引用恢复 Claude Code、Codex、Devin CLI、Droid、Kimi Code CLI、Qoder CLI、Cursor Agent CLI、GitHub Copilot CLI、Pi、OMP、Hermes Agent、OpenCode、Kilo Code CLI 和 MastraCode 的窗格。 +一些集成会上报智能体的原生会话引用。除非被 `[session] resume_agents_on_restore = false` 禁用,Herdr 会在服务器重启后使用官方会话引用恢复 Claude Code、Codex、Devin CLI、Droid、Kimi Code CLI、Qoder CLI、Cursor Agent CLI、Grok CLI、GitHub Copilot CLI、Pi、OMP、Hermes Agent、OpenCode、Kilo Code CLI、MastraCode 和 Antigravity CLI 的窗格。 -原生会话恢复需要最新的 Herdr 集成: Pi 集成版本 `2`、OMP 版本 `3`、Claude Code 版本 `6`、Codex 版本 `5`、GitHub Copilot CLI 版本 `2`、Devin CLI 版本 `2`、Droid 版本 `2`、Kimi Code CLI 版本 `3`、Qoder CLI 版本 `2`、Cursor Agent CLI 版本 `1`、OpenCode 版本 `5`、Kilo Code CLI 版本 `1`、Hermes Agent 版本 `2`、MastraCode 版本 `1`。用 `herdr integration status` 查看已安装版本。 +原生会话恢复需要最新的 Herdr 集成: Pi 集成版本 `2`、OMP 版本 `3`、Claude Code 版本 `6`、Codex 版本 `5`、GitHub Copilot CLI 版本 `2`、Devin CLI 版本 `2`、Droid 版本 `2`、Kimi Code CLI 版本 `3`、Qoder CLI 版本 `2`、Cursor Agent CLI 版本 `1`、Grok CLI 版本 `1`、OpenCode 版本 `5`、Kilo Code CLI 版本 `1`、Hermes Agent 版本 `2`、MastraCode 版本 `1`、Antigravity CLI 版本 `1`。用 `herdr integration status` 查看已安装版本。 ## Pi @@ -256,6 +260,34 @@ Herdr 使用 `~/.mastracode`。安装会写入 `hooks/herdr-agent-state.sh`,并 Herdr 用 `mastracode --thread ` 恢复保存的 MastraCode 线程。 +## Antigravity CLI + +安装 Antigravity CLI 钩子: + +```bash +herdr integration install antigravity-cli +``` + +Herdr 默认使用 `~/.gemini/config/`,设置了 `ANTIGRAVITY_CLI_CONFIG_DIR` 时使用后者。这是 Antigravity CLI 读取全局自定义配置的目录,并且必须已经存在。安装会写入 `hooks/herdr-agent-state.sh`(Windows 上为 `herdr-agent-state.ps1`),并向 `hooks.json` 添加由 Herdr 管理的 `herdr` 块。Antigravity CLI 按钩子名称管理 `hooks.json`,因此安装只会重写该块,不会改动其他命名钩子。卸载会移除 `herdr` 块并删除钩子脚本。 + +该集成仅上报会话。它会上报窗格当前运行的对话,但不上报智能体状态,因此 Herdr 仍根据 Antigravity CLI 在屏幕上绘制的内容判断 `working`、`idle` 和 `blocked`。 + +钩子在 `PreInvocation` 时运行,所以 Herdr 会在发送第一个提示词后获知对话。此后,Herdr 服务器重启时可以用 `agy --conversation ` 恢复窗格。 + +## Grok CLI + +安装 Grok CLI 钩子: + +```bash +herdr integration install grok +``` + +该钩子在 Grok CLI 运行于 Herdr 窗格内时,通过 Grok 的 `SessionStart` 钩子上报会话身份。Grok 的状态来自 Herdr 的屏幕清单检测。 + +Herdr 默认使用 `~/.grok`,设置了 `GROK_HOME` 时使用后者。Grok 配置目录必须已经存在。Grok 会合并该目录中的所有 `hooks/*.json` 文件,因此安装会写入独立的 `hooks/herdr.json`,其中包含 Herdr 的 `SessionStart` 条目,并同时写入 `hooks/herdr-agent-state.sh`;它不会修改其他钩子文件。卸载只会删除这两个由 Herdr 管理的文件。 + +Grok 发出会话启动事件后,Herdr 可以用上报的会话 id 通过 `grok --resume ` 恢复窗格。 + ## 自定义状态标签 集成只把生命周期状态作为语义状态上报。例如,上报智能体为 `working` 时,不在生命周期报告中添加展示字段。 diff --git a/docs/preview/README.md b/docs/preview/README.md new file mode 100644 index 00000000..f8ba2c6a --- /dev/null +++ b/docs/preview/README.md @@ -0,0 +1,9 @@ +# Preview documentation + +`website/` is the committed documentation snapshot for the active preview release in `website/preview.json`. + +Do not edit it manually. Preview CI replaces it from the selected commit's `docs/next/website` tree and commits the snapshot together with `website/preview.json`. Validate it with: + +```bash +node website/scripts/docs-preview.mjs check +``` diff --git a/docs/preview/website/src/content/docs/agent-automation.mdx b/docs/preview/website/src/content/docs/agent-automation.mdx new file mode 100644 index 00000000..c3b1525e --- /dev/null +++ b/docs/preview/website/src/content/docs/agent-automation.mdx @@ -0,0 +1,123 @@ +--- +title: Agent automation +description: Use Herdr's layout, pane, and agent primitives to coordinate coding agents from scripts or other agents. +--- + +Herdr can act as an automation layer for coding agents. A script can control them, or one agent can create work for other agents, inspect their state, and collect their results. The important part is choosing the primitive that matches the job. + +## Three primitives + +| Primitive | Responsibility | +| --- | --- | +| Layout (`workspace`, `tab`, and pane topology) | Create and organize terminal locations. | +| Pane | Control a raw terminal: run commands, send input, read output, and wait for output. | +| Agent | Control a recognized coding agent by name or pane and lifecycle state. | + +A pane exists whether or not it contains an agent. An agent is the recognized process currently running inside a pane. `agent start` therefore requires an existing shell pane and never creates, splits, or moves layout. + +Creating a workspace also creates its first tab and root pane; creating a tab creates its root pane. Use the returned pane ID for the first process, and split only when that layout needs another terminal. + +Creation commands print JSON. Capture IDs from the response instead of predicting them: + +```bash +created=$(herdr workspace create --cwd ~/project --label api --no-focus) +pane_id=$(printf '%s\n' "$created" | jq -r '.result.root_pane.pane_id') + +split=$(herdr pane split "$pane_id" --direction right --no-focus) +review_pane=$(printf '%s\n' "$split" | jq -r '.result.pane.pane_id') +``` + +`workspace create` returns `.result.workspace`, `.result.tab`, and `.result.root_pane`. `tab create` returns `.result.tab` and `.result.root_pane`. `pane split` returns the new pane as `.result.pane`. + +Moving a pane to another workspace changes its workspace-qualified pane ID. After any `pane move`, continue with `.result.move_result.pane.pane_id`; the response keeps the old value at `.result.move_result.previous_pane_id`. A running process keeps its launch-time Herdr environment, but the old `HERDR_PANE_ID` remains an alias for that terminal, so `--current` stays safe. New commands can still resolve the agent by name after the move, but a wait already in progress ends with `agent_not_running`. + +Use pane commands for shells, tests, servers, CI watchers, and other ordinary terminal processes. Use agent commands when Herdr needs to understand which agent is running or whether it is `working`, `blocked`, `done`, `idle`, or `unknown`. + +## Agent identity and launch + +A pane ID such as `w1:p2` identifies the terminal location. An agent name such as `reviewer` is a convenient alias for the current agent in that pane. Names must match `[a-z][a-z0-9_-]{0,31}` and be unique among live agents. The alias is cleared when that agent exits, is released, or is replaced; it does not permanently rename the pane. + +Agent commands accept either a unique live name or the pane ID that currently hosts the agent. + +An available shell pane is at its interactive shell prompt: the shell itself owns the foreground, with no foreground command, editor, or agent running. Return the pane to its prompt before calling `agent start`. + +`--kind` selects a supported agent and its canonical executable. Supported kinds are `pi`, `claude`, `codex`, `gemini`, `cursor`, `devin`, `agy`, `cline`, `omp`, `mastracode`, `opencode`, `copilot`, `kimi`, `kiro`, `droid`, `amp`, `grok`, `hermes`, `kilo`, `qodercli`, and `maki`. Arguments after `--` are passed unchanged to that executable. + +`agent start` returns only after Herdr detects the expected agent in the same terminal and marks it ready for interactive input. Startup waits for 30 seconds by default; `--timeout` must be greater than 3000 and no more than 300000 milliseconds. + +```bash +herdr agent start reviewer --kind codex --pane "$review_pane" -- -m gpt-5.4 +``` + +Agents launched manually are detected automatically and can be addressed by pane ID. Give one a name when a stable human-readable target is useful: + +```bash +herdr agent get w1:p2 +herdr agent rename w1:p2 reviewer +``` + +## Choose the control surface + +| Goal | Command | +| --- | --- | +| Run a shell command and submit it | `pane run` | +| Send literal text without Enter | `pane send-text` | +| Send terminal keys or modifier chords | `pane send-keys` | +| Wait for text or a regular expression | `pane wait-output` | +| Start a supported agent in an existing pane | `agent start` | +| Submit a prompt, optionally waiting for it | `agent prompt` | +| Send keys to an agent's interactive UI | `agent send-keys` | +| Wait for agent lifecycle state | `agent wait` | + +`agent prompt` submits text plus encoded Enter and honors the terminal's live bracketed-paste mode. It can prompt an agent that is already working. Use `agent send-keys` for interactions such as `esc`, `up`, `enter`, or `ctrl+c`; `escape` is accepted as an alias for `esc`. Use the pane input commands when you deliberately want raw terminal control. + +Pane input addresses the terminal regardless of its current occupant. Agent input resolves the live agent and rejects the operation if that agent no longer controls the pane. + +`agent prompt --wait` submits immediately. When the agent starts from a non-working state, Herdr first requires an observed lifecycle change within five seconds. If the state sequence does not advance, it returns `agent_prompt_stalled` instead of waiting indefinitely; a caller `--timeout` of five seconds or less returns the normal `timeout` error. After activity is observed, it waits for the requested settled status. It does not track individual turns. If the agent is already working, completion of that active turn may satisfy the wait. Standalone `agent wait` observes the current agent and returns immediately if its status already matches. Both commands default to `idle`, `done`, or `blocked`. Repeat `--until` to accept several exact states, for example `--until idle --until done`; use `--until unknown` explicitly when needed. On `agent prompt`, `--until` requires `--wait`. + +`idle` means the agent is ready for input and its tab has been seen in the focused Herdr UI. `done` is the same underlying idle state after background work finishes, until that tab is focused or `pane focus` / `agent focus` targets it. Reading through the CLI does not mark it seen. `blocked` means Herdr recognized an approval or question UI. `unknown` means an agent is present but Herdr cannot classify its lifecycle confidently; it does not prove successful completion. Use exact `--until` states when that distinction matters. + +`pane wait-output` does not interpret agent lifecycle. It polls the selected terminal snapshot and searches it immediately, so text that was already present can match. The default source name is `recent`; matching treats that source as unwrapped recent output from the latest 80 rendered terminal rows. `--lines` changes that row limit, and `--regex` uses Rust regular-expression syntax and matches one line at a time. + +At the CLI, both `pane read` and `agent read` print terminal text directly. The default is UTF-8 text with ANSI escapes stripped; use `--format ansi` or `--ansi` to preserve terminal escapes where the source exposes them. The `detection` source is always plain text. For recent sources, `--lines N` selects the last N rendered terminal rows before optional unwrapping; without it, reads default to 80 rows. For `visible` and `detection`, omitting `--lines` returns the full snapshot, while specifying it keeps the last N newline-delimited lines. The socket API returns the text at `.result.read.text`. + +## Known caveat: alternate-screen output + +Full-screen agents such as Claude Code and OpenCode may render in the terminal's alternate screen. Alternate-screen rows do not enter Herdr's host scrollback. `--lines` can only request more rows from the pane's available screen and host scrollback; it cannot create missing history. If increasing `--lines` returns no additional response text, the pane is probably using the alternate screen and those response rows are no longer retained. A larger font or smaller pane makes this more likely. + +Ask the agent for concise responses, enlarge the pane or use a smaller font, or use the agent's own transcript and scrolling controls. After scrolling inside the agent, use `--source visible` to read the page it currently renders. + +After that failed read, ask the agent to write its complete response as Markdown in a temporary directory and reply only with the file path, then read the file directly. Use this only as a fallback; do not request file output in the initial prompt. + +Successful `agent start`, `agent prompt`, and `agent wait` commands return the current agent at `.result.agent`. `pane wait-output` returns `.result.pane_id`, `.result.matched_line`, and the matched snapshot at `.result.read`. + +Wait commands have no default timeout and can wait indefinitely. On timeout or another server error, CLI commands print a JSON error to stderr and exit with status 1; invalid CLI syntax exits with status 2. + +## Recipes + +Start a helper, give it work, and wait for that work to settle: + +```bash +split=$(herdr pane split --current --direction right --no-focus) +review_pane=$(printf '%s\n' "$split" | jq -r '.result.pane.pane_id') +herdr agent start reviewer --kind codex --pane "$review_pane" -- -m gpt-5.4 +herdr agent prompt reviewer "Review the current diff" --wait --timeout 120000 +herdr agent read reviewer --source recent-unwrapped --lines 120 +``` + +Wait for an agent to ask for input, inspect it, and interact with its UI: + +```bash +herdr agent wait reviewer --until blocked --timeout 120000 +herdr agent read reviewer --source recent-unwrapped --lines 80 +herdr agent send-keys reviewer esc +``` + +Run an ordinary process and wait for its output without treating it as an agent: + +```bash +herdr pane run w1:p3 "just test --watch" +herdr pane wait-output w1:p3 --regex "passed|failed" --timeout 120000 +``` + +See the [CLI reference](/docs/cli-reference/) for the complete command and option list. Shell completions expose the same command tree interactively. diff --git a/docs/preview/website/src/content/docs/agent-skill.mdx b/docs/preview/website/src/content/docs/agent-skill.mdx new file mode 100644 index 00000000..498861f7 --- /dev/null +++ b/docs/preview/website/src/content/docs/agent-skill.mdx @@ -0,0 +1,65 @@ +--- +title: Agent skill file +description: Install Herdr instructions for Claude Code or another coding agent. +--- + +Herdr ships a reusable agent skill file at [`SKILL.md`](https://github.com/ogulcancelik/herdr/blob/master/SKILL.md). + +Install that file into any coding agent that supports reusable skills or custom instructions. The skill teaches the agent how to control Herdr from inside a Herdr pane. + +Herdr also serves a separate guide at [`herdr.dev/agent-guide.md`](https://herdr.dev/agent-guide.md) for a different job: an agent helping a human learn, set up, or troubleshoot Herdr. The skill is for an agent operating Herdr; the guide is for an agent teaching a human. + +## What the skill does + +The skill tells an agent to use the `herdr` CLI when `HERDR_ENV=1` is set. That means the agent is running inside a Herdr-managed pane and can safely talk to the local Herdr socket. + +With the skill installed, an agent can: + +- inspect workspaces, tabs, panes, and neighboring agents +- split panes and run commands without stealing focus +- read pane output and recent logs +- wait for servers, tests, or another agent to finish +- start helper agents in sibling panes + +The skill is not a separate app or service. It is a markdown instruction file for agents. + +## Install it + +Install the skill with `npx skills`: + +```bash +npx skills add ogulcancelik/herdr --skill herdr -g +``` + +The `-g` flag installs it globally for supported agents. Omit `-g` to install it into the current project. + +Use the repository copy as the manual fallback and source of truth: + +```text +https://github.com/ogulcancelik/herdr/blob/master/SKILL.md +``` + +For agents with a skill system, install that file as a skill named `herdr`. For agents without a skill system, paste the file into the agent's project or user instructions. + +After installation, start the agent inside Herdr: + +```bash +herdr +claude +``` + +Or use any other coding agent in a Herdr pane. The important part is that the agent process runs inside Herdr, so `HERDR_ENV=1` is available. + +## Safety rule + +The skill starts with one guardrail: if `HERDR_ENV=1` is not set, the agent should stop and say it is not running inside a Herdr-managed pane. + +This prevents an agent outside Herdr from trying to control a session it does not own. + +## Agent-facing reference + +The full command guide lives in the skill file itself. It covers pane IDs, `pane split`, `pane run`, `pane read`, `pane wait-output`, `agent wait`, workspace and tab commands, and coordination recipes. + +Read the source file here: + +[Open `SKILL.md` on GitHub →](https://github.com/ogulcancelik/herdr/blob/master/SKILL.md) diff --git a/docs/preview/website/src/content/docs/agents.mdx b/docs/preview/website/src/content/docs/agents.mdx new file mode 100644 index 00000000..f5a4ac48 --- /dev/null +++ b/docs/preview/website/src/content/docs/agents.mdx @@ -0,0 +1,154 @@ +--- +title: Agents +description: See what Herdr can detect, how agent state works, and how integrations improve it. +--- + +Herdr is built for running more than one coding agent at a time. Each agent stays in a real terminal pane with its shell, logs, prompts, and running processes intact. Herdr tracks which panes contain agents, rolls their state up to tabs and workspaces, and lets you jump straight to the pane that needs attention instead of polling every terminal by hand. + +To coordinate agents from scripts or from another agent, see [Agent automation](/docs/agent-automation/). + +## Supported agents + +Automatic detection works out of the box for common coding agents. The important difference is not whether Herdr can see an agent. It is which signal is allowed to author `idle`, `working`, and `blocked`. + +| Agent | State authority | Integration role | +| --- | --- | --- | +| Pi | lifecycle hooks when installed; otherwise screen manifest | state and session | +| OMP | lifecycle hooks when installed | state | +| GitHub Copilot CLI | screen manifest | session | +| Devin CLI | screen manifest | session | +| Kimi Code CLI | lifecycle hooks when installed; otherwise screen manifest | state and session | +| Hermes Agent | lifecycle hooks when installed; otherwise screen manifest | state and session | +| Qoder CLI | screen manifest | session | +| Droid | screen manifest | session | +| OpenCode | lifecycle plugin when installed; otherwise screen manifest | state and session | +| Kilo Code CLI | lifecycle plugin when installed; otherwise screen manifest | state and session | +| MastraCode | lifecycle hooks when installed | state and session | +| Claude Code | screen manifest | session | +| Codex | screen manifest | session | +| Cursor Agent CLI | screen manifest | session | +| Amp | screen manifest | none | +| Grok CLI | screen manifest | session | +| Antigravity CLI | screen manifest | none | +| Kiro CLI | screen manifest | none | +| Maki | screen manifest | none | + +Detected but less thoroughly tested: Gemini CLI and Cline. Unsupported agents still run normally as terminal processes. They just may not get rich state unless you add an integration or report state over the socket API. + +## Status authority + +Herdr first detects the foreground process in each pane. After that, each pane has one status authority. + +For agents with complete lifecycle hooks, the integration is authoritative when it is installed and actively reporting for the running pane. Herdr uses those hook reports for `idle`, `working`, `blocked`, and session identity. It does not also run screen manifest fallback for that same lifecycle authority. This avoids two competing sources of truth. + +For agents without complete lifecycle hooks, Herdr identifies the foreground process and reads the live bottom-buffer screen snapshot. It evaluates TOML manifests against that snapshot to classify `idle`, `working`, and `blocked`. For agents that emit them, manifests can also match terminal title and progress (OSC) sequences as detection evidence; when that evidence is absent, screen rules carry detection on their own. + +The screen snapshot comes from the recent bottom of the pane buffer, not the scrolled viewport. If you scroll back in Herdr, detection still follows the live agent UI at the bottom. + +Claude Code, Codex, GitHub Copilot CLI, Droid, Qoder CLI, Cursor Agent CLI, and Grok CLI integrations are intentionally not lifecycle authorities. They provide native session identity for restore, but their hooks do not cover the whole lifecycle. They can miss permission approval results, escape interrupts, or other transitions. For those agents, Herdr still uses screen manifest detection. + +## VMs and sandbox wrappers + +On Linux and macOS, a host-visible wrapper can hide the real agent process from Herdr. Set `HERDR_AGENT=` on the wrapper command to tell Herdr which existing agent screen manifest to use. For example, run `HERDR_AGENT=claude fence -- claude` on Linux or `HERDR_AGENT=claude nono run --profile claude-code -- claude` on macOS. The hint is scoped to that foreground process; setting it only inside a VM or container is not visible to Herdr, and you should avoid exporting it globally unless every inherited foreground process should be treated as that agent. + +## Blocked state + +Blocked detection is deliberately strict for screen-manifest agents. Herdr only marks `blocked` when the live bottom-buffer snapshot matches known visible approval, question, or permission UI. If no manifest rule matches for a known agent, Herdr falls back to `idle` and labels that fallback as `default_known_agent_idle_fallback` in explain output. + +This means unusual new agent prompts may initially show as `idle` instead of `blocked` until Herdr learns that screen shape. Those interactions should not make Herdr send input or take destructive action; they only affect the visible status and waits. + +## Detection manifests + +Bundled manifests live inside Herdr. Herdr also checks herdr.dev for remote manifest updates and applies valid per-agent rule updates automatically without requiring a Herdr restart. Remote manifests are stored in Herdr's state directory. Set `[update] manifest_check = false` to disable background remote manifest checks. + +Local overrides can replace a remote or bundled manifest from the platform config directory: + +```text +~/.config/herdr/agent-detection/.toml +``` + +Local overrides always win. Without a local override, Herdr uses the newer compatible manifest between the cached remote manifest and the bundled manifest in the running binary. On debug builds, the same config helper may use a development directory such as `herdr-dev`. Invalid override files are ignored with a warning and Herdr falls back to the cached remote or bundled manifest for that agent. + +Remote manifests patch detection rules for agents Herdr already knows how to identify. Adding a completely new agent still requires a Herdr binary update for process detection, labels, and integration behavior. + +The running server loads active manifests into memory on startup. Automatic remote manifest updates reload that in-memory cache after new rules are written. Run `herdr server update-agent-manifests` to fetch remote manifest updates immediately and reload the running server. After editing a local override manually, restart Herdr or run `herdr server reload-agent-manifests` to apply the file to the running server. + +Use `herdr agent explain` when a pane shows the wrong state: + +```bash +herdr agent explain +herdr agent explain --file screen.txt --agent codex --json +``` + +Live explain is evaluated by the running server, so it reflects the active manifest cache. The explain output shows the agent, final state, whether screen detection was skipped by a full lifecycle authority, manifest source and version, cached remote version, local override shadowing, remote update status, matched rule, visible evidence flags, matcher and region evidence for evaluated rules, skipped-update reason for transcript viewers, and the idle fallback reason when no rule matched. + +Herdr can run inside tmux as the outer terminal environment. Agent detection does not inspect tmux sessions launched inside a Herdr pane. If a shell framework auto-enters tmux inside Herdr, Herdr sees `tmux` as the pane process instead of the agent behind it. + +## State rollups + +The sidebar rolls state upward. + +A blocked agent makes its pane, tab, and workspace look blocked. A working agent makes the workspace look active. A done agent stays visible until you view it. + +This is the main Herdr workflow: start several agents, let them work in parallel, and use the sidebar to see which project needs a decision, which one is still running, and which one is ready to review. + +## Direct integrations + +Install the integration for each agent you use; it gives Herdr hook or plugin reports instead of screen detection alone: + +```bash +herdr integration install claude +herdr integration status +``` + +Each supported agent has its own integration name and behavior. See [Integrations](/docs/integrations/) for the per-agent details and the full install list. + +## Custom agent labels + +You can rename an agent target for display: + +```bash +herdr agent rename w1:p1 reviewer +herdr agent rename reviewer --clear +``` + +Targets accept a unique live agent name or the pane ID that currently hosts the agent. Terminal IDs and bare agent-kind labels are not accepted. + +## Custom status labels + +Integrations report lifecycle state as semantic state only. Add display customization separately with pane metadata tokens. + +```bash +herdr pane report-agent w1:p1 \ + --source custom:indexer \ + --agent docs-bot \ + --state working + +herdr pane report-metadata w1:p1 \ + --source custom:indexer-display \ + --token summary=indexing +``` + +`state` controls waits, notifications, and rollups. The `summary` token is display-only and can be used as `$summary` in an Agent sidebar row. + +Agent sidebar rows can also opt into `terminal_title` or `terminal_title_stripped`; neither appears in the default rows. The first shows the latest safety-normalized OSC 0/2 terminal title. The second removes one recognized leading activity or spinner glyph and following whitespace. Herdr owns these values on the server; they are ephemeral across a cold restart and remain independent of metadata titles and semantic agent state. Spinner animation can therefore update the raw title without producing a pane update when the stripped text stays the same. + +## Attach directly to an agent + +Attach your current terminal to one agent terminal instead of the full Herdr UI: + +```bash +herdr agent attach reviewer +``` + +Detach with `ctrl+b q`. Send a literal `ctrl+b` with `ctrl+b ctrl+b`. + +Scroll with the mouse wheel or plain page up/page down. Normal input jumps back to the bottom. + +Use `--takeover` if another direct attach client already owns input: + +```bash +herdr agent attach reviewer --takeover +``` + +Use `herdr terminal attach ` when you want the same direct attach behavior for a non-agent terminal. diff --git a/docs/preview/website/src/content/docs/cli-reference.mdx b/docs/preview/website/src/content/docs/cli-reference.mdx new file mode 100644 index 00000000..a49a4212 --- /dev/null +++ b/docs/preview/website/src/content/docs/cli-reference.mdx @@ -0,0 +1,487 @@ +--- +title: CLI reference +description: Herdr commands for sessions, workspaces, tabs, panes, notifications, agents, waits, integrations, and status. +--- + +Herdr’s CLI talks to the running server over the same local socket API used by integrations and agents. + +Most commands print JSON responses. Use them from scripts when you want deterministic automation. + +## Launch and status + +```bash +herdr # launch or attach to the default session +herdr --session work # launch or attach to a named session +herdr --remote workbox # attach through SSH, using local keybindings +herdr --remote workbox --remote-keybindings server +herdr --remote workbox --handoff +herdr --no-session # single-process escape hatch +herdr --default-config # print default config +herdr update # download and install from the configured channel +herdr update --handoff # opt into live handoff for supported running servers +herdr completion zsh # generate a zsh completion script +herdr channel show # print stable or preview +herdr channel set preview # opt into preview builds +herdr channel set stable # return Linux/macOS direct installs to stable +herdr --version # print version +``` + +Status commands: + +```bash +herdr status +herdr status server +herdr status client +``` + +API schema commands: + +```bash +herdr api schema +herdr api schema --json +herdr api schema --output herdr-api.schema.json +``` + +`herdr api schema` prints a short summary of the socket protocol schema bundled +with the installed binary. Use `--json` for the full JSON Schema document, or +`--output PATH` to write that document to a file. + +## Shell completions + +```bash +herdr completion zsh +herdr completions zsh +herdr completion bash +herdr completion fish +herdr completion powershell +herdr completion elvish +``` + +`completion` prints the script to stdout. `completions` is an alias. For a +temporary zsh session, load the script directly: + +```bash +source <(herdr completion zsh) +``` + +For a persistent zsh setup, write the generated `_herdr` function somewhere on +your `fpath` before `compinit` runs: + +```bash +mkdir -p ~/.zfunc +herdr completion zsh > ~/.zfunc/_herdr +``` + +Then make sure your `.zshrc` contains: + +```zsh +fpath=(~/.zfunc $fpath) +autoload -Uz compinit +compinit +``` + +## Server + +```bash +herdr server +herdr server stop +herdr server reload-config +herdr server agent-manifests [--json] +herdr server update-agent-manifests [--json] +herdr server reload-agent-manifests +``` + +`herdr server` runs the headless server explicitly. Use it for supervised or service-style setups. `reload-config` applies reloadable settings without restarting panes. `agent-manifests` shows the active agent detection manifest sources, cached remote versions, and last remote update results. `update-agent-manifests` fetches remote manifest updates immediately, reloads them into the running server, and prints the updated manifest status; pass `--json` for the raw status response. `reload-agent-manifests` reloads agent detection manifests into the running server after local override edits. + +## Notifications + +```bash +herdr notification show [--body TEXT] [--position top-left|top-right|bottom-left|bottom-right] [--sound none|done|request] +``` + +`notification show` uses the configured `[ui.toast]` delivery. `--position` only affects in-app Herdr toasts. `--sound` defaults to `none`; `done` and `request` play the existing finished and needs-attention sounds only when the notification is shown. + +## Sessions + +```bash +herdr session list [--json] +herdr session attach <name> +herdr session stop <name> [--json] +herdr session delete <name> [--json] +``` + +Use `default` as the session name when you need to stop the default session explicitly. + +## Workspaces + +```bash +herdr workspace list +herdr workspace create [--cwd PATH] [--label TEXT] [--env KEY=VALUE] [--focus] [--no-focus] +herdr workspace get <workspace_id> +herdr workspace focus <workspace_id> +herdr workspace rename <workspace_id> <label> +herdr workspace report-metadata <workspace_id> --source ID [--token NAME=VALUE] [--clear-token NAME] [--seq N] [--ttl-ms N] +herdr workspace close <workspace_id> +``` + +Create a workspace without stealing focus: + +```bash +herdr workspace create --cwd ~/project --label api --no-focus +``` + +A workspace is the top-level project or work context. Creating one also creates its first tab and root pane. The JSON response exposes their IDs as `.result.workspace.workspace_id`, `.result.tab.tab_id`, and `.result.root_pane.pane_id`. + +## Worktrees + +```bash +herdr worktree list [--workspace ID | --cwd PATH] [--json] +herdr worktree create [--workspace ID | --cwd PATH] [--branch NAME] [--base REF] [--path PATH] [--label TEXT] [--focus] [--no-focus] [--json] +herdr worktree open [--workspace ID | --cwd PATH] (--path PATH | --branch NAME) [--label TEXT] [--focus] [--no-focus] [--json] +herdr worktree remove --workspace ID [--force] [--json] +``` + +Worktrees are normal Herdr workspaces with Git checkout provenance. `worktree create` creates a Git worktree checkout, opens it as a workspace, and groups it with the parent repo workspace. If `--branch` names an existing local branch, Herdr checks it out; otherwise it creates the branch from `--base` or `HEAD`. Without `--path`, Herdr creates the checkout under `<worktrees.directory>/<repo>/<branch-slug>`. + +`workspace close` closes Herdr state only. `worktree remove` is the explicit checkout deletion path; it runs `git worktree remove`, never deletes the branch, and requires `--force` when Git refuses a dirty checkout. + +## Tabs + +```bash +herdr tab list [--workspace <workspace_id>] +herdr tab create [--workspace <workspace_id>] [--cwd PATH] [--label TEXT] [--env KEY=VALUE] [--focus] [--no-focus] +herdr tab get <tab_id> +herdr tab focus <tab_id> +herdr tab rename <tab_id> <label> +herdr tab close <tab_id> +``` + +A tab is another terminal layout inside a workspace. Without `--workspace`, `tab create` uses the active workspace and fails if none exists. Its JSON response exposes `.result.tab.tab_id` and `.result.root_pane.pane_id`. Closing a workspace's last tab also closes the workspace, matching the TUI close-tab action; when `confirm_close` is enabled and that would also close a whole worktree group, `tab close` returns a `confirmation_required` error instead. + +Workspace and tab creation, and pane splitting, leave focus unchanged by default. `--focus` selects the new layout; `--no-focus` states the default explicitly. Without `--cwd`, new terminals follow the configured `terminal.new_cwd` policy, which follows the source pane or workspace by default. Each `--env KEY=VALUE` adds or replaces that variable in the new root shell. + +## Panes + +```bash +herdr pane list [--workspace <workspace_id>] +herdr pane current [--pane ID|--current] +herdr pane get <pane_id> +herdr pane layout [--pane ID|--current] +herdr pane process-info [--pane ID|--current] +herdr pane neighbor --direction left|right|up|down [--pane ID|--current] +herdr pane edges [--pane ID|--current] +herdr pane focus --direction left|right|up|down [--pane ID|--current] +herdr pane resize --direction left|right|up|down [--amount FLOAT] [--pane ID|--current] +herdr pane zoom [<pane_id>|--pane ID|--current] [--toggle|--on|--off] +herdr pane rename <pane_id> <label>|--clear +herdr pane split [<pane_id>|--pane ID|--current] --direction right|down [--ratio FLOAT] [--cwd PATH] [--env KEY=VALUE] [--focus] [--no-focus] +herdr pane swap --direction left|right|up|down [--pane ID|--current] +herdr pane swap --source-pane ID --target-pane ID +herdr pane move <pane_id> --tab <tab_id> --split right|down [--target-pane ID] [--ratio FLOAT] [--focus|--no-focus] +herdr pane move <pane_id> --new-tab [--workspace ID] [--label TEXT] [--focus|--no-focus] +herdr pane move <pane_id> --new-workspace [--label TEXT] [--tab-label TEXT] [--focus|--no-focus] +herdr pane close <pane_id> +``` + +For pane commands that accept `--current`, Herdr uses the calling pane's +`HERDR_PANE_ID` when the command runs inside a Herdr pane. For `pane split`, +an explicit pane id or `--pane ID` splits that pane, `--current` splits the +calling pane, and an omitted target keeps using the UI-focused pane. +The split response exposes the new pane ID as `.result.pane.pane_id`. +After `pane move`, use `.result.move_result.pane.pane_id` for later commands. A cross-workspace move changes the workspace-qualified pane ID; the prior value remains at `.result.move_result.previous_pane_id`. The running process keeps its launch-time `HERDR_PANE_ID`, `HERDR_TAB_ID`, and `HERDR_WORKSPACE_ID`; Herdr retains the old pane ID as an alias for that terminal, so pane commands using `--current` still resolve it. A live agent name follows the terminal and continues to resolve after the move. + +Read output: + +```bash +herdr pane read <pane_id> [--source visible|recent|recent-unwrapped|detection] [--lines N] [--format text|ansi] [--ansi] [--raw] +herdr pane read <pane_id> --source visible --ansi +herdr pane read <pane_id> --source recent-unwrapped --lines 120 +``` + +`pane read` prints UTF-8 terminal text directly. ANSI escapes are stripped by default; use `--format ansi` or `--ansi` to preserve them where the source exposes styling. The `detection` source is always plain text. For recent sources, `--lines N` selects the last N rendered terminal rows before optional unwrapping; without it, reads default to 80 rows. For `visible` and `detection`, omitting `--lines` returns the full snapshot, while specifying it keeps the last N newline-delimited lines. `agent read` uses the same output and line behavior. + +Send input: + +```bash +herdr pane send-text <pane_id> <text> +herdr pane send-keys <pane_id> <key> [key ...] +herdr pane run <pane_id> <command> +``` + +`<key>` uses Herdr key-combo syntax: plain printable keys such as `a`, +special keys such as `enter`, `tab`, `esc`, `backspace`, `left`, `right`, +`up`, and `down`, modifier chords such as `ctrl+h`, `control+j`, `alt+x`, +and `shift+tab`, function keys such as `f1`, and named punctuation such as +`minus`, `plus`, and `backtick`. Legacy `C-c` and `c-c` are accepted as +aliases for `ctrl+c`. +`esc` is the canonical spelling; `escape` is also accepted. + +`pane run` honors live bracketed-paste mode and submits text plus Enter atomically. Prefer it over `send-text` plus `send-keys Enter` for commands; the separate send operations remain low-level and non-submitting. + +Report agent state from custom hooks: + +```bash +herdr pane report-agent <pane_id> \ + --source ID \ + --agent LABEL \ + --state idle|working|blocked|unknown \ + [--message TEXT] \ + [--seq N] \ + [--agent-session-id ID] \ + [--agent-session-path PATH] + +herdr pane report-agent-session <pane_id> \ + --source ID \ + --agent LABEL \ + [--seq N] \ + [--agent-session-id ID] \ + [--agent-session-path PATH] \ + [--session-start-source SOURCE] + +herdr pane release-agent <pane_id> \ + --source ID \ + --agent LABEL \ + [--seq N] +``` + +`report-agent-session` updates native session identity without reporting lifecycle state. `release-agent` ends that source's lifecycle authority when its agent process exits. + +`pane get`, `pane list`, `agent get`, and `agent list` include a read-only `agent_session` object when an official integration has reported a native session reference. If no native session reference is stored, the field is omitted. + +Those commands include `foreground_cwd` when Herdr can resolve the cwd of the foreground process controlling the pane. The existing `cwd` field remains the pane/workspace cwd used for labels and follow-cwd behavior. + +`pane get` and `pane list` include `scroll` when terminal scroll metrics are available. `scroll.offset_from_bottom == 0` means the pane is at the bottom of its scrollback. + +Report display-only pane metadata without taking over semantic state: + +```bash +herdr pane report-metadata <pane_id> \ + --source ID \ + [--agent LABEL] \ + [--applies-to-source ID] \ + [--title TEXT|--clear-title] \ + [--display-agent TEXT|--clear-display-agent] \ + [--state-label STATUS=TEXT] \ + [--clear-state-labels] \ + [--token NAME=VALUE] \ + [--clear-token NAME] \ + [--seq N] \ + [--ttl-ms N] +``` + +`STATUS` is one of `idle`, `working`, `blocked`, `done`, or `unknown`. `--agent` and `--applies-to-source` guard only `--title`, `--display-agent`, and `--state-label`. They do not guard token patches; token reporters own clearing or TTL refresh. Use `--display-agent` to change the visible name. + +Metadata text is normalized before storage. Herdr trims surrounding whitespace, removes control characters, and caps `--title`, `--display-agent`, each `--state-label`, and token values at 80 characters. Empty normalized token values clear that key. + +`--token` patches one named display value; `--clear-token` removes one. Unmentioned tokens remain unchanged. Pane tokens are available to Agent sidebar rows as `$name`; workspace tokens are available to Space rows. TTL applies independently to the token keys updated by that call. + +`--source` and `--applies-to-source` must be 80 characters or fewer and may contain only ASCII letters, digits, colon, dot, underscore, and hyphen. `--ttl-ms` makes metadata expire automatically and must be between `1` and `86400000` milliseconds. Omit it for metadata that should stay until replaced, cleared, or the pane closes. `--seq` lets Herdr ignore stale reports from the same `--source`; stale reports are accepted by the API but ignored by pane state. A pane or workspace accepts sequenced token reports from at most 32 distinct sources during its lifetime; clearing or expiry does not release those source slots. + +## Agents + +For the pane-versus-agent model and complete orchestration examples, see [Agent automation](/docs/agent-automation/). + +```bash +herdr agent list +herdr agent get <target> +herdr agent read <target> [--source visible|recent|recent-unwrapped|detection] [--lines N] [--format text|ansi] [--ansi] +herdr agent send-keys <target> <key> [key ...] +herdr agent prompt <target> <text> [--wait] [--until STATUS]... [--timeout MS] +herdr agent rename <target> <name>|--clear +herdr agent focus <target> +herdr agent wait <target> [--until STATUS]... [--timeout MS] +herdr agent attach <target> [--takeover] +herdr agent start <name> --kind KIND --pane ID [--timeout MS] [-- <agent-args...>] +herdr agent explain <target> [--json|--verbose] +herdr agent explain --file PATH --agent LABEL [--json|--verbose] +``` + +Agent targets are either a unique live agent name or the pane ID that currently hosts the agent. Terminal IDs and bare agent-kind labels are not agent targets. Agents started through `agent start` require a name; manually launched agents remain unnamed and use their pane ID. + +`agent start` activates an existing available shell pane: the pane's interactive shell must own the foreground, with no foreground command, editor, or agent running. Topology must be created separately. Names are unique among live agents and must match `[a-z][a-z0-9_-]{0,31}`. The kind selects Herdr's canonical interactive executable, while arguments after `--` are passed to that executable. Supported kinds are `pi`, `claude`, `codex`, `gemini`, `cursor`, `devin`, `agy`, `cline`, `omp`, `mastracode`, `opencode`, `copilot`, `kimi`, `kiro`, `droid`, `amp`, `grok`, `hermes`, `kilo`, `qodercli`, and `maki`. A name follows the current pane occupant and is cleared when that agent exits, is released, or is replaced. Temporary detection uncertainty does not clear it. + +Start returns only after the expected agent owns the same terminal and is ready for interactive input. The default startup timeout is 30000 milliseconds; explicit values must be greater than 3000 and no more than 300000. + +`agent prompt` honors live bracketed-paste mode and submits text plus encoded Enter atomically, including while the agent is working. With `--wait`, a prompt sent from a non-working state must produce an observed lifecycle change within five seconds or Herdr returns `agent_prompt_stalled`; a caller timeout of five seconds or less keeps the normal `timeout` error. After activity is observed, it waits for the first requested settled status. It does not track individual turns. If the agent is already working, completion of that active turn may satisfy the wait. `--until` narrows the matching states and is rejected unless `--wait` is also present. Standalone `agent wait` returns immediately when the current status matches. Both default to `idle`, `done`, or `blocked`; use `--until unknown` explicitly when needed. + +`idle` means ready for input after its tab has been seen in the focused Herdr UI; `done` is the same underlying idle state after unseen background work completes. Focusing that tab or targeting it with `pane focus` / `agent focus` marks it seen; reading it through the CLI does not. `blocked` means Herdr recognized an approval or question UI. `unknown` means an agent is present but Herdr cannot classify it confidently, not that its work succeeded. + +`agent send-keys` sends logical terminal keys such as `enter`, `up`, `esc`, or `ctrl+c`. Herdr validates every key before writing any bytes. `agent read` reads the resolved terminal stream, and `agent rename` names an already detected agent. + +`agent explain` asks the running server to classify the same bottom-buffer detection snapshot used by screen detection, so live output reflects the server's active manifest cache. Because this uses the `agent.explain` socket method, restart or hand off to an updated server after upgrading Herdr before using live explain. Use `--file PATH --agent LABEL` to explain a saved fixture locally instead. The default output shows the agent, final state, manifest source and version, matched rule with its region evidence, and any fallback, skip, or warning reasons. Add `--verbose` for visible evidence flags, cached remote version, local override shadowing, remote update status, and the full evaluated-rules list with matcher and region evidence. Add `--json` for issue reports or tests. + +Use `pane send-text`, `pane send-keys`, `pane run`, and `terminal attach` for ordinary terminals, servers, tests, shells, or low-level terminal control. Use `pane run` when you want to submit a command with Enter. + +## Direct terminal attach + +```bash +herdr terminal attach <terminal_id> [--takeover] +herdr terminal session control <target> [--takeover] [--cols N] [--rows N] +herdr terminal session observe <target> [--cols N] [--rows N] +herdr terminal title set <title> +herdr terminal title clear +``` + +Detach from direct attach with `ctrl+b q`. Send literal `ctrl+b` with `ctrl+b ctrl+b`. +`terminal session control` opens a writable live terminal stream for a pane, +terminal, or agent target. It prints the same newline-delimited +`terminal.frame` and `terminal.closed` records as observe mode. It reads +newline-delimited JSON commands on stdin: `terminal.input`, `terminal.resize`, +`terminal.scroll`, and `terminal.release`. One controller can own a terminal at +a time; use `--takeover` to replace it. +`terminal session observe` opens a read-only live terminal stream for a pane, +terminal, or agent target. It prints newline-delimited JSON `terminal.frame` +records with base64-encoded ANSI bytes, then a `terminal.closed` record when +the server closes the stream. Multiple observers can watch the same terminal +without taking input, resize, scroll, or takeover authority. +`terminal title clear` restores Herdr's default outer terminal window title. + +## Output waits + +Wait for output in a pane: + +```bash +herdr pane wait-output <pane_id> (--match <text> | --regex <pattern>) [--source visible|recent|recent-unwrapped] [--lines N] [--timeout MS] [--raw] +``` + +Use `pane wait-output` for normal commands and servers. Use `agent wait` for coding agents. + +`pane wait-output` checks the selected snapshot immediately, including output that already exists, then polls until it matches. The default source name is `recent`; matching treats it as unwrapped recent output from the latest 80 rendered terminal rows. `--lines` changes that row limit. `--match` finds a literal substring on one line, and `--regex` uses Rust regular-expression syntax and also matches one line at a time. + +`pane wait-output`, `agent wait`, and `agent prompt --wait` wait indefinitely when `--timeout` is omitted. A timeout or server error is emitted as JSON on stderr with exit status 1. CLI usage errors exit with status 2. + +## Integrations + +```bash +herdr integration install pi +herdr integration install omp +herdr integration install claude +herdr integration install codex +herdr integration install copilot +herdr integration install devin +herdr integration install droid +herdr integration install kimi +herdr integration install opencode +herdr integration install kilo +herdr integration install hermes +herdr integration install qodercli +herdr integration install cursor +herdr integration install mastracode +herdr integration install grok +herdr integration uninstall pi +herdr integration uninstall omp +herdr integration uninstall claude +herdr integration uninstall codex +herdr integration uninstall copilot +herdr integration uninstall devin +herdr integration uninstall droid +herdr integration uninstall kimi +herdr integration uninstall opencode +herdr integration uninstall kilo +herdr integration uninstall hermes +herdr integration uninstall qodercli +herdr integration uninstall cursor +herdr integration uninstall mastracode +herdr integration uninstall grok +herdr integration status [--outdated-only] +``` + +## Plugins + +Plugin commands install and run local executable workflow plugins. A plugin is a manifest plus out-of-process commands; Herdr owns the host surface and plugins own their implementation language. + +Install, list, and remove plugins: + +```bash +herdr plugin install <owner>/<repo>[/subdir...] [--ref REF] [--yes] +herdr plugin list [--plugin ID] [--json] +herdr plugin uninstall <plugin_id|owner/repo[/subdir...]> +herdr plugin enable <plugin_id> +herdr plugin disable <plugin_id> +``` + +`plugin install` accepts GitHub shorthand only, such as `ogulcancelik/herdr-plugin-examples/worktree-bootstrap`. It uses `git`, shows a trust preview in interactive terminals, runs supported manifest build commands, and stores GitHub installs in a Herdr-managed directory. Use `--yes` for noninteractive installs. Reinstalling a GitHub-managed plugin replaces that managed checkout. Installing over a locally linked plugin is refused. Plugin manifests must declare `min_herdr_version`; install and link fail when the plugin requires a newer Herdr binary. `plugin list` is human-readable by default; pass `--json` for the raw API response. + +Plugin installation and enabled state are global to the current user. A plugin installed, linked, enabled, or disabled through one Herdr session is immediately available with the same state in every session. + +Local development: + +```bash +herdr plugin link <path> [--disabled] +herdr plugin unlink <plugin_id> +``` + +`plugin link` accepts a plugin directory containing `herdr-plugin.toml` or a direct manifest path. It is still the right command while authoring or testing a plugin from a local checkout. `plugin unlink` unregisters the plugin and leaves files alone. `plugin uninstall` unregisters a plugin and also removes Herdr-managed GitHub checkout files. For GitHub installs, uninstall accepts either the plugin id or the same `owner/repo[/subdir...]` shorthand used by install. Actions, event hooks, panes, and link handlers are declared in the manifest; runtime action registration is not part of v1. + +Config directory: + +```bash +herdr plugin config-dir <plugin_id> +``` + +`plugin config-dir` prints the plugin's config directory, creating it (and seeding it from legacy plugin config locations when present) if needed. Use it in setup docs and shell scripts to point users at a stable path for `.env` files and other user-editable config, separate from the managed plugin checkout. + +Actions: + +```bash +herdr plugin action list [--plugin ID] +herdr plugin action invoke <action_id> [--plugin ID] +``` + +`plugin action invoke` starts the manifest command for an installed, enabled, +platform-compatible plugin action and prints the started command log record in +the JSON response. Use the qualified action id (`plugin.id.action`) when more +than one plugin uses the same action id. Local action ids cannot contain dots, +so qualified ids remain unambiguous even when plugin ids contain dots. + +Logs: + +```bash +herdr plugin log list [--plugin ID] [--limit N] +``` + +Managed terminal panes: + +```bash +herdr plugin pane open --plugin ID --entrypoint ID [--placement overlay|popup|split|tab|zoomed] [--width SIZE] [--height SIZE] [--workspace ID] [--target-pane PANE] [--direction right|down] [--cwd PATH] [--env KEY=VALUE] [--focus|--no-focus] +herdr plugin pane focus <pane_id> +herdr plugin pane close <pane_id> +``` + +`plugin pane open` requires the plugin to be linked, enabled, and compatible +with the current platform. It starts a manifest-declared `[[panes]]` command as +a Herdr-managed terminal pane. The manifest default is `overlay`, which opens a +temporary zoomed overlay over the active pane. It can also open as a split, a +new tab, a zoomed pane, or a session-modal `popup` that does not change the tab +layout. `--width` and `--height` set the outer popup dimensions in terminal +cells or percentages such as `80%`; omitted dimensions default to half the +terminal size, and values smaller than the popup minimum are clamped. A popup +is not a Herdr pane, does not export `HERDR_PANE_ID`, and does not participate +in pane or agent APIs. Native non-terminal plugin panes are a later surface. + +`--env KEY=VALUE` can be repeated on process-launching commands. It applies to the newly launched process only. Herdr-managed variables such as `HERDR_SOCKET_PATH`, `HERDR_BIN_PATH`, `HERDR_ENV`, `HERDR_WORKSPACE_ID`, `HERDR_TAB_ID`, `HERDR_PANE_ID`, `HERDR_PLUGIN_ID`, `HERDR_PLUGIN_ROOT`, `HERDR_PLUGIN_CONFIG_DIR`, `HERDR_PLUGIN_STATE_DIR`, `HERDR_PLUGIN_ENTRYPOINT_ID`, and `HERDR_PLUGIN_CONTEXT_JSON` stay authoritative when they conflict with caller-provided env. + +## Read sources + +| Source | Meaning | +| --- | --- | +| `visible` | Current rendered screen. Best for UI feedback loops. | +| `recent` | Recent scrollback with terminal wrapping. | +| `recent-unwrapped` | Recent scrollback without soft wrapping. Best for logs. | +| `detection` | Bottom-buffer snapshot used by agent screen detection. | + +These meanings apply to reads. For `pane wait-output` only, both `recent` and `recent-unwrapped` search the unwrapped recent snapshot; `recent` remains the default spelling. + +## Environment variables + +| Variable | Purpose | +| --- | --- | +| `HERDR_CONFIG_PATH` | Override the config file path. | +| `HERDR_SESSION` | Select a named session for CLI commands. | +| `HERDR_SOCKET_PATH` | Low-level socket path override. | +| `HERDR_ENV` | Set to `1` inside Herdr-managed pane processes. | +| `HERDR_PANE_ID` | Public pane id for the running pane process. | +| `HERDR_TAB_ID` | Public tab id for the running pane process. | +| `HERDR_WORKSPACE_ID` | Public workspace id for the running pane process. | +| `HERDR_LOG` | Set log filter, for example `HERDR_LOG=herdr=debug`. | +| `HERDR_DISABLE_SOUND` | Disable sound playback even when sound notifications are enabled. | diff --git a/docs/preview/website/src/content/docs/concepts.mdx b/docs/preview/website/src/content/docs/concepts.mdx new file mode 100644 index 00000000..2ac32cc9 --- /dev/null +++ b/docs/preview/website/src/content/docs/concepts.mdx @@ -0,0 +1,85 @@ +--- +title: Concepts +description: Understand Herdr workspaces, tabs, panes, agents, sessions, and modes. +--- + +Herdr is a terminal workspace manager. It keeps real terminal processes running and adds structure around them. + +## Workspace + +A workspace is the top-level project container. Use one workspace per repo, task, or investigation. + +A workspace owns tabs and panes. Its sidebar state rolls up from the agents inside it, so you can see which project needs attention. + +## Tab + +A tab is a layout inside a workspace. Use tabs to separate views like `agents`, `logs`, `server`, or `review`. + +Tabs are addressable from the CLI and socket API. + +## Pane + +A pane is a real terminal. Herdr renders the terminal output, sends input back to the process, and preserves the pane across client detach. + +Panes can be split right or down. They can be renamed manually, read from the CLI, sent input, and closed. + +## Mouse UI + +Herdr is mouse-native. You can click panes, tabs, workspaces, and agents. You can drag split borders, select text, and use right-click menus. Everything below is also reachable by mouse; keyboard bindings are an optional layer. + +If you prefer keyboard-only control, or you want Herdr to stop capturing mouse input, disable mouse capture: + +```toml +[ui] +mouse_capture = false +``` + +## Agent + +An agent is a process Herdr recognizes inside a pane. Herdr detects agents from foreground processes, screen manifests, and optional integrations. + +Agent states are: + +| State | Meaning | +| --- | --- | +| `blocked` | The agent needs input, approval, or a decision. | +| `working` | The agent is actively running. | +| `done` | The agent finished and you have not looked at it yet. | +| `idle` | The agent is finished or waiting and has been seen. | +| `unknown` | Herdr cannot confidently classify the state. | + +## Session + +A session is a persistent Herdr server namespace. The default `herdr` command attaches to the default session. + +Named sessions are separate runtime namespaces: + +```bash +herdr session list +herdr session attach work +herdr session attach side-project +``` + +Use workspaces first. Use named sessions when you need completely separate panes, sockets, and persisted runtime state. + +## Client and server + +By default, Herdr runs as a background server plus one or more attached clients. + +The server owns panes and process state. The client is the terminal UI attached to that server. + +Detach the client with `ctrl+b q`. The server and agents continue running. + +If you want to end the session and stop its panes, stop the server: + +```bash +herdr server stop +``` + +## Modes + +Herdr has terminal mode, prefix mode, and navigate mode. + +Terminal mode sends keys to the focused pane. Prefix mode waits for one Herdr action after the prefix key. Navigate mode is the persistent workspace navigation surface. + +Press the prefix key, default `ctrl+b`, then an action key such as `c` for a new tab or `w` for workspace navigation. New to the prefix idea? See [Keyboard](/docs/keyboard/). diff --git a/docs/preview/website/src/content/docs/config-reference.mdx b/docs/preview/website/src/content/docs/config-reference.mdx new file mode 100644 index 00000000..499ee390 --- /dev/null +++ b/docs/preview/website/src/content/docs/config-reference.mdx @@ -0,0 +1,21 @@ +--- +title: Config reference +description: Every canonical config.toml key, with types, defaults, and allowed values. +--- + +import ConfigReference from '../../components/ConfigReference.astro'; + +Every canonical key Herdr reads from `config.toml`, flat and filterable. For guided setup +and the reasoning behind these options, see [Configuration](/docs/configuration/). + +Print the full commented default config at any time: + +```bash +herdr --default-config +``` + +Custom command bindings (`[[keys.command]]`) are user-defined tables and are not +listed per key here; see +[Custom command keybindings](/docs/configuration/#custom-command-keybindings). + +<ConfigReference /> diff --git a/docs/preview/website/src/content/docs/configuration.mdx b/docs/preview/website/src/content/docs/configuration.mdx new file mode 100644 index 00000000..df6dc8d3 --- /dev/null +++ b/docs/preview/website/src/content/docs/configuration.mdx @@ -0,0 +1,470 @@ +--- +title: Configuration +description: Configure Herdr keybindings, themes, sidebar behavior, notifications, and advanced options. +--- + +Herdr works without a config file. Add one when you want custom keys, themes, sidebar layouts, notifications, or advanced behavior. + +Looking for any setting or keybinding? Search the [Config reference](/docs/config-reference/) for every key, type, default, and allowed value. This page focuses on setup, common recipes, and configuration structures that need more explanation than a reference row. + +## Config file + +Herdr reads config from: + +```text +Linux and macOS: ~/.config/herdr/config.toml +Windows: %APPDATA%\herdr\config.toml +``` + +Run `herdr --help` to see the resolved config path for your system. + +Print the full default config: + +```bash +herdr --default-config +``` + +Save it as your config if you want a complete starting point: + +```bash +herdr --default-config > ~/.config/herdr/config.toml +``` + +If a config value is invalid, Herdr falls back to a safe default and shows a startup warning. + +Herdr shows first-run setup when `onboarding` is missing or true. Continuing from onboarding writes `onboarding = false` and opens settings on the integrations tab. Set it when you want to skip that flow after setup. + +```toml +onboarding = false +``` + +## Reload config + +Reload a running server after editing `config.toml`: + +```bash +herdr server reload-config +``` + +You can also open the global menu in Herdr and choose `reload config`. + +Reload applies most UI settings without restarting panes. Startup-only settings still need a restart. + +## Terminal defaults + +Set the executable Herdr uses for newly created interactive panes: + +```toml +[terminal] +default_shell = "nu" +``` + +When unset or empty, Herdr uses `$SHELL`, then `/bin/sh` on Unix and PowerShell on Windows. This is an executable name or path, not a shell command line. Existing panes keep their current shell until they are recreated. Custom command keybinding strings run through `/bin/sh -c` for pane commands and `/bin/sh -lc` for detached commands on Unix; on Windows they run through `cmd.exe /d /c`. + +Set how Herdr starts newly created interactive pane shells: + +```toml +[terminal] +shell_mode = "auto" +``` + +`shell_mode = "auto"` starts login shells on macOS so login-only PATH setup such as `/usr/libexec/path_helper` and Homebrew shell initialization runs in new panes. On other platforms, it keeps the existing non-login shell behavior. Use `"login"` to force login-shell startup, or `"non_login"` to opt out. Command panes, detached custom command keybindings, and explicit argv launches keep their existing command execution paths. + +Set the working directory policy for new panes, tabs, and workspaces: + +```toml +[terminal] +new_cwd = "follow" +``` + +`new_cwd = "follow"` keeps the default behavior and inherits the source pane or workspace. When there is no source workspace, Herdr starts in `$HOME`. Use `"home"` to always start in `$HOME`, `"current"` to use Herdr's process directory, or a fixed path such as `"~/Projects"`. Explicit `--cwd` values from the CLI or socket API still take precedence. + +## Worktrees + +Set the root directory Herdr uses for Git worktree checkouts created from the sidebar: + +```toml +[worktrees] +directory = "~/.herdr/worktrees" +``` + +Herdr creates checkouts under `<directory>/<repo>/<branch-slug>`. For sibling-style checkouts, set this to a directory such as `~/Projects/herdr-worktrees`. Relative values are resolved to an absolute path when the app applies the config. + +Worktree actions are available from Git workspace rows. `New worktree` creates a checkout, checks out an existing local branch when the entered branch already exists, otherwise creates the branch, opens it as a new Herdr workspace, and groups it under the source workspace. `Open worktree...` lists existing Git worktree checkouts for that repo; choosing an already-open checkout focuses it, and choosing a closed checkout opens it in the same group. + +Grouped worktrees still behave like normal Herdr workspaces: they can be focused, renamed, closed, and contain their own tabs and panes. The parent row is the original workspace. Closing the parent row closes the whole Herdr group, but it does not delete checkout folders or branches. + +Deleting a worktree checkout is explicit. Use `Delete worktree checkout...` on a grouped child workspace to run `git worktree remove`. Herdr first asks Git to remove safely. If Git refuses because the checkout has modified or untracked files, Herdr asks again before running the forced remove. Branches are not deleted. + +## Remote attach + +Remote attach manages its SSH connection with a temporary keepalive and connection-reuse fallback by default. + +```toml +[remote] +manage_ssh_config = true +``` + +When enabled, `herdr --remote` writes a private temporary SSH config that includes your `~/.ssh/config` and `/etc/ssh/ssh_config` first, then adds fallback `ServerAliveInterval` and `ServerAliveCountMax` values. Your own keepalive settings win. Herdr also uses a private per-attach OpenSSH control socket to reuse the first authenticated connection. Set `manage_ssh_config = false` to run remote attach through plain `ssh` without Herdr's generated config or control socket. + +## Keybindings + +For a guided introduction to the prefix and a vetted prefix-free setup, see [Keyboard](/docs/keyboard/). + +Herdr has a prefix mode similar to tmux. The default prefix is `ctrl+b`. Keybinding strings are explicit: `prefix+n` means press the configured prefix and then `n`; `ctrl+alt+n` is a direct terminal-mode shortcut. + +A small keybinding override looks like this: + +```toml +[keys] +prefix = "ctrl+b" +goto = "prefix+g" +new_tab = "prefix+c" +next_tab = "prefix+n" +previous_tab = "prefix+p" +focus_pane_left = "prefix+h" +navigate_workspace_down = "j" +navigate_pane_down = "ctrl+j" +split_horizontal = "prefix+minus" +``` + +The default keymap is prefix-first so Herdr does not steal input from shells, editors, tmux, or terminal apps. Search `keys.` in the [Config reference](/docs/config-reference/) to see every action and default binding. The in-app help panel at `prefix+?` shows the active bindings. + +A binding may also be an array when one action needs multiple shortcuts: + +```toml +[keys] +next_tab = ["prefix+n", "ctrl+alt+]"] +``` + +Optional actions are unset by default. Bind them with `prefix+` for prefix-mode behavior, or use an explicit modified chord when you intentionally want a direct shortcut. + +Key strings accept plain keys, modifier combinations such as `ctrl+a`, `shift+n`, `alt+1`, `cmd+k`, and special keys such as `enter`, `tab`, `esc`, `left`, `right`, `up`, and `down`. Named punctuation such as `minus`, `comma`, `ampersand`, `plus`, and `backtick` is also accepted. Plain direct printable keys such as `n` are unsafe because they intercept typing; use `prefix+n` unless you intentionally want a direct binding. The `navigate_workspace_*` and `navigate_pane_*` fields are navigate-mode-only and may use plain keys such as `j` or `k`; they must not use `prefix+`, `esc`, `enter`, `tab`, `shift+tab`, `left`, `right`, or unmodified `1` through `9`. Left and right arrows are permanent aliases for pane-left and pane-right navigation. These navigate-mode shortcuts are independent from general action bindings such as `focus_pane_down = "prefix+j"`; when both use the same key, the navigate-mode shortcut wins while navigate mode is open. Alt, Cmd/Super, and punctuation with modifiers depend on your terminal and tmux settings. + +If you have old custom keybindings and want the new defaults, run `herdr config reset-keys`. Herdr backs up `config.toml`, removes `[keys]` and `[[keys.command]]`, and uses built-in v2 defaults after restart or `herdr server reload-config`. + +## Indexed jumps + +Indexed keybindings use `1..9` in normal keybinding fields: + +```toml +[keys] +switch_tab = "prefix+1..9" +switch_workspace = "prefix+shift+1..9" +focus_agent = "prefix+alt+1..9" +``` + +The legacy `[keys.indexed]` table is still parsed for compatibility, but new configs should prefer the explicit action fields. + +## Custom command keybindings + +Custom commands use the same keybinding syntax. + +```toml +[[keys.command]] +key = "prefix+alt+g" +type = "popup" +command = "lazygit" +description = "run lazygit" +width = "80%" +height = "80%" +``` + +`type = "popup"` opens a session-modal popup without changing the tab layout. +The popup receives all terminal input, including Escape, until its command +exits. `width` and `height` are optional; omit them for the default half-size +popup, use numbers for terminal cells, or use strings like `"80%"` for a +percentage of the terminal area. Dimensions include the popup border, and +values smaller than the popup minimum are clamped. Popup commands do not receive +`HERDR_PANE_ID`; use `HERDR_ACTIVE_PANE_ID` for the underlying tiled pane. + +On Unix and macOS, a popup command can also provide an ad-hoc terminal without +adding a split or tab: + +```toml +[[keys.command]] +key = "prefix+t" +type = "popup" +command = "exec \"${SHELL:-sh}\"" +description = "open scratch terminal" +width = "80%" +height = "80%" +``` + +On Windows, use a shell command such as `command = "powershell.exe -NoLogo"` +instead. Exit the shell to close the popup and restore the tiled terminal view. + +`type = "pane"` opens a temporary zoomed pane and closes it when the command exits. + +`type = "shell"` runs detached in the background. + +`type = "plugin_action"` invokes an installed plugin action id. Use the +qualified id when action ids are not globally unique: + +```toml +[[keys.command]] +key = "prefix+l" +type = "plugin_action" +command = "example.layout.apply" +description = "apply layout" +``` + +An optional `description` can be provided. When specified, this description is displayed in the keybind help panel (opened with `prefix+?`) in place of the default `'custom command'` label. + +Custom commands receive `HERDR_SOCKET_PATH`, `HERDR_BIN_PATH`, `HERDR_ACTIVE_WORKSPACE_ID`, `HERDR_ACTIVE_TAB_ID`, `HERDR_ACTIVE_PANE_ID`, and `HERDR_ACTIVE_PANE_CWD` when those values are available. Shell commands run from the focused pane's working directory when Herdr can detect it. + +On Windows, custom command strings use `cmd.exe /d /c`, so environment variables use `%HERDR_BIN_PATH%` syntax. To run PowerShell syntax, invoke it explicitly, for example `powershell.exe -NoProfile -Command "..."`. + +## Theme + +Choose a built-in theme: + +```toml +[theme] +name = "catppuccin" +``` + +Search `theme.name` in the [Config reference](/docs/config-reference/) for every built-in theme. Use `terminal` when you want Herdr UI colors to follow your host terminal's ANSI palette. + +To let Herdr switch its own UI theme when the host terminal reports a light/dark appearance change, enable theme auto-switching: + +```toml +[theme] +name = "catppuccin" +auto_switch = true +light_name = "catppuccin-latte" +dark_name = "catppuccin" +``` + +`auto_switch` defaults to `false`, so existing theme configs keep manual behavior. If `light_name` or `dark_name` is omitted, Herdr uses the matching built-in sibling for the configured `name` when one exists, such as `tokyo-night`/`tokyo-night-day` or `gruvbox`/`gruvbox-light`. Manual theme selection in Settings disables `auto_switch`. + +You can override individual colors: + +```toml +[theme.custom] +panel_bg = "reset" +accent = "#a6e3a1" +green = "#a6e3a1" +blue = "#89b4fa" +red = "#f38ba8" +yellow = "#f9e2af" +``` + +Color values accept hex, named colors, `rgb(r,g,b)`, or reset aliases like `reset`, `default`, `none`, and `transparent`. + +## UI and sidebar + +The sidebar is the main Herdr dashboard. Search `ui.` in the [Config reference](/docs/config-reference/) for sizing, collapsed mode, Agent panel ordering, mouse behavior, pane borders, and other presentation settings. + +### Sidebar row layouts + +The expanded desktop sidebar renders each inner array in `rows` as one line. These are the complete default layouts: + +```toml +[ui.sidebar.agents] +row_gap = 0 +rows = [ + ["state_icon", "workspace", "tab"], + ["agent"], +] + +[ui.sidebar.spaces] +row_gap = 0 +rows = [ + ["state_icon", "workspace"], + ["branch", "git_status"], +] +``` + +Agent rows accept these built-in tokens: + +- `state_icon` — colored icon for the agent's semantic state. +- `state_text` — `idle`, `working`, `blocked`, `done`, or `unknown`, including a reported display label when present. +- `workspace` — workspace name. +- `tab` — tab name when available. +- `pane` — pane name when available. +- `agent` — detected or reported agent display name. +- `terminal_title` — latest OSC 0/2 terminal title after safety normalization. +- `terminal_title_stripped` — the terminal title with one recognized leading activity or spinner glyph and its following whitespace removed. +- `$name` — custom pane metadata named `name`. + +Space rows accept these built-in tokens: + +- `state_icon` — colored icon for the space's rolled-up agent state. +- `state_text` — text for the rolled-up agent state. +- `workspace` — workspace name. +- `branch` — Git branch when available. +- `git_status` — Git ahead and behind counts when nonzero. +- `$name` — custom workspace metadata named `name`. + +Tokens render in their configured order. Herdr normally separates adjacent values with ` · ` and uses a single space after `state_icon`. Missing values and their separators disappear; a row disappears when none of its tokens have a value. Each layout may contain at most 16 rows, with at most 16 tokens in each row. + +A token entry can also be an inline style table: + +```toml +[ui.sidebar.agents] +rows = [ + ["state_icon", { token = "workspace", bold = false }, "tab"], + [{ token = "$summary", fg = "#89b4fa", bold = true, dim = false }], +] +``` + +`fg` accepts strict `#RGB` or `#RRGGBB`. `bold` and `dim` accept booleans. Omitted fields preserve the token's contextual style; explicit `false` removes that modifier. Styling applies to one occurrence, so the same token may look different in another row or agent override. A foreground override replaces all semantic foregrounds inside that occurrence: for example, styled `git_status` ahead and behind counts use one color instead of their default green and red. Token styles never change separators or row backgrounds. + +`row_gap` controls the blank terminal rows between entries, independently for the Agent and Space panels. It defaults to `0`, which packs entries together; set it to `1` to restore the previous spacing. It does not add spacing between the content lines declared in `rows`. Consecutive indented worktree children remain packed as one Space group. + +Override the complete Agent layout for a known agent under `rows_by_agent`: + +```toml +[ui.sidebar.agents] +rows = [ + ["state_icon", "agent", "state_text"], + ["workspace", "tab"], +] + +[ui.sidebar.agents.rows_by_agent] +claude = [ + ["state_icon", "agent", "state_text"], + ["terminal_title_stripped"], + ["workspace", "tab"], +] +``` + +An override replaces `rows`; it does not extend it. Override keys are case-sensitive canonical agent IDs such as `claude`, `codex`, and `pi`. Detection aliases such as `claude-code` are not accepted. Agents without an override, including custom reported agents, use `rows`. + +Custom `$name` tokens are dynamic values, not literal text. Add the token to a layout, then report its value from a script or plugin: + +```toml +[ui.sidebar.agents] +rows = [ + ["state_icon", "agent", "$model"], + ["$summary"], + ["workspace", "tab"], +] +``` + +```bash +herdr pane report-metadata <pane_id> \ + --source my-agent-hook \ + --token model=opus \ + --token summary="reviewing authentication" +``` + +Use `herdr workspace report-metadata` in the same way for custom Space tokens. Unreported custom tokens simply disappear. + +Metadata reporters provide values only; styling stays in the local sidebar configuration. See [CLI reference: report metadata](/docs/cli-reference/#panes) for limits, clearing, sequencing, and expiry. + +Sidebar row settings affect only the expanded desktop sidebar. Collapsed and mobile views keep their compact layouts. + +## Notifications + +Herdr can notify you when a background agent finishes or needs input: + +```toml +[ui.toast] +delivery = "herdr" +delay_seconds = 1 + +[ui.toast.herdr] +position = "bottom-right" +``` + +Choose `herdr` for an in-app toast, `terminal` for an outer-terminal notification that works well over SSH, `system` for the local OS notification service, or `off` to disable popups. Herdr suppresses popups for the active tab. Search `ui.toast` in the [Config reference](/docs/config-reference/) for positions, delay behavior, and clipboard feedback settings. + +## Sound + +Sound notifications play through the local Herdr client. Custom sounds must be mp3 files; relative paths are resolved from the config file's directory. + +```toml +[ui.sound] +path = "sounds/notification.mp3" +done_path = "sounds/done.mp3" +request_path = "sounds/request.mp3" +``` + +`path` sets one sound for all sound notifications. `done_path` and `request_path` override only the finished and needs-input sounds. + +Per-agent sound overrides accept `default`, `on`, or `off`. Use detected agent labels such as `claude`, `codex`, `devin`, or `droid` as keys. Droid is muted by default. + +```toml +[ui.sound.agents] +droid = "off" +claude = "on" +``` + +## Advanced configuration + +Search the [Config reference](/docs/config-reference/) for scrollback limits, nested launches, and other advanced or experimental settings. See [Session state and restore](/docs/session-state/) before enabling pane screen history; that guide explains the security trade-off of saving pane contents. + +## Kitty graphics + +Kitty graphics rendering for attached local clients is experimental and disabled by default: + +```toml +[experimental] +kitty_graphics = true +``` + +Enable it only when testing terminal image behavior. + +## Agent session restore + +Herdr resumes supported Agent conversations after a server restart by default: + +```toml +[session] +resume_agents_on_restore = true +``` + +Only panes with a valid native session reference from an official integration can resume; other panes restore as normal shells. See [Session state and restore](/docs/session-state/) for supported Agents and persistence behavior. + +## IME cursor tracking + +On macOS, AI Agent TUIs that hide the hardware cursor can prevent native input-method candidate windows from following the focused pane. Reveal a cursor anchor for those panes with: + +```toml +[experimental] +reveal_hidden_cursor_for_cjk_ime = true +cjk_ime_agents = ["claude", "pi", "codex"] +``` + +Restricting `cjk_ime_agents` avoids showing an extra hardware cursor in unrelated applications. Search these keys in the [Config reference](/docs/config-reference/) for accepted Agent names and cursor shapes. + +## Prefix input source switching + +On macOS and Windows, Herdr can temporarily switch to an ASCII-capable input source while prefix commands and prefix-launched modes are active: + +```toml +[experimental] +switch_ascii_input_source_in_prefix = true +``` + +On macOS this switches to the current ASCII-capable keyboard layout; on Windows it switches the IME to English (ASCII) input. Herdr restores the previous input source when returning to terminal input or entering a text field. This setting has no effect on other platforms. + +:::caution[Windows support is Korean IME only] +On Windows, support is currently limited to the Korean IME. With an IME for any other language, this setting leaves the input source unchanged. +::: + +## Environment variables + +| Variable | Purpose | +| --- | --- | +| `HERDR_CONFIG_PATH` | Override the config file path. | +| `HERDR_SESSION` | Select a named session for CLI commands. | +| `HERDR_SOCKET_PATH` | Low-level socket path override. | +| `HERDR_LOG` | Set log filtering, for example `HERDR_LOG=herdr=debug`. | +| `HERDR_DISABLE_SOUND` | Disable sound playback even when `[ui.sound] enabled = true`. | + +## Logs + +Logs are useful when diagnosing startup warnings, integration state, or socket API behavior. + +Common log files: + +```text +~/.config/herdr/herdr.log +~/.config/herdr/herdr-client.log +~/.config/herdr/herdr-server.log +``` + +Logs rotate automatically. Include the current log and rotated siblings when reporting issues. diff --git a/docs/preview/website/src/content/docs/how-to-work.mdx b/docs/preview/website/src/content/docs/how-to-work.mdx new file mode 100644 index 00000000..812aae9f --- /dev/null +++ b/docs/preview/website/src/content/docs/how-to-work.mdx @@ -0,0 +1,103 @@ +--- +title: How to work with Herdr +description: Run Herdr locally, inside SSH, or through remote attach. +--- + +Run Herdr where the work lives. Attach from wherever you are. + +Herdr is a background session server plus one or more terminal clients. Panes keep running in the server. Clients attach, detach, and render the session. + +## Local work + +Start Herdr from the project directory: + +```bash +herdr +``` + +Herdr starts or attaches to your local background session automatically. You do not manage sockets. Run shells, servers, tests, and agents normally inside panes. + +Detach the client with `ctrl+b q`. Your panes keep running. + +Reattach later: + +```bash +herdr +``` + +If you want to end the session and stop its panes, stop the server: + +```bash +herdr server stop +``` + +## Remote work through normal SSH + +SSH to the machine that has the code and credentials, then run Herdr there: + +```bash +ssh you@server +herdr +``` + +This works like a terminal multiplexer. Your shell is remote. The Herdr server is remote. The agents and panes run on the remote machine. Detach with `ctrl+b q`, disconnect, then SSH back and run `herdr` again. + +Use this path when you already live inside an SSH shell, when you are on a phone or tablet SSH client, or when you want the simplest possible setup. + +## Work from your phone + +You do not need a Herdr mobile app or a web dashboard. Install any SSH client on your phone, connect to the machine where your agents run, and start Herdr there: + +```bash +ssh you@server +herdr +``` + +The same persistent Herdr session opens in your phone terminal. The TUI adapts to narrow screens, so you can inspect agents, switch workspaces, and check panes without leaving SSH. + +On iPhone, apps like [moshi](https://getmoshi.app/) work well. + +<div class="mobile-doc-shots"> + <figure> + <img src="/assets/mobile-agent-session-v2.jpeg" alt="Herdr agent session over SSH on a phone" loading="lazy" /> + <figcaption>agent session over SSH</figcaption> + </figure> + <figure> + <img src="/assets/mobile-switch-menu-v2.jpeg" alt="Herdr responsive switch menu on a phone" loading="lazy" /> + <figcaption>responsive switch menu</figcaption> + </figure> +</div> + +## Remote work from your local terminal + +Attach through SSH without opening a shell first: + +```bash +herdr --remote workbox +herdr --remote ssh://you@server:2222 +``` + +Your local Herdr acts as a thin client. It connects over SSH, starts or attaches to the remote Herdr server, and streams the UI back to your local terminal. + +Use this path when you want the remote session to feel local. The client runs on your machine, so local desktop features such as image clipboard paste can be bridged to the remote server. If you SSH first and run `herdr` on the server, Herdr runs entirely on that server and cannot read your local desktop clipboard. + +For repeat targets, put the host in your SSH config: + +```text +Host workbox + HostName server.example.com + User you + Port 2222 +``` + +Then attach with: + +```bash +herdr --remote workbox +``` + +## Which path to use + +Use `herdr` for local work. Use `ssh you@server` then `herdr` when you want Herdr to behave like tmux on that remote shell or when you are using a phone SSH client. Use `herdr --remote <host>` when you want a local thin client for a remote session, including local clipboard image paste bridging. + +For remote bootstrap details, named remote sessions, custom binaries, direct terminal attach, and `--no-session`, see [Persistence and remote access](/docs/persistence-remote/). diff --git a/docs/preview/website/src/content/docs/index.mdx b/docs/preview/website/src/content/docs/index.mdx new file mode 100644 index 00000000..1502462d --- /dev/null +++ b/docs/preview/website/src/content/docs/index.mdx @@ -0,0 +1,77 @@ +--- +title: Herdr documentation +description: Terminal workspace manager for AI coding agents. +template: splash +hero: + tagline: "Install, learn, and configure Herdr. Start on the path that matches you — no multiplexer experience required." + image: + file: ../../../public/assets/logo.svg + actions: + - text: Install Herdr + link: /docs/install/ + - text: Quick start + link: /docs/quick-start/ + variant: secondary +--- + +import { Card, CardGrid } from '@astrojs/starlight/components'; + +## Pick your path + +<CardGrid> + <Card title="New to terminal multiplexers?"> + You don't need to learn shortcuts to start. Herdr is mouse-first: click panes, drag borders, split and switch from right-click menus. + + [Quick start →](/docs/quick-start/) + </Card> + <Card title="Coming from tmux or zellij?"> + You already know the model. The prefix is `ctrl+b`, panes persist, detach and reattach work the way you expect. + + [Concepts →](/docs/concepts/) · [Keybindings →](/docs/configuration/#keybindings) + </Card> +</CardGrid> + +## Or let your agent introduce you + +Already running an AI coding agent? Let it do the onboarding. Paste this prompt: + +```text +Help me understand and set up Herdr. Read https://herdr.dev/agent-guide.md first, then walk me through it step by step. +``` + +The guide teaches your agent Herdr's concepts, setup, configuration, and common fixes, so its answers stay accurate instead of improvised. + +## Core guides + +<CardGrid> + <Card title="Agents"> + See supported agents, detection behavior, integrations, custom labels, and direct attach. + + [Understand agents →](/docs/agents/) + </Card> + <Card title="Session state"> + Understand detach, restart restore, pane history replay, native agent resume, and live handoff. + + [Compare state paths →](/docs/session-state/) + </Card> + <Card title="Configuration"> + Configure keybindings, themes, sidebar behavior, notifications, scrollback, and advanced options. + + [Configure Herdr →](/docs/configuration/) + </Card> + <Card title="API"> + Control Herdr from scripts, tools, and agents through the CLI and local socket API. + + [Read the API guide →](/docs/socket-api/) + </Card> + <Card title="Plugins"> + Author local executable workflow plugins with manifest actions and event hooks. + + [Write a plugin →](/docs/plugins/) + </Card> + <Card title="Marketplace"> + Share plugins from GitHub today and tag your repo to be listed when the marketplace launches. + + [Publish a plugin →](/docs/marketplace/) + </Card> +</CardGrid> diff --git a/docs/preview/website/src/content/docs/install.mdx b/docs/preview/website/src/content/docs/install.mdx new file mode 100644 index 00000000..3312397d --- /dev/null +++ b/docs/preview/website/src/content/docs/install.mdx @@ -0,0 +1,153 @@ +--- +title: Install Herdr +description: Install, update, and verify Herdr on Linux, macOS, and Windows beta. +--- + +Herdr ships stable binaries for Linux and macOS. Native Windows support is preview-only beta. + +## Install + +On Linux or macOS, run: + +```bash +curl -fsSL https://herdr.dev/install.sh | sh +``` + +On Windows preview beta, install the preview channel: + +```powershell +powershell -ExecutionPolicy Bypass -c "irm https://herdr.dev/install.ps1 | iex" +``` + +The installer downloads the right release binary for your platform and places it on your PATH. Herdr defaults to preview on Windows without changing your config. The installer uses versioned install folders and updates a `current` junction, so updates do not need to overwrite a running `herdr.exe`. + +## Install with Homebrew + +If you already use Homebrew: + +```bash +brew install herdr +``` + +## Install with mise + +If you already use mise: + +```bash +mise use -g herdr +``` + +If mise reports `herdr not found in mise tool registry`, update mise and retry. Older mise versions predate the Herdr registry entry; `mise use -g github:ogulcancelik/herdr` works as a temporary fallback. + +## Install with Nix + +If you already use Nix, Herdr provides a flake that builds Herdr from source: + +```bash +nix run github:ogulcancelik/herdr/v0.x.y +nix build github:ogulcancelik/herdr/v0.x.y +nix profile install github:ogulcancelik/herdr/v0.x.y +``` + +Replace `v0.x.y` with the latest release tag. You can omit the tag to track `master`, but release tags are recommended for normal installs. + +The flake also exposes a development shell: + +```bash +nix develop github:ogulcancelik/herdr +``` + +Update through the same Nix workflow you used to install Herdr. For a profile install, list your profile entries and upgrade the Herdr entry: + +```bash +nix profile list +nix profile upgrade <index-or-name> +``` + +If Herdr is an input in your own flake, update that input and rebuild your system, Home Manager, or development environment: + +```bash +nix flake update herdr +``` + +## Download manually + +You can also download a binary from [GitHub releases](https://github.com/ogulcancelik/herdr/releases). + +Choose the asset that matches your system: + +| System | Asset | +| --- | --- | +| Linux x86_64 | `herdr-linux-x86_64` | +| Linux aarch64 | `herdr-linux-aarch64` | +| macOS Intel | `herdr-macos-x86_64` | +| macOS Apple silicon | `herdr-macos-aarch64` | + +On Linux or macOS, make it executable and move it somewhere on your PATH. + +```bash +chmod +x herdr-linux-x86_64 +mv herdr-linux-x86_64 ~/.local/bin/herdr +``` + +### Windows beta downloads + +Windows binaries are published only on preview releases while native Windows support is in beta. Use the preview installer above for normal testing, or download the Windows asset from a preview GitHub prerelease: + +| System | Asset | +| --- | --- | +| Windows x86_64 beta | `herdr-windows-x86_64.zip` | + +The archive contains `herdr.exe` and its app-local ConPTY runtime. Keep the extracted directory together; do not copy only `herdr.exe`. + +## Verify + +Start Herdr: + +```bash +herdr +``` + +If your shell cannot find `herdr`, restart the terminal or check that the install directory is on your PATH. + +## Update + +Herdr checks for new releases and notifies you in the app. You can update manually: + +```bash +herdr update +``` + +`herdr update` is for installs managed by Herdr's own installer. Homebrew, mise, and Nix installs are updated through those package managers instead. + +On Linux and macOS, Herdr uses the stable update channel by default. To opt into preview builds from `master`, set the channel: + +```bash +herdr channel set preview +``` + +Switch Linux and macOS direct installs back to stable the same way: + +```bash +herdr channel set stable +``` + +For direct installs, changing channels also checks that channel and installs its latest binary. If that update fails, run `herdr update` to retry from the configured channel. + +Preview builds are manually published GitHub prereleases from the current development branch. They are useful when you want fixes before the next stable release, but they can regress. Homebrew, mise, and Nix installs do not use the preview channel. + +Windows beta builds are preview-only for now. `herdr channel set stable` is rejected on Windows until stable Windows releases are available. + +By default, `herdr update` installs the new binary and leaves compatible running sessions alone. If an update changes Herdr's client/server protocol, Herdr asks whether to stop the old server after installing. Stop the old server to use the new version. Stopping exits pane processes. For the default session, run `herdr server stop`, then run `herdr` again. For a named session, run `herdr session stop <name>`, then run `herdr session attach <name>` again. + +To opt into experimental live server handoff for supported running sessions, run: + +```bash +herdr update --handoff +``` + +Live handoff does not apply to Homebrew, mise, or Nix package-manager updates. For those installs, update with the package manager, then restart that Herdr session when you are ready to use the new server. If a running session still uses the old server, stop it with `herdr server stop` or `herdr session stop <name>`, then run Herdr again. + +## Requirements + +Stable Herdr releases support Linux and macOS. Native Windows builds are preview-only beta releases; see [Windows beta](/docs/windows-beta/) for supported workflows and known limitations. diff --git a/docs/preview/website/src/content/docs/integrations.mdx b/docs/preview/website/src/content/docs/integrations.mdx new file mode 100644 index 00000000..fcc908a7 --- /dev/null +++ b/docs/preview/website/src/content/docs/integrations.mdx @@ -0,0 +1,315 @@ +--- +title: Integrations +description: Install Herdr integrations for Pi, OMP, Claude Code, Codex, GitHub Copilot CLI, Devin CLI, Droid, Kimi Code CLI, OpenCode, Kilo Code CLI, Hermes Agent, Qoder CLI, Cursor Agent CLI, MastraCode, and Grok CLI. +--- + +Herdr detects supported agents automatically. Official integrations can add native session identity for restore, lifecycle state reports, or both. + +Use integrations when you want native agent session restore from Claude Code/Codex/Copilot/Devin-style hooks, direct lifecycle reports from Pi/OMP/Kimi/OpenCode/Kilo/Hermes-style hooks or plugins, or both. See [Agents](/docs/agents/) for the full status authority model. + +## Install integrations + +Open settings inside Herdr and use the integrations tab to install recommended integrations for agents found on your `PATH`, or run commands manually: + +```bash +herdr integration install pi +herdr integration install omp +herdr integration install claude +herdr integration install codex +herdr integration install copilot +herdr integration install devin +herdr integration install droid +herdr integration install kimi +herdr integration install opencode +herdr integration install kilo +herdr integration install hermes +herdr integration install qodercli +herdr integration install cursor +herdr integration install mastracode +herdr integration install grok +``` + +## Uninstall integrations + +```bash +herdr integration uninstall pi +herdr integration uninstall omp +herdr integration uninstall claude +herdr integration uninstall codex +herdr integration uninstall copilot +herdr integration uninstall devin +herdr integration uninstall droid +herdr integration uninstall kimi +herdr integration uninstall opencode +herdr integration uninstall kilo +herdr integration uninstall hermes +herdr integration uninstall qodercli +herdr integration uninstall cursor +herdr integration uninstall mastracode +herdr integration uninstall grok +``` + +## How Herdr uses integrations + +Herdr uses integrations in two different ways: + +| Integration type | Agents | Effect | +| --- | --- | --- | +| Lifecycle authority | Pi, OMP, Kimi Code CLI, OpenCode, Kilo Code CLI, Hermes Agent, MastraCode | When installed and actively reporting for the pane, hook or plugin events author `idle`, `working`, and `blocked`. Herdr does not also use screen manifest fallback for that same lifecycle authority. | +| Session identity | Claude Code, Codex, GitHub Copilot CLI, Devin CLI, Droid, Qoder CLI, Cursor Agent CLI, Grok CLI | The integration reports native session references for restore. State still comes from Herdr's screen manifest detection. | + +Custom socket integrations can also report state when they define state that is not visible in the native terminal UI. + +Some integrations report native agent session references. Herdr uses official session references to resume Claude Code, Codex, Devin CLI, Droid, Kimi Code CLI, Qoder CLI, Cursor Agent CLI, Grok CLI, GitHub Copilot CLI, Pi, OMP, Hermes Agent, OpenCode, Kilo Code CLI, and MastraCode panes after a Herdr server restart unless `[session] resume_agents_on_restore = false` disables it. + +Native session restore requires current Herdr integrations: Pi integration version `2`, OMP version `3`, Claude Code version `6`, Codex version `5`, GitHub Copilot CLI version `2`, Devin CLI version `2`, Droid version `2`, Kimi Code CLI version `3`, Qoder CLI version `2`, Cursor Agent CLI version `1`, Grok CLI version `1`, OpenCode version `5`, Kilo Code CLI version `1`, Hermes Agent version `2`, or MastraCode version `1`. Check installed versions with `herdr integration status`. + +## Pi + +Install the Pi integration: + +```bash +herdr integration install pi +``` + +Herdr writes the bundled extension to: + +```text +~/.pi/agent/extensions/herdr-agent-state.ts +``` + +If `PI_CODING_AGENT_DIR` is set, Herdr writes to `$PI_CODING_AGENT_DIR/extensions/herdr-agent-state.ts` instead. Herdr creates the extensions directory when the Pi agent directory already exists. Uninstall removes only that extension file. + +## OMP + +Install the OMP integration: + +```bash +herdr integration install omp +``` + +Herdr writes the bundled extension to: + +```text +~/.omp/agent/extensions/herdr-omp-agent-state.ts +``` + +Herdr uses `PI_CODING_AGENT_DIR` as the complete agent directory when set. Otherwise, it uses `$HOME/$PI_CONFIG_DIR/agent` when `PI_CONFIG_DIR` is set, falling back to `~/.omp/agent`. If Pi and OMP resolve to the same extension directory, Herdr refuses the OMP install so the OMP extension cannot be loaded by Pi. Configure separate agent directories before installing both integrations. Herdr creates the extensions directory when the resolved OMP agent directory already exists. Uninstall removes only that extension file. + +The OMP integration reports `omp` as the agent label, lifecycle state, and native session identity through Herdr's socket API. It does not require native process detection for the `omp` executable, and Herdr can resume an OMP pane with `omp --resume=<session>` after a server restart. + +## Claude Code + +Install the Claude Code hook: + +```bash +herdr integration install claude +``` + +The hook reports Claude Code session identity to the local Herdr socket on session start. Claude Code state comes from Herdr's screen manifest detection. + +Herdr uses `~/.claude` by default, or `CLAUDE_CONFIG_DIR` when set. The Claude config directory must already exist. Install writes `hooks/herdr-agent-state.sh` and updates `settings.json` with Herdr hook entries. Uninstall removes the matching hook entries and deletes the hook script. + +## Codex + +Install the Codex hook: + +```bash +herdr integration install codex +``` + +The Codex hook reports session identity through the same local socket API used by other integrations. Codex state comes from Herdr's screen manifest detection. + +Herdr uses `~/.codex` by default, or `CODEX_HOME` when set. The Codex config directory must already exist. Install writes `herdr-agent-state.sh`, updates `hooks.json`, and ensures `[features] hooks = true` in `config.toml`. It also removes the deprecated top-level `codex_hooks` flag when present. Uninstall removes Herdr entries from `hooks.json` and deletes the hook script, but leaves `config.toml` unchanged. + +## GitHub Copilot CLI + +Install the GitHub Copilot CLI hook: + +```bash +herdr integration install copilot +``` + +The Copilot hook reports session identity through the same local socket API used by other integrations. Copilot state comes from Herdr's screen manifest detection. + +Herdr uses `~/.copilot` by default, or `COPILOT_HOME` when set. The Copilot config directory must already exist. Install writes `hooks/herdr-agent-state.sh` and updates `settings.json` with a `SessionStart` hook entry. Uninstall removes Herdr entries from `settings.json` and deletes the hook script. + +After Copilot emits a session-bearing event, Herdr can use the reported session id to resume the pane with `copilot --resume=<id>`. + +## Devin CLI + +Install the Devin CLI hook: + +```bash +herdr integration install devin +``` + +The hook reports native session identity from Devin session, prompt, tool-use, permission, and stop events. Devin state still comes from Herdr's screen manifest and OSC detection because Devin hooks do not emit a reliable state transition after every permission cancellation or user interrupt. + +Herdr uses `~/.config/devin` by default, or `$XDG_CONFIG_HOME/devin` when `XDG_CONFIG_HOME` is set. The Devin config directory must already exist. Install writes `herdr-agent-state.sh` and updates `config.json` with Herdr hook entries. The hook refreshes the session reference while Devin runs. Uninstall removes Herdr entries from `config.json` and deletes the hook script. + +Herdr resumes stored Devin sessions with `devin --resume <id>`. Native screen manifest detection remains the state authority whether or not the hook is installed. + +## Kimi Code CLI + +Install the Kimi Code CLI hook: + +```bash +herdr integration install kimi +``` + +The hook reports Kimi session identity and lifecycle state to Herdr for native restore and authoritative `idle`, `working`, and `blocked` status. It requires Kimi Code CLI `0.14.0` or newer. + +Herdr uses `~/.kimi-code` by default, or `KIMI_CODE_HOME` when set. The Kimi Code config directory must already exist. Install writes `hooks/herdr-agent-state.sh` and appends Herdr-managed `[[hooks]]` entries to `config.toml`. Uninstall removes the Herdr-managed config block and deletes the hook script. + +Herdr resumes stored Kimi sessions with `kimi --session <id>`. + +## Droid + +Install the Droid hook: + +```bash +herdr integration install droid +``` + +The Droid hook reports session identity through the same local socket API used by other integrations. Lifecycle state still comes from Herdr's screen manifest detection because Droid hooks do not cover every lifecycle transition. + +Herdr uses `~/.factory` for Droid hooks. The Factory config directory must already exist. Install writes `hooks/herdr-agent-state.sh`, updates `settings.json` with a Herdr `SessionStart` hook entry, and removes older Herdr Droid hook entries from `hooks.json` if present. Uninstall removes Herdr entries from both config files and deletes the hook script. + +After Droid emits a session start event, Herdr can use the reported session id to resume the pane with `droid --resume <id>`. + +## OpenCode + +Install the OpenCode plugin: + +```bash +herdr integration install opencode +``` + +Herdr writes the plugin to `~/.config/opencode/plugins/herdr-agent-state.js`. The OpenCode config directory must already exist. Uninstall removes only that plugin file. + +The plugin reports lifecycle state and session identity while OpenCode runs inside a Herdr pane. After OpenCode emits a session-bearing event, Herdr can use the reported session id to resume the pane with `opencode --session <id>`. Native screen manifest detection remains available when the plugin is not installed. + +## Kilo Code CLI + +Install the Kilo Code CLI plugin: + +```bash +herdr integration install kilo +``` + +Herdr writes the plugin to `~/.config/kilo/plugin/herdr-agent-state.js`. The Kilo config directory must already exist. Uninstall removes only that plugin file. + +The plugin reports lifecycle state and session identity while Kilo runs inside a Herdr pane. After Kilo emits a session-bearing event, Herdr can use the reported session id to resume the pane with `kilo --session <id>`. Native screen manifest detection remains available when the plugin is not installed. + +## Hermes Agent + +Install the Hermes Agent plugin: + +```bash +herdr integration install hermes +``` + +Herdr writes `~/.hermes/plugins/herdr-agent-state/` and enables `herdr-agent-state` in `~/.hermes/config.yaml`. The Hermes config directory must already exist. Restart Hermes after installing so the plugin loads. Uninstall removes the plugin directory and removes `herdr-agent-state` from `plugins.enabled`. + +The plugin reports lifecycle, tool, approval state, and session id while Hermes runs inside a Herdr pane. Herdr can use the reported session id to resume the pane with `hermes --resume <id>`. Native screen manifest detection remains available when the plugin is not installed. + +## Qoder CLI + +Install the Qoder CLI hook: + +```bash +herdr integration install qodercli +``` + +The hook reports Qoder CLI session identity to Herdr for native restore. Lifecycle state still comes from Herdr's screen manifest detection because Qoder hooks do not cover every lifecycle transition. + +Herdr uses `~/.qoder` by default, or `QODER_CONFIG_DIR` when set. The Qoder config directory must already exist. Install writes `hooks/herdr-agent-state.sh` and updates `settings.json` with Herdr hook entries. Uninstall removes the matching hook entries and deletes the hook script. + +Herdr resumes stored Qoder CLI sessions with `qodercli --resume <id>`. + +Native screen manifest detection remains available when the hook is not installed. + +## Cursor Agent CLI + +Install the Cursor Agent CLI hook: + +```bash +herdr integration install cursor +``` + +The hook reports session identity through Cursor's `sessionStart` hook while Cursor Agent CLI runs inside a Herdr pane. Cursor state comes from Herdr's screen manifest detection. + +Herdr uses `~/.cursor` by default, or `CURSOR_CONFIG_DIR` when set. The Cursor config directory must already exist. Install writes `herdr-agent-state.sh` and adds a Herdr `sessionStart` entry to `hooks.json`. Uninstall removes the matching hook entry and deletes the hook script. + +After Cursor emits a session start event, Herdr can use the reported session id to resume the pane with `cursor-agent --resume <id>`. The `cursor-agent` command must be on `PATH` when Herdr restores the pane; Herdr does not launch the generic `agent` command. + +## MastraCode + +Install the MastraCode hook: + +```bash +herdr integration install mastracode +``` + +The hook reports MastraCode lifecycle state and thread identity to Herdr for authoritative `idle`, `working`, and `blocked` status and native restore. MastraCode has no screen manifest fallback; state comes from the hook while MastraCode runs inside a Herdr pane. + +Herdr uses `~/.mastracode`. Install writes `hooks/herdr-agent-state.sh` and adds Herdr command entries to `hooks.json`, creating the directory when missing. Uninstall removes the matching hook entries and deletes the hook script. + +Herdr resumes stored MastraCode threads with `mastracode --thread <id>`. + +## Grok CLI + +Install the Grok CLI hook: + +```bash +herdr integration install grok +``` + +The hook reports session identity through Grok's `SessionStart` hook while Grok CLI runs inside a Herdr pane. Grok state comes from Herdr's screen manifest detection. + +Herdr uses `~/.grok` by default, or `GROK_HOME` when set. The Grok config directory must already exist. Grok merges every `hooks/*.json` file in that directory, so install writes a self-contained `hooks/herdr.json` with the Herdr `SessionStart` entry next to the `hooks/herdr-agent-state.sh` script, and never edits other hook files. Uninstall removes exactly those two Herdr-owned files. + +After Grok emits a session start event, Herdr can use the reported session id to resume the pane with `grok --resume <id>`. + +## Custom status labels + +Integrations report lifecycle state as semantic state only. For example, report an agent as `working` without adding display fields to the lifecycle report. + +```bash +herdr pane report-agent w1:p1 \ + --source custom:docs \ + --agent docs-bot \ + --state working +``` + +User hooks that run next to a Herdr-managed integration should use metadata instead of `report-agent`. Metadata changes presentation without taking over the integration's `idle`, `working`, `blocked`, or session restore authority. `--agent` and `--applies-to-source` guard only presentation fields (`--title`, `--display-agent`, and `--state-label`). Token patches always apply; their reporter owns clearing or TTL refresh. `--display-agent` changes the visible name. + +```bash +herdr pane report-metadata "$HERDR_PANE_ID" \ + --source user:claude-title \ + --agent claude \ + --title "Refactor auth middleware" \ + --display-agent "Claude: auth" \ + --token summary="refactor auth" \ + --state-label working="refactoring auth" \ + --ttl-ms 3600000 +``` + +Tokens and state labels are visual-only. Waits, notifications, and workspace rollups still use the semantic state. + +## Debug integration state + +List known agents: + +```bash +herdr agent list +``` + +Read a pane when you need to verify what Herdr can see: + +```bash +herdr pane read w1:p1 --source recent --lines 50 +``` + +If integration state looks wrong, first confirm the agent is running inside Herdr and that the relevant hook or plugin was installed for the same user account. diff --git a/docs/preview/website/src/content/docs/ja/agent-automation.mdx b/docs/preview/website/src/content/docs/ja/agent-automation.mdx new file mode 100644 index 00000000..2b9a9d30 --- /dev/null +++ b/docs/preview/website/src/content/docs/ja/agent-automation.mdx @@ -0,0 +1,123 @@ +--- +title: エージェント自動化 +description: Herdr のレイアウト、ペイン、エージェントの各プリミティブを使い、スクリプトや別のエージェントからコーディングエージェントを連携させます。 +--- + +Herdr はコーディングエージェントの自動化レイヤーとして使えます。スクリプトから制御することも、ひとつのエージェントから別のエージェントへ作業を渡し、状態を確認して結果を集めることもできます。重要なのは、目的に合うプリミティブを選ぶことです。 + +## 3 つのプリミティブ + +| プリミティブ | 責務 | +| --- | --- | +| レイアウト(`workspace`、`tab`、ペイン構成) | ターミナルの場所を作成し、整理する。 | +| ペイン | 生のターミナルを制御する。コマンド実行、入力送信、出力読み取り、出力待機を行う。 | +| エージェント | 認識済みコーディングエージェントを、名前またはペインとライフサイクル状態に基づいて制御する。 | + +ペインは、エージェントが入っていなくても存在します。エージェントは、ペイン内で現在実行されていると Herdr が認識したプロセスです。そのため `agent start` には既存のシェルペインが必要で、レイアウトの作成、分割、移動は行いません。 + +ワークスペースを作成すると最初のタブとルートペインも作られ、タブを作成するとそのルートペインも作られます。最初のプロセスには返されたペイン ID を使い、そのレイアウトに別のターミナルが必要なときだけ分割します。 + +作成コマンドは JSON を出力します。ID を推測せず、レスポンスから取得してください: + +```bash +created=$(herdr workspace create --cwd ~/project --label api --no-focus) +pane_id=$(printf '%s\n' "$created" | jq -r '.result.root_pane.pane_id') + +split=$(herdr pane split "$pane_id" --direction right --no-focus) +review_pane=$(printf '%s\n' "$split" | jq -r '.result.pane.pane_id') +``` + +`workspace create` は `.result.workspace`、`.result.tab`、`.result.root_pane` を返します。`tab create` は `.result.tab` と `.result.root_pane` を返し、`pane split` は新しいペインを `.result.pane` として返します。 + +ペインを別のワークスペースへ移動すると、ワークスペース修飾付きのペイン ID が変わります。`pane move` の後は `.result.move_result.pane.pane_id` を使い、以前の値が必要なら `.result.move_result.previous_pane_id` を参照してください。実行中プロセスは起動時の Herdr 環境を保ちますが、以前の `HERDR_PANE_ID` はそのターミナルの別名として残るため、`--current` は引き続き安全に使えます。移動後に始めるコマンドはエージェント名で解決できますが、すでに進行中の待機は `agent_not_running` で終了します。 + +シェル、テスト、サーバー、CI ウォッチャーなどの通常のターミナルプロセスにはペインコマンドを使います。どのエージェントが動いているか、または状態が `working`、`blocked`、`done`、`idle`、`unknown` のどれかを Herdr に判断させる場合はエージェントコマンドを使います。 + +## エージェントの識別子と起動 + +`w1:p2` のようなペイン ID はターミナルの場所を識別します。`reviewer` のようなエージェント名は、そのペインに現在いるエージェントの扱いやすい別名です。名前は `[a-z][a-z0-9_-]{0,31}` に一致し、ライブエージェント間で一意でなければなりません。エージェントが終了、解放、または置換されると別名は消えます。ペイン自体の恒久的な名前ではありません。 + +エージェントコマンドは、一意なライブ名、または現在そのエージェントをホストしているペイン ID のどちらでも指定できます。 + +利用可能なシェルペインとは、対話シェルのプロンプトに戻っており、フォアグラウンドをシェル自身が所有し、フォアグラウンドのコマンド、エディタ、エージェントが動いていないペインです。`agent start` の前にプロンプトへ戻してください。 + +`--kind` は対応済みエージェントとその標準実行ファイルを選びます。対応する kind は `pi`、`claude`、`codex`、`gemini`、`cursor`、`devin`、`agy`、`cline`、`omp`、`mastracode`、`opencode`、`copilot`、`kimi`、`kiro`、`droid`、`amp`、`grok`、`hermes`、`kilo`、`qodercli`、`maki` です。`--` より後の引数は、その実行ファイルへそのまま渡されます。 + +`agent start` は、同じターミナルで期待したエージェントを検出し、対話入力の準備ができたと Herdr が判断してから返ります。起動待機はデフォルトで 30 秒です。`--timeout` は 3000 より大きく 300000 以下のミリ秒で指定します。 + +```bash +herdr agent start reviewer --kind codex --pane "$review_pane" -- -m gpt-5.4 +``` + +手動で起動したエージェントも自動検出され、ペイン ID で指定できます。人が読みやすい安定した対象名が必要なら名前を付けます: + +```bash +herdr agent get w1:p2 +herdr agent rename w1:p2 reviewer +``` + +## 操作面を選ぶ + +| 目的 | コマンド | +| --- | --- | +| シェルコマンドを実行して送信する | `pane run` | +| Enter を付けずに文字列を送る | `pane send-text` | +| ターミナルキーや修飾キーの組み合わせを送る | `pane send-keys` | +| 文字列や正規表現を待つ | `pane wait-output` | +| 既存のペインで対応済みエージェントを起動する | `agent start` | +| プロンプトを送信し、必要なら完了を待つ | `agent prompt` | +| エージェントの対話 UI にキーを送る | `agent send-keys` | +| エージェントのライフサイクル状態を待つ | `agent wait` | + +`agent prompt` は文字列とエンコード済み Enter を送信し、ターミナルの現在の bracketed paste モードを尊重します。working 中のエージェントにも送信できます。`esc`、`up`、`enter`、`ctrl+c` のような操作には `agent send-keys` を使います。`escape` も `esc` のエイリアスとして使えます。意図的に生のターミナルを操作したい場合は、ペイン入力コマンドを使ってください。 + +ペイン入力は、現在のプロセスに関係なくターミナルを指定します。エージェント入力はライブエージェントを解決し、そのエージェントがペインを制御しなくなっていれば操作を拒否します。 + +`agent prompt --wait` はプロンプトをすぐ送信します。エージェントが non-working 状態から始まる場合、Herdr はまず 5 秒以内にライフサイクル変化が観測されることを要求します。状態シーケンスが進まなければ、無期限に待たず `agent_prompt_stalled` を返します。呼び出し側の `--timeout` が 5 秒以下なら、通常の `timeout` エラーを返します。活動を観測した後、要求された安定状態を待ちます。個々のターンは追跡しません。エージェントがすでに working の場合、進行中ターンの完了が待機を満たすことがあります。単独の `agent wait` は現在のエージェントを監視し、すでに状態が一致していれば即座に返ります。どちらもデフォルトでは `idle`、`done`、`blocked` を待ちます。複数の正確な状態を許可するには、`--until idle --until done` のように `--until` を繰り返します。`unknown` が必要な場合は `--until unknown` を明示してください。`agent prompt` では `--until` に `--wait` が必要です。 + +`idle` は、エージェントが入力待ちで、そのタブがフォーカス中の Herdr UI ですでに表示済みであることを示します。`done` は同じ基礎的な idle 状態ですが、バックグラウンド作業が完了してから、そのタブをフォーカスするか `pane focus` / `agent focus` で対象にするまで使われます。CLI で読み取るだけでは表示済みになりません。`blocked` は承認または質問 UI を Herdr が認識した状態です。`unknown` はエージェントが存在するもののライフサイクルを確実に分類できない状態で、成功完了を意味しません。違いが重要なら正確な `--until` を指定してください。 + +`pane wait-output` はエージェントのライフサイクルを解釈しません。選択したターミナルスナップショットをポーリングし、最初にすぐ検索するため、すでに存在する文字列も一致します。デフォルトのソース名は `recent` で、直近 80 行の描画済みターミナル行を折り返し前の出力として扱います。`--lines` でその行数を変更でき、`--regex` は Rust の正規表現構文で 1 行ずつ一致します。 + +CLI では、`pane read` と `agent read` のどちらもターミナルテキストを直接出力します。デフォルトは ANSI エスケープを除去した UTF-8 テキストです。ソースに装飾が含まれる場合、`--format ansi` または `--ansi` でエスケープを残します。`detection` ソースは常にプレーンテキストです。recent 系ソースでは、`--lines N` は折り返し解除の前に末尾の N 描画行を選び、省略時は 80 行です。`visible` と `detection` では、省略時にスナップショット全体を返し、指定時は改行で区切られた末尾 N 行を返します。socket API ではテキストを `.result.read.text` に返します。 + +## 既知の注意点: 代替画面の出力 + +Claude Code や OpenCode などの全画面エージェントは、ターミナルの代替画面に描画することがあります。代替画面の行は Herdr のホスト側スクロールバックには入りません。`--lines` で取得できるのは、ペインの現在の画面とホスト側スクロールバックに存在する行だけであり、失われた履歴を作り出すことはできません。`--lines` を増やしても応答テキストが増えない場合、そのペインは代替画面を使っている可能性が高く、応答の行はすでに保持されていません。フォントが大きい場合やペインが小さい場合は、より起こりやすくなります。 + +エージェントに簡潔な応答を求めるか、ペインを広げる、フォントを小さくする、またはエージェント自身のトランスクリプトやスクロール操作を使ってください。エージェント内でスクロールした後、`--source visible` を使うと、現在描画されているページを読み取れます。 + +その読み取りに失敗した後、エージェントに完全な応答を一時ディレクトリ内へ Markdown 形式で書き込み、ファイルパスだけを返すよう依頼してから、そのファイルを直接読み取ってください。これはフォールバックとしてのみ使い、最初のプロンプトではファイル出力を求めないでください。 + +成功した `agent start`、`agent prompt`、`agent wait` は現在のエージェントを `.result.agent` に返します。`pane wait-output` は `.result.pane_id`、`.result.matched_line`、一致したスナップショット `.result.read` を返します。 + +wait コマンドにはデフォルトのタイムアウトがなく、無期限に待機できます。タイムアウトやサーバーエラーでは JSON エラーを stderr に出して終了コード 1、CLI 構文エラーでは終了コード 2 を返します。 + +## レシピ + +補助エージェントを起動して作業を渡し、その作業が落ち着くまで待ちます: + +```bash +split=$(herdr pane split --current --direction right --no-focus) +review_pane=$(printf '%s\n' "$split" | jq -r '.result.pane.pane_id') +herdr agent start reviewer --kind codex --pane "$review_pane" -- -m gpt-5.4 +herdr agent prompt reviewer "Review the current diff" --wait --timeout 120000 +herdr agent read reviewer --source recent-unwrapped --lines 120 +``` + +エージェントが入力を求めるまで待ち、内容を確認して対話 UI を操作します: + +```bash +herdr agent wait reviewer --until blocked --timeout 120000 +herdr agent read reviewer --source recent-unwrapped --lines 80 +herdr agent send-keys reviewer esc +``` + +通常のプロセスを実行し、エージェントとして扱わずに出力を待ちます: + +```bash +herdr pane run w1:p3 "just test --watch" +herdr pane wait-output w1:p3 --regex "passed|failed" --timeout 120000 +``` + +コマンドとオプションの完全な一覧は [CLI リファレンス](/ja/docs/cli-reference/) を参照してください。シェル補完でも同じコマンドツリーを対話的に確認できます。 diff --git a/docs/preview/website/src/content/docs/ja/agent-skill.mdx b/docs/preview/website/src/content/docs/ja/agent-skill.mdx new file mode 100644 index 00000000..97d109f7 --- /dev/null +++ b/docs/preview/website/src/content/docs/ja/agent-skill.mdx @@ -0,0 +1,65 @@ +--- +title: エージェントスキルファイル +description: Claude Code などのコーディングエージェントに Herdr の使い方をインストールします。 +--- + +Herdr は再利用可能なエージェントスキルファイルを [`SKILL.md`](https://github.com/ogulcancelik/herdr/blob/master/SKILL.md) として提供しています。 + +このファイルを、再利用可能なスキルやカスタム指示に対応した任意のコーディングエージェントにインストールしてください。このスキルは、Herdr のペイン内から Herdr を制御する方法をエージェントに教えます。 + +Herdr は別の用途向けに [`herdr.dev/agent-guide.md`](https://herdr.dev/agent-guide.md) というガイドも提供しています。こちらは、人間が Herdr を学習・セットアップ・トラブルシューティングするのをエージェントが手伝うためのものです。スキルは Herdr を操作するエージェントのため、ガイドは人間に教えるエージェントのためのものです。 + +## スキルがすること + +このスキルは、`HERDR_ENV=1` が設定されているときに `herdr` CLI を使うようエージェントに指示します。これは、エージェントが Herdr 管理下のペイン内で動作しており、ローカルの Herdr ソケットと安全に通信できることを意味します。 + +スキルをインストールすると、エージェントは次のことができます: + +- ワークスペース、タブ、ペイン、隣のエージェントを調べる +- フォーカスを奪わずにペインを分割してコマンドを実行する +- ペインの出力と最近のログを読む +- サーバー、テスト、別のエージェントの完了を待つ +- 隣のペインでヘルパーエージェントを起動する + +このスキルは独立したアプリやサービスではありません。エージェント向けの markdown 指示ファイルです。 + +## インストールする + +`npx skills` でスキルをインストールします: + +```bash +npx skills add ogulcancelik/herdr --skill herdr -g +``` + +`-g` フラグは、対応エージェントにグローバルインストールします。現在のプロジェクトにインストールする場合は `-g` を省略してください。 + +手動でのフォールバックおよび信頼できるソースとしては、リポジトリのコピーを使ってください: + +```text +https://github.com/ogulcancelik/herdr/blob/master/SKILL.md +``` + +スキルシステムを持つエージェントには、このファイルを `herdr` という名前のスキルとしてインストールしてください。スキルシステムを持たないエージェントには、ファイルの内容をプロジェクト指示またはユーザー指示に貼り付けてください。 + +インストール後、Herdr の中でエージェントを起動します: + +```bash +herdr +claude +``` + +他のコーディングエージェントを Herdr のペインで使っても構いません。重要なのは、エージェントのプロセスが Herdr 内で動作していて `HERDR_ENV=1` が利用できることです。 + +## 安全ルール + +このスキルはひとつのガードレールから始まります: `HERDR_ENV=1` が設定されていない場合、エージェントは停止して、Herdr 管理下のペイン内で動作していないと伝えるべきです。 + +これにより、Herdr の外にいるエージェントが自分のものではないセッションを制御しようとするのを防ぎます。 + +## エージェント向けリファレンス + +コマンドの完全なガイドはスキルファイル自体にあります。ペイン ID、`pane split`、`pane run`、`pane read`、`pane wait-output`、`agent wait`、ワークスペースとタブのコマンド、協調動作のレシピを扱っています。 + +ソースファイルはこちら: + +[GitHub で `SKILL.md` を開く →](https://github.com/ogulcancelik/herdr/blob/master/SKILL.md) diff --git a/docs/preview/website/src/content/docs/ja/agents.mdx b/docs/preview/website/src/content/docs/ja/agents.mdx new file mode 100644 index 00000000..4b08da69 --- /dev/null +++ b/docs/preview/website/src/content/docs/ja/agents.mdx @@ -0,0 +1,154 @@ +--- +title: エージェント +description: Herdr が何を検出できるか、エージェント状態の仕組み、インテグレーションによる精度向上。 +--- + +Herdr は複数のコーディングエージェントを同時に動かすために作られています。各エージェントは、シェル、ログ、プロンプト、実行中プロセスをそのまま保った実際のターミナルペインの中にいます。Herdr はどのペインにエージェントがいるかを追跡し、その状態をタブとワークスペースに集約し、すべてのターミナルを手作業で見回る代わりに、注意が必要なペインへ直接ジャンプできるようにします。 + +スクリプトや別のエージェントから連携させる方法は、[エージェント自動化](/ja/docs/agent-automation/) を参照してください。 + +## 対応エージェント + +一般的なコーディングエージェントは、追加設定なしで自動検出されます。重要な違いは Herdr がエージェントを見えるかどうかではありません。どのシグナルが `idle`、`working`、`blocked` を決定する権限を持つかです。 + +| エージェント | 状態の権威 | インテグレーションの役割 | +| --- | --- | --- | +| Pi | インストール時はライフサイクルフック。それ以外はスクリーンマニフェスト | 状態とセッション | +| OMP | インストール時はライフサイクルフック | 状態 | +| GitHub Copilot CLI | スクリーンマニフェスト | セッション | +| Devin CLI | スクリーンマニフェスト | セッション | +| Kimi Code CLI | インストール時はライフサイクルフック。それ以外はスクリーンマニフェスト | 状態とセッション | +| Hermes Agent | インストール時はライフサイクルフック。それ以外はスクリーンマニフェスト | 状態とセッション | +| Qoder CLI | スクリーンマニフェスト | セッション | +| Droid | スクリーンマニフェスト | セッション | +| OpenCode | インストール時はライフサイクルプラグイン。それ以外はスクリーンマニフェスト | 状態とセッション | +| Kilo Code CLI | インストール時はライフサイクルプラグイン。それ以外はスクリーンマニフェスト | 状態とセッション | +| MastraCode | インストール時はライフサイクルフック | 状態とセッション | +| Claude Code | スクリーンマニフェスト | セッション | +| Codex | スクリーンマニフェスト | セッション | +| Cursor Agent CLI | スクリーンマニフェスト | セッション | +| Amp | スクリーンマニフェスト | なし | +| Grok CLI | スクリーンマニフェスト | なし | +| Antigravity CLI | スクリーンマニフェスト | なし | +| Kiro CLI | スクリーンマニフェスト | なし | +| Maki | スクリーンマニフェスト | なし | + +検出されるもののテストが薄いもの: Gemini CLI と Cline。未対応のエージェントも通常のターミナルプロセスとして問題なく動きます。ただし、インテグレーションを追加するかソケット API で状態を報告しない限り、詳細な状態は得られない可能性があります。 + +## 状態の権威 + +Herdr はまず各ペインのフォアグラウンドプロセスを検出します。その後、各ペインはひとつの状態権威を持ちます。 + +完全なライフサイクルフックを持つエージェントでは、インテグレーションがインストールされ、実行中のペインについて能動的に報告している間は、インテグレーションが権威です。Herdr はそのフック報告を `idle`、`working`、`blocked` とセッション識別に使います。同じライフサイクル権威に対してスクリーンマニフェストのフォールバックを並走させることはしません。これにより、真実の情報源が 2 つ競合する状況を避けます。 + +完全なライフサイクルフックを持たないエージェントでは、Herdr はフォアグラウンドプロセスを識別し、ライブの下部バッファのスクリーンスナップショットを読みます。そのスナップショットに対して TOML マニフェストを評価し、`idle`、`working`、`blocked` を分類します。それらを発するエージェントでは、マニフェストはターミナルタイトルと進捗 (OSC) シーケンスも検出の証拠としてマッチできます。その証拠がない場合は、スクリーンルールが単独で検出を担います。 + +スクリーンスナップショットは、スクロールされたビューポートではなく、ペインバッファの直近の下部から取得されます。Herdr でスクロールバックしても、検出は下部のライブなエージェント UI を追い続けます。 + +Claude Code、Codex、GitHub Copilot CLI、Droid、Qoder CLI、Cursor Agent CLI のインテグレーションは、意図的にライフサイクル権威にしていません。これらは復元のためのネイティブセッション識別を提供しますが、フックがライフサイクル全体をカバーしていません。許可承認の結果、Esc による中断、その他の遷移を見逃すことがあります。これらのエージェントでは、Herdr は引き続きスクリーンマニフェスト検出を使います。 + +## VM とサンドボックスラッパー + +Linux と macOS では、ホストから見えるラッパーが実際のエージェントプロセスを Herdr から隠すことがあります。ラッパーのコマンドに `HERDR_AGENT=<agent>` を設定して、どの既存エージェントのスクリーンマニフェストを使うべきか Herdr に伝えてください。たとえば、Linux では `HERDR_AGENT=claude fence -- claude`、macOS では `HERDR_AGENT=claude nono run --profile claude-code -- claude` を実行します。このヒントはそのフォアグラウンドプロセスにスコープされます。VM やコンテナの内部だけで設定しても Herdr からは見えません。また、継承されるすべてのフォアグラウンドプロセスをそのエージェントとして扱いたいのでない限り、グローバルな export は避けてください。 + +## blocked 状態 + +スクリーンマニフェスト方式のエージェントでは、blocked の検出は意図的に厳格です。Herdr が `blocked` と判定するのは、ライブの下部バッファスナップショットが既知の承認・質問・許可 UI にマッチしたときだけです。既知のエージェントでどのマニフェストルールにもマッチしない場合、Herdr は `idle` にフォールバックし、explain の出力ではそのフォールバックに `default_known_agent_idle_fallback` というラベルを付けます。 + +つまり、見慣れない新しいエージェントプロンプトは、Herdr がその画面の形を学習するまで、最初は `blocked` ではなく `idle` と表示されることがあります。こうしたやり取りによって Herdr が入力を送ったり破壊的な操作をしたりすることはありません。影響するのは表示上の状態と wait だけです。 + +## 検出マニフェスト + +バンドルされたマニフェストは Herdr の内部にあります。Herdr は herdr.dev でリモートマニフェストの更新も確認し、有効なエージェント別ルール更新を Herdr の再起動なしで自動適用します。リモートマニフェストは Herdr の state ディレクトリに保存されます。バックグラウンドのリモートマニフェスト確認を無効にするには `[update] manifest_check = false` を設定します。 + +ローカルオーバーライドは、プラットフォームの設定ディレクトリからリモートまたはバンドルのマニフェストを置き換えられます: + +```text +~/.config/herdr/agent-detection/<agent>.toml +``` + +ローカルオーバーライドが常に優先されます。ローカルオーバーライドがない場合、Herdr はキャッシュされたリモートマニフェストと実行中バイナリにバンドルされたマニフェストのうち、新しくて互換性のある方を使います。デバッグビルドでは、同じ設定ヘルパーが `herdr-dev` のような開発用ディレクトリを使うことがあります。無効なオーバーライドファイルは警告付きで無視され、Herdr はそのエージェントについてキャッシュされたリモートまたはバンドルのマニフェストにフォールバックします。 + +リモートマニフェストは、Herdr がすでに識別方法を知っているエージェントの検出ルールにパッチを当てるものです。完全に新しいエージェントの追加には、プロセス検出、ラベル、インテグレーション挙動のために Herdr バイナリのアップデートが引き続き必要です。 + +実行中のサーバーは起動時にアクティブなマニフェストをメモリに読み込みます。リモートマニフェストの自動更新は、新しいルールが書き込まれた後にそのメモリ内キャッシュをリロードします。`herdr server update-agent-manifests` を実行すると、リモートマニフェストの更新を即座に取得して実行中のサーバーをリロードします。ローカルオーバーライドを手で編集した後は、Herdr を再起動するか `herdr server reload-agent-manifests` を実行して、実行中のサーバーにファイルを適用してください。 + +ペインの状態表示がおかしいときは `herdr agent explain` を使ってください: + +```bash +herdr agent explain <target> +herdr agent explain --file screen.txt --agent codex --json +``` + +ライブの explain は実行中のサーバーが評価するので、アクティブなマニフェストキャッシュを反映します。explain の出力には次が表示されます: エージェント、最終状態、完全なライフサイクル権威によってスクリーン検出がスキップされたかどうか、マニフェストのソースとバージョン、キャッシュされたリモートバージョン、ローカルオーバーライドによるシャドーイング、リモート更新の状況、マッチしたルール、可視の証拠フラグ、評価されたルールのマッチャーとリージョンの証拠、トランスクリプトビューアーでの更新スキップ理由、そしてどのルールにもマッチしなかったときの idle フォールバック理由です。 + +Herdr は外側のターミナル環境として tmux の中で動かせます。エージェント検出は、Herdr のペイン内で起動された tmux セッションの中までは調べません。シェルフレームワークが Herdr 内で自動的に tmux に入る場合、Herdr はペインのプロセスとして背後のエージェントではなく `tmux` を見ることになります。 + +## 状態のロールアップ + +サイドバーは状態を上位へ集約します。 + +blocked なエージェントは、そのペイン、タブ、ワークスペースを blocked に見せます。working なエージェントはワークスペースをアクティブに見せます。done なエージェントは、あなたが確認するまで表示され続けます。 + +これが Herdr の中心的なワークフローです: 複数のエージェントを起動し、並行して働かせ、サイドバーでどのプロジェクトが判断を必要としているか、どれがまだ実行中か、どれがレビュー待ちかを把握します。 + +## ダイレクトインテグレーション + +使っている各エージェントのインテグレーションをインストールしてください。スクリーン検出だけに頼らず、フックやプラグインの報告を Herdr に提供します: + +```bash +herdr integration install claude +herdr integration status +``` + +対応エージェントごとに、インテグレーションの名前と挙動は異なります。エージェント別の詳細と完全なインストール一覧は[インテグレーション](/ja/docs/integrations/)を参照してください。 + +## カスタムエージェントラベル + +表示用にエージェントターゲットの名前を変えられます: + +```bash +herdr agent rename w1:p1 reviewer +herdr agent rename reviewer --clear +``` + +ターゲットには一意なライブエージェント名、または現在そのエージェントをホストしているペイン ID を使います。ターミナル ID とエージェント kind のラベルだけでは指定できません。 + +## カスタムステータスラベル + +インテグレーションは、ライフサイクル状態を意味的な状態としてのみ報告します。表示のカスタマイズは、ペインメタデータのトークンとして別に追加します。 + +```bash +herdr pane report-agent w1:p1 \ + --source custom:indexer \ + --agent docs-bot \ + --state working + +herdr pane report-metadata w1:p1 \ + --source custom:indexer-display \ + --token summary=indexing +``` + +`state` は wait、通知、ロールアップを制御します。`summary` トークンは表示専用で、エージェントサイドバー行では `$summary` として使えます。 + +エージェントサイドバー行では、`terminal_title` または `terminal_title_stripped` も任意で使えます。どちらもデフォルト行には含まれません。前者は安全性のために正規化された最新の OSC 0/2 ターミナルタイトルを表示し、後者は先頭にある認識済みのアクティビティまたはスピナーのグリフ 1 つと、それに続く空白を除去します。これらの値は Herdr サーバーが所有し、コールドリスタートをまたいで保持されません。また、メタデータのタイトルや意味的なエージェント状態から独立しています。そのため、除去後のテキストが同じなら、スピナーのアニメーションで生のタイトルが変わってもペイン更新は発行されません。 + +## エージェントに直接アタッチする + +完全な Herdr UI ではなく、ひとつのエージェントターミナルに現在のターミナルをアタッチします: + +```bash +herdr agent attach reviewer +``` + +`ctrl+b q` でデタッチします。リテラルの `ctrl+b` は `ctrl+b ctrl+b` で送ります。 + +マウスホイールまたは通常の page up/page down でスクロールします。通常の入力をすると最下部に戻ります。 + +別のダイレクトアタッチクライアントがすでに入力を所有している場合は `--takeover` を使います: + +```bash +herdr agent attach reviewer --takeover +``` + +エージェントではないターミナルで同じダイレクトアタッチ挙動が欲しいときは `herdr terminal attach <terminal_id>` を使ってください。 diff --git a/docs/preview/website/src/content/docs/ja/cli-reference.mdx b/docs/preview/website/src/content/docs/ja/cli-reference.mdx new file mode 100644 index 00000000..3b96247e --- /dev/null +++ b/docs/preview/website/src/content/docs/ja/cli-reference.mdx @@ -0,0 +1,444 @@ +--- +title: CLI リファレンス +description: セッション、ワークスペース、タブ、ペイン、通知、エージェント、wait、インテグレーション、ステータスのための Herdr コマンド。 +--- + +Herdr の CLI は、インテグレーションやエージェントが使うのと同じローカルソケット API を通じて、実行中のサーバーと通信します。 + +ほとんどのコマンドは JSON レスポンスを出力します。決定的な自動化が欲しいときはスクリプトから使ってください。 + +## 起動とステータス + +```bash +herdr # デフォルトセッションを起動またはアタッチ +herdr --session work # 名前付きセッションを起動またはアタッチ +herdr --remote workbox # SSH 越しにアタッチ (ローカルのキーバインドを使用) +herdr --remote workbox --remote-keybindings server +herdr --remote workbox --handoff +herdr --no-session # シングルプロセスの逃げ道 +herdr --default-config # デフォルト設定を表示 +herdr update # 設定済みチャンネルからダウンロードしてインストール +herdr update --handoff # 対応する実行中サーバーでライブハンドオフにオプトイン +herdr completion zsh # zsh 補完スクリプトを生成 +herdr channel show # stable または preview を表示 +herdr channel set preview # プレビュービルドにオプトイン +herdr channel set stable # Linux/macOS の直接インストールを安定版に戻す +herdr --version # バージョンを表示 +``` + +ステータスコマンド: + +```bash +herdr status +herdr status server +herdr status client +``` + +API スキーマコマンド: + +```bash +herdr api schema +herdr api schema --json +herdr api schema --output herdr-api.schema.json +``` + +`herdr api schema` は、インストール済みバイナリに同梱されたソケットプロトコルスキーマの短い概要を表示します。完全な JSON Schema ドキュメントが欲しいときは `--json` を使い、ファイルに書き出すには `--output PATH` を使ってください。 + +## シェル補完 + +```bash +herdr completion zsh +herdr completions zsh +herdr completion bash +herdr completion fish +herdr completion powershell +herdr completion elvish +``` + +`completion` はスクリプトを標準出力に表示します。`completions` はエイリアスです。一時的な zsh セッションでは、スクリプトを直接読み込めます: + +```bash +source <(herdr completion zsh) +``` + +永続的な zsh 設定では、生成された `_herdr` 関数を `compinit` が実行される前の `fpath` 上に置いてください: + +```bash +mkdir -p ~/.zfunc +herdr completion zsh > ~/.zfunc/_herdr +``` + +その後、`.zshrc` に次の内容があることを確認してください: + +```zsh +fpath=(~/.zfunc $fpath) +autoload -Uz compinit +compinit +``` + +## サーバー + +```bash +herdr server +herdr server stop +herdr server reload-config +herdr server agent-manifests [--json] +herdr server update-agent-manifests [--json] +herdr server reload-agent-manifests +``` + +`herdr server` はヘッドレスサーバーを明示的に起動します。監視下やサービス的な構成で使ってください。`reload-config` はペインを再起動せずにリロード可能な設定を適用します。`agent-manifests` は、アクティブなエージェント検出マニフェストのソース、キャッシュされたリモートバージョン、直近のリモート更新結果を表示します。`update-agent-manifests` はリモートマニフェストの更新を即座に取得し、実行中のサーバーにリロードして、更新後のマニフェスト状態を表示します。生のステータスレスポンスが欲しいときは `--json` を渡してください。`reload-agent-manifests` は、ローカルオーバーライドの編集後にエージェント検出マニフェストを実行中のサーバーにリロードします。 + +## 通知 + +```bash +herdr notification show <title> [--body TEXT] [--position top-left|top-right|bottom-left|bottom-right] [--sound none|done|request] +``` + +`notification show` は設定済みの `[ui.toast]` 配信を使います。`--position` はアプリ内の Herdr トーストにのみ影響します。`--sound` のデフォルトは `none` で、`done` と `request` は通知が表示されたときにのみ、既存の完了音と要注意音を再生します。 + +## セッション + +```bash +herdr session list [--json] +herdr session attach <name> +herdr session stop <name> [--json] +herdr session delete <name> [--json] +``` + +デフォルトセッションを明示的に停止する必要があるときは、セッション名として `default` を使ってください。 + +## ワークスペース + +```bash +herdr workspace list +herdr workspace create [--cwd PATH] [--label TEXT] [--env KEY=VALUE] [--focus] [--no-focus] +herdr workspace get <workspace_id> +herdr workspace focus <workspace_id> +herdr workspace rename <workspace_id> <label> +herdr workspace report-metadata <workspace_id> --source ID [--token NAME=VALUE] [--clear-token NAME] [--seq N] [--ttl-ms N] +herdr workspace close <workspace_id> +``` + +フォーカスを奪わずにワークスペースを作成します: + +```bash +herdr workspace create --cwd ~/project --label api --no-focus +``` + +ワークスペースは最上位のプロジェクトまたは作業コンテキストです。作成時に最初のタブとルートペインも作られます。JSON レスポンスの `.result.workspace.workspace_id`、`.result.tab.tab_id`、`.result.root_pane.pane_id` から各 ID を取得できます。 + +## Worktree + +```bash +herdr worktree list [--workspace ID | --cwd PATH] [--json] +herdr worktree create [--workspace ID | --cwd PATH] [--branch NAME] [--base REF] [--path PATH] [--label TEXT] [--focus] [--no-focus] [--json] +herdr worktree open [--workspace ID | --cwd PATH] (--path PATH | --branch NAME) [--label TEXT] [--focus] [--no-focus] [--json] +herdr worktree remove --workspace ID [--force] [--json] +``` + +worktree は、Git チェックアウトの出自情報を持つ通常の Herdr ワークスペースです。`worktree create` は Git worktree のチェックアウトを作成し、ワークスペースとして開き、親リポジトリのワークスペースとグループ化します。`--branch` が既存のローカルブランチを指す場合はそれをチェックアウトし、そうでなければ `--base` または `HEAD` からブランチを作成します。`--path` がない場合、チェックアウトは `<worktrees.directory>/<repo>/<branch-slug>` の下に作成されます。 + +`workspace close` は Herdr の状態だけを閉じます。`worktree remove` が明示的なチェックアウト削除の経路です。`git worktree remove` を実行し、ブランチは決して削除せず、Git がダーティなチェックアウトを拒否する場合は `--force` が必要です。 + +## タブ + +```bash +herdr tab list [--workspace <workspace_id>] +herdr tab create [--workspace <workspace_id>] [--cwd PATH] [--label TEXT] [--env KEY=VALUE] [--focus] [--no-focus] +herdr tab get <tab_id> +herdr tab focus <tab_id> +herdr tab rename <tab_id> <label> +herdr tab close <tab_id> +``` + +タブはワークスペース内の別のターミナルレイアウトです。`--workspace` を省略すると、`tab create` はアクティブなワークスペースを使い、存在しなければ失敗します。JSON レスポンスの `.result.tab.tab_id` と `.result.root_pane.pane_id` から ID を取得できます。 + +ワークスペースとタブの作成、およびペインの分割は、デフォルトではフォーカスを変えません。`--focus` は新しいレイアウトを選択し、`--no-focus` はデフォルトを明示します。`--cwd` がない場合、新しいターミナルは `terminal.new_cwd` の設定に従い、デフォルトでは元のペインまたはワークスペースを引き継ぎます。各 `--env KEY=VALUE` は新しいルートシェルの環境変数を追加または置換します。 + +## ペイン + +```bash +herdr pane list [--workspace <workspace_id>] +herdr pane current [--pane ID|--current] +herdr pane get <pane_id> +herdr pane layout [--pane ID|--current] +herdr pane process-info [--pane ID|--current] +herdr pane neighbor --direction left|right|up|down [--pane ID|--current] +herdr pane edges [--pane ID|--current] +herdr pane focus --direction left|right|up|down [--pane ID|--current] +herdr pane resize --direction left|right|up|down [--amount FLOAT] [--pane ID|--current] +herdr pane zoom [<pane_id>|--pane ID|--current] [--toggle|--on|--off] +herdr pane rename <pane_id> <label>|--clear +herdr pane split [<pane_id>|--pane ID|--current] --direction right|down [--ratio FLOAT] [--cwd PATH] [--env KEY=VALUE] [--focus] [--no-focus] +herdr pane swap --direction left|right|up|down [--pane ID|--current] +herdr pane swap --source-pane ID --target-pane ID +herdr pane move <pane_id> --tab <tab_id> --split right|down [--target-pane ID] [--ratio FLOAT] [--focus|--no-focus] +herdr pane move <pane_id> --new-tab [--workspace ID] [--label TEXT] [--focus|--no-focus] +herdr pane move <pane_id> --new-workspace [--label TEXT] [--tab-label TEXT] [--focus|--no-focus] +herdr pane close <pane_id> +``` + +`--current` を受け付けるペインコマンドは、Herdr ペイン内から実行した場合、呼び出し元の `HERDR_PANE_ID` を使います。`pane split` では、明示したペイン ID または `--pane ID` がそのペインを、`--current` が呼び出し元ペインを分割します。対象を省略すると UI でフォーカス中のペインを使います。分割レスポンスでは、新しいペイン ID は `.result.pane.pane_id` にあります。 + +`pane move` の後続コマンドには `.result.move_result.pane.pane_id` を使います。別ワークスペースへの移動ではワークスペース修飾付きのペイン ID が変わり、以前の値は `.result.move_result.previous_pane_id` に残ります。実行中プロセスの `HERDR_PANE_ID`、`HERDR_TAB_ID`、`HERDR_WORKSPACE_ID` は起動時の値のままですが、Herdr は以前のペイン ID をそのターミナルの別名として保持するため、`--current` を使うペインコマンドは引き続き解決できます。ライブエージェント名もターミナルに追従します。 + +出力を読む: + +```bash +herdr pane read <pane_id> [--source visible|recent|recent-unwrapped|detection] [--lines N] [--format text|ansi] [--ansi] [--raw] +herdr pane read <pane_id> --source visible --ansi +herdr pane read <pane_id> --source recent-unwrapped --lines 120 +``` + +`pane read` は UTF-8 のターミナルテキストを直接出力します。デフォルトでは ANSI エスケープを除去し、ソースに装飾が含まれる場合は `--format ansi` または `--ansi` で保持します。`detection` ソースは常にプレーンテキストです。recent 系ソースでは、`--lines N` は折り返し解除の前に末尾の N 描画行を選び、省略時は 80 行です。`visible` と `detection` では、省略時にスナップショット全体を返し、指定時は改行で区切られた末尾 N 行を返します。`agent read` も同じ出力と行数の動作です。 + +入力を送る: + +```bash +herdr pane send-text <pane_id> <text> +herdr pane send-keys <pane_id> <key> [key ...] +herdr pane run <pane_id> <command> +``` + +`<key>` は Herdr のキーコンボ構文を使います: `a` のような通常の印字可能キー、`enter`、`tab`、`esc`、`backspace`、`left`、`right`、`up`、`down` のような特殊キー、`ctrl+h`、`control+j`、`alt+x`、`shift+tab` のような修飾キーコード、`f1` のようなファンクションキー、そして `minus`、`plus`、`backtick` のような名前付き記号です。レガシーな `C-c` と `c-c` は `ctrl+c` のエイリアスとして受け付けられます。 + +標準表記は `esc` ですが、`escape` も使えます。 + +`pane run` は現在の bracketed paste モードを尊重し、テキストと Enter をアトミックに送信します。コマンドには `send-text` + `send-keys Enter` よりこちらを使ってください。個別の送信操作は低レベルのままで、コマンドを送信しません。 + +カスタムフックからエージェント状態を報告する: + +```bash +herdr pane report-agent <pane_id> \ + --source ID \ + --agent LABEL \ + --state idle|working|blocked|unknown \ + [--message TEXT] \ + [--seq N] \ + [--agent-session-id ID] \ + [--agent-session-path PATH] + +herdr pane report-agent-session <pane_id> \ + --source ID \ + --agent LABEL \ + [--seq N] \ + [--agent-session-id ID] \ + [--agent-session-path PATH] \ + [--session-start-source SOURCE] + +herdr pane release-agent <pane_id> \ + --source ID \ + --agent LABEL \ + [--seq N] +``` + +`report-agent-session` はライフサイクル状態を報告せずにネイティブセッション識別子を更新します。`release-agent` は、そのエージェントプロセスが終了したときに、そのソースのライフサイクル権限を終了します。 + +公式インテグレーションがネイティブセッション参照を報告している場合、`pane get`、`pane list`、`agent get`、`agent list` は読み取り専用の `agent_session` オブジェクトを含みます。ネイティブセッション参照が保存されていない場合、このフィールドは省略されます。 + +これらのコマンドは、ペインを制御しているフォアグラウンドプロセスの cwd を解決できる場合に `foreground_cwd` を含みます。既存の `cwd` フィールドは、ラベルと follow-cwd 挙動に使われるペイン/ワークスペースの cwd のままです。 + +意味的な状態を奪わずに、表示専用のペインメタデータを報告する: + +```bash +herdr pane report-metadata <pane_id> \ + --source ID \ + [--agent LABEL] \ + [--applies-to-source ID] \ + [--title TEXT|--clear-title] \ + [--display-agent TEXT|--clear-display-agent] \ + [--state-label STATUS=TEXT] \ + [--clear-state-labels] \ + [--token NAME=VALUE] \ + [--clear-token NAME] \ + [--seq N] \ + [--ttl-ms N] +``` + +`STATUS` は `idle`、`working`、`blocked`、`done`、`unknown` のいずれかです。`--agent` と `--applies-to-source` は `--title`、`--display-agent`、`--state-label` だけを保護し、トークンパッチは保護しません。トークンのクリアまたは TTL の更新は報告側が管理します。表示名を変えるには `--display-agent` を使ってください。 + +メタデータのテキストは保存前に正規化されます。Herdr は前後の空白を取り除き、制御文字を除去し、`--title`、`--display-agent`、各 `--state-label`、トークンの値を 80 文字に制限します。正規化後に空になったトークン値は、そのキーをクリアします。 + +`--token` は名前付き表示値を設定し、`--clear-token` は削除します。指定しなかったトークンは変わりません。ペイントークンはエージェントサイドバー行で `$name` として、ワークスペーストークンはスペース行で使えます。TTL は、その呼び出しで更新した各トークンキーに個別に適用されます。 + +`--source` と `--applies-to-source` は 80 文字以下で、ASCII の英字、数字、コロン、ドット、アンダースコア、ハイフンのみを含められます。`--ttl-ms` はメタデータを自動失効させ、`1` から `86400000` ミリ秒の間でなければなりません。置き換え・クリア・ペインまたはワークスペースのクローズまで残るべきメタデータでは省略してください。`--seq` により、Herdr は同じ `--source` からの古い報告を無視できます。古い報告は API には受理されますが、ペイン状態には無視されます。ペインまたはワークスペースが存続する間、シーケンス付きトークン報告を送れる異なるソースは最大 32 個で、クリアや失効でもその枠は解放されません。 + +## エージェント + +ペインとエージェントの使い分けや一連の連携例は、[エージェント自動化](/ja/docs/agent-automation/) を参照してください。 + +```bash +herdr agent list +herdr agent get <target> +herdr agent read <target> [--source visible|recent|recent-unwrapped|detection] [--lines N] [--format text|ansi] [--ansi] +herdr agent send-keys <target> <key> [key ...] +herdr agent prompt <target> <text> [--wait] [--until STATUS]... [--timeout MS] +herdr agent rename <target> <name>|--clear +herdr agent focus <target> +herdr agent wait <target> [--until STATUS]... [--timeout MS] +herdr agent attach <target> [--takeover] +herdr agent start <name> --kind KIND --pane ID [--timeout MS] [-- <agent-args...>] +herdr agent explain <target> [--json|--verbose] +herdr agent explain --file PATH --agent LABEL [--json|--verbose] +``` + +エージェントターゲットは、一意なライブエージェント名、または現在そのエージェントをホストしているペイン ID です。ターミナル ID とエージェント kind のラベルだけでは指定できません。`agent start` で起動するエージェントには名前が必須で、手動で起動したエージェントは名前なしのままペイン ID で指定します。 + +`agent start` は既存の利用可能なシェルペインを起動対象にします。対話シェル自身がフォアグラウンドを所有し、フォアグラウンドのコマンド、エディタ、エージェントが動いていない必要があります。トポロジーは別に作成します。名前はライブエージェント間で一意で、`[a-z][a-z0-9_-]{0,31}` に一致する必要があります。対応する kind は `pi`、`claude`、`codex`、`gemini`、`cursor`、`devin`、`agy`、`cline`、`omp`、`mastracode`、`opencode`、`copilot`、`kimi`、`kiro`、`droid`、`amp`、`grok`、`hermes`、`kilo`、`qodercli`、`maki` です。名前は現在のペイン占有者に属し、そのエージェントの終了、release、置換で消えます。一時的に検出できないだけでは消えません。 + +start は、期待したエージェントが同じターミナルを所有し、対話入力の準備ができてから返ります。デフォルトの起動タイムアウトは 30000 ミリ秒で、明示する値は 3000 より大きく 300000 以下でなければなりません。 + +`agent prompt` は現在の bracketed paste モードを尊重し、working 中でもテキストとエンコード済み Enter をアトミックに送信します。`--wait` を使う場合、non-working 状態から送信したプロンプトは 5 秒以内に観測可能なライフサイクル変化を起こす必要があり、変化がなければ Herdr は `agent_prompt_stalled` を返します。呼び出し側のタイムアウトが 5 秒以下なら、通常の `timeout` エラーを維持します。活動を観測した後、要求された安定状態を待ちます。個々のターンは追跡しません。すでに working の場合、進行中ターンの完了が待機を満たすことがあります。`--until` は一致状態を絞り込み、`--wait` なしでは拒否されます。単独の `agent wait` は現在の状態が一致すれば即座に返ります。どちらもデフォルトは `idle`、`done`、`blocked` です。 + +`idle` は入力待ちで、そのタブがフォーカス中の Herdr UI ですでに表示済みの状態です。`done` は同じ基礎的な idle 状態ですが、表示していないバックグラウンド作業が完了したときに使われます。そのタブをフォーカスするか `pane focus` / `agent focus` で対象にすると表示済みになりますが、CLI で読み取るだけでは変わりません。`blocked` は承認または質問 UI を Herdr が認識した状態です。`unknown` はエージェントが存在するものの確実に分類できない状態で、作業の成功を意味しません。 + +`agent send-keys` は `enter`、`up`、`esc`、`ctrl+c` などの論理キーを送ります。Herdr は 1 バイトも書き込む前に全キーを検証します。`agent read` は解決されたターミナルストリームを読み、`agent rename` は検出済みエージェントに名前を付けます。 + +`agent explain` は、スクリーン検出が使うのと同じ下部バッファの検出スナップショットの分類を実行中のサーバーに依頼します。そのためライブの出力はサーバーのアクティブなマニフェストキャッシュを反映します。これは `agent.explain` ソケットメソッドを使うので、Herdr のアップグレード後にライブ explain を使う前に、サーバーを再起動するか更新済みサーバーへハンドオフしてください。保存済みフィクスチャをローカルで説明するには `--file PATH --agent LABEL` を使います。デフォルトの出力には、エージェント、最終状態、マニフェストのソースとバージョン、リージョンの証拠付きでマッチしたルール、そしてフォールバック・スキップ・警告の理由が表示されます。`--verbose` を付けると、可視の証拠フラグ、キャッシュされたリモートバージョン、ローカルオーバーライドのシャドーイング、リモート更新状況、マッチャーとリージョンの証拠付きの評価済みルール全リストが表示されます。issue の報告やテストには `--json` を付けてください。 + +通常のターミナル、サーバー、テスト、シェル、低レベルなターミナル制御には `pane send-text`、`pane send-keys`、`pane run`、`terminal attach` を使ってください。Enter 付きでコマンドを送信したいときは `pane run` を使います。 + +## ダイレクトターミナルアタッチ + +```bash +herdr terminal attach <terminal_id> [--takeover] +herdr terminal title set <title> +herdr terminal title clear +``` + +ダイレクトアタッチからは `ctrl+b q` でデタッチします。リテラルの `ctrl+b` は `ctrl+b ctrl+b` で送ります。 +`terminal title clear` は Herdr のデフォルトの外側ターミナルウィンドウタイトルを復元します。 + +## 出力待機 + +ペインの出力を待つ: + +```bash +herdr pane wait-output <pane_id> (--match <text> | --regex <pattern>) [--source visible|recent|recent-unwrapped] [--lines N] [--timeout MS] [--raw] +``` + +通常のコマンドやサーバーには `pane wait-output` を使います。コーディングエージェントには `agent wait` を使います。 + +`pane wait-output` は選択したスナップショットを最初にすぐ検索するため、すでに存在する出力も一致します。デフォルトのソース名は `recent` で、直近 80 行の描画済みターミナル行を折り返し前の出力として扱います。`--lines` でその行数を変更できます。`--match` は 1 行内のリテラル部分文字列を検索し、`--regex` は Rust の正規表現構文で 1 行ずつ一致します。 + +`pane wait-output`、`agent wait`、`agent prompt --wait` は、`--timeout` を省略すると無期限に待ちます。タイムアウトまたはサーバーエラーは JSON として stderr に出力され、終了コード 1 になります。CLI 使用法のエラーは終了コード 2 です。 + +## インテグレーション + +```bash +herdr integration install pi +herdr integration install omp +herdr integration install claude +herdr integration install codex +herdr integration install copilot +herdr integration install devin +herdr integration install droid +herdr integration install kimi +herdr integration install opencode +herdr integration install kilo +herdr integration install hermes +herdr integration install mastracode +herdr integration install qodercli +herdr integration install cursor +herdr integration uninstall pi +herdr integration uninstall omp +herdr integration uninstall claude +herdr integration uninstall codex +herdr integration uninstall copilot +herdr integration uninstall devin +herdr integration uninstall droid +herdr integration uninstall kimi +herdr integration uninstall opencode +herdr integration uninstall kilo +herdr integration uninstall hermes +herdr integration uninstall mastracode +herdr integration uninstall qodercli +herdr integration uninstall cursor +herdr integration status [--outdated-only] +``` + +## プラグイン + +プラグインコマンドは、ローカル実行型ワークフロープラグインをインストールして実行します。プラグインはマニフェストとプロセス外コマンドの組み合わせです。ホスト側の面は Herdr が、実装言語はプラグインが担います。 + +プラグインのインストール、一覧、削除: + +```bash +herdr plugin install <owner>/<repo>[/subdir...] [--ref REF] [--yes] +herdr plugin list [--plugin ID] [--json] +herdr plugin uninstall <plugin_id|owner/repo[/subdir...]> +herdr plugin enable <plugin_id> +herdr plugin disable <plugin_id> +``` + +`plugin install` は `ogulcancelik/herdr-plugin-examples/worktree-bootstrap` のような GitHub 省略記法のみを受け付けます。`git` を使い、対話的なターミナルでは信頼プレビューを表示し、サポートされるマニフェストのビルドコマンドを実行し、GitHub インストールを Herdr 管理のディレクトリに保存します。非対話的なインストールには `--yes` を使ってください。GitHub 管理プラグインの再インストールは、その管理チェックアウトを置き換えます。ローカルにリンクされたプラグインへの上書きインストールは拒否されます。プラグインのマニフェストは `min_herdr_version` を宣言しなければならず、プラグインがより新しい Herdr バイナリを要求する場合、install と link は失敗します。`plugin list` はデフォルトで人間可読です。生の API レスポンスが欲しいときは `--json` を渡してください。 + +ローカル開発: + +```bash +herdr plugin link <path> [--disabled] +herdr plugin unlink <plugin_id> +``` + +`plugin link` は `herdr-plugin.toml` を含むプラグインディレクトリ、またはマニフェストへの直接パスを受け付けます。ローカルチェックアウトからプラグインを作成・テストしている間はこれが正しいコマンドです。`plugin unlink` はプラグインの登録を解除し、ファイルには触れません。`plugin uninstall` はプラグインの登録を解除し、Herdr 管理の GitHub チェックアウトファイルも削除します。GitHub インストールの場合、uninstall はプラグイン id と、install で使うのと同じ `owner/repo[/subdir...]` 省略記法の両方を受け付けます。アクション、イベントフック、ペイン、リンクハンドラーはマニフェストで宣言します。ランタイムでのアクション登録は v1 の範囲外です。 + +設定ディレクトリ: + +```bash +herdr plugin config-dir <plugin_id> +``` + +`plugin config-dir` はプラグインの設定ディレクトリを表示し、必要なら作成します (レガシーなプラグイン設定の場所が存在すれば、そこから初期内容を移します)。セットアップドキュメントやシェルスクリプトで、管理されたプラグインチェックアウトとは別の、`.env` ファイルなどユーザーが編集する設定のための安定したパスをユーザーに示すのに使ってください。 + +アクション: + +```bash +herdr plugin action list [--plugin ID] +herdr plugin action invoke <action_id> [--plugin ID] +``` + +`plugin action invoke` は、インストール済みで有効かつプラットフォーム互換のプラグインアクションのマニフェストコマンドを起動し、開始されたコマンドのログレコードを JSON レスポンスに表示します。複数のプラグインが同じアクション id を使っている場合は、修飾されたアクション id (`plugin.id.action`) を使ってください。ローカルのアクション id はドットを含められないため、プラグイン id がドットを含んでいても修飾 id は曖昧になりません。 + +ログ: + +```bash +herdr plugin log list [--plugin ID] [--limit N] +``` + +管理されたターミナルペイン: + +```bash +herdr plugin pane open --plugin ID --entrypoint ID [--placement overlay|popup|split|tab|zoomed] [--width SIZE] [--height SIZE] [--workspace ID] [--target-pane PANE] [--direction right|down] [--cwd PATH] [--env KEY=VALUE] [--focus|--no-focus] +herdr plugin pane focus <pane_id> +herdr plugin pane close <pane_id> +``` + +`plugin pane open` は、プラグインがリンクされ、有効で、現在のプラットフォームと互換であることを要求します。マニフェストで宣言された `[[panes]]` コマンドを Herdr 管理のターミナルペインとして起動します。マニフェストのデフォルトは `overlay` で、アクティブなペインの上に一時的なズームオーバーレイを開きます。分割、新しいタブ、ズームされたペイン、またはタブレイアウトを変更しないセッションモーダルな `popup` として開くこともできます。`--width` と `--height` は、外側のポップアップ寸法をターミナルセル数または `80%` のような割合で指定します。省略した寸法はデフォルトでターミナルの半分になり、小さすぎる値はポップアップの最小サイズに制限されます。ポップアップは Herdr ペインではなく、`HERDR_PANE_ID` を受け取らず、pane API やエージェント API に参加しません。ターミナル以外のネイティブなプラグインペインは今後の対応面です。 + +`--env KEY=VALUE` はプロセスを起動するコマンドで繰り返し指定できます。新しく起動されるプロセスにのみ適用されます。`HERDR_SOCKET_PATH`、`HERDR_BIN_PATH`、`HERDR_ENV`、`HERDR_WORKSPACE_ID`、`HERDR_TAB_ID`、`HERDR_PANE_ID`、`HERDR_PLUGIN_ID`、`HERDR_PLUGIN_ROOT`、`HERDR_PLUGIN_CONFIG_DIR`、`HERDR_PLUGIN_STATE_DIR`、`HERDR_PLUGIN_ENTRYPOINT_ID`、`HERDR_PLUGIN_CONTEXT_JSON` のような Herdr 管理の変数は、呼び出し側が与えた環境変数と衝突した場合も権威を保ちます。 + +## 読み取りソース + +| ソース | 意味 | +| --- | --- | +| `visible` | 現在レンダリングされている画面。UI のフィードバックループに最適。 | +| `recent` | ターミナルの折り返しを含む直近のスクロールバック。 | +| `recent-unwrapped` | ソフト折り返しなしの直近のスクロールバック。ログに最適。 | +| `detection` | エージェントのスクリーン検出が使う下部バッファのスナップショット。 | + +この表は読み取り時の意味です。`pane wait-output` に限り、`recent` と `recent-unwrapped` はどちらも recent の折り返し前スナップショットを検索し、デフォルト表記は `recent` です。 + +## 環境変数 + +| 変数 | 目的 | +| --- | --- | +| `HERDR_CONFIG_PATH` | 設定ファイルパスを上書きする。 | +| `HERDR_SESSION` | CLI コマンドの名前付きセッションを選択する。 | +| `HERDR_SOCKET_PATH` | 低レベルなソケットパスの上書き。 | +| `HERDR_ENV` | Herdr 管理のペインプロセス内で `1` に設定される。 | +| `HERDR_PANE_ID` | 実行中ペインプロセスの公開ペイン id。 | +| `HERDR_TAB_ID` | 実行中ペインプロセスの公開タブ id。 | +| `HERDR_WORKSPACE_ID` | 実行中ペインプロセスの公開ワークスペース id。 | +| `HERDR_LOG` | ログフィルターを設定する。例: `HERDR_LOG=herdr=debug`。 | +| `HERDR_DISABLE_SOUND` | サウンド通知が有効でも音の再生を無効にする。 | diff --git a/docs/preview/website/src/content/docs/ja/concepts.mdx b/docs/preview/website/src/content/docs/ja/concepts.mdx new file mode 100644 index 00000000..640ba8a4 --- /dev/null +++ b/docs/preview/website/src/content/docs/ja/concepts.mdx @@ -0,0 +1,85 @@ +--- +title: コンセプト +description: Herdr のワークスペース、タブ、ペイン、エージェント、セッション、モードを理解します。 +--- + +Herdr はターミナルワークスペースマネージャーです。実際のターミナルプロセスを動かし続け、その周りに構造を加えます。 + +## ワークスペース + +ワークスペースは最上位のプロジェクトコンテナです。リポジトリ、タスク、調査ごとにひとつのワークスペースを使ってください。 + +ワークスペースはタブとペインを所有します。サイドバーの状態は内部のエージェントから集約されるので、どのプロジェクトが注意を必要としているかが分かります。 + +## タブ + +タブはワークスペース内のレイアウトです。`agents`、`logs`、`server`、`review` のようにビューを分けるために使います。 + +タブは CLI とソケット API からアドレス指定できます。 + +## ペイン + +ペインは実際のターミナルです。Herdr はターミナル出力を描画し、入力をプロセスに送り返し、クライアントのデタッチ後もペインを維持します。 + +ペインは右または下に分割できます。手動での名前変更、CLI からの読み取り、入力の送信、クローズができます。 + +## マウス UI + +Herdr はマウスネイティブです。ペイン、タブ、ワークスペース、エージェントをクリックできます。分割境界のドラッグ、テキスト選択、右クリックメニューも使えます。以下で説明する操作はすべてマウスでも行えます。キーボードバインドは任意のレイヤーです。 + +キーボードのみで操作したい場合や、Herdr にマウス入力をキャプチャさせたくない場合は、マウスキャプチャを無効にしてください: + +```toml +[ui] +mouse_capture = false +``` + +## エージェント + +エージェントは、Herdr がペイン内で認識するプロセスです。Herdr はフォアグラウンドプロセス、スクリーンマニフェスト、任意のインテグレーションからエージェントを検出します。 + +エージェントの状態は次のとおりです: + +| 状態 | 意味 | +| --- | --- | +| `blocked` | エージェントが入力、承認、または判断を必要としている。 | +| `working` | エージェントが実行中。 | +| `done` | エージェントが完了し、まだ確認されていない。 | +| `idle` | エージェントが完了または待機していて、確認済み。 | +| `unknown` | Herdr が状態を確信を持って分類できない。 | + +## セッション + +セッションは永続的な Herdr サーバーの名前空間です。デフォルトの `herdr` コマンドはデフォルトセッションにアタッチします。 + +名前付きセッションは独立したランタイム名前空間です: + +```bash +herdr session list +herdr session attach work +herdr session attach side-project +``` + +まずはワークスペースを使ってください。ペイン、ソケット、永続化されたランタイム状態を完全に分離する必要があるときに名前付きセッションを使います。 + +## クライアントとサーバー + +デフォルトでは、Herdr はバックグラウンドサーバーと、それにアタッチした 1 つ以上のクライアントとして動作します。 + +サーバーはペインとプロセス状態を所有します。クライアントはそのサーバーにアタッチしたターミナル UI です。 + +`ctrl+b q` でクライアントをデタッチします。サーバーとエージェントは動き続けます。 + +セッションを終了してそのペインを停止したい場合は、サーバーを停止します: + +```bash +herdr server stop +``` + +## モード + +Herdr にはターミナルモード、プレフィックスモード、ナビゲートモードがあります。 + +ターミナルモードはフォーカス中のペインにキーを送ります。プレフィックスモードはプレフィックスキーの後に Herdr のアクションをひとつ待ちます。ナビゲートモードは常駐のワークスペースナビゲーション画面です。 + +プレフィックスキー (デフォルト `ctrl+b`) を押してから、新しいタブなら `c`、ワークスペースナビゲーションなら `w` のようにアクションキーを押します。プレフィックスの考え方が初めてなら[キーボード](/ja/docs/keyboard/)を参照してください。 diff --git a/docs/preview/website/src/content/docs/ja/config-reference.mdx b/docs/preview/website/src/content/docs/ja/config-reference.mdx new file mode 100644 index 00000000..7bbb60aa --- /dev/null +++ b/docs/preview/website/src/content/docs/ja/config-reference.mdx @@ -0,0 +1,27 @@ +--- +title: 設定リファレンス +description: Herdr の正規の config.toml キーを、型・デフォルト値・許容値つきで一覧します。 +--- + +import ConfigReference from '../../../components/ConfigReference.astro'; + +Herdr が `config.toml` から読み取る正規キーを、フラットに一覧して絞り込めます。 +ガイド付きの設定手順や各オプションの背景は[設定](/ja/docs/configuration/)を参照してください。 + +コメント付きのデフォルト設定全体はいつでも出力できます: + +```bash +herdr --default-config +``` + +カスタムコマンドバインディング(`[[keys.command]]`)はユーザー定義のテーブルのため、 +ここではキーごとに掲載していません。 +[カスタムコマンドキーバインド](/ja/docs/configuration/#カスタムコマンドキーバインド)を参照してください。 + +各キーの説明は現時点では英語です。 + +<ConfigReference + placeholder="キーを絞り込み(例: sidebar, prefix, sound…)" + noMatches="一致するキーはありません。" + keyCountLabel="キー" +/> diff --git a/docs/preview/website/src/content/docs/ja/configuration.mdx b/docs/preview/website/src/content/docs/ja/configuration.mdx new file mode 100644 index 00000000..e2b5ea5e --- /dev/null +++ b/docs/preview/website/src/content/docs/ja/configuration.mdx @@ -0,0 +1,466 @@ +--- +title: 設定 +description: Herdr のキーバインド、テーマ、サイドバーの挙動、通知、高度なオプションを設定します。 +--- + +Herdr は設定ファイルなしで動作します。キー、テーマ、サイドバーのレイアウト、通知、高度な挙動をカスタマイズしたい場合に追加してください。 + +設定やキーバインドを探していますか?すべてのキー、型、デフォルト値、許容値は[設定リファレンス](/docs/config-reference/)で検索できます。このページでは、セットアップ、一般的なレシピ、リファレンスの一行だけでは説明しきれない設定構造を中心に説明します。 + +## 設定ファイル + +Herdr は次の場所から設定を読み込みます: + +```text +Linux and macOS: ~/.config/herdr/config.toml +Windows: %APPDATA%\herdr\config.toml +``` + +システム上で解決された設定ファイルのパスは、`herdr --help` で確認できます。 + +完全なデフォルト設定を表示します: + +```bash +herdr --default-config +``` + +完全な出発点が欲しい場合は、これを設定として保存してください: + +```bash +herdr --default-config > ~/.config/herdr/config.toml +``` + +設定値が無効な場合、Herdr は安全なデフォルト値にフォールバックし、起動時に警告を表示します。 + +`onboarding` が存在しないか true の場合、Herdr は初回セットアップを表示します。オンボーディングから先へ進むと `onboarding = false` が書き込まれ、設定のインテグレーションタブが開きます。セットアップ後にこのフローをスキップしたい場合に設定してください。 + +```toml +onboarding = false +``` + +## 設定のリロード + +`config.toml` を編集した後、実行中のサーバーをリロードします: + +```bash +herdr server reload-config +``` + +Herdr のグローバルメニューを開いて `reload config` を選ぶこともできます。 + +リロードは、ペインを再起動せずにほとんどの UI 設定を適用します。起動時のみの設定には引き続き再起動が必要です。 + +## ターミナルのデフォルト + +新しく作成される対話型ペインに Herdr が使う実行ファイルを設定します: + +```toml +[terminal] +default_shell = "nu" +``` + +未設定または空の場合、Herdr は `$SHELL`、次に Unix では `/bin/sh`、Windows では PowerShell を使います。これは実行ファイル名またはパスであり、シェルのコマンドラインではありません。既存のペインは再作成されるまで現在のシェルを保ちます。カスタムコマンドキーバインドの文字列は、Unix ではペインコマンドに `/bin/sh -c`、デタッチコマンドに `/bin/sh -lc` を通して実行され、Windows では `cmd.exe /d /c` を通して実行されます。 + +新しく作成される対話型ペインのシェルの起動方法を設定します: + +```toml +[terminal] +shell_mode = "auto" +``` + +`shell_mode = "auto"` は macOS でログインシェルを起動するため、`/usr/libexec/path_helper` のようなログイン時のみの PATH 設定や Homebrew のシェル初期化が新しいペインで実行されます。他のプラットフォームでは、既存の非ログインシェルの挙動を保ちます。ログインシェルでの起動を強制するには `"login"` を、無効にするには `"non_login"` を使ってください。コマンドペイン、デタッチされたカスタムコマンドキーバインド、明示的な argv 起動は、既存のコマンド実行経路を保ちます。 + +新しいペイン、タブ、ワークスペースの作業ディレクトリポリシーを設定します: + +```toml +[terminal] +new_cwd = "follow" +``` + +`new_cwd = "follow"` はデフォルトの挙動を保ち、元のペインまたはワークスペースを継承します。元のワークスペースがない場合、Herdr は `$HOME` で起動します。常に `$HOME` で起動するには `"home"` を、Herdr のプロセスディレクトリを使うには `"current"` を、または `"~/Projects"` のような固定パスを使ってください。CLI またはソケット API から明示的に指定された `--cwd` の値は引き続き優先されます。 + +## Worktree + +サイドバーから作成される Git worktree チェックアウトに Herdr が使うルートディレクトリを設定します: + +```toml +[worktrees] +directory = "~/.herdr/worktrees" +``` + +Herdr は `<directory>/<repo>/<branch-slug>` の下にチェックアウトを作成します。隣接ディレクトリ方式のチェックアウトには、`~/Projects/herdr-worktrees` のようなディレクトリを設定してください。相対値は、アプリが設定を適用するときに絶対パスへ解決されます。 + +worktree アクションは Git ワークスペースの行から使えます。`New worktree` はチェックアウトを作成し、入力したブランチがすでに存在する場合は既存のローカルブランチをチェックアウトし、存在しない場合はブランチを作成します。その後、新しい Herdr ワークスペースとして開き、元のワークスペースの下にグループ化します。`Open worktree...` はそのリポジトリにある既存の Git worktree チェックアウトを一覧表示します。すでに開いているチェックアウトを選ぶとそこへフォーカスし、閉じているチェックアウトを選ぶと同じグループで開きます。 + +グループ化された worktree も通常の Herdr ワークスペースと同様に動作します。フォーカス、名前変更、クローズができ、それぞれ独自のタブとペインを持ちます。親の行は元のワークスペースです。親の行を閉じると Herdr のグループ全体が閉じますが、チェックアウトのフォルダーやブランチは削除されません。 + +worktree チェックアウトの削除は明示的に行います。グループ化された子ワークスペースで `Delete worktree checkout...` を使うと `git worktree remove` が実行されます。Herdr はまず Git に安全な削除を要求します。チェックアウトに変更済みまたは未追跡のファイルがあり Git が拒否した場合、Herdr は強制削除を実行する前にもう一度確認します。ブランチは削除されません。 + +## リモートアタッチ + +リモートアタッチは、デフォルトで一時的なキープアライブと接続再利用のフォールバックを使って SSH 接続を管理します。 + +```toml +[remote] +manage_ssh_config = true +``` + +有効な場合、`herdr --remote` は最初に `~/.ssh/config` と `/etc/ssh/ssh_config` を取り込み、その後にフォールバックの `ServerAliveInterval` と `ServerAliveCountMax` の値を加えた、プライベートな一時 SSH 設定を書き込みます。ユーザー自身のキープアライブ設定が優先されます。Herdr は、最初に認証した接続を再利用するため、アタッチごとにプライベートな OpenSSH コントロールソケットも使います。Herdr が生成した設定やコントロールソケットを使わず、通常の `ssh` でリモートアタッチを実行するには `manage_ssh_config = false` を設定してください。 + +## キーバインド + +プレフィックスの導入ガイドと検証済みのプレフィックスなし構成については、[キーボード](/docs/keyboard/)を参照してください。 + +Herdr には tmux に似たプレフィックスモードがあります。デフォルトのプレフィックスは `ctrl+b` です。キーバインド文字列は明示的です。`prefix+n` は設定されたプレフィックスを押してから `n` を押すという意味で、`ctrl+alt+n` はターミナルモードの直接ショートカットです。 + +小さなキーバインドの上書きは次のようになります: + +```toml +[keys] +prefix = "ctrl+b" +goto = "prefix+g" +new_tab = "prefix+c" +next_tab = "prefix+n" +previous_tab = "prefix+p" +focus_pane_left = "prefix+h" +navigate_workspace_down = "j" +navigate_pane_down = "ctrl+j" +split_horizontal = "prefix+minus" +``` + +デフォルトのキーマップはプレフィックス優先なので、Herdr がシェル、エディタ、tmux、ターミナルアプリから入力を奪うことはありません。[設定リファレンス](/docs/config-reference/)で `keys.` を検索すると、すべてのアクションとデフォルトのバインドを確認できます。`prefix+?` で開くアプリ内ヘルプパネルには、現在有効なバインドが表示されます。 + +1 つのアクションに複数のショートカットが必要な場合、バインドを配列にすることもできます: + +```toml +[keys] +next_tab = ["prefix+n", "ctrl+alt+]"] +``` + +任意のアクションはデフォルトでは未設定です。プレフィックスモードの挙動には `prefix+` でバインドし、意図的に直接ショートカットにする場合は明示的な修飾キーコードを使ってください。 + +キー文字列には、通常のキー、`ctrl+a`、`shift+n`、`alt+1`、`cmd+k` のような修飾キーの組み合わせ、`enter`、`tab`、`esc`、`left`、`right`、`up`、`down` のような特殊キーが使えます。`minus`、`comma`、`ampersand`、`plus`、`backtick` のような名前付き記号も使えます。`n` のような通常の印字可能キーの直接バインドは入力を妨げるため危険です。意図的に直接バインドするのでなければ `prefix+n` を使ってください。`navigate_workspace_*` と `navigate_pane_*` のフィールドはナビゲートモード専用で、`j` や `k` のような通常のキーを使えます。これらには `prefix+`、`esc`、`enter`、`tab`、`shift+tab`、`left`、`right`、修飾なしの `1` から `9` は使えません。左右の矢印キーは、左ペインと右ペインへのナビゲーションの恒久的なエイリアスです。これらのナビゲートモードショートカットは、`focus_pane_down = "prefix+j"` のような一般アクションのバインドから独立しています。両方に同じキーが使われている場合、ナビゲートモードが開いている間はナビゲートモードのショートカットが優先されます。Alt、Cmd/Super、修飾キー付き記号は、ターミナルと tmux の設定に依存します。 + +古いカスタムキーバインドを使っていて新しいデフォルトが欲しい場合は、`herdr config reset-keys` を実行してください。Herdr は `config.toml` をバックアップし、`[keys]` と `[[keys.command]]` を削除し、再起動または `herdr server reload-config` の後に組み込みの v2 デフォルトを使います。 + +## インデックス付きジャンプ + +インデックス付きキーバインドは、通常のキーバインドフィールドで `1..9` を使います: + +```toml +[keys] +switch_tab = "prefix+1..9" +switch_workspace = "prefix+shift+1..9" +focus_agent = "prefix+alt+1..9" +``` + +レガシーな `[keys.indexed]` テーブルは互換性のために引き続きパースされますが、新しい設定では明示的なアクションフィールドを使ってください。 + +## カスタムコマンドキーバインド + +カスタムコマンドも同じキーバインド構文を使います。 + +```toml +[[keys.command]] +key = "prefix+alt+g" +type = "popup" +command = "lazygit" +description = "run lazygit" +width = "80%" +height = "80%" +``` + +`type = "popup"` は、タブレイアウトを変更せずにセッションモーダルなポップアップを開きます。 +ポップアップは、コマンドが終了するまで Escape を含むすべてのターミナル入力を受け取ります。 +`width` と `height` は任意です。省略するとデフォルトの半分のサイズになり、数値ならターミナルセル数、`"80%"` のような文字列ならターミナル領域に対する割合を指定できます。 +寸法にはポップアップの枠が含まれ、小さすぎる値は最小サイズに制限されます。 +ポップアップコマンドは `HERDR_PANE_ID` を受け取りません。背後のタイルペインには `HERDR_ACTIVE_PANE_ID` を使ってください。 + +Unix と macOS では、分割やタブを追加せずに一時的なターミナルを開くこともできます: + +```toml +[[keys.command]] +key = "prefix+t" +type = "popup" +command = "exec \"${SHELL:-sh}\"" +description = "open scratch terminal" +width = "80%" +height = "80%" +``` + +Windows では、代わりに `command = "powershell.exe -NoLogo"` のようなシェルコマンドを使ってください。 +シェルを終了するとポップアップが閉じ、タイル表示に戻ります。 + +`type = "pane"` は一時的なペインを開き、コマンドの終了時に閉じます。 + +`type = "shell"` はバックグラウンドでデタッチ実行します。 + +`type = "plugin_action"` はインストール済みプラグインのアクション id を呼び出します。アクション id がグローバルに一意でない場合は修飾 id を使ってください: + +```toml +[[keys.command]] +key = "prefix+l" +type = "plugin_action" +command = "example.layout.apply" +description = "apply layout" +``` + +任意で `description` を指定できます。指定すると、キーバインドヘルプパネル(`prefix+?` で開く)に、デフォルトの `'custom command'` ラベルの代わりに表示されます。 + +カスタムコマンドは、利用可能な場合に `HERDR_SOCKET_PATH`、`HERDR_BIN_PATH`、`HERDR_ACTIVE_WORKSPACE_ID`、`HERDR_ACTIVE_TAB_ID`、`HERDR_ACTIVE_PANE_ID`、`HERDR_ACTIVE_PANE_CWD` を受け取ります。Herdr が検出できる場合、シェルコマンドはフォーカス中のペインの作業ディレクトリから実行されます。 + +Windows では、カスタムコマンド文字列に `cmd.exe /d /c` が使われるため、環境変数には `%HERDR_BIN_PATH%` 構文を使います。PowerShell 構文を実行するには、たとえば `powershell.exe -NoProfile -Command "..."` のように明示的に呼び出してください。 + +## テーマ + +組み込みテーマを選びます: + +```toml +[theme] +name = "catppuccin" +``` + +すべての組み込みテーマは、[設定リファレンス](/docs/config-reference/)で `theme.name` を検索してください。Herdr の UI 色をホストターミナルの ANSI パレットに従わせたいときは `terminal` を使ってください。 + +ホストターミナルがライト/ダークの外観変更を報告したときに、Herdr が自身の UI テーマを切り替えるようにするには、テーマの自動切り替えを有効にします: + +```toml +[theme] +name = "catppuccin" +auto_switch = true +light_name = "catppuccin-latte" +dark_name = "catppuccin" +``` + +`auto_switch` のデフォルトは `false` なので、既存のテーマ設定は手動の挙動を保ちます。`light_name` または `dark_name` を省略すると、設定された `name` に対応する組み込みの姉妹テーマが存在する場合はそれを使います。たとえば `tokyo-night`/`tokyo-night-day` や `gruvbox`/`gruvbox-light` です。設定画面でテーマを手動選択すると `auto_switch` は無効になります。 + +個々の色を上書きできます: + +```toml +[theme.custom] +panel_bg = "reset" +accent = "#a6e3a1" +green = "#a6e3a1" +blue = "#89b4fa" +red = "#f38ba8" +yellow = "#f9e2af" +``` + +色の値には、hex、名前付きの色、`rgb(r,g,b)`、または `reset`、`default`、`none`、`transparent` のようなリセットエイリアスが使えます。 + +## UI とサイドバー + +サイドバーは Herdr のメインダッシュボードです。サイズ、折りたたみモード、Agent パネルの並び順、マウスの挙動、ペインの境界線、その他の表示設定については、[設定リファレンス](/docs/config-reference/)で `ui.` を検索してください。 + +### サイドバーの行レイアウト + +展開されたデスクトップサイドバーでは、`rows` の各内側の配列が 1 行として描画されます。完全なデフォルトレイアウトは次のとおりです: + +```toml +[ui.sidebar.agents] +row_gap = 0 +rows = [ + ["state_icon", "workspace", "tab"], + ["agent"], +] + +[ui.sidebar.spaces] +row_gap = 0 +rows = [ + ["state_icon", "workspace"], + ["branch", "git_status"], +] +``` + +Agent の行では、次の組み込みトークンを使えます: + +- `state_icon` — エージェントの意味的な状態を示す色付きアイコン。 +- `state_text` — `idle`、`working`、`blocked`、`done`、`unknown`。報告された表示ラベルがある場合はそれも含みます。 +- `workspace` — ワークスペース名。 +- `tab` — 利用可能な場合はタブ名。 +- `pane` — 利用可能な場合はペイン名。 +- `agent` — 検出または報告されたエージェントの表示名。 +- `terminal_title` — 安全性のための正規化後の最新の OSC 0/2 ターミナルタイトル。 +- `terminal_title_stripped` — 認識された先頭のアクティビティまたはスピナーのグリフ 1 つと、それに続く空白を除去したターミナルタイトル。 +- `$name` — `name` という名前のカスタムペインメタデータ。 + +Space の行では、次の組み込みトークンを使えます: + +- `state_icon` — Space 内で集約されたエージェント状態を示す色付きアイコン。 +- `state_text` — Space 内で集約されたエージェント状態のテキスト。 +- `workspace` — ワークスペース名。 +- `branch` — 利用可能な場合は Git ブランチ。 +- `git_status` — 0 でない場合は Git の ahead と behind の件数。 +- `$name` — `name` という名前のカスタムワークスペースメタデータ。 + +トークンは設定された順序で描画されます。Herdr は通常、隣接する値を ` · ` で区切り、`state_icon` の後には空白を 1 つ入れます。値がない場合、その値と区切りは表示されません。すべてのトークンに値がない行は表示されません。各レイアウトは最大 16 行、各行は最大 16 トークンです。 + +トークンエントリにはインラインのスタイルテーブルも使えます: + +```toml +[ui.sidebar.agents] +rows = [ + ["state_icon", { token = "workspace", bold = false }, "tab"], + [{ token = "$summary", fg = "#89b4fa", bold = true, dim = false }], +] +``` + +`fg` は厳密な `#RGB` または `#RRGGBB`、`bold` と `dim` は真偽値です。省略したフィールドはコンテキストのスタイルを保持し、明示的な `false` はその修飾を解除します。スタイルは 1 つの出現箇所だけに適用されます。`fg` を指定した `git_status` では、既定の緑/赤ではなく ahead と behind の両方が同じ色になります。区切りと行の背景は変更されません。 + +`row_gap` は、Agent パネルと Space パネルごとに、エントリ間の空白行数を指定します。デフォルトは `0` で、エントリを詰めて表示します。以前の間隔に戻すには `1` に設定します。`rows` で定義したコンテンツ行の間隔には影響しません。連続するインデントされた worktree の子は、1 つの Space グループとして詰めて表示されます。 + +既知のエージェントについて Agent の完全なレイアウトを上書きするには、`rows_by_agent` を使います: + +```toml +[ui.sidebar.agents] +rows = [ + ["state_icon", "agent", "state_text"], + ["workspace", "tab"], +] + +[ui.sidebar.agents.rows_by_agent] +claude = [ + ["state_icon", "agent", "state_text"], + ["terminal_title_stripped"], + ["workspace", "tab"], +] +``` + +上書きは `rows` を置き換えるもので、拡張するものではありません。上書きのキーには、`claude`、`codex`、`pi` のような大文字と小文字を区別する正規エージェント ID を使います。`claude-code` のような検出エイリアスは使えません。カスタム報告されたエージェントを含め、上書きがないエージェントには `rows` が使われます。 + +カスタム `$name` トークンは動的な値であり、リテラルテキストではありません。トークンをレイアウトに追加してから、スクリプトまたはプラグインで値を報告します: + +```toml +[ui.sidebar.agents] +rows = [ + ["state_icon", "agent", "$model"], + ["$summary"], + ["workspace", "tab"], +] +``` + +```bash +herdr pane report-metadata <pane_id> \ + --source my-agent-hook \ + --token model=opus \ + --token summary="reviewing authentication" +``` + +カスタム Space トークンには、同じ方法で `herdr workspace report-metadata` を使います。報告されていないカスタムトークンは表示されません。 + +メタデータの報告元が提供できるのは値だけで、スタイルはローカルのサイドバー設定で管理します。上限、クリア、シーケンス、有効期限については、[CLI リファレンス: report metadata](/docs/cli-reference/#panes)を参照してください。 + +サイドバーの行設定は、展開されたデスクトップサイドバーにだけ適用されます。折りたたみ表示とモバイル表示はコンパクトなレイアウトを保ちます。 + +## 通知 + +Herdr は、バックグラウンドのエージェントが完了したり入力を必要としたりしたときに通知できます: + +```toml +[ui.toast] +delivery = "herdr" +delay_seconds = 1 + +[ui.toast.herdr] +position = "bottom-right" +``` + +アプリ内トーストには `herdr`、SSH 越しでも使いやすい外側のターミナル通知には `terminal`、ローカル OS の通知サービスには `system`、ポップアップを無効にするには `off` を選びます。Herdr はアクティブなタブのポップアップを抑制します。位置、遅延の挙動、クリップボードのフィードバック設定については、[設定リファレンス](/docs/config-reference/)で `ui.toast` を検索してください。 + +## サウンド + +サウンド通知はローカルの Herdr クライアントで再生されます。カスタムサウンドは mp3 ファイルでなければなりません。相対パスは設定ファイルのディレクトリから解決されます。 + +```toml +[ui.sound] +path = "sounds/notification.mp3" +done_path = "sounds/done.mp3" +request_path = "sounds/request.mp3" +``` + +`path` はすべてのサウンド通知に 1 つの音を設定します。`done_path` と `request_path` は、完了音と入力要求音だけを上書きします。 + +エージェント別のサウンド上書きには `default`、`on`、`off` を指定できます。キーには `claude`、`codex`、`devin`、`droid` のような検出されたエージェントラベルを使います。Droid はデフォルトでミュートされています。 + +```toml +[ui.sound.agents] +droid = "off" +claude = "on" +``` + +## 高度な設定 + +スクロールバックの上限、ネストされた起動、その他の高度な設定や実験的設定については、[設定リファレンス](/docs/config-reference/)を検索してください。ペイン画面履歴を有効にする前に、[セッション状態と復元](/docs/session-state/)を参照してください。このガイドでは、ペインの内容を保存する場合のセキュリティ上のトレードオフを説明しています。 + +## Kitty graphics + +アタッチされたローカルクライアントでの Kitty graphics の描画は実験的機能で、デフォルトでは無効です: + +```toml +[experimental] +kitty_graphics = true +``` + +ターミナルの画像表示をテストする場合にのみ有効にしてください。 + +## Agent セッション復元 + +Herdr はデフォルトで、サーバーの再起動後に対応する Agent の会話を再開します: + +```toml +[session] +resume_agents_on_restore = true +``` + +公式インテグレーションからの有効なネイティブセッション参照を持つペインだけが再開できます。その他のペインは通常のシェルとして復元されます。対応する Agent と永続化の挙動については、[セッション状態と復元](/docs/session-state/)を参照してください。 + +## IME カーソルトラッキング + +macOS では、ハードウェアカーソルを隠す AI Agent TUI によって、ネイティブ入力メソッドの変換候補ウィンドウがフォーカス中のペインを追跡できなくなることがあります。これらのペインでカーソルアンカーを表示するには、次のように設定します: + +```toml +[experimental] +reveal_hidden_cursor_for_cjk_ime = true +cjk_ime_agents = ["claude", "pi", "codex"] +``` + +`cjk_ime_agents` で対象を限定すると、関係のないアプリケーションに余分なハードウェアカーソルが表示されるのを避けられます。使用できる Agent 名とカーソル形状については、[設定リファレンス](/docs/config-reference/)でこれらのキーを検索してください。 + +## プレフィックス入力ソースの切り替え + +macOS と Windows では、プレフィックスコマンドとプレフィックスから起動したモードがアクティブな間、Herdr が ASCII 対応の入力ソースへ一時的に切り替えるようにできます: + +```toml +[experimental] +switch_ascii_input_source_in_prefix = true +``` + +macOS ではその時点の ASCII 対応キーボードレイアウトへ切り替えます。Windows では IME を英語(ASCII)入力へ切り替えます。ターミナル入力に戻るかテキストフィールドに入ると、Herdr は以前の入力ソースを復元します。この設定は他のプラットフォームには影響しません。 + +:::caution[Windows では韓国語 IME のみ対応] +Windows でのサポートは現時点で韓国語 IME に限定されています。それ以外の言語の IME を使用している場合、この設定は入力ソースを変更しません。 +::: + +## 環境変数 + +| 変数 | 目的 | +| --- | --- | +| `HERDR_CONFIG_PATH` | 設定ファイルのパスを上書きする。 | +| `HERDR_SESSION` | CLI コマンドで名前付きセッションを選択する。 | +| `HERDR_SOCKET_PATH` | 低レベルなソケットパスを上書きする。 | +| `HERDR_LOG` | ログフィルタリングを設定する。例: `HERDR_LOG=herdr=debug`。 | +| `HERDR_DISABLE_SOUND` | `[ui.sound] enabled = true` でも音の再生を無効にする。 | + +## ログ + +ログは、起動時の警告、インテグレーションの状態、ソケット API の挙動を診断するときに役立ちます。 + +主なログファイル: + +```text +~/.config/herdr/herdr.log +~/.config/herdr/herdr-client.log +~/.config/herdr/herdr-server.log +``` + +ログは自動的にローテーションされます。問題を報告するときは、現在のログとローテーションされた関連ファイルを含めてください。 diff --git a/docs/preview/website/src/content/docs/ja/how-to-work.mdx b/docs/preview/website/src/content/docs/ja/how-to-work.mdx new file mode 100644 index 00000000..0365df0b --- /dev/null +++ b/docs/preview/website/src/content/docs/ja/how-to-work.mdx @@ -0,0 +1,103 @@ +--- +title: Herdr での作業の進め方 +description: Herdr をローカルで、SSH 内で、またはリモートアタッチで使います。 +--- + +Herdr は作業がある場所で動かし、どこからでもアタッチします。 + +Herdr はバックグラウンドのセッションサーバーと、1 つ以上のターミナルクライアントで構成されます。ペインはサーバー内で動き続けます。クライアントはアタッチ、デタッチし、セッションを描画します。 + +## ローカルでの作業 + +プロジェクトディレクトリから Herdr を起動します: + +```bash +herdr +``` + +Herdr はローカルのバックグラウンドセッションを自動的に起動するか、それにアタッチします。ソケットを管理する必要はありません。ペインの中でシェル、サーバー、テスト、エージェントを普段どおり実行してください。 + +`ctrl+b q` でクライアントをデタッチします。ペインは動き続けます。 + +あとで再アタッチします: + +```bash +herdr +``` + +セッションを終了してペインを停止したい場合は、サーバーを停止します: + +```bash +herdr server stop +``` + +## 通常の SSH 越しのリモート作業 + +コードと認証情報のあるマシンに SSH して、そこで Herdr を実行します: + +```bash +ssh you@server +herdr +``` + +これはターミナルマルチプレクサと同じように動作します。シェルはリモート、Herdr サーバーもリモート、エージェントとペインはリモートマシン上で動きます。`ctrl+b q` でデタッチして切断し、あとで SSH し直して再び `herdr` を実行してください。 + +すでに SSH シェルの中で作業している場合、スマートフォンやタブレットの SSH クライアントを使っている場合、あるいは最もシンプルな構成にしたい場合はこの方法を使ってください。 + +## スマートフォンから作業する + +Herdr のモバイルアプリや Web ダッシュボードは不要です。スマートフォンに任意の SSH クライアントを入れ、エージェントが動いているマシンに接続して、そこで Herdr を起動するだけです: + +```bash +ssh you@server +herdr +``` + +同じ永続 Herdr セッションがスマートフォンのターミナルに開きます。TUI は狭い画面に適応するので、SSH から離れることなくエージェントの確認、ワークスペースの切り替え、ペインのチェックができます。 + +iPhone では [moshi](https://getmoshi.app/) のようなアプリがよく動作します。 + +<div class="mobile-doc-shots"> + <figure> + <img src="/assets/mobile-agent-session-v2.jpeg" alt="スマートフォンで SSH 越しに表示した Herdr のエージェントセッション" loading="lazy" /> + <figcaption>SSH 越しのエージェントセッション</figcaption> + </figure> + <figure> + <img src="/assets/mobile-switch-menu-v2.jpeg" alt="スマートフォンで表示した Herdr のレスポンシブ切り替えメニュー" loading="lazy" /> + <figcaption>レスポンシブな切り替えメニュー</figcaption> + </figure> +</div> + +## ローカルターミナルからのリモート作業 + +先にシェルを開かずに SSH 越しでアタッチします: + +```bash +herdr --remote workbox +herdr --remote ssh://you@server:2222 +``` + +ローカルの Herdr はシンクライアントとして動作します。SSH 越しに接続し、リモートの Herdr サーバーを起動またはアタッチして、UI をローカルターミナルにストリーミングします。 + +リモートセッションをローカルのように感じたいときはこの方法を使ってください。クライアントは手元のマシンで動くので、画像クリップボードの貼り付けのようなローカルデスクトップ機能をリモートサーバーにブリッジできます。先に SSH してサーバー上で `herdr` を実行した場合、Herdr は完全にそのサーバー上で動くため、ローカルデスクトップのクリップボードは読めません。 + +繰り返し接続する相手は SSH config に登録しておきましょう: + +```text +Host workbox + HostName server.example.com + User you + Port 2222 +``` + +その後はこれでアタッチできます: + +```bash +herdr --remote workbox +``` + +## どの方法を使うか + +ローカル作業には `herdr` を使います。リモートシェル上で Herdr を tmux のように使いたいときや、スマートフォンの SSH クライアントを使っているときは `ssh you@server` してから `herdr` を使います。リモートセッション用のローカルシンクライアントが欲しいとき (ローカルクリップボードの画像貼り付けブリッジを含む) は `herdr --remote <host>` を使います。 + +リモートブートストラップの詳細、名前付きリモートセッション、カスタムバイナリ、ダイレクトターミナルアタッチ、`--no-session` については[永続化とリモートアクセス](/ja/docs/persistence-remote/)を参照してください。 diff --git a/docs/preview/website/src/content/docs/ja/index.mdx b/docs/preview/website/src/content/docs/ja/index.mdx new file mode 100644 index 00000000..ce26d5fb --- /dev/null +++ b/docs/preview/website/src/content/docs/ja/index.mdx @@ -0,0 +1,77 @@ +--- +title: Herdr ドキュメント +description: AI コーディングエージェントのためのターミナルワークスペースマネージャー。 +template: splash +hero: + tagline: "Herdr のインストール、学習、設定。あなたに合ったパスから始めましょう — マルチプレクサの経験は不要です。" + image: + file: ../../../../public/assets/logo.svg + actions: + - text: Herdr をインストール + link: /ja/docs/install/ + - text: クイックスタート + link: /ja/docs/quick-start/ + variant: secondary +--- + +import { Card, CardGrid } from '@astrojs/starlight/components'; + +## パスを選ぶ + +<CardGrid> + <Card title="ターミナルマルチプレクサは初めて?"> + ショートカットを覚えなくても始められます。Herdr はマウスファーストです。ペインをクリックし、境界をドラッグし、右クリックメニューから分割や切り替えができます。 + + [クイックスタート →](/ja/docs/quick-start/) + </Card> + <Card title="tmux や zellij から乗り換え?"> + モデルはすでにご存じのとおりです。プレフィックスは `ctrl+b`、ペインは永続化され、デタッチと再アタッチは期待どおりに動作します。 + + [コンセプト →](/ja/docs/concepts/) · [キーバインド →](/ja/docs/configuration/#キーバインド) + </Card> +</CardGrid> + +## またはエージェントに案内してもらう + +すでに AI コーディングエージェントを使っていますか? オンボーディングはエージェントに任せましょう。このプロンプトを貼り付けてください: + +```text +Help me understand and set up Herdr. Read https://herdr.dev/agent-guide.md first, then walk me through it step by step. +``` + +このガイドはエージェントに Herdr のコンセプト、セットアップ、設定、よくある問題の解決方法を教えるので、エージェントの回答が即興ではなく正確なままになります。 + +## コアガイド + +<CardGrid> + <Card title="エージェント"> + 対応エージェント、検出の挙動、インテグレーション、カスタムラベル、ダイレクトアタッチについて。 + + [エージェントを理解する →](/ja/docs/agents/) + </Card> + <Card title="セッション状態"> + デタッチ、再起動時の復元、ペイン履歴のリプレイ、エージェントネイティブの resume、ライブハンドオフについて。 + + [状態管理の方式を比較する →](/ja/docs/session-state/) + </Card> + <Card title="設定"> + キーバインド、テーマ、サイドバーの挙動、通知、スクロールバック、高度なオプションを設定します。 + + [Herdr を設定する →](/ja/docs/configuration/) + </Card> + <Card title="API"> + CLI とローカルソケット API を通じて、スクリプト・ツール・エージェントから Herdr を制御します。 + + [API ガイドを読む →](/ja/docs/socket-api/) + </Card> + <Card title="プラグイン"> + マニフェストのアクションとイベントフックを備えた、ローカル実行型ワークフロープラグインを作成します。 + + [プラグインを書く →](/ja/docs/plugins/) + </Card> + <Card title="マーケットプレイス"> + 今すぐ GitHub からプラグインを共有し、リポジトリにトピックを付けてマーケットプレイス公開時に掲載されるようにしましょう。 + + [プラグインを公開する →](/ja/docs/marketplace/) + </Card> +</CardGrid> diff --git a/docs/preview/website/src/content/docs/ja/install.mdx b/docs/preview/website/src/content/docs/ja/install.mdx new file mode 100644 index 00000000..de37c9ca --- /dev/null +++ b/docs/preview/website/src/content/docs/ja/install.mdx @@ -0,0 +1,153 @@ +--- +title: Herdr のインストール +description: Linux、macOS、Windows ベータでの Herdr のインストール、アップデート、動作確認。 +--- + +Herdr は Linux と macOS 向けに安定版バイナリを提供しています。ネイティブ Windows 対応はプレビュー限定のベータです。 + +## インストール + +Linux または macOS では次を実行します: + +```bash +curl -fsSL https://herdr.dev/install.sh | sh +``` + +Windows プレビューベータでは、プレビューチャンネルをインストールします: + +```powershell +powershell -ExecutionPolicy Bypass -c "irm https://herdr.dev/install.ps1 | iex" +``` + +インストーラーはプラットフォームに合ったリリースバイナリをダウンロードして PATH 上に配置します。Windows では Herdr が設定を変更せずにデフォルトでプレビューを使います。インストーラーはバージョン付きインストールフォルダーを使い、`current` ジャンクションを更新します。そのため、アップデート時に実行中の `herdr.exe` を上書きする必要がありません。 + +## Homebrew でインストール + +すでに Homebrew を使っている場合: + +```bash +brew install herdr +``` + +## mise でインストール + +すでに mise を使っている場合: + +```bash +mise use -g herdr +``` + +mise が `herdr not found in mise tool registry` と報告する場合は、mise をアップデートして再試行してください。古い mise は Herdr のレジストリ登録より前のバージョンです。一時的な回避策として `mise use -g github:ogulcancelik/herdr` も使えます。 + +## Nix でインストール + +すでに Nix を使っている場合、Herdr はソースからビルドする flake を提供しています: + +```bash +nix run github:ogulcancelik/herdr/v0.x.y +nix build github:ogulcancelik/herdr/v0.x.y +nix profile install github:ogulcancelik/herdr/v0.x.y +``` + +`v0.x.y` は最新のリリースタグに置き換えてください。タグを省略すると `master` を追跡できますが、通常のインストールにはリリースタグを推奨します。 + +flake は開発用シェルも公開しています: + +```bash +nix develop github:ogulcancelik/herdr +``` + +アップデートは、Herdr のインストールに使ったのと同じ Nix ワークフローで行います。プロファイルインストールの場合は、プロファイルのエントリを一覧して Herdr のエントリをアップグレードします: + +```bash +nix profile list +nix profile upgrade <index-or-name> +``` + +Herdr が自分の flake の input になっている場合は、その input を更新してシステム、Home Manager、または開発環境を再ビルドします: + +```bash +nix flake update herdr +``` + +## 手動でダウンロード + +[GitHub releases](https://github.com/ogulcancelik/herdr/releases) からバイナリをダウンロードすることもできます。 + +システムに合ったアセットを選んでください: + +| システム | アセット | +| --- | --- | +| Linux x86_64 | `herdr-linux-x86_64` | +| Linux aarch64 | `herdr-linux-aarch64` | +| macOS Intel | `herdr-macos-x86_64` | +| macOS Apple silicon | `herdr-macos-aarch64` | + +Linux または macOS では、実行可能にして PATH 上のどこかに移動します。 + +```bash +chmod +x herdr-linux-x86_64 +mv herdr-linux-x86_64 ~/.local/bin/herdr +``` + +### Windows ベータのダウンロード + +ネイティブ Windows 対応がベータの間、Windows バイナリはプレビューリリースでのみ公開されます。通常のテストには上記のプレビューインストーラーを使うか、プレビューの GitHub プレリリースから Windows アセットをダウンロードしてください: + +| システム | アセット | +| --- | --- | +| Windows x86_64 ベータ | `herdr-windows-x86_64.zip` | + +アーカイブには `herdr.exe` とアプリローカルの ConPTY ランタイムが含まれます。展開したディレクトリをまとめて保持し、`herdr.exe` だけをコピーしないでください。 + +## 動作確認 + +Herdr を起動します: + +```bash +herdr +``` + +シェルが `herdr` を見つけられない場合は、ターミナルを再起動するか、インストール先ディレクトリが PATH に含まれているか確認してください。 + +## アップデート + +Herdr は新しいリリースをチェックし、アプリ内で通知します。手動でアップデートすることもできます: + +```bash +herdr update +``` + +`herdr update` は Herdr 自身のインストーラーで管理されているインストール向けです。Homebrew、mise、Nix のインストールは、それぞれのパッケージマネージャーでアップデートしてください。 + +Linux と macOS では、Herdr はデフォルトで安定版アップデートチャンネルを使います。`master` からのプレビュービルドにオプトインするには、チャンネルを設定します: + +```bash +herdr channel set preview +``` + +Linux と macOS の直接インストールを安定版に戻すのも同じ方法です: + +```bash +herdr channel set stable +``` + +直接インストールの場合、チャンネルの変更はそのチャンネルをチェックして最新バイナリをインストールします。そのアップデートが失敗した場合は、`herdr update` を実行して設定済みチャンネルから再試行してください。 + +プレビュービルドは、現在の開発ブランチから手動で公開される GitHub プレリリースです。次の安定版リリースより前に修正が欲しいときに便利ですが、リグレッションの可能性があります。Homebrew、mise、Nix のインストールはプレビューチャンネルを使いません。 + +Windows ベータビルドは今のところプレビュー限定です。安定版の Windows リリースが提供されるまで、Windows では `herdr channel set stable` は拒否されます。 + +デフォルトでは、`herdr update` は新しいバイナリをインストールし、互換性のある実行中セッションには手を付けません。アップデートが Herdr のクライアント/サーバープロトコルを変更する場合、Herdr はインストール後に古いサーバーを停止するか尋ねます。新しいバージョンを使うには古いサーバーを停止してください。停止するとペインのプロセスは終了します。デフォルトセッションでは `herdr server stop` を実行してから再度 `herdr` を実行します。名前付きセッションでは `herdr session stop <name>` を実行してから再度 `herdr session attach <name>` を実行します。 + +対応する実行中セッションで実験的なライブサーバーハンドオフにオプトインするには: + +```bash +herdr update --handoff +``` + +ライブハンドオフは Homebrew、mise、Nix のパッケージマネージャー経由のアップデートには適用されません。それらのインストールではパッケージマネージャーでアップデートし、新しいサーバーを使う準備ができたらその Herdr セッションを再起動してください。実行中のセッションがまだ古いサーバーを使っている場合は、`herdr server stop` または `herdr session stop <name>` で停止してから、再度 Herdr を実行します。 + +## 動作要件 + +Herdr の安定版リリースは Linux と macOS をサポートします。ネイティブ Windows ビルドはプレビュー限定のベータリリースです。サポートされるワークフローと既知の制限は [Windows ベータ](/ja/docs/windows-beta/)を参照してください。 diff --git a/docs/preview/website/src/content/docs/ja/integrations.mdx b/docs/preview/website/src/content/docs/ja/integrations.mdx new file mode 100644 index 00000000..fe4e41b1 --- /dev/null +++ b/docs/preview/website/src/content/docs/ja/integrations.mdx @@ -0,0 +1,299 @@ +--- +title: インテグレーション +description: Pi、OMP、Claude Code、Codex、GitHub Copilot CLI、Devin CLI、Droid、Kimi Code CLI、OpenCode、Kilo Code CLI、Hermes Agent、Qoder CLI、Cursor Agent CLI、MastraCode 向けの Herdr インテグレーションをインストールします。 +--- + +Herdr は対応エージェントを自動的に検出します。公式インテグレーションは、復元のためのネイティブセッション識別、ライフサイクル状態の報告、またはその両方を追加できます。 + +Claude Code/Codex/Copilot/Devin 系のフックによるエージェントネイティブのセッション復元、Pi/OMP/Kimi/OpenCode/Kilo/Hermes/MastraCode 系のフックまたはプラグインによる直接のライフサイクル報告、あるいはその両方が欲しいときにインテグレーションを使ってください。状態権威モデルの全体像は[エージェント](/ja/docs/agents/)を参照してください。 + +## インテグレーションをインストールする + +Herdr 内で設定を開き、インテグレーションタブから `PATH` 上で見つかったエージェント向けの推奨インテグレーションをインストールするか、コマンドを手動で実行します: + +```bash +herdr integration install pi +herdr integration install omp +herdr integration install claude +herdr integration install codex +herdr integration install copilot +herdr integration install devin +herdr integration install droid +herdr integration install kimi +herdr integration install opencode +herdr integration install kilo +herdr integration install hermes +herdr integration install mastracode +herdr integration install qodercli +herdr integration install cursor +``` + +## インテグレーションをアンインストールする + +```bash +herdr integration uninstall pi +herdr integration uninstall omp +herdr integration uninstall claude +herdr integration uninstall codex +herdr integration uninstall copilot +herdr integration uninstall devin +herdr integration uninstall droid +herdr integration uninstall kimi +herdr integration uninstall opencode +herdr integration uninstall kilo +herdr integration uninstall hermes +herdr integration uninstall mastracode +herdr integration uninstall qodercli +herdr integration uninstall cursor +``` + +## Herdr がインテグレーションをどう使うか + +Herdr はインテグレーションを 2 つの異なる方法で使います: + +| インテグレーションの種類 | エージェント | 効果 | +| --- | --- | --- | +| ライフサイクル権威 | Pi、OMP、Kimi Code CLI、OpenCode、Kilo Code CLI、Hermes Agent、MastraCode | インストールされ、そのペインについて能動的に報告している間は、フックまたはプラグインのイベントが `idle`、`working`、`blocked` を決定します。同じライフサイクル権威に対して、Herdr はスクリーンマニフェストのフォールバックを併用しません。 | +| セッション識別 | Claude Code、Codex、GitHub Copilot CLI、Devin CLI、Droid、Qoder CLI、Cursor Agent CLI | インテグレーションは復元用のネイティブセッション参照を報告します。状態は引き続き Herdr のスクリーンマニフェスト検出から得られます。 | + +カスタムソケットインテグレーションも、ネイティブのターミナル UI では見えない状態を定義する場合に状態を報告できます。 + +一部のインテグレーションは、エージェントのネイティブセッション参照を報告します。Herdr は公式のセッション参照を使って、`[session] resume_agents_on_restore = false` で無効化されていない限り、Herdr サーバーの再起動後に Claude Code、Codex、Devin CLI、Droid、Kimi Code CLI、Qoder CLI、Cursor Agent CLI、GitHub Copilot CLI、Pi、OMP、Hermes Agent、OpenCode、Kilo Code CLI、MastraCode のペインを resume します。 + +エージェントネイティブのセッション復元には最新の Herdr インテグレーションが必要です: Pi インテグレーションはバージョン `2`、OMP は `3`、Claude Code は `6`、Codex は `5`、GitHub Copilot CLI は `2`、Devin CLI は `2`、Droid は `2`、Kimi Code CLI は `3`、Qoder CLI は `2`、Cursor Agent CLI は `1`、OpenCode は `5`、Kilo Code CLI は `1`、Hermes Agent は `2`、MastraCode は `1` です。インストール済みバージョンは `herdr integration status` で確認してください。 + +## Pi + +Pi インテグレーションをインストールします: + +```bash +herdr integration install pi +``` + +Herdr はバンドルされた拡張を次の場所に書き込みます: + +```text +~/.pi/agent/extensions/herdr-agent-state.ts +``` + +`PI_CODING_AGENT_DIR` が設定されている場合は、代わりに `$PI_CODING_AGENT_DIR/extensions/herdr-agent-state.ts` に書き込みます。Pi の agent ディレクトリが存在する場合、Herdr は extensions ディレクトリを作成します。アンインストールはその拡張ファイルだけを削除します。 + +## OMP + +OMP インテグレーションをインストールします: + +```bash +herdr integration install omp +``` + +Herdr はバンドルされた拡張を次の場所に書き込みます: + +```text +~/.omp/agent/extensions/herdr-omp-agent-state.ts +``` + +`PI_CODING_AGENT_DIR` が設定されている場合は、代わりに `$PI_CODING_AGENT_DIR/extensions/herdr-omp-agent-state.ts` に書き込みます。OMP の agent ディレクトリが存在する場合、Herdr は extensions ディレクトリを作成します。アンインストールはその拡張ファイルだけを削除します。 + +OMP インテグレーションは、Herdr のソケット API を通じてエージェントラベル `omp`、ライフサイクル状態、ネイティブセッション識別を報告します。`omp` 実行ファイルのネイティブプロセス検出は不要で、Herdr はサーバー再起動後に `omp --resume=<session>` で OMP ペインを resume できます。 + +## Claude Code + +Claude Code フックをインストールします: + +```bash +herdr integration install claude +``` + +このフックは、セッション開始時に Claude Code のセッション識別をローカルの Herdr ソケットに報告します。Claude Code の状態は Herdr のスクリーンマニフェスト検出から得られます。 + +Herdr はデフォルトで `~/.claude` を使い、`CLAUDE_CONFIG_DIR` が設定されていればそちらを使います。Claude の設定ディレクトリはあらかじめ存在している必要があります。インストールは `hooks/herdr-agent-state.sh` を書き込み、`settings.json` に Herdr のフックエントリを追加します。アンインストールは一致するフックエントリを削除し、フックスクリプトを削除します。 + +## Codex + +Codex フックをインストールします: + +```bash +herdr integration install codex +``` + +Codex フックは、他のインテグレーションと同じローカルソケット API を通じてセッション識別を報告します。Codex の状態は Herdr のスクリーンマニフェスト検出から得られます。 + +Herdr はデフォルトで `~/.codex` を使い、`CODEX_HOME` が設定されていればそちらを使います。Codex の設定ディレクトリはあらかじめ存在している必要があります。インストールは `herdr-agent-state.sh` を書き込み、`hooks.json` を更新し、`config.toml` に `[features] hooks = true` があることを保証します。非推奨のトップレベル `codex_hooks` フラグが存在すれば削除もします。アンインストールは `hooks.json` から Herdr のエントリを削除してフックスクリプトを削除しますが、`config.toml` は変更しません。 + +## GitHub Copilot CLI + +GitHub Copilot CLI フックをインストールします: + +```bash +herdr integration install copilot +``` + +Copilot フックは、他のインテグレーションと同じローカルソケット API を通じてセッション識別を報告します。Copilot の状態は Herdr のスクリーンマニフェスト検出から得られます。 + +Herdr はデフォルトで `~/.copilot` を使い、`COPILOT_HOME` が設定されていればそちらを使います。Copilot の設定ディレクトリはあらかじめ存在している必要があります。インストールは `hooks/herdr-agent-state.sh` を書き込み、`settings.json` に `SessionStart` フックエントリを追加します。アンインストールは `settings.json` から Herdr のエントリを削除し、フックスクリプトを削除します。 + +Copilot がセッション情報を含むイベントを発行した後、Herdr は報告されたセッション id を使って `copilot --resume=<id>` でペインを resume できます。 + +## Devin CLI + +Devin CLI フックをインストールします: + +```bash +herdr integration install devin +``` + +このフックは、Devin のセッション、プロンプト、ツール使用、許可、停止の各イベントからネイティブセッション識別を報告します。Devin のフックはすべての許可キャンセルやユーザー割り込みの後に信頼できる状態遷移を発行しないため、Devin の状態は引き続き Herdr のスクリーンマニフェストと OSC 検出から得られます。 + +Herdr はデフォルトで `~/.config/devin` を使い、`XDG_CONFIG_HOME` が設定されていれば `$XDG_CONFIG_HOME/devin` を使います。Devin の設定ディレクトリはあらかじめ存在している必要があります。インストールは `herdr-agent-state.sh` を書き込み、`config.json` に Herdr のフックエントリを追加します。フックは Devin の実行中にセッション参照を更新します。アンインストールは `config.json` から Herdr のエントリを削除し、フックスクリプトを削除します。 + +Herdr は保存された Devin セッションを `devin --resume <id>` で resume します。フックのインストール有無にかかわらず、スクリーンマニフェスト検出が状態の権威のままです。 + +## Kimi Code CLI + +Kimi Code CLI フックをインストールします: + +```bash +herdr integration install kimi +``` + +このフックは、ネイティブ復元と権威ある `idle`、`working`、`blocked` 状態のために、Kimi のセッション識別とライフサイクル状態を Herdr に報告します。Kimi Code CLI `0.14.0` 以上が必要です。 + +Herdr はデフォルトで `~/.kimi-code` を使い、`KIMI_CODE_HOME` が設定されていればそちらを使います。Kimi Code の設定ディレクトリはあらかじめ存在している必要があります。インストールは `hooks/herdr-agent-state.sh` を書き込み、`config.toml` に Herdr 管理の `[[hooks]]` エントリを追記します。アンインストールは Herdr 管理の設定ブロックを削除し、フックスクリプトを削除します。 + +Herdr は保存された Kimi セッションを `kimi --session <id>` で resume します。 + +## Droid + +Droid フックをインストールします: + +```bash +herdr integration install droid +``` + +Droid フックは、他のインテグレーションと同じローカルソケット API を通じてセッション識別を報告します。Droid のフックはすべてのライフサイクル遷移をカバーしていないため、ライフサイクル状態は引き続き Herdr のスクリーンマニフェスト検出から得られます。 + +Herdr は Droid フックに `~/.factory` を使います。Factory の設定ディレクトリはあらかじめ存在している必要があります。インストールは `hooks/herdr-agent-state.sh` を書き込み、`settings.json` に Herdr の `SessionStart` フックエントリを追加し、`hooks.json` に古い Herdr Droid フックエントリがあれば削除します。アンインストールは両方の設定ファイルから Herdr のエントリを削除し、フックスクリプトを削除します。 + +Droid がセッション開始イベントを発行した後、Herdr は報告されたセッション id を使って `droid --resume <id>` でペインを resume できます。 + +## OpenCode + +OpenCode プラグインをインストールします: + +```bash +herdr integration install opencode +``` + +Herdr はプラグインを `~/.config/opencode/plugins/herdr-agent-state.js` に書き込みます。OpenCode の設定ディレクトリはあらかじめ存在している必要があります。アンインストールはそのプラグインファイルだけを削除します。 + +このプラグインは、OpenCode が Herdr のペイン内で動いている間、ライフサイクル状態とセッション識別を報告します。OpenCode がセッション情報を含むイベントを発行した後、Herdr は報告されたセッション id を使って `opencode --session <id>` でペインを resume できます。プラグインがインストールされていないときは、スクリーンマニフェスト検出が引き続き利用できます。 + +## Kilo Code CLI + +Kilo Code CLI プラグインをインストールします: + +```bash +herdr integration install kilo +``` + +Herdr はプラグインを `~/.config/kilo/plugin/herdr-agent-state.js` に書き込みます。Kilo の設定ディレクトリはあらかじめ存在している必要があります。アンインストールはそのプラグインファイルだけを削除します。 + +このプラグインは、Kilo が Herdr のペイン内で動いている間、ライフサイクル状態とセッション識別を報告します。Kilo がセッション情報を含むイベントを発行した後、Herdr は報告されたセッション id を使って `kilo --session <id>` でペインを resume できます。プラグインがインストールされていないときは、スクリーンマニフェスト検出が引き続き利用できます。 + +## Hermes Agent + +Hermes Agent プラグインをインストールします: + +```bash +herdr integration install hermes +``` + +Herdr は `~/.hermes/plugins/herdr-agent-state/` を書き込み、`~/.hermes/config.yaml` で `herdr-agent-state` を有効にします。Hermes の設定ディレクトリはあらかじめ存在している必要があります。プラグインを読み込ませるため、インストール後に Hermes を再起動してください。アンインストールはプラグインディレクトリを削除し、`plugins.enabled` から `herdr-agent-state` を削除します。 + +このプラグインは、Hermes が Herdr のペイン内で動いている間、ライフサイクル、ツール、承認状態、セッション id を報告します。Herdr は報告されたセッション id を使って `hermes --resume <id>` でペインを resume できます。プラグインがインストールされていないときは、スクリーンマニフェスト検出が引き続き利用できます。 + +## Qoder CLI + +Qoder CLI フックをインストールします: + +```bash +herdr integration install qodercli +``` + +このフックは、ネイティブ復元のために Qoder CLI のセッション識別を Herdr に報告します。Qoder のフックはすべてのライフサイクル遷移をカバーしていないため、ライフサイクル状態は引き続き Herdr のスクリーンマニフェスト検出から得られます。 + +Herdr はデフォルトで `~/.qoder` を使い、`QODER_CONFIG_DIR` が設定されていればそちらを使います。Qoder の設定ディレクトリはあらかじめ存在している必要があります。インストールは `hooks/herdr-agent-state.sh` を書き込み、`settings.json` に Herdr のフックエントリを追加します。アンインストールは一致するフックエントリを削除し、フックスクリプトを削除します。 + +Herdr は保存された Qoder CLI セッションを `qodercli --resume <id>` で resume します。 + +フックがインストールされていないときは、スクリーンマニフェスト検出が引き続き利用できます。 + +## Cursor Agent CLI + +Cursor Agent CLI フックをインストールします: + +```bash +herdr integration install cursor +``` + +このフックは、Cursor Agent CLI が Herdr のペイン内で動いている間、Cursor の `sessionStart` フックを通じてセッション識別を報告します。Cursor の状態は Herdr のスクリーンマニフェスト検出から得られます。 + +Herdr はデフォルトで `~/.cursor` を使い、`CURSOR_CONFIG_DIR` が設定されていればそちらを使います。Cursor の設定ディレクトリはあらかじめ存在している必要があります。インストールは `herdr-agent-state.sh` を書き込み、`hooks.json` に Herdr の `sessionStart` エントリを追加します。アンインストールは一致するフックエントリを削除し、フックスクリプトを削除します。 + +Cursor がセッション開始イベントを発行した後、Herdr は報告されたセッション id を使って `cursor-agent --resume <id>` でペインを resume できます。Herdr がペインを復元するとき、`cursor-agent` コマンドが `PATH` にある必要があります。Herdr は汎用の `agent` コマンドを起動しません。 + +## MastraCode + +MastraCode フックをインストールします: + +```bash +herdr integration install mastracode +``` + +このフックは、MastraCode のライフサイクル状態とスレッド識別を Herdr に報告し、権威ある `idle`、`working`、`blocked` 状態とネイティブ復元を提供します。MastraCode にはスクリーンマニフェストのフォールバックはありません。MastraCode が Herdr ペイン内で動いている間、状態はフックから得られます。 + +Herdr は `~/.mastracode` を使います。インストールは `hooks/herdr-agent-state.sh` を書き込み、`hooks.json` に Herdr のコマンドエントリを追加します。ディレクトリがなければ作成します。アンインストールは一致するフックエントリを削除し、フックスクリプトを削除します。 + +Herdr は保存された MastraCode スレッドを `mastracode --thread <id>` で resume します。 + +## カスタムステータスラベル + +インテグレーションは、ライフサイクル状態を意味的な状態としてのみ報告します。たとえば、ライフサイクル報告に表示フィールドを加えず、エージェントを `working` として報告します。 + +```bash +herdr pane report-agent w1:p1 \ + --source custom:docs \ + --agent docs-bot \ + --state working +``` + +Herdr 管理のインテグレーションと並走するユーザーフックは、`report-agent` ではなくメタデータを使うべきです。メタデータは、インテグレーションの `idle`、`working`、`blocked` やセッション復元の権威を奪わずに表示を変えます。`--agent` と `--applies-to-source` は表示フィールド (`--title`、`--display-agent`、`--state-label`) だけを保護します。トークンパッチは常に適用され、クリアまたは TTL の更新は報告側が管理します。`--display-agent` は表示名を変更します。 + +```bash +herdr pane report-metadata "$HERDR_PANE_ID" \ + --source user:claude-title \ + --agent claude \ + --title "Refactor auth middleware" \ + --display-agent "Claude: auth" \ + --token summary="refactor auth" \ + --state-label working="refactoring auth" \ + --ttl-ms 3600000 +``` + +トークンと状態ラベルは表示専用です。wait、通知、ワークスペースのロールアップは引き続き意味的な状態を使います。 + +## インテグレーション状態のデバッグ + +既知のエージェントを一覧します: + +```bash +herdr agent list +``` + +Herdr に何が見えているか確認する必要があるときはペインを読みます: + +```bash +herdr pane read w1:p1 --source recent --lines 50 +``` + +インテグレーションの状態がおかしく見えるときは、まずエージェントが Herdr 内で動いていること、そして該当のフックまたはプラグインが同じユーザーアカウントにインストールされていることを確認してください。 diff --git a/docs/preview/website/src/content/docs/ja/keyboard.mdx b/docs/preview/website/src/content/docs/ja/keyboard.mdx new file mode 100644 index 00000000..24b554c8 --- /dev/null +++ b/docs/preview/website/src/content/docs/ja/keyboard.mdx @@ -0,0 +1,112 @@ +--- +title: キーボード +description: プレフィックスとは何か、最初に覚えるべきバインド、プレフィックスなしで運用する方法。 +--- + +:::tip[tmux や zellij から乗り換え?] +このモデルはすでにご存じのはずです。完全なデフォルトキーマップと設定構文は[キーバインドリファレンス](/ja/docs/configuration/#キーバインド)へどうぞ。 +::: + +Herdr はマウスネイティブです。キーバインドをひとつも覚えなくても、ペイン、タブ、ワークスペース、エージェントをクリックし、分割境界をドラッグし、右クリックメニューを使えます。キーボード操作は任意のレイヤーであり、必須ではありません。 + +## プレフィックスとは何か + +ターミナルマルチプレクサは、ターミナルとその中で動くプログラムの間に位置します。プログラムはすでにほとんどのキーの組み合わせを使っています: `ctrl+c` は中断、`ctrl+r` は履歴検索、エディタは残りのほぼすべてを占有します。もし Herdr がよく使われるキーを直接奪えば、中のプログラムを壊してしまいます。 + +プレフィックスはこれを解決します。プレフィックスキー (デフォルト `ctrl+b`) を押すと、次のキー入力はターミナルではなく Herdr に送られます。`prefix+c` は「`ctrl+b` を押して離し、次に `c` を押す」という意味です。何十ものキーを予約する代わりに、ひとつだけ予約します。 + +いつでも `prefix+?` を押すと、すべての有効なバインドが表示されます。キーバインドヘルプで `/` を押すと、アクションとショートカットを絞り込めます。Backspace でフィルターを編集し、`ctrl+u` でクリアできます。 + +## まずこの 5 つを覚える + +| アクション | キー | +| --- | --- | +| 新しいタブ | `prefix+c` | +| 右 / 下に分割 | `prefix+v` / `prefix+minus` | +| ペイン間の移動 | `prefix+h/j/k/l` | +| ワークスペースナビゲーション | `prefix+w` | +| すべてを動かしたままデタッチ | `prefix+q` | + +これで日常の移動のほとんどをカバーできます。それ以外はマウスのままで大丈夫です。 + +## 残りはタスク別に + +ペイン: + +| アクション | キー | +| --- | --- | +| フォーカス中のペインをズーム | `prefix+z` | +| ペインを閉じる | `prefix+x` | +| ペインを入れ替える | `prefix+shift+h/j/k/l` | +| リサイズモード | `prefix+r` | +| コピーモード | `prefix+[` | + +タブ: + +| アクション | キー | +| --- | --- | +| 次 / 前のタブ | `prefix+n` / `prefix+p` | +| タブ 1–9 にジャンプ | `prefix+1..9` | +| タブの名前を変更 | `prefix+shift+t` | +| タブを閉じる | `prefix+shift+x` | + +ワークスペースとセッション: + +| アクション | キー | +| --- | --- | +| 新しいワークスペース | `prefix+shift+n` | +| ワークスペースの名前を変更 | `prefix+shift+w` | +| ワークスペースを閉じる | `prefix+shift+d` | +| Goto ピッカー | `prefix+g` | +| サイドバーの表示切り替え | `prefix+b` | + +完全なキーマップとバインドの構文は[キーバインドリファレンス](/ja/docs/configuration/#キーバインド)にあります。 + +## コピーモード + +`prefix+[` を押すと、フォーカス中のペインでコピーモードに入ります。`h/j/k/l`、tmux 形式の `w/b/e`、`{`/`}` で移動します。`/` または `?` で前方または後方のリテラル検索を開始し、`n` または `N` で同じ方向または逆方向に繰り返します。クエリに大文字が含まれる場合だけ大文字と小文字を区別します。`v` または Space で選択を開始し、`y` または Enter でコピーし、`q` または Esc でコピーせずに抜けます。Esc は終了する前に、アクティブな選択または検索を消します。コピーモードはペインのプロセスを停止しません。最下部では出力を追従し、履歴へ移動するとその位置を保ちます。マウスのドラッグ選択なら、コピーモードに入らずにそのままコピーできます。 + +## 何でも変更できる + +プレフィックス自体を含め、すべてのバインドは設定可能です: + +```toml +[keys] +prefix = "ctrl+a" +``` + +## プレフィックスなしで運用する + +Herdr のアクションを、プレフィックス不要の直接コードに割り当てることもできます。難しいのはどのコードが安全かを知ることです。ターミナル、シェル、デスクトップ環境がすでにキーボードの大半を占有しているからです。 + +どんなコードでもバインドにできます: `ctrl+j`、`alt+k`、手に馴染むものなら何でも。ただしコードが Herdr に届くまでには 3 つの層を通り抜ける必要があります: オペレーティングシステム、外側のターミナル (Ghostty、iTerm2 などは独自のデフォルトを持ちます)、そしてペイン内で動くプログラムです。`ctrl+j` は Herdr まで問題なく届きますが、シェルやエディタはそれを Enter として扱います。`alt+k` は Linux では空いていますが、macOS ではほとんどのターミナルで特殊文字に合成されます。これらの系統からコードを選ぶ場合は、自分のターミナルと OS のショートカットと衝突しないか確認してください。 + +私たちは Ghostty、iTerm2、Terminal.app、kitty、WezTerm、Alacritty、Warp、Windows Terminal、GNOME Terminal、Konsole のデフォルトキーバインドと、GNOME および KDE のグローバルショートカットを調査しました。ほぼどこでも使われていない修飾キーの系統がひとつあります: `ctrl+alt` です。ターミナルはこれを空けたままにしており、素の `alt` コードを阻む macOS の option キー合成の影響も受けず、モダンなキーボードプロトコルのないターミナルでも送信されます。安全なデフォルトとしてお勧めしますが、選択はあなた次第です。 + +この設定はプレフィックスバインドを維持したまま、直接コードを追加します: + +```toml +[keys] +focus_pane_left = ["prefix+h", "ctrl+alt+h"] +focus_pane_down = ["prefix+j", "ctrl+alt+j"] +focus_pane_up = ["prefix+k", "ctrl+alt+k"] +focus_pane_right = ["prefix+l", "ctrl+alt+l"] +previous_tab = ["prefix+p", "ctrl+alt+["] +next_tab = ["prefix+n", "ctrl+alt+]"] +new_tab = ["prefix+c", "ctrl+alt+c"] +split_vertical = ["prefix+v", "ctrl+alt+d"] +split_horizontal = ["prefix+minus", "ctrl+alt+shift+d"] +zoom = ["prefix+z", "ctrl+alt+z"] +``` + +いくつかの `ctrl+alt` コードは他で使われています。これらは避けてください: + +| コード | 使用しているもの | +| --- | --- | +| `ctrl+alt+arrows` | GNOME のワークスペース切り替え、Ghostty と Konsole のデフォルト | +| `ctrl+alt+t` | Ubuntu と Fedora の「ターミナルを起動」 | +| `ctrl+alt+l` / `ctrl+alt+a` | KDE のロック画面 / アテンションウィンドウ | +| `ctrl+alt+s` / `ctrl+alt+u` | Konsole | +| `ctrl+alt+f1..f12` | Linux の仮想コンソール切り替え | + +直接コードを押しても何も起きない場合、Herdr に届く前にターミナルかデスクトップ環境がそれを消費しています。どちらかを再設定してください: ターミナルの設定でそのコードを解放するか、Herdr で別のコードを選びます。 diff --git a/docs/preview/website/src/content/docs/ja/marketplace.mdx b/docs/preview/website/src/content/docs/ja/marketplace.mdx new file mode 100644 index 00000000..5f0a5c4c --- /dev/null +++ b/docs/preview/website/src/content/docs/ja/marketplace.mdx @@ -0,0 +1,53 @@ +--- +title: マーケットプレイス +description: GitHub 上のコミュニティ製 Herdr プラグインを探し、自分のプラグインを掲載する方法。 +--- + +Herdr プラグインマーケットプレイスは、コミュニティ製プラグインを探せるインデックスです。 +[herdr.dev/plugins](/plugins/) で閲覧できます。これは公開 GitHub リポジトリの +自動インデックスであり、審査済みカタログではありません。 + +## プラグインを探す + +[マーケットプレイス](/plugins/)には、GitHub トピック `herdr-plugin` が付いた +すべての公開リポジトリが掲載されます。名前、オーナー、説明、言語で検索でき、 +人気順、最近の活動順、新着順で並べ替えられます。各掲載はソースリポジトリの +GitHub ページに直接リンクしています。 + +掲載は自動かつ無審査です。掲載されているのはリポジトリが自らトピックを +付けたからであって、Herdr が検証したからではありません。インストールする前に +[信頼に関するガイダンス](/ja/docs/plugins/#信頼とセキュリティ)を確認してください。 + +## プラグインをインストールする + +マーケットプレイスはインストールの上に発見の仕組みを足すものであり、 +インストールを置き換えるものではありません。どのプラグインも GitHub から +直接インストールできます: + +```bash +herdr plugin install owner/repo[/subdir...] +``` + +ルートまたはサブディレクトリに `herdr-plugin.toml` マニフェストを置いた +通常の公開 GitHub リポジトリを公開すれば、このコマンドが機能します。 +マニフェストと作成方法のリファレンスは[プラグイン](/ja/docs/plugins/)を +参照してください。 + +## 自分のプラグインを掲載する + +公開リポジトリに GitHub トピック `herdr-plugin` を追加してください。 +インデックスが使うシグナルはこのトピックだけなので、公開プラグインに +トピックを付けるだけで掲載されます。インデックスは 30 分ごとに自動更新される +ため、新しくトピックを付けたリポジトリはまもなく表示され、トピックを外した +リポジトリは次回の更新で消えます。 + +## 掲載に表示される内容 + +各カードには GitHub リポジトリのメタデータが表示されます: リポジトリ名と +オーナー、説明、スター数、主要言語、最終 push 時刻、そしてソースへのリンクです。 +インデックスは GitHub のリポジトリ検索からこれらを読み取るため、リポジトリの +説明とトピックを正確に保つことが、掲載を有用にする鍵になります。 + +インデックスはまだ `herdr-plugin.toml` を解析しないため、プラグインの `id`、 +宣言された `platforms`、`min_herdr_version` といったマニフェストのフィールドは +v1 では表示されません。フォークとアーカイブ済みリポジトリは一覧から除外されます。 diff --git a/docs/preview/website/src/content/docs/ja/persistence-remote.mdx b/docs/preview/website/src/content/docs/ja/persistence-remote.mdx new file mode 100644 index 00000000..f8d3ff02 --- /dev/null +++ b/docs/preview/website/src/content/docs/ja/persistence-remote.mdx @@ -0,0 +1,132 @@ +--- +title: 永続化とリモートアクセス +description: Herdr からのデタッチ、あとからの再アタッチ、名前付きセッション、SSH 越しの接続。 +--- + +Herdr はペインをバックグラウンドサーバーで動かし続けます。ターミナルクライアントはデタッチして、あとで再接続できます。 + +ローカル、SSH、`herdr --remote` の各ワークフローについては [Herdr での作業の進め方](/ja/docs/how-to-work/)を参照してください。 + +## デタッチと再アタッチ + +`ctrl+b q` でクライアントをデタッチします。ペインとエージェントは動き続けます。再度 `herdr` を実行すると再アタッチします。セッションとそのペインを停止するには `herdr server stop` を使います。 + +サーバーの完全停止後に Herdr が再起動すると、保存されたセッションの形を復元します。デタッチ、サーバー再起動、画面履歴リプレイ、エージェントネイティブのセッション復元、ライブハンドオフでそれぞれ何が生き残るかは[セッション状態と復元](/ja/docs/session-state/)を参照してください。 + +## 名前付きセッション + +独立した Herdr サーバーが欲しいときは名前付きセッションを使います。 + +```bash +herdr session list +herdr session attach work +herdr session attach side-project +herdr session stop work +herdr session delete side-project +``` + +名前付きセッションは独自のペイン、タブ、ワークスペース、ソケット、ランタイム状態を持ちます。グローバル設定ファイルは共有されます。 + +スクリプトでは `--json` を使ってください: + +```bash +herdr session list --json +herdr session stop work --json +herdr session delete side-project --json +``` + +## SSH 越しのリモートアタッチ + +リモートモードは 2 つあり、[Herdr での作業の進め方](/ja/docs/how-to-work/)で比較しています。tmux スタイルの方法では、サーバーに SSH してそこで `herdr` を実行します。もうひとつは、ローカルマシンから SSH 越しにアタッチする方法です: + +```bash +herdr --remote workbox +herdr --remote ssh://you@server:2222 +``` + +このモードでは、ローカルの Herdr はシンクライアントです。SSH 越しに接続し、リモートの Herdr サーバーを起動またはアタッチして、UI をローカルターミナルにストリーミングします。クライアントがローカルで動くため、Herdr は画像クリップボードの貼り付けのようなローカルデスクトップ機能をリモートセッションにブリッジできます。画像をリモートの一時ファイルにコピーし、そのパスを貼り付けます。 + +デフォルトでは、`herdr --remote` はそのアタッチにローカルの Herdr キーバインドを使います。リモートサーバーの設定が異なっていても、ローカルの操作感覚を維持できます。ローカルキーバインドはアタッチ時のスナップショットなので、ローカルのキーバインドを編集したらデタッチして再アタッチしてください。リモートサーバーの設定を使いたい場合は `--remote-keybindings server` を使います。ローカルのカスタムコマンドキーバインドは送信されません。それらのコマンドはリモートホスト上で実行されてしまうからです。 + +繰り返し接続する相手は SSH config を使ってください: + +```text +Host workbox + HostName server.example.com + User you + Port 2222 +``` + +その後はこれでアタッチできます: + +```bash +herdr --remote workbox +``` + +リモートアタッチは x86_64 と aarch64 の Linux および macOS ホストをサポートします。Herdr はリモートのプラットフォームを確認し、リモートの `PATH` 上にある一致する `herdr` を優先し、次に `~/.local/bin/herdr` を確認します。一致するバイナリがない場合、対話的な実行では `~/.local/bin/herdr` へのインストールを提案します。非対話的な実行はホストを変更せずに失敗します。`~/.local/bin` がリモートの `PATH` にない場合、Herdr はインストール後に警告します。 + +ネイティブ Windows の `herdr --remote` は Windows ベータの範囲外です。Windows からはサーバーに SSH してそこで `herdr` を実行してください。 + +デフォルトでは、`herdr --remote` はあなたの SSH config を最初に include し、その後にフォールバックのキープアライブ設定を加えた一時的な SSH config を通してブリッジを実行します。既存のユーザーのキープアライブ設定が優先されます。Herdr が生成するブリッジ設定を使わず素の `ssh` を使うには `[remote].manage_ssh_config = false` を設定してください。 + +デフォルトでは、実行中のリモートサーバーの置き換えや再起動が必要な場合、リモートアタッチは通常の再起動/停止フローを使います。対応する実行中リモートサーバーで実験的なライブハンドオフにオプトインするには `--handoff` を渡します: + +```bash +herdr --remote workbox --handoff +``` + +先にサーバーへ SSH してそこで `herdr` を実行した場合、Herdr は完全にサーバー上で動きます。このモードは便利でシンプルですが、通常のターミナルテキスト貼り付けを超えてローカルデスクトップのクリップボードにはアクセスできません。 + +ローカルとリモートのプラットフォームが一致する場合、直接インストールでは Herdr は現在のローカルバイナリをコピーできます。Homebrew、mise、Nix のインストールの場合、またはプラットフォームが異なる場合は、`https://herdr.dev/latest.json` から現在のクライアントバージョンに一致するリリースアセットをダウンロードします。 + +ローカルビルドやカスタムバイナリの場合は、リモートアタッチの前に `HERDR_REMOTE_BINARY` にローカルファイルパスを設定してください。 + +```bash +HERDR_REMOTE_BINARY=target/release/herdr herdr --remote workbox +``` + +## リモートの名前付きセッション + +リモートホスト上の名前付きセッションにアタッチするには、`--remote` と一緒に `--session` を使います: + +```bash +herdr --remote workbox --session agents +``` + +## ダイレクトターミナルアタッチ + +完全な Herdr アタッチはワークスペース UI 全体を開きます。ダイレクトアタッチは、サーバーが所有するターミナルをひとつだけ、現在のターミナルに開きます。 + +ダイレクトターミナルアタッチは Windows ベータでは Unix 専用です。 + +エージェントターゲットでアタッチ: + +```bash +herdr agent attach reviewer +``` + +ターミナル ID でアタッチ: + +```bash +herdr terminal attach term_abc123 +``` + +ダイレクトアタッチは、現在レンダリングされているターミナル状態をストリーミングし、その後ライブの ANSI フレームを流します。入力はそのターミナルに直接送られます。 + +`ctrl+b q` でデタッチします。リテラルの `ctrl+b` は `ctrl+b ctrl+b` で送ります。 + +ひとつのターミナルの入力とリサイズを所有できる書き込み可能なダイレクトアタッチクライアントはひとつだけです。既存の所有者を置き換えるには `--takeover` を使います: + +```bash +herdr terminal attach term_abc123 --takeover +``` + +## シングルプロセスの逃げ道 + +バックグラウンドのサーバー/クライアント分離なしで Herdr を実行するには `--no-session` を使います: + +```bash +herdr --no-session +``` + +これは主にデバッグや互換性のための逃げ道です。デフォルトの永続セッションモードが通常の使い方です。 diff --git a/docs/preview/website/src/content/docs/ja/plugins.mdx b/docs/preview/website/src/content/docs/ja/plugins.mdx new file mode 100644 index 00000000..db5342cd --- /dev/null +++ b/docs/preview/website/src/content/docs/ja/plugins.mdx @@ -0,0 +1,367 @@ +--- +title: プラグイン +description: マニフェストのアクション、イベントフック、ペインを備えたローカル Herdr プラグインを作成します。 +--- + +Herdr プラグインは、共有可能で実行可能なワークフローパッケージです。プラグインは +Bash スクリプト、JavaScript アプリ、Lua スクリプト、Rust バイナリ、その他マシンで +実行できる任意の argv コマンドにできます。Herdr はホスト側の面を担います: +インストール、マニフェスト検証、キーバインド、ターミナルペイン、イベント、 +呼び出しコンテキスト、ソケットアクセスです。プラグインは自分の実装言語、依存関係、 +ファイル、永続状態を担います。 + +プラグインは Herdr を軽量に保つために存在します。コアはターミナルワークスペース、 +ペイン、エージェント、安定した CLI/ソケット API に集中し続けます。プラグインは +その既存の拡張面を、あらゆるワークフローを Herdr 本体に追加することなく、 +誰もが作成・インストール・共有できる再利用可能なワークフローに変えます。 + +プラグインは SDK インテグレーションではありません。`herdr-plugin.toml` +マニフェストと、Herdr が起動できるコマンドを持つディレクトリです。Herdr は +マニフェストを検証し、ランタイムコンテキストを注入し、宣言されたコマンドを起動し、 +ログを記録します。コマンドはさらに作業が必要なときに、CLI またはソケット経由で +Herdr を呼び出します。 + +独立したプラグイン SDK や制限付きコマンドセットはありません。Herdr CLI 全体が +プラグイン API です: [CLI リファレンス](/ja/docs/cli-reference/)のすべてのコマンドを +プラグインから使えます。自分で `herdr ...` として実行できるものは、プラグインも +実行できます。ほとんどのプラグインは、実行中の Herdr バイナリを指す +`HERDR_BIN_PATH` を通じて Herdr を呼び出すべきです。これにより、Unix ソケットと +Windows 名前付きパイプの両方でプラグインの移植性が保たれます。生の JSON リクエストを +自分で送りたいときは[ソケット API](/ja/docs/socket-api/)を使ってください。 + +ランタイムでのアクション登録と、ターミナル以外のネイティブなプラグイン UI は +プラグイン v1 の範囲外です。アクション、イベントフック、ペイン、リンクハンドラーは +すべてマニフェストで宣言します。 + +## 信頼とセキュリティ + +プラグインはあなたのマシンで動く普通のコードです。インストールまたはリンクすると、 +そのビルドコマンドとランタイムコマンドはあなたのユーザーとして、あなたの環境で +実行され、Herdr CLI 全体を呼び出せます — エディタ、シェル、コーディングエージェントに +追加する拡張機能と同じです。この開放性こそが狙いであり、少しの判断力があれば +安全に保てます。 + +信頼できる作者とリポジトリからプラグインをインストールし、新しいプラグインが +何をするのか先に目を通してください: `herdr-plugin.toml` マニフェストと、実行される +スクリプトやバイナリです。`herdr plugin install` は対話的なターミナルでソースと +実行されるコマンドのプレビューを表示するので、確定する前に確認できます。すでに +信頼しているソースには `--yes` を使い、特定のリビジョンが欲しいときは `--ref` で +固定してください。 + +Herdr はマニフェストを検証し、各プラグインの設定と状態を専用ディレクトリに +保ちますが、プラグインの動作をレビューしたりサンドボックス化したりはしません。 +サードパーティのプラグインは Herdr ではなく作者のものです。検証と実行の判断は +あなた自身に委ねられます。 + +## マニフェスト + +マニフェストは Herdr とプラグインの間の契約です。パッケージのメタデータ、対応 +プラットフォーム、任意のビルドコマンド、Herdr が実行できるエントリーポイントを +宣言します。 + +```toml +id = "example.layout" +name = "Layout" +version = "0.1.0" +min_herdr_version = "0.7.0" +description = "Apply project layouts" +platforms = ["linux", "macos", "windows"] + +[[build]] +command = ["npm", "ci"] + +[[build]] +command = ["npm", "run", "build"] +platforms = ["linux", "macos"] + +[[startup]] +command = ["node", "dist/restore.js"] + +[[actions]] +id = "apply" +title = "Apply layout" +contexts = ["workspace"] +command = ["node", "dist/apply.js"] + +[[events]] +on = "worktree.created" +command = ["herdr", "workspace", "list"] + +[[panes]] +id = "board" +title = "Project board" +placement = "overlay" +command = ["herdr-board"] + +[[link_handlers]] +id = "github-issue" +title = "Open GitHub issue" +pattern = "^https://github\\.com/[^/]+/[^/]+/(issues|pull)/[0-9]+$" +action = "apply" +``` + +トップレベルの `id`、`name`、`version`、`min_herdr_version` は必須です。 +`min_herdr_version` には、プラグインが使うプラグイン API、イベント名、 +マニフェストフィールドをサポートする最も古い Herdr バージョンを設定してください。 +プラグインの最低バージョンが現在のバイナリより新しい場合、Herdr はリンクや +インストールを拒否します。`description` は任意です。プラグイン id には ASCII の +英字、数字、ドット、コロン、アンダースコア、ハイフンが使えます。 + +アクション id、ペイン id、リンクハンドラー id はプラグイン内部のローカル id です。 +ASCII の英字、数字、コロン、アンダースコア、ハイフンが使えますが、ドットは +使えません。各 id の種類はプラグイン内で一意でなければなりません。グローバルに +一意な名前が必要なとき、Herdr はアクション id を `plugin.id.action` の形に修飾します。 + +プラグインが動作する場所は `platforms = ["linux", "macos", "windows"]` で宣言します。 +ビルドコマンド、スタートアップフック、アクション、イベントフック、ペイン、 +リンクハンドラーも独自の `platforms` を宣言でき、項目レベルの platforms は +トップレベルのリストを上書きします。トップレベルの `platforms` がないローカル +プラグインは警告付きでリンクされます。 + +`command` の値は argv 配列です。Herdr はこれをシェル経由で実行しないため、 +コマンド自身がシェルを起動しない限りシェル展開はありません。言語固有の挙動は +スクリプトやバイナリの側に置いてください。 + +## 最初のプラグイン + +`herdr-plugin.toml` と実行可能なスクリプトまたはプログラムをひとつ含む +ディレクトリから始めます: + +```text +my-plugin/ + herdr-plugin.toml + index.js +``` + +```toml +id = "example.workspace-tools" +name = "Workspace Tools" +version = "0.1.0" +min_herdr_version = "0.7.0" +description = "Small workspace helpers" +platforms = ["linux", "macos", "windows"] + +[[actions]] +id = "list-workspaces" +title = "List workspaces" +contexts = ["workspace"] +command = ["node", "index.js"] +``` + +コマンドの中からは `HERDR_BIN_PATH` で Herdr を呼び出します: + +```js +const { spawnSync } = require("node:child_process"); + +const herdr = process.env.HERDR_BIN_PATH ?? "herdr"; +const result = spawnSync(herdr, ["workspace", "list"], { + encoding: "utf8", + stdio: ["ignore", "pipe", "pipe"], +}); + +process.stdout.write(result.stdout); +process.stderr.write(result.stderr); +process.exit(result.status ?? 1); +``` + +この例は Node を使っていますが、プラグインに Node は必須ではありません。 +マニフェストは Bash、PowerShell、Python、Rust、Go、Lua、Bun など、ユーザーの +マシンで使える任意のコマンドを起動できます。 + +## インストールとリンク + +サンプルプラグインをインストールします: + +```bash +herdr plugin install ogulcancelik/herdr-plugin-examples/agent-telegram-notify +herdr plugin config-dir examples.agent-telegram-notify +herdr plugin list +herdr plugin action list --plugin examples.agent-telegram-notify +``` + +ローカルでプラグインを作成しているときは、代わりに作業ディレクトリをリンクします: + +```bash +herdr plugin link /path/to/plugin +herdr plugin config-dir example.layout +herdr plugin action list --plugin example.layout +herdr plugin action invoke example.layout.apply +herdr plugin pane open --plugin example.layout --entrypoint board +herdr plugin log list --plugin example.layout +``` + +`plugin install` は `owner/repo/subdir` のような GitHub 省略記法のみを受け付けます。 +`git` でクローンし、対話的なターミナルではプレビューを表示し、サポートされる +ビルドコマンドを実行し、チェックアウトを Herdr 管理のプラグインデータの下に保存して +登録します。非対話的なインストールには `--yes` を使ってください。GitHub 管理の +プラグインを再インストールすると、その管理チェックアウトが置き換えられます。 +インストール済みおよびリンク済みのプラグインと、その有効・無効の状態は現在の +ユーザー全体で共有され、すべての Herdr セッションから利用できます。Herdr サーバーが +動作していないときでも `plugin install` と `plugin link` で登録できます。 +Herdr 0.7.3 の名前付きセッションだけにインストールしたプラグインは、再度 install +または link してください。既存のプラグイン設定と状態はそのまま残ります。 +ローカルにリンクされたプラグインへの上書きインストールは拒否されます。先に +ローカルプラグインを unlink または uninstall してください。`plugin install` と +`plugin link` はプラグインの設定・状態ディレクトリを作成し、 +`plugin config-dir <id>` はセットアップドキュメントやシェルスクリプト向けに +設定ディレクトリを表示します。 + +`plugin uninstall <id-or-source>` はプラグインの登録を解除します。GitHub 管理の +インストールでは管理チェックアウトも削除し、プラグイン id と、install で使うのと +同じ `owner/repo[/subdir...]` 省略記法の両方を受け付けます。`plugin unlink <id>` は +登録解除のみを行いファイルには触れないため、ローカル開発に便利です。v1 に独立した +`plugin update` はありません。管理プラグインを更新するには GitHub から +再インストールしてください。 + +サンプル集のリポジトリは `ogulcancelik/herdr-plugin-examples` です。 +`agent-telegram-notify`、`github-link-preview`、`dev-layout-bootstrap` を含む +独立したサンプルプラグインがサブディレクトリに入っています。これらはコピーする +ためのサンプルであり、メンテナンスされる公式プラグインではありません。 + +## ビルドコマンド + +ビルドコマンドは GitHub からの `plugin install` 中、確認の後、Herdr がプラグインを +登録する前に実行されます。ビルドコマンドが失敗するとインストールは中止され、 +プラグインは登録されません。`plugin link` はビルドコマンドを実行しません。 +ローカルの作者は自分で作業ツリーをビルドします。ビルドコマンドはファイルを生成して +構いませんが、インストールプレビュー後に `herdr-plugin.toml` を変更すると +インストールは中止されます。ビルド失敗時には、プラグイン id、ビルドのインデックス、 +作業ディレクトリ、コマンド、終了ステータスまたは spawn エラー、上限付きの +stdout/stderr が、ツールの出力を解釈せずに表示されます。 + +ビルドコマンドも素の argv コマンドですが、ランタイムのプラグインコンテキストや +Herdr のソケット環境変数は受け取りません。プラグインの作者は `cargo`、`npm`、 +`bun`、`lua` といった必要なシステムツールをドキュメントに書いてください。Herdr は +ビルドの失敗を報告しますが、足りないツールチェーンをインストールすることはありません。 + +## スタートアップフック + +`[[startup]]` コマンドは、Herdr がセッションを復元し、API ソケットの準備が整った後に、 +有効なプラグインごとに一度実行されます。ライブハンドオフで新しいサーバーが引き継いだ +ときにも再実行されますが、クライアントの接続、設定の再読み込み、プラグインの link や +enable では実行されません。Herdr はこれらを非同期で開始し、完了を通常のプラグイン +コマンドログに記録します。スタートアップの失敗でサーバーが停止することはありません。 + +スタートアップフックは一度限りの初期化コマンドであり、監視されるデーモンではありません。 +フックはプラグインが所有する状態を復元し、必要な Herdr API を呼び出して終了してください。 +たとえば宣言型の Agent ビューを `HERDR_PLUGIN_STATE_DIR` に保存し、スタートアップフックで +読み込んで再適用できます。 + +スタートアップフックは通常のランタイムプラグイン環境と +`HERDR_PLUGIN_EVENT=startup` を受け取ります。インストールプレビューには、自動実行される +コードを確認できるよう、すべてのスタートアップコマンドが表示されます。 + +## コマンドと環境 + +ランタイムコマンドは、プラグインディレクトリを作業ディレクトリとして実行されます。 +Herdr は `HERDR_SOCKET_PATH`、`HERDR_BIN_PATH`、`HERDR_ENV=1`、 +`HERDR_PLUGIN_ID`、`HERDR_PLUGIN_ROOT`、`HERDR_PLUGIN_CONFIG_DIR`、 +`HERDR_PLUGIN_STATE_DIR`、`HERDR_PLUGIN_CONTEXT_JSON`、そして利用可能なら +`HERDR_WORKSPACE_ID`、`HERDR_TAB_ID`、`HERDR_PANE_ID` を注入します。アクション +コマンドは加えて `HERDR_PLUGIN_ACTION_ID` を受け取ります。スタートアップフックと +イベントフックは `HERDR_PLUGIN_EVENT`(スタートアップフックでは `startup`)を受け取り、 +イベントフックはさらに `HERDR_PLUGIN_EVENT_JSON` を、ペインコマンドは +`HERDR_PLUGIN_ENTRYPOINT_ID` を受け取ります。 + +`HERDR_PLUGIN_ROOT` は、インストールまたはリンクされたプラグインディレクトリです。 +GitHub からインストールされたプラグインのルートは管理されたソースチェックアウト +なので、そこにユーザーの認証情報や永続状態を保存しないでください。`.env` ファイルの +ようなユーザーが編集する設定は `HERDR_PLUGIN_CONFIG_DIR` の下に、ローカルの +ランタイム状態は `HERDR_PLUGIN_STATE_DIR` の下に置いてください。Herdr はこれらの +ディレクトリを作成し、レガシーなプラグイン設定の場所が存在すれば +`HERDR_PLUGIN_CONFIG_DIR` に初期内容を移しますが、その中身の検証、同期、削除は +しません。ファイル形式とライフサイクルはプラグインが所有します。 + +`HERDR_PLUGIN_CONTEXT_JSON` には、その呼び出しで利用可能な場合、ワークスペース、 +タブ、フォーカス中のペイン、worktree、エージェント、選択テキスト、クリックされた +URL、リンクハンドラーのフィールドが含まれます。シェルプラグインは、よく使う id は +個別の環境変数から読み、完全な形が必要ならコンテキスト JSON をパースできます。 + +Node、PowerShell、Bash など別のランタイムから移植性を保って Herdr を呼び出す必要が +あるときは `HERDR_BIN_PATH` を使ってください。`HERDR_SOCKET_PATH` の背後にある生の +ソケットトランスポートは OS 固有です: Unix クライアントは Unix ソケットパスに、 +Windows クライアントは名前付きパイプに接続します。`HERDR_BIN_PATH` を通じた CLI +呼び出しなら、そのトランスポートの違いを気にせずに済みます。利用可能なコマンドは +[CLI リファレンス](/ja/docs/cli-reference/)、生のリクエスト形式は +[ソケット API](/ja/docs/socket-api/)を参照してください。 + +## ペイン + +マニフェストのペイン `placement` のデフォルトは `overlay` で、アクティブなペインの +上に一時的なズームオーバーレイを開き、閉じるときに以前のフォーカスとズームを +復元します。`plugin.pane.open` リクエストは、マニフェストの placement を +`overlay`、`popup`、`split`、`tab`、`zoomed` で上書きできます。 + +`placement = "popup"` は、タイルレイアウトを変更せずにセッションモーダルなターミナルポップアップを開きます。 +マニフェストまたは open リクエストで任意の `width` と `height` を指定できます。 +省略するとデフォルトの半分のサイズになり、数値なら外側のターミナルセル数、`"80%"` のような文字列ならターミナル領域に対する割合になります。 +Escape を含むすべてのターミナル入力を受け取り、コマンドが終了するか `popup.close` リクエストが送られると閉じます。 +最小サイズより小さい寸法は最小値に制限されます。 + +ペインを常に一時的にしたい場合は、プラグインペインのエントリーポイントに placement を直接宣言します: + +```toml +[[panes]] +id = "picker" +title = "Picker" +platforms = ["linux", "macos"] +placement = "popup" +width = "80%" +height = 20 +command = ["sh", "picker.sh"] +``` + +split、tab、zoomed、overlay のプラグインペインは、開いた後は通常の Herdr ペインです。プラグインはソケットや CLI を +通じて `pane.move`、`pane.swap`、`pane.resize`、`pane.zoom` といった標準のペイン +API を呼び出せます。ペインがタブやワークスペースをまたいで移動しても、Herdr は +プラグインペインの所有権を元のペインに結び付けたまま維持します。 +ポップアップは Herdr ペインではなく、セッションに 1 つだけ存在できるリソースです。 +ペイン id を持たず、プラグインのフォーカスコンテキストを変更せず、ペインのライフサイクルイベントを発行せず、pane、layout、永続化、エージェント API に参加しません。 +そのプロセスには `HERDR_PANE_ID` が渡されず、背後のタイルペインは `HERDR_PLUGIN_CONTEXT_JSON` から参照できます。 +Settings、コピーモード、または別の Herdr モーダルが開いている間にポップアップを開くと `ui_busy` が返り、起動後の `plugin.pane.open` は `ok` を返します。 + +Windows では、ビルドコマンド、アクションコマンド、イベントコマンドは、素の +コマンドが `PATH` にあれば `npm.cmd`、`bun.cmd`、`pnpm.cmd` のような一般的な +`PATHEXT` shim を解決します。ペインコマンドは Herdr の通常の Windows ペイン +ランチャーを使うため、引き続き有効な Windows の argv コマンドでなければなりません。 + +## キーバインド + +インストール済みプラグインのアクションにキーを割り当てます: + +```toml +[[keys.command]] +key = "prefix+l" +type = "plugin_action" +command = "example.layout.apply" +description = "apply layout" +``` + +## リンクハンドラー + +`[[link_handlers]]` を使うと、マッチしたターミナル URL への修飾キー付きクリックを、 +ブラウザで URL を開く代わりにプラグインのアクションにルーティングできます。 +修飾キー付きクリックの修飾キーは macOS を含むすべてのプラットフォームで Control +です。キャプチャされたターミナルのマウスレポートは、Command/Super を通常のクリックと +区別して伝えないからです。`pattern` はクリックされた URL に対してマッチする Rust の +正規表現で、`action` には同じプラグインが宣言したアクション名を指定します。リンク +ハンドラーのアクションは `HERDR_PLUGIN_CONTEXT_JSON` で +`invocation_source = "link_click"`、`clicked_url`、`link_handler_id` を受け取ります。 +シェルプラグインは `HERDR_PLUGIN_CLICKED_URL` と `HERDR_PLUGIN_LINK_HANDLER_ID` も +読めます。ハンドラーは各プラグイン内でマニフェストの順にチェックされます。 + +## ストレージ + +v1 には Herdr が管理するプラグインストレージ API はありません。永続状態が必要な +プラグインは、自分のファイルやデータベースを持ってください。 + +## マーケットプレイス + +コミュニティ製プラグインは[マーケットプレイス](/plugins/)で探せます。これは +GitHub トピック `herdr-plugin` が付いた公開 GitHub リポジトリの自動インデックスです。 +プラグインは普通の GitHub リポジトリのままです: `herdr-plugin.toml` を含めて公開し、 +`herdr plugin install owner/repo[/subdir]` を共有してください。 + +プラグインを掲載するには、公開リポジトリに GitHub トピック `herdr-plugin` を +追加します。インデックスは 30 分ごとに更新されます。発見の仕組みは +[マーケットプレイス](/ja/docs/marketplace/)を参照してください。 diff --git a/docs/preview/website/src/content/docs/ja/quick-start.mdx b/docs/preview/website/src/content/docs/ja/quick-start.mdx new file mode 100644 index 00000000..4de15f61 --- /dev/null +++ b/docs/preview/website/src/content/docs/ja/quick-start.mdx @@ -0,0 +1,69 @@ +--- +title: クイックスタート +description: 最初の Herdr ワークスペースを作成し、永続的なターミナルペインでエージェントを動かします。 +--- + +Herdr をまだインストールしていない場合は[インストール](/ja/docs/install/)を参照してください。その後、任意のプロジェクトディレクトリから Herdr を起動します: + +```bash +herdr +``` + +Herdr はデフォルトのバックグラウンドセッションを起動するか、それにアタッチします。ソケットを管理する必要はありません。デタッチしてもエージェントは動き続けます。 + +## ワークスペースを作成する + +セッションにワークスペースがない場合、Herdr は自動的にひとつ開きます。ワークスペースはタブ、ペイン、エージェントを入れるプロジェクト単位のコンテナです。アクティブなプロジェクトごとに専用のワークスペースを作ってください。そうすることでサイドバーのエージェント状態が読みやすくなります。 + +## マウスを使う + +Herdr はマウスネイティブなので、まずはクリックから始めてください。ペイン、タブ、ワークスペース、エージェントをクリックしてフォーカスします。分割境界をドラッグしてリサイズします。右クリックでコンテキストメニューが開き、ペインの分割やタブの作成ができます。テキストをドラッグ選択するとクリップボードにコピーされます。トークンをダブルクリックすると直接コピーされます。コピーに Ctrl+C は不要です。 + +ターミナルが修飾キー付きクリックを Herdr に渡す場合、Ctrl+クリックでペイン内のリンクを開けます。これは OSC 8 ハイパーリンクと、表示されている `http://` または `https://` の URL で機能します。macOS では、マウスキャプチャが有効な間は Herdr が処理するペインリンクに Ctrl+クリックを使ってください。Cmd+クリックは Shift+Cmd+クリックや `ui.mouse_capture = false` といったターミナルネイティブのバイパス経路でのみ使えます。 + +`ui.right_click_passthrough_modifier` を設定すると、その修飾キー + 右クリックで、右クリック・ホールド・ドラッグのジェスチャーがマウスレポーティング対応のペインアプリに送られます。 + +## エージェントを動かす + +ペインの中でコーディングエージェントを起動します: + +```bash +claude +``` + +`codex`、`pi`、`opencode` など、他の[対応エージェント](/ja/docs/agents/)でも構いません。Herdr は自動的に検出します。サイドバーには各エージェントが `working`、`blocked`、`done`、`idle` のどれかが表示されます — すべてのワークスペースを横断して表示されるので、どのプロジェクトがあなたを必要としているか常に分かります。 + +## キーボード操作 + +キーボード操作は任意です。マウスですべての操作ができます。`ctrl+b` を押してプレフィックスモードに入り、アクションキーを押します。 + +よく使うアクション: + +| アクション | キー | +| --- | --- | +| 右に分割 | `prefix+v` | +| 下に分割 | `prefix+minus` | +| 新しいタブ | `prefix+c` | +| 次 / 前のタブ | `prefix+n` / `prefix+p` | +| ワークスペースナビゲーション | `prefix+w` | +| 新しいワークスペース | `prefix+shift+n` | +| クライアントをデタッチ | `prefix+q` | + +プレフィックスの考え方が初めてなら、[キーボード](/ja/docs/keyboard/)でプレフィックスとは何か、なぜマルチプレクサがこれを使うのか、プレフィックスなしで運用する方法を説明しています。Herdr 内で `prefix+?` を押すとすべての有効なバインドが表示され、`prefix+[` でコピーモードに入ってキーボードからコピーできます。 + +## デタッチして戻ってくる + +`prefix+q` を押すか、単純にターミナルウィンドウを閉じてください。Herdr サーバーとすべてのエージェントは動き続けます。もう一度 `herdr` を実行すると同じセッションに再アタッチします。 + +セッションを本当に終了してペインを停止するには: + +```bash +herdr server stop +``` + +## 次に読むもの + +- [コンセプト](/ja/docs/concepts/) — ワークスペース、タブ、ペイン、エージェントのモデルを 2 分で。 +- [Herdr での作業の進め方](/ja/docs/how-to-work/) — ローカル、SSH、スマートフォン、`herdr --remote` のワークフロー。 +- [エージェント](/ja/docs/agents/) — 対応エージェント、検出、状態の精度を高めるインテグレーション。 +- [設定](/ja/docs/configuration/) — キーバインド、テーマ、通知、その他すべて。 diff --git a/docs/preview/website/src/content/docs/ja/session-state.mdx b/docs/preview/website/src/content/docs/ja/session-state.mdx new file mode 100644 index 00000000..2567fc3f --- /dev/null +++ b/docs/preview/website/src/content/docs/ja/session-state.mdx @@ -0,0 +1,107 @@ +--- +title: セッション状態と復元 +description: Herdr が何をライブで維持し、再起動後に何を復元し、履歴から何をリプレイし、エージェントインテグレーションで何を resume し、アップデート時に何をハンドオフするかを理解します。 +--- + +Herdr には複数の状態管理経路があります。それぞれが異なる問題を解決します。 + +## 何が生き残るか + +| ケース | プロセスは動き続ける | レイアウトは戻る | 直近の画面は戻る | エージェントの会話は再開する | +| --- | --- | --- | --- | --- | +| デタッチと再アタッチ | はい | はい | はい (ライブのターミナルから) | はい (プロセスが止まらないため) | +| サーバー再起動 | いいえ | はい | ペイン画面履歴が有効な場合のみ | エージェントネイティブのセッション復元がある場合のみ | +| `--handoff` なしのアップデート | 互換性のあるサーバーは動き続ける。再起動が必要なサーバーは停止/再起動が必要な場合がある | 再起動後は戻る | ペイン画面履歴が有効な場合のみ | エージェントネイティブのセッション復元がある場合のみ | +| `--handoff` ありのアップデート | 対応する稼働中サーバーではベストエフォート | はい | はい (ハンドオフが成功すればライブのターミナルから) | はい (ハンドオフが成功すればプロセスが動き続けるため) | + +以下のセクションでそれぞれの経路を説明します。 + +## ライブ永続化 + +通常のデタッチでは Herdr サーバーは動き続けます。ペイン、シェル、エージェント、サーバー、テスト、コマンドプロセスはそのサーバー内で動き続けます。 + +`ctrl+b q` でクライアントをデタッチします。あとで再アタッチします: + +```bash +herdr +``` + +元のプロセスが一度も止まらないため、これが最も強力な永続化経路です。 + +## スナップショット復元 + +Herdr サーバーが停止して再起動すると、元のペインのプロセスは失われています。Herdr は保存されたセッションの形を復元します: ワークスペース、タブ、ペイン、cwd、レイアウト、フォーカスです。 + +スナップショット復元は、動作中のシェル、サーバー、テスト、その他任意のプロセスを保存しません。より強力な復元経路が使えないペインは、保存されたディレクトリで新しいシェルとして戻ってきます。 + +## ペイン画面履歴のリプレイ + +ペイン画面履歴は、サーバーの完全な再起動後に直近のターミナル内容を復元します。復元されるのは Herdr が表示できるものであって、元のプロセスではありません。 + +ペイン出力にはシークレット、トークン、プロンプト、コマンド出力が含まれうるため、これはデフォルトで無効です。Settings > Experiments > pane screen history から、または次の設定で有効にします: + +```toml +[experimental] +pane_history = true +``` + +有効にすると、Herdr は保存したペイン履歴を `session.json` の隣の `session-history.json` に保存します。Herdr の設定/セッションディレクトリはターミナル履歴と同じ感覚で扱ってください。 + +## エージェントネイティブのセッション復元 + +一部のエージェントは自分の会話セッションを resume できます。Herdr は、公式インテグレーションが報告したセッション参照を使って、Herdr サーバーの再起動後に対応エージェントのペインを再起動できます。 + +これはデフォルトで有効です。無効にするには: + +```toml +[session] +resume_agents_on_restore = false +``` + +Herdr が resume するのは、現行の公式 Herdr インテグレーションを通じてネイティブセッション参照を報告したペインだけです。 + +クライアントがアタッチしてターミナルサイズとテーマのコンテキストを提供すると、Herdr は各ペインがフォーカスされるのを待たずに、ワークスペースとタブをまたいで復元対象のエージェントペインを resume します。 + +エージェントネイティブのセッション復元には、次の Herdr インテグレーションバージョン以上が必要です: + +| エージェント | 最低 Herdr インテグレーションバージョン | resume コマンド | +| --- | --- | --- | +| Pi | `2` | `pi --session <path-or-id>` | +| OMP | `3` | `omp --resume=<path-or-id>` | +| Claude Code | `6` | `claude --resume <id>` | +| Codex | `5` | `codex resume <id>` | +| Cursor Agent CLI | `1` | `cursor-agent --resume <id>` | +| GitHub Copilot CLI | `2` | `copilot --resume=<id>` | +| Devin CLI | `2` | `devin --resume <id>` | +| Droid | `2` | `droid --resume <id>` | +| Kimi Code CLI | `3` | `kimi --session <id>` | +| Qoder CLI | `2` | `qodercli --resume <id>` | +| OpenCode | `5` | `opencode --session <id>` | +| Kilo Code CLI | `1` | `kilo --session <id>` | +| Hermes Agent | `2` | `hermes --resume <id>` | +| MastraCode | `1` | `mastracode --thread <id>` | + +インストール済みインテグレーションのバージョンは `herdr integration status` で確認できます。古いインテグレーションは `herdr integration install <agent>` で再インストールしてください。 + +未対応、欠落、無効、重複、または古くなったセッション参照は、保存されたペインディレクトリで通常のシェルとして復元されます。 + +あるペインにエージェントネイティブのセッション復元が適用される場合、Herdr はそのペインでは保存済みペイン履歴のリプレイではなくエージェントセッションの resume を行います。 + +## ライブハンドオフ + +ライブハンドオフは、稼働中の Herdr サーバーを置き換える必要があるアップデートやリモートアタッチのフローのためのものです。古いサーバーにライブペインを新しいサーバーへ移すよう依頼し、サーバー交換をまたいでペインのプロセスが動き続けられるようにします。 + +これはスナップショット復元、ペイン履歴リプレイ、エージェントネイティブのセッション復元とは異なります。ハンドオフは現在のプロセスを生かし続けようとします。他の経路は、古いサーバーが停止した後に状態を再構築します。 + +ハンドオフが保護するのは、ペインの PTY とプロセス、エージェントの識別情報と永続メタデータ、交換後のサーバーに必要なプラグイン/セッション状態など、サーバーが所有する長寿命のセッション状態です。交換境界をまたぐ一時的な協調状態は保持しません。処理中の CLI/API リクエスト、wait、購読ストリーム、クライアントソケット、ペイン間メッセージは中断される可能性があるため、クライアントは再接続して再試行してください。 + +ライブハンドオフは実験的機能で、オプトインです: + +```bash +herdr update --handoff +herdr --remote workbox --handoff +``` + +素の `herdr update` と素の `herdr --remote workbox` は、デフォルトでは通常の再起動/停止フローを使います。 + +`herdr update --handoff` は Herdr 自身のアップデーターが管理するインストールにのみ適用されます。Homebrew、mise、Nix のインストールはそれぞれのパッケージマネージャーで更新されるため、そこでは `herdr update` は無効化されており、ライブハンドオフは実行できません。 diff --git a/docs/preview/website/src/content/docs/ja/socket-api.mdx b/docs/preview/website/src/content/docs/ja/socket-api.mdx new file mode 100644 index 00000000..7b9ee4cc --- /dev/null +++ b/docs/preview/website/src/content/docs/ja/socket-api.mdx @@ -0,0 +1,752 @@ +--- +title: ソケット API +description: スクリプト、ツール、コーディングエージェントから実行中の Herdr サーバーを制御します。 +--- + +Herdr は、実行中のセッションを調査・制御する必要があるスクリプトやエージェント向けに、ローカルソケット API を公開しています。 + +ほとんどの自動化は CLI ラッパーから始めるべきです。生のソケット API は、直接のリクエスト/レスポンス制御や長寿命のイベント購読が必要なときだけ使ってください。 + +## インテグレーション層を選ぶ + +| 層 | 用途 | +| --- | --- | +| エージェントスキル | ペイン内からの Herdr の使い方をコーディングエージェントに教える。 | +| CLI ラッパー | シェルスクリプト、シンプルなオーケストレーション、人間によるデバッグ。 | +| 生のソケット API | カスタムツール、プロトコルクライアント、イベント購読者。 | + +これらの層は同じ制御面を共有します。 + +## スキーマ + +インストール済み CLI は、その Herdr バイナリに同梱されたソケットプロトコルスキーマを表示できます: + +```bash +herdr api schema +herdr api schema --json +herdr api schema --output herdr-api.schema.json +``` + +通常の `herdr api schema` は短い概要を表示します。`--json` はツール向けの完全な JSON Schema を表示し、`--output PATH` はそのドキュメントをファイルに書き込みます。スキーマには、生のリクエスト、成功レスポンス、エラーレスポンス、発行イベント、購読イベントが含まれます。 + +## 制御できるもの + +ソケット API では次のことができます: + +- ワークスペースの作成、一覧、フォーカス、名前変更、クローズ +- タブの作成、一覧、フォーカス、名前変更、クローズ +- ペインの一覧、調査、分割、入れ替え、フォーカス、リサイズ、名前変更、読み取り、クローズ、入力送信 +- CLI ヘルパーを通じたエージェントの一覧、調査、読み取り、プロンプト、待機、名前変更、フォーカス、起動、アタッチ +- フックやプラグインからのカスタムエージェント状態の報告 +- イベントの購読と、出力や状態変化の待機 +- 組み込みインテグレーションのインストールとアンインストール +- サーバーの停止と設定のリロード + +## CLI の例 + +ワークスペースを作成する: + +```bash +herdr workspace create --cwd ~/project --label api +``` + +タブを作成する: + +```bash +herdr tab create --label logs +``` + +ペインを分割してコマンドを実行する: + +```bash +herdr pane split w1:p1 --direction right +herdr pane run w1:p2 "npm test" +``` + +ペインを調査して並べ替える: + +```bash +herdr pane layout --current +herdr pane neighbor --direction right --current +herdr pane resize --direction right --amount 0.1 --current +herdr pane swap --direction right --current +herdr pane zoom --on --current +herdr pane split w1:p1 --direction right --ratio 0.333 +``` + +エージェントを待つ: + +```bash +herdr agent wait w1:p1 --until done +``` + +ペインの出力を読む: + +```bash +herdr pane read w1:p2 --source recent --lines 50 +``` + +## 生のメソッド + +生のソケットメソッド名はドット記法を使います: + +| 領域 | メソッド | +| --- | --- | +| サーバー | `ping`、`server.stop`、`server.reload_config`、`server.agent_manifests`、`server.reload_agent_manifests` | +| 通知 | `notification.show` | +| クライアント | `client.window_title.set`、`client.window_title.clear` | +| セッション | `session.snapshot` | +| ワークスペース | `workspace.create`、`workspace.list`、`workspace.get`、`workspace.focus`、`workspace.rename`、`workspace.move`、`workspace.move_block`、`workspace.report_metadata`、`workspace.close` | +| Worktree | `worktree.list`、`worktree.create`、`worktree.open`、`worktree.remove` | +| タブ | `tab.create`、`tab.list`、`tab.get`、`tab.focus`、`tab.rename`、`tab.move`、`tab.close` | +| ペイン | `pane.split`、`pane.swap`、`pane.move`、`pane.zoom`、`pane.layout`、`pane.process_info`、`pane.neighbor`、`pane.edges`、`pane.focus_direction`、`pane.resize`、`pane.list`、`pane.current`、`pane.get`、`pane.rename`、`pane.send_text`、`pane.send_keys`、`pane.send_input`、`pane.read`、`pane.graphics.info`、`pane.graphics.set`、`pane.graphics.clear`、`pane.graphics.stream`、`pane.report_agent`、`pane.report_agent_session`、`pane.report_metadata`、`pane.clear_agent_authority`、`pane.release_agent`、`pane.close`、`pane.wait_for_output` | +| ポップアップ | `popup.close` | +| レイアウト | `layout.export`、`layout.apply`、`layout.set_split_ratio` | +| エージェント | `agent.list`、`agent.get`、`agent.read`、`agent.explain`、`agent.send_keys`、`agent.prompt`、`agent.wait`、`agent.rename`、`agent.focus`、`agent.start` | +| イベント | `events.subscribe`、`events.wait` | +| インテグレーション | `integration.install`、`integration.uninstall` | +| プラグイン | `plugin.link`、`plugin.list`、`plugin.unlink`、`plugin.enable`、`plugin.disable`、`plugin.action.list`、`plugin.action.invoke`、`plugin.log.list`、`plugin.pane.open`、`plugin.pane.focus`、`plugin.pane.close` | + +`agent.wait` はサーバー所有でイベント駆動です。解決したペイン占有者に固定されるため、置換されたエージェントが待機を満たすことはありません。`agent.prompt` は `until` と `timeout_ms` を持つ省略可能な `wait` オブジェクトを受け付けます。これにより、プロンプト送信と待機開始を 1 つのリクエストで行い、別々の呼び出し間の競合を避けられます。 + +`workspace.move_block` は、順序付きの `workspace_ids` を `before_workspace_id` の前へアトミックに移動します。アンカーを省略するとブロックを末尾へ移動します。id は一意である必要があり、アンカーをブロックに含めることはできません。レスポンスにはサーバーが確定した順序付きワークスペース一覧が含まれます。 + +`session.snapshot` は、独自のローカルランタイムキャッシュを持つクライアント向けに、一度限りのブートストラップスナップショットを返します。レスポンスには、バージョン/プロトコルメタデータ、フォーカス中のワークスペース/タブ/ペイン id、ワークスペース、タブ、ペイン、タブレイアウト、エージェントの各レコードが含まれます。これは購読ではありません。取得後はリソースイベントを購読し、そのイベントでローカルキャッシュを更新してください。再接続後やキャッシュが古い可能性がある場合は、`session.snapshot` を再度呼び出します。ワークスペースレコードには、関連付けられた worktree の出自情報も含まれます。リポジトリ全体の worktree 検出には引き続き `worktree.list` を使います。 + +CLI の `herdr api snapshot` は、クライアントやエージェントが簡単にブートストラップできるよう、現在の `session.snapshot` レスポンスを JSON で出力します。 + +ペイン制御メソッドは `w1:p1` のような公開ペイン id を使います。スキーマ上 `pane_id` が省略可能なメソッドは、省略時にサーバーのアクティブなフォーカス中ペインを使います。`pane.move` は常に送信元の `pane_id` を要求します。 + +`pane.send_keys` と `pane.send_input.keys` は Herdr のキーコンボ文字列を受け付けます: 通常の印字可能キー、`enter` や `esc` のような特殊キー、`ctrl+h`、`control+j`、`alt+x`、`shift+tab` のような修飾キーコード、`f1` のようなファンクションキー、`minus` や `plus` のような名前付き記号です。`prefix+` のバインド文字列は受け付けません。 + +```json +{"id":"req_current","method":"pane.current","params":{"caller_pane_id":"w1:p1"}} +{"id":"req_layout","method":"pane.layout","params":{"pane_id":"w1:p1"}} +{"id":"req_neighbor","method":"pane.neighbor","params":{"pane_id":"w1:p1","direction":"right"}} +{"id":"req_edges","method":"pane.edges","params":{"pane_id":"w1:p1"}} +{"id":"req_focus","method":"pane.focus_direction","params":{"direction":"right"}} +{"id":"req_resize","method":"pane.resize","params":{"pane_id":"w1:p1","direction":"right","amount":0.1}} +{"id":"req_zoom","method":"pane.zoom","params":{"pane_id":"w1:p1","mode":"toggle"}} +{"id":"req_split","method":"pane.split","params":{"direction":"right","ratio":0.333,"env":{"HERDR_ROLE":"tests"}}} +{"id":"req_process","method":"pane.process_info","params":{"pane_id":"w1:p1"}} +``` + +`pane.current` は単一の `PaneInfo` を返します。`caller_pane_id` があるときはそのペインを返します。省略時はアクティブなフォーカス中ペインを返します。 + +ターミナルのスクロール情報を取得できる場合、`PaneInfo` には `scroll` が含まれます: + +```json +{ + "offset_from_bottom": 12, + "max_offset_from_bottom": 240, + "viewport_rows": 30 +} +``` + +クライアントは `offset_from_bottom == 0` を最下部にいる状態として扱えます。 + +### 実験的なペイングラフィックス + +ペイングラフィックスを使うと、プラグインがペイン上に画像データを配置できます。`[experimental].kitty_graphics = true` の場合にのみ利用でき、それ以外ではすべてのペイングラフィックスメソッドが `feature_disabled` を返します。`pane.graphics.info` はグラフィックスレイヤーを作成せず、接続中クライアントのセルの幅と高さをピクセル単位で返します。`pane.graphics.set` は `data_base64` の `png`、`rgb`、`rgba` データを受け取り、`pane.graphics.clear` はレイヤーを削除します。 + +```json +{"id":"graphics_info","method":"pane.graphics.info","params":{"pane_id":"w1:p1"}} +{"id":"graphics_set","method":"pane.graphics.set","params":{"pane_id":"w1:p1","format":"png","image_width":800,"image_height":600,"data_base64":"...","placement":{"viewport_col":0,"viewport_row":0,"grid_cols":80,"grid_rows":30}}} +{"id":"graphics_clear","method":"pane.graphics.clear","params":{"pane_id":"w1:p1"}} +``` + +フレームを繰り返し送る場合は、`pane.graphics.stream` で専用ソケットを開きます。Herdr が `ok` を返した後、フレームごとに 1 つの JSON ヘッダーと正確に `data_length` バイトの生データを送ります。ソケットが閉じるまでストリームがそのペインのグラフィックスレイヤーを所有し、同時に set、clear、stream を要求すると `stream_conflict` を返します。 + +```json +{"id":"graphics_stream","method":"pane.graphics.stream","params":{"pane_id":"w1:p1"}} +{"format":"png","image_width":800,"image_height":600,"data_length":12345,"placement":{"viewport_col":0,"viewport_row":0,"grid_cols":80,"grid_rows":30}} +``` + +`pane.layout` は、`workspace_id`、`tab_id`、`zoomed`、外側の `area`、`focused_pane_id`、ペインの矩形、分割の矩形/比率を含むタブレイアウトのスナップショットを返します。`pane.neighbor` と `pane.edges` は同じレイアウトスナップショットを含むので、クライアントは非公開のレイアウト状態なしに次の判断を下せます。 + +`pane.process_info` は、ペインのシェル pid、利用可能な場合はフォアグラウンドプロセスグループ id、そしてプラットフォームが公開している場合は pid、名前、argv/cmdline、cwd を含むフォアグラウンドプロセスを返します。 + +`layout.export` は移植可能なタブレイアウトツリーを返します。アクティブなタブをエクスポートするには `tab_id` と `pane_id` の両方を省略し、特定のタブなら `tab_id` を、そのペインを含むタブなら `pane_id` を渡します。 + +```json +{"id":"req_export","method":"layout.export","params":{"tab_id":"w1:t1"}} +``` + +レスポンスには `workspace_id`、`tab_id`、`zoomed`、`focused_pane_id`、`root` が含まれます。`root` は `pane` ノードと `split` ノードの BSP ツリーです。ペインノードは `pane_id`、`label`、`cwd`、argv の `command` を含められます。分割ノードは `direction` (`right` または `down`)、`ratio`、`first`、`second` を使います。 + +`layout.apply` は宣言的なツリーから新しいタブを作成します。`tab_id` が与えられた場合、Herdr は先に置き換え用のタブを作成してから古いタブを閉じます。これは構造、ラベル、cwd、env、任意の argv コマンドを復元しますが、ライブの PTY、スクロールバック、実行中プロセスは保存しません。 + +```json +{ + "id": "req_apply", + "method": "layout.apply", + "params": { + "workspace_id": "wabc", + "tab_label": "dev", + "focus": true, + "root": { + "type": "split", + "direction": "right", + "ratio": 0.65, + "first": { + "type": "pane", + "label": "editor", + "cwd": "/repo" + }, + "second": { + "type": "pane", + "label": "tests", + "cwd": "/repo", + "command": ["sh", "-c", "just test"], + "env": { "HERDR_ROLE": "tests" } + } + } + } +} +``` + +`layout.set_split_ratio` は、タブレイアウト内の既存の分割比率を更新します。レスポンスは、更新済みの移植可能な `layout` を含む `type: "layout_split_ratio_set"` です。 + +```json +{"id":"req_ratio","method":"layout.set_split_ratio","params":{"tab_id":"w1:t1","path":[],"ratio":0.6}} +``` + +プロセスを起動するメソッドは `env` オブジェクトを受け付けます。Herdr はそのキー/値ペアを新しく起動されるプロセスにのみ適用します。Herdr は管理下のペインプロセスに `HERDR_SOCKET_PATH`、`HERDR_ENV=1`、`HERDR_WORKSPACE_ID`、`HERDR_TAB_ID`、`HERDR_PANE_ID` も注入します。呼び出し側が与えた環境変数と衝突した場合、Herdr 管理の変数が権威を持ちます。 + +`pane.swap` は方向指定と明示指定の両方の形式をサポートします: + +```json +{"id":"req_swap_dir","method":"pane.swap","params":{"pane_id":"w1:p1","direction":"right"}} +{"id":"req_swap_explicit","method":"pane.swap","params":{"source_pane_id":"w1:p1","target_pane_id":"w1:p2"}} +``` + +入れ替えは同じタブ内に限られます。分割の形、分割比率、ペイン id、実行中のプロセスを保存します。レスポンスは `type: "pane_swap"` で、`changed`、任意の `reason`、`source_pane_id`、任意の `target_pane_id`、`focused_pane_id`、`layout` を含みます。reason の値は `no_neighbor`、`same_pane`、`not_found`、`cross_tab` です。タブがズームされている場合、入れ替えはズームを維持したまま隠れた全タブレイアウトを変更します。 + +`pane.move` は、実行中のペインを別のタブ、新しいタブ、または新しいワークスペースに移動します: + +```json +{"id":"req_move_tab","method":"pane.move","params":{"pane_id":"w1:p2","destination":{"type":"tab","tab_id":"w1:t2","target_pane_id":"w1:p3","split":"right","ratio":0.5},"focus":true}} +{"id":"req_move_new_tab","method":"pane.move","params":{"pane_id":"w1:p2","destination":{"type":"new_tab","workspace_id":"w1","label":"logs"},"focus":true}} +{"id":"req_move_new_workspace","method":"pane.move","params":{"pane_id":"w1:p2","destination":{"type":"new_workspace","label":"logs","tab_label":"main"},"focus":true}} +``` + +既存タブへの移動には `split: "right" | "down"` が必要です。`target_pane_id` は任意で、デフォルトは移動先タブのフォーカス中ペインです。同じタブ内のレイアウト変更は引き続き `pane.swap` です。元のタブへの移動は `reason: "same_tab"` 付きで `changed: false` を返します。ズームされた移動元/移動先タブを含む移動は `reason: "zoomed_tab"` 付きで `changed: false` を返します。 + +レスポンスは `type: "pane_move"` で、`changed`、任意の `reason`、`previous_pane_id`、`previous_workspace_id`、`previous_tab_id`、移動された `pane`、任意の `source_layout`、`target_layout`、任意の作成されたワークスペース/タブのレコード、任意の閉じられたワークスペース/タブの id、`focused_pane_id` を含みます。ワークスペースをまたぐ移動では、内部のペインとターミナルは生きたまま、移動先ワークスペースで新しい公開ペイン id が割り当てられます。購読者は `pane.moved` を購読できます。Herdr は移動されたターミナルプロセスについて偽のペインの close/create イベントを発行しません。 + +`pane.zoom` は、対象ペインのタブのズームをトグル、有効化、無効化します: + +```json +{"id":"req_zoom_toggle","method":"pane.zoom","params":{"pane_id":"w1:p1"}} +{"id":"req_zoom_on","method":"pane.zoom","params":{"pane_id":"w1:p1","mode":"on"}} +{"id":"req_zoom_off","method":"pane.zoom","params":{"pane_id":"w1:p1","mode":"off"}} +``` + +`pane_id` を省略すると、サーバーのアクティブなフォーカス中ペインが対象になります。レスポンスは `type: "pane_zoom"` で、`changed`、`zoom_changed`、`focus_changed`、任意の `reason`、`pane_id`、`focused_pane_id`、`zoomed`、`layout` を含みます。`changed` は、ズーム状態またはフォーカスのどちらかが変わったときに true です。reason の値は `single_pane`、`already_zoomed`、`already_unzoomed` です。 + +`notification.show` の CLI ラッパーは次のとおりです: + +```bash +herdr notification show "build failed" --body "api workspace" --position top-left --sound request +``` + +設定済みのトースト配信を通じてユーザー通知を表示します: + +```json +{"id":"req_notify","method":"notification.show","params":{"title":"build failed","body":"api workspace","position":"top-left","sound":"request"}} +``` + +`title` は必須で、制御文字と連続する空白を除去した後に可視のテキストが残っていなければなりません。`body` は任意です。Herdr は改行、タブ、キャリッジリターン、連続する空白をスペースにまとめてから、通知テキストを `title` は 80 文字、`body` は 240 文字に切り詰めます。サニタイズ後の `title` が空の場合は `invalid_params` を返します。`position` は任意で、`ui.toast.delivery = "herdr"` のときのみ適用されます。デスクトップの位置は Herdr のフレーム全体を基準とし、省略時は `ui.toast.herdr.position` を使います。terminal、system、off の配信では `position` は無視されます。`sound` は任意で `none`、`done`、`request` のいずれかを指定でき、デフォルトは `none` で、通知が表示されたときにのみ再生されます。 + +レスポンスは何かが表示されたかどうかを報告します: + +```json +{"id":"req_notify","result":{"type":"notification_show","shown":true,"reason":"shown"}} +``` + +reason の候補は `shown`、`disabled`、`rate_limited`、`no_foreground_client`、`busy` です。`disabled` は `ui.toast.delivery = "off"` を意味します。`busy` は既存のアプリ内トーストが置き換えられなかったことを意味します。terminal と system の配信は、現在フォアグラウンドでアタッチしている Herdr クライアントを通じたベストエフォートです。 + +フォアグラウンドクライアントの外側ターミナルウィンドウタイトルを設定またはクリアします: + +```json +{"id":"req_title","method":"client.window_title.set","params":{"title":"herdr api"}} +{"id":"req_title_clear","method":"client.window_title.clear","params":{}} +``` + +`client.window_title.clear` は Herdr のデフォルトタイトルを復元します。レスポンスは `type: "client_window_title"` で、`changed` と、`set`、`cleared`、`no_foreground_client` のいずれかの reason を含みます。 + +Worktree メソッドは Git チェックアウトを Herdr ワークスペースとして管理します。`worktree.create` はチェックアウトを作成し、新しい `workspace`、`tab`、`root_pane`、`worktree` のレコードを返します。要求されたブランチがローカルに既存ならそれをチェックアウトし、なければ要求されたベースまたは `HEAD` からブランチを作成します。`worktree.open` は既存のチェックアウトを開くか、すでに開いているワークスペースを返します。`worktree.remove` はリンクされた子ワークスペースに対して `git worktree remove` を実行し、ブランチは決して削除しません。 + +元のワークスペースから worktree を作成する: + +```json +{"id":"req_1","method":"worktree.create","params":{"workspace_id":"w1","branch":"worktree/api","focus":false}} +``` + +既存のチェックアウトを開く: + +```json +{"id":"req_2","method":"worktree.open","params":{"workspace_id":"w1","branch":"worktree/api","focus":true}} +``` + +リンクされたチェックアウトを削除する: + +```json +{"id":"req_3","method":"worktree.remove","params":{"workspace_id":"2","force":false}} +``` + +`worktree.list`、`worktree.create`、`worktree.open` では `workspace_id` と `cwd` は最大どちらか一方だけを使い、両方省略するとアクティブなワークスペースを使います。`worktree.open` では `path` と `branch` のちょうど一方を使います。生のソケットの `cwd` と `path` の値は絶対パスでなければなりません。CLI は相対の `--cwd` と `--path` の値をリクエスト送信前に展開します。ワークスペースが Herdr の worktree グループに属している場合、ワークスペースのレスポンスには任意の `worktree` 出自情報が含まれます。既存のワークスペースが worktree の出自情報を獲得または変更したとき、worktree コマンドは `workspace.updated` を発行することがあります。 + +worktree コマンドはライフサイクルイベントも発行します。`worktree.create` は `workspace.created`、`tab.created`、`pane.created`、`worktree.created` を発行します。`worktree.open` は `worktree.opened` を発行し、新しい Herdr ワークスペースを開いた場合はワークスペース/タブ/ペインの作成イベントも発行します。`worktree.remove` は `worktree.removed` を発行し、リンクされたワークスペースがまだ開いている場合は `workspace.closed` も発行します。 + +## エージェントビューのクエリ + +`agent.view.set` は、組み込みの Agents ビューに一つの一時的な宣言型プロジェクションを +設定します。エージェントの情報や現在の UI コンテキストが変わるたびに再評価されます。 +展開・折りたたみサイドバー、モバイルの Agents リスト、マウスターゲット、番号付き +フォーカス、前後のエージェントへの移動順を制御します。`agent.list`、通知、検出、 +グローバルな要対応件数は変更しません。 + +現在表示中の Space にいるエージェント、または別の場所で対応が必要なエージェントを +表示し、要対応度と直近の状態遷移で並べ替える例です: + +```json +{ + "id": "view_set", + "method": "agent.view.set", + "params": { + "source": "plugin:example.agent-views", + "label": "focus", + "filter": { + "op": "any", + "filters": [ + { + "op": "eq", + "field": "workspace_id", + "value": {"context": "current_workspace_id"} + }, + { + "op": "in", + "field": "status", + "values": ["blocked", "done"] + } + ] + }, + "sort": [ + {"field": "attention", "order": "desc"}, + {"field": "state_change_seq", "order": "desc"} + ] + } +} +``` + +フィルターノードの `op` には `all`、`any`、`not`、`eq`、`in`、`exists` を使います。 +組み込みフィールドは `status`、`workspace_id`、`tab_id`、`pane_id`、`agent`、 +`seen`、`state_change_seq` です。プラグインが報告したペインメタデータを絞り込むには、 +フィールドとして `{"token":"name"}` を使います。値には文字列、真偽値、符号なし整数、 +またはコンテキストオブジェクトを指定できます。コンテキスト値は +`current_workspace_id` と `current_tab_id` で、対応する ID フィールドとの比較にだけ +使えます。有効な status は `idle`、`working`、`blocked`、`done`、`unknown` です。 +`done` は idle かつ未確認の状態を表します。 + +ソートフィールドは `workspace_order`、`tab_order`、`pane_order`、`attention`、 +`status`、`agent`、`seen`、`state_change_seq`、または `{"token":"name"}` です。 +ソートは安定しており、指定順に評価され、`asc` または `desc` を受け付けます。値がない +項目は値がある項目の後に残ります。`sort` を省略すると既存の +`ui.agent_panel_sort` ポリシーが有効です。カスタムソートは設定を書き換えず、一時的に +そのポリシーを置き換えます。 + +`source` は所有者を識別します。プラグインは `plugin:<HERDR_PLUGIN_ID>` を使います。 +所有元のプラグインが存在しないか無効な場合、Herdr は設定を拒否します。他の呼び出し元は +独自の非 `plugin:` source を使えます。設定に成功すると以前のビューはアトミックに +置き換えられます。ビューは clear または置換されるか、所有プラグインが disable、unlink、 +uninstall されるか、サーバーが終了するまで有効です。永続的な動作が必要なプラグインは、 +クエリを `HERDR_PLUGIN_STATE_DIR` に保存し、`[[startup]]` フックから再適用してください。 + +無条件で clear するか、指定した source がまだ所有している場合だけ clear できます: + +```json +{"id":"view_clear","method":"agent.view.clear","params":{}} +{"id":"view_clear_owned","method":"agent.view.clear","params":{"source":"plugin:example.agent-views"}} +``` + +source が一致しない場合、アクティブなビューは変わりません。set と clear のレスポンスは +`type: "agent_view"` で、`active`、`source`、任意の `label` を報告します。 + +## プラグイン API + +プラグイン API は、実行可能なワークフローツールのための初期のホスト面です。プラグインは `herdr-plugin.toml` マニフェストを持つパッケージです。マニフェストは、共有可能なアクション、イベントフック、ターミナルペインのエントリーポイント、リンクハンドラーを宣言します。アクションとペインはマニフェスト専用です。ランタイムでのアクション登録とランタイムでの argv ペイン作成は v1 の範囲外です。 + +インストールおよびリンクされたプラグインは再起動をまたいで永続化されます。Herdr は `plugin.link`、`plugin.unlink`、`plugin.enable`、`plugin.disable` の際に、`session.json` の隣に `plugins.json` レジストリファイルを書き込みます。`herdr plugin install` CLI も、Herdr が動作していないときに同じレジストリを書き込み、起動時に自動的に読み込まれます。起動時、Herdr は各マニフェストを元のパスから再読み込みします。ファイルが欠落しているかパースできない場合、エントリは `warnings` フィールド付きで保持され、`plugin.list` がそれを表面化します。 + +イベントフックの `on` の値は、リンク時に既知の Herdr イベント名と照合されます。認識されない名前はエラーではなく、リンクは成功しますが、返されるプラグイン情報に警告が含まれます (例: `"unknown event 'worktree.craeted'"`)。`plugin.link` と `plugin.list` のレスポンスの `warnings` フィールドを確認してください。 + +ローカルのプラグインマニフェストをリンクする: + +```json +{"id":"req_plugin_link","method":"plugin.link","params":{"path":"/path/to/plugin","enabled":true}} +``` + +`plugin.link` は任意の `source` メタデータも受け付けます。CLI は GitHub からインストールするときにこれを使い、`plugin.list` が出自、要求された ref、解決されたコミット、管理チェックアウトのパスを表示できるようにします: + +```json +{"id":"req_plugin_link","method":"plugin.link","params":{"path":"/managed/plugin/herdr-plugin.toml","enabled":true,"source":{"kind":"github","owner":"ogulcancelik","repo":"herdr-plugin-examples","subdir":"worktree-bootstrap","requested_ref":"main","resolved_commit":"abc123","managed_path":"/data/plugins/github/<managed-checkout>","installed_unix_ms":1780000000000}}} +``` + +パスは `herdr-plugin.toml` を含むプラグインディレクトリでも、マニフェストへの直接パスでも構いません。マニフェストの形式は次のとおりです: + +```toml +id = "example.worktree-bootstrap" +name = "Worktree Bootstrap" +version = "0.1.0" +min_herdr_version = "0.7.0" +description = "Prepare new worktrees" +platforms = ["linux", "macos", "windows"] + +[[build]] +command = ["bun", "install"] + +[[actions]] +id = "bootstrap" +title = "Bootstrap worktree" +contexts = ["workspace"] +command = ["bun", "run", "bootstrap.ts"] + +[[events]] +on = "worktree.created" +command = ["bun", "run", "bootstrap.ts"] + +[[panes]] +id = "board" +title = "Worktree board" +placement = "overlay" +command = ["bun", "run", "board.ts"] + +[[link_handlers]] +id = "github-issue" +title = "Open GitHub issue" +pattern = "^https://github\\.com/[^/]+/[^/]+/(issues|pull)/[0-9]+$" +action = "bootstrap" +``` + +`min_herdr_version` は必須です。このフィールドが欠落している、無効、または実行中の Herdr バイナリより新しい場合、サーバーはプラグインのリンクを拒否します。 + +プラグインが対応する OS 識別子 (`linux`、`macos`、`windows`) をトップレベルの `platforms` で宣言してください。ローカル開発では `platforms` の省略が許されます — `plugin.link` は成功しますが、レスポンスに警告が含まれます。個々のビルドコマンド、アクション、イベントフック、ペイン、リンクハンドラーは独自の `platforms` を宣言してプラグインレベルのリストを上書きできます。省略時はプラグインから継承します。実効的な platforms が現在の OS を含まないアクションの呼び出しやペインのオープンは `platform_unsupported` エラーを返します。 + +リンク済みプラグインの一覧、有効化、無効化、リンク解除: + +```json +{"id":"req_plugin_list","method":"plugin.list","params":{}} +{"id":"req_plugin_disable","method":"plugin.disable","params":{"plugin_id":"example.worktree-bootstrap"}} +{"id":"req_plugin_enable","method":"plugin.enable","params":{"plugin_id":"example.worktree-bootstrap"}} +{"id":"req_plugin_unlink","method":"plugin.unlink","params":{"plugin_id":"example.worktree-bootstrap"}} +``` + +アクションはリンクされたマニフェストから解決されます。`plugin.action.list` はインストール済みプラグイン全体のすべてのアクションを返します。絞り込むには `plugin_id` を渡してください。 + +```json +{"id":"req_plugin_actions","method":"plugin.action.list","params":{}} +{"id":"req_plugin_actions_filtered","method":"plugin.action.list","params":{"plugin_id":"example.worktree-bootstrap"}} +``` + +`plugin.action.list` は、プラグインレベルの継承を適用した後の各アクションの実効的な `platforms` を返します。 + +修飾 id または素のアクション id でアクションを呼び出す: + +```json +{"id":"req_plugin_invoke","method":"plugin.action.invoke","params":{"action_id":"example.worktree-bootstrap.bootstrap","context":{"invocation_source":"keybinding"}}} +``` + +`plugin.action.invoke` はマニフェストのアクションを解決し、マニフェストのコマンドを起動し、Herdr が構築した呼び出しコンテキストと、開始されたコマンドのログレコードを返します。欠けているコンテキストフィールドは、アクティブなワークスペース、タブ、フォーカス中のペイン、worktree の出自情報、リクエスト id から補完されます。無効化されたプラグインのアクションの呼び出しは `plugin_disabled` エラーを返します。 + +Herdr は `HERDR_SOCKET_PATH`、`HERDR_BIN_PATH`、`HERDR_ENV=1`、`HERDR_PLUGIN_ID`、`HERDR_PLUGIN_ROOT`、`HERDR_PLUGIN_CONFIG_DIR`、`HERDR_PLUGIN_STATE_DIR`、`HERDR_PLUGIN_CONTEXT_JSON`、そして利用可能な `HERDR_WORKSPACE_ID`、`HERDR_TAB_ID`、`HERDR_PANE_ID` の値を注入します。アクションコマンドは加えて `HERDR_PLUGIN_ACTION_ID` を受け取ります。イベントフックは `HERDR_PLUGIN_EVENT` と `HERDR_PLUGIN_EVENT_JSON` を、ペインコマンドは `HERDR_PLUGIN_ENTRYPOINT_ID` を受け取ります。 + +直近のアクションおよびイベントコマンドのログを一覧する: + +```json +{"id":"req_plugin_logs","method":"plugin.log.list","params":{"plugin_id":"example.worktree-bootstrap","limit":20}} +``` + +イベントフックは、Herdr が `worktree.created` のような一致するイベント名を発行したときに、有効なインストール済みプラグインに対して実行されます。 + +v1 には Herdr が管理するプラグインストレージ API はありません。`HERDR_PLUGIN_CONFIG_DIR` と `HERDR_PLUGIN_STATE_DIR` はパスの発見だけを提供します。ファイル、スキーマ、マイグレーション、クリーンアップはプラグインが所有します。 + +管理されたターミナル UI を開く: + +```json +{"id":"req_plugin_pane","method":"plugin.pane.open","params":{"plugin_id":"example.board","entrypoint":"board","placement":"zoomed","target_pane_id":"w1:p1","env":{"HERDR_ROLE":"board"},"focus":true}} +``` + +`plugin.pane.open` は、インストール済みで有効かつプラットフォーム互換のプラグインを要求し、要求されたマニフェストの `[[panes]]` エントリーポイントを argv ベースのターミナルペインとして起動します。マニフェストのペイン `placement` のデフォルトは `overlay` です。リクエストの `placement` は `overlay`、`popup`、`split`、`tab`、`zoomed` でマニフェストを上書きできます。overlay と popup はアクティブなタイルペインを起動コンテキストとして使います。ポップアップターミナルはセッションモーダルで、タブレイアウトを変更しません。任意の `width` と `height` は、外側のサイズをターミナルセル数または `"80%"` のような割合で指定します。省略した寸法はデフォルトでターミナルの半分になり、小さすぎる値は最小サイズに制限されます。ポップアップはペイン id を持たず、`pane.*` とエージェント API の対象外で、ペインのライフサイクルイベントを発行せず、プラグインのフォーカスコンテキストを背後のタイルペインに維持し、そのプロセスへ `HERDR_PANE_ID` を渡しません。起動は `ok` を返し、`popup.close` はアクティブなポップアップを閉じ、ポップアップがない場合は `popup_not_open` を返します。split と zoomed は既存のペインを対象とし、tab はワークスペースを対象にできます。split、tab、zoomed、overlay は通常の Herdr ペインとして振る舞い、`plugin.pane.focus` と `plugin.pane.close` は引き続きそれらのペインに作用します。 + +## ソケットトランスポート + +Herdr はローカルソケット上の改行区切り JSON を使います。Unix では Unix ドメインソケットです。Windows では名前付きパイプです。 + +1 行につき 1 リクエストを送ります: + +```json +{"id":"req_1","method":"ping","params":{}} +``` + +成功レスポンスは同じ `id` を含みます: + +```json +{"id":"req_1","result":{"type":"pong"}} +``` + +イベント購読は、最初のレスポンスの後も接続を開いたままにします。 + +## ソケットパス + +デフォルトのソケットは Herdr の設定ディレクトリの下にあります。 + +名前付きセッションは別のソケットを持ちます: + +```text +~/.config/herdr/herdr.sock +~/.config/herdr/sessions/<name>/herdr.sock +``` + +解決順: + +1. 明示的な CLI の `--session <name>` +2. `HERDR_SOCKET_PATH` +3. `HERDR_SESSION=<name>` +4. デフォルトセッションのソケット + +`HERDR_SOCKET_PATH` は低レベルな上書きにのみ使ってください。 + +プラグインでは、移植性のある Windows の挙動が必要なときは `HERDR_BIN_PATH` と CLI ラッパーの呼び出しを推奨します。生のソケットクライアントは、プラットフォームネイティブのローカルソケット形式を使う責任を負います。 + +## エージェント状態の報告 + +インテグレーションは `pane.report_agent` でエージェント状態を報告します。 + +```json +{ + "id": "req_1", + "method": "pane.report_agent", + "params": { + "pane_id": "w1:p1", + "source": "custom:docs", + "agent": "docs-bot", + "state": "working", + "message": "building docs" + } +} +``` + +`state` は意味的です。wait、通知、ロールアップに影響します。表示専用の値はメタデータで別に報告します。 + +セッションのみの公式インテグレーションは、`pane.report_agent_session` でネイティブセッション参照を報告します。状態を報告するインテグレーションは、`pane.report_agent` にネイティブセッション参照を含めることもできます。状態に依存しないセッション報告は、wait、通知、ロールアップに影響しません。 + +```json +{ + "id": "req_2", + "method": "pane.report_agent_session", + "params": { + "pane_id": "w1:p1", + "source": "herdr:codex", + "agent": "codex", + "agent_session_id": "..." + } +} +``` + +Herdr に保存されたネイティブセッション参照があるとき、`pane.get`、`pane.list`、`agent.get`、`agent.list` は読み取り専用の `agent_session` オブジェクトを公開します: + +```json +{ + "agent_session": { + "source": "herdr:codex", + "agent": "codex", + "kind": "id", + "value": "..." + } +} +``` + +ネイティブセッション参照が保存されていない場合、このフィールドは省略されます。 + +`pane.get`、`pane.list`、`agent.get`、`agent.list` は、ペインの PTY を現在制御しているプロセスの cwd を解決できるときに `foreground_cwd` も公開します。既存の `cwd` フィールドは、ラベル、follow-cwd 挙動、復元されたセッション状態に使われるペイン/ワークスペースの cwd のままです。 + +`PaneInfo` と `AgentInfo` は、任意の `terminal_title` と `terminal_title_stripped` フィールドを公開します。`terminal_title` は、安全性のための正規化を行った最新の OSC 0/2 タイトルです。`terminal_title_stripped` は、先頭にある認識済みのアクティビティまたはスピナーのグリフ 1 つと、それに続く空白を除去します。これらはサーバーが所有する値で、コールドリスタートをまたいで保持されず、メタデータの `title` や意味的なエージェント状態から独立しています。 + +ユーザーフックが Herdr インテグレーションからライフサイクル状態を奪わずに表示をカスタマイズしたいときは `pane.report_metadata` を使ってください。 + +```json +{ + "id": "req_2", + "method": "pane.report_metadata", + "params": { + "pane_id": "w1:p1", + "source": "user:claude-title", + "agent": "claude", + "title": "Refactor auth middleware", + "display_agent": "Claude: auth", + "state_labels": { + "working": "refactoring auth", + "idle": "ready", + "done": "review ready" + }, + "tokens": { + "summary": "refactor auth", + "model": "opus" + }, + "ttl_ms": 3600000 + } +} +``` + +メタデータ報告は表示専用です。有効なメタデータは、ペインのタイトル、表示されるエージェント名、可視の状態ラベル、任意の名前付きトークンを上書きできます。`working`、`blocked`、`idle`、wait、通知、ロールアップは引き続き意味的な状態から得られます。エージェントネイティブのセッション復元は、保存された公式セッション参照から得られます。`agent` は表示フィールドを権威あるエージェントラベルに対して保護する任意のガードで、`applies_to_source` も表示フィールドをアクティブなライフサイクル権威ソースに対して保護します。これらのガードはトークンパッチには適用されません。トークンのクリアと TTL の更新は報告側が管理します。表示名を変えるには `display_agent` を使います。`state_labels` のキーは `idle`、`working`、`blocked`、`done`、`unknown` のいずれかでなければなりません。 + +トークンマップはリソースごとのパッチです。文字列はキーを設定し、JSON の `null` はクリアし、省略したキーは変わりません。最後に受理された更新が優先されます。任意の TTL は、その報告で更新した各トークンキーに個別に適用されます。ペイントークンは pane と agent の get/list 応答で公開され、エージェントサイドバー行では `$name` として描画できます。1 回の報告で指定できるトークンキーは最大 16 個、ペインまたはワークスペースが保持できるキーは最大 32 個です。トークン名は 1〜32 文字の ASCII 英数字、アンダースコア、ハイフンに限られます。 + +ワークスペーストークンも同じ規約を使います: + +```json +{"id":"req_3","method":"workspace.report_metadata","params":{"workspace_id":"w1","source":"user:jj","tokens":{"jj_status":"2 changes","old":null},"ttl_ms":5000}} +``` + +workspace の get/list 応答は結果の `tokens` マップを公開し、スペースサイドバー行では `$jj_status` のような値を描画できます。変更と TTL の失効は、最新のワークスペーススナップショットを含む `workspace.metadata_updated` を発行します。このメタデータイベントは API 購読者に届きますが、プラグインイベントフックは実行しません。 + +表示テキストは保存前に正規化されます。Herdr は前後の空白を取り除き、制御文字を除去し、`title`、`display_agent`、各状態ラベル、トークン値を 80 文字に制限します。正規化後に空になったトークン値は、そのキーをクリアします。 + +`source` と `applies_to_source` はソース識別子です。80 文字以下で、ASCII の英字、数字、コロン、ドット、アンダースコア、ハイフンのみを含められます。 + +短寿命のメタデータには `ttl_ms` を使ってください。`1` から `86400000` ミリ秒の間でなければなりません。置き換え・クリア・ペインまたはワークスペースのクローズまで残るべきメタデータでは省略します。表示フィールドは従来どおりソース単位で失効し、呼び出しで更新した各トークンには個別の期限が設定されます。トークンメタデータはサーバー再起動後に復元されません。 + +フックが順不同で更新を送る可能性がある場合は `seq` を使ってください。同じ `source` について、最後に受理されたシーケンス以下のシーケンス番号を持つ報告は、API には受理されますがペイン状態には無視されます。ペインまたはワークスペースが存続する間、シーケンス付きトークン報告を送れる異なるソースは最大 32 個です。クリアや失効でもそのソース枠は解放されません。 + +## イベント購読 + +長寿命のストリームが必要なときはイベントを購読します: + +```json +{ + "id": "sub_1", + "method": "events.subscribe", + "params": { + "subscriptions": [ + { "type": "pane.agent_status_changed", "pane_id": "w1:p1", "agent_status": "blocked" } + ] + } +} +``` + +最初のレスポンスは購読の確認応答です。以降の行はプッシュされるイベントです。 + +ワークスペースのイベント購読には `workspace.created`、`workspace.updated`、`workspace.metadata_updated`、`workspace.renamed`、`workspace.moved`、`workspace.reordered`、`workspace.closed`、`workspace.focused` があります。`workspace.metadata_updated` はプラグインイベントフックを実行せずにトークン変更と TTL 失効を報告します。その他のワークスペースイベントは Herdr の UI/ランタイムのライフサイクルを記述します。ワークスペースが worktree グループに属している場合、`workspace.created` は任意の `workspace.worktree` 出自情報を含みます。`workspace.moved` は、移動した `workspace_id`、要求された `insert_index`、更新済みの順序付き `workspaces` リストを含みます。`workspace.reordered` は、アトミックに移動した `workspace_ids`、省略可能な `before_workspace_id`、サーバーが確定した順序付き `workspaces` リストを含みます。削除前に Herdr がまだ識別できる場合、`workspace.closed` は最終的な `workspace` スナップショットを含みます。 +タブのイベント購読には `tab.created`、`tab.closed`、`tab.focused`、`tab.renamed`、`tab.moved` があります。`tab.moved` は、移動した `tab_id`、`workspace_id`、要求された `insert_index`、そのワークスペースの更新済みの順序付き `tabs` リストを含みます。 +ペインのイベント購読には `pane.created`、`pane.updated`、`pane.closed`、`pane.focused`、`pane.moved`、`pane.exited`、`pane.agent_detected`、`pane.output_matched`、`pane.agent_status_changed`、`pane.scroll_changed` があります。ターミナルタイトルの変更は `pane.updated` を発行することがありますが、生のタイトルでスピナーだけが変化し、`terminal_title_stripped` が変わらない場合は発行しません。`pane.scroll_changed` は 1 つの `pane_id` を対象とし、Herdr がスクロールスナップショットの変化を検出するたびに `pane_id`、`workspace_id`、現在の `scroll` 情報を発行します。 +レイアウトのイベント購読には `layout.updated` があります。このイベントは 1 つのタブについて更新済みの `PaneLayoutSnapshot` を運びます。`session.snapshot` でブートストラップするクライアントは、同じ `workspace_id` と `tab_id` のキャッシュ済みレイアウトを置き換えてください。 + +worktree のイベント購読には `worktree.created`、`worktree.opened`、`worktree.removed` があります。worktree イベントは Git チェックアウトのライフサイクルを記述します。`worktree.created` は開かれた `workspace` と作成された `worktree` を含みます。`worktree.opened` は対象の `workspace`、開かれた `worktree`、`already_open` を含みます。`worktree.removed` は `workspace_id`、削除された `worktree`、`forced` を含みます。 + +ライフサイクルイベントには `events.subscribe` を使ってください。ワンショットの wait がサポートされる場合、専用の wait ヘルパーが別途ドキュメント化されます。 + +## ペインの読み取り + +プロトコルクライアントを書いているのでなければ、CLI 経由で `pane.read` を使ってください。 + +```bash +herdr pane read w1:p1 --source visible --lines 80 +herdr pane read w1:p1 --source recent --lines 120 +herdr pane read w1:p1 --source recent-unwrapped --lines 120 +herdr pane read w1:p1 --source detection +``` + +`recent-unwrapped` はソフト折り返しを無視するのでログに便利です。 +`detection` は、エージェントのスクリーン検出が使う下部バッファのスナップショットを返します。 + +## 状態の待機 + +エージェントとスクリプトの協調には wait を使います。 + +```bash +herdr agent wait w1:p1 --until done +herdr agent wait w1:p1 --until blocked +``` + +エージェントの wait は、任意のコマンドの完了ではなく意味的な状態を観測します。 + +## レスポンスの形式 + +成功レスポンスは次のようになります: + +```json +{ + "id": "req_1", + "result": { + "type": "pane_info", + "pane": { + "pane_id": "w1:p1", + "terminal_id": "term_abc123", + "workspace_id": "w1", + "tab_id": "w1:t1", + "focused": true, + "agent_status": "working", + "revision": 42 + } + } +} +``` + +`server.agent_manifests` は、ルールをリロードせずに、アクティブなエージェント検出マニフェストのソースとリモート更新の診断情報を返します: + +```json +{ + "id": "req_1", + "result": { + "type": "agent_manifest_status", + "last_check_unix": 1781043522, + "last_result": "checked", + "manifests": [ + { + "agent": "cursor", + "source": "/home/me/.config/herdr/agent-detection/cursor.toml", + "source_kind": "local override", + "active_version": "2026.06.10.1", + "cached_remote_version": "2026.06.10.1", + "local_override_shadowing_remote": true, + "remote_update_result": "current" + } + ] + } +} +``` + +`last_check_unix`、`last_result`、`active_version`、`cached_remote_version`、`remote_update_result`、`remote_update_error`、`remote_last_checked_unix`、`warning` のようなフィールドは、利用できないときは省略されます。`server.reload_agent_manifests` は、メモリ内のルールキャッシュをリロードした後、同じ `manifests` の項目形式を持つ `agent_manifest_reload` を返します。 + +`agent.explain` は、サーバーのアクティブなマニフェストキャッシュを使って、対象ペインの検出スナップショットを実行中のサーバーで評価します: + +```json +{ + "id": "req_2", + "method": "agent.explain", + "params": { "target": "w1:p1" } +} +``` + +レスポンスには `herdr agent explain --json` が出力するのと同じ explain オブジェクトが含まれます: 最終状態、マニフェストのソースとバージョン、マッチしたルール、評価されたルールの証拠、スキップ状態の理由、idle フォールバックの理由、そして完全なライフサイクルフック権威によってスクリーンルールが権威でなくなったときの `screen_detection_skip_reason` です。 + +クライアントには `agent.explain` をサポートする実行中のサーバーが必要です。Herdr のアップグレード後は、このメソッドに頼る前にサーバーを再起動するかライブハンドオフしてください。 + +エラーは次のようになります: + +```json +{ + "id": "req_1", + "error": { + "code": "not_found", + "message": "pane not found" + } +} +``` + +## プロトコルの安定性 + +Herdr にはクライアント/サーバー互換性のためのプロトコルバージョンがあります。プロトコルの変更は、リリースの互換性を念頭にレビューされます。 + +新しい挙動に依存する前に、`ping` または `herdr status` でサーバーのプロトコルを確認してください。未知のフィールドは寛容に扱ってください。 diff --git a/docs/preview/website/src/content/docs/ja/troubleshooting.mdx b/docs/preview/website/src/content/docs/ja/troubleshooting.mdx new file mode 100644 index 00000000..d1769038 --- /dev/null +++ b/docs/preview/website/src/content/docs/ja/troubleshooting.mdx @@ -0,0 +1,95 @@ +--- +title: トラブルシューティング +description: インストール、ターミナル入力、セッション、キーバインド、リモート接続の一般的な問題を診断します。 +--- + +まずバージョンとセッション状態を確認します: + +```bash +herdr -V +herdr status +``` + +OS、外側のターミナル名とバージョン、ローカルまたはリモートのどちらか、tmux を使用しているかも記録してください。 + +## Windows で CJK IME の UI 位置がずれる、またはカーソルがちらつく + +Herdr は、ConPTY ベースのマルチプレクサーによる画面再描画中にネイティブカーソルがちらついたり、ジャンプしたり、古い位置を表示したりすることを避けるため、ネイティブ Windows と WSL ではデフォルトでセル描画カーソルを使います。セル描画カーソルは、韓国語、日本語、中国語 IME の変換中テキストや候補 UI が使用するネイティブカーソルの基準位置を提供できません。 + +IME の変換中テキストや候補ウィンドウが誤った位置に表示される場合は、ネイティブカーソルを有効にしてください: + +```toml +[ui] +host_cursor = "native" +``` + +ネイティブモードでは IME の基準位置が正しくなりますが、出力中にカーソル表示が乱れることがあります。視覚的に安定したカーソルへ戻すには `host_cursor = "drawn"` を設定するか、設定を削除して Windows のデフォルトを使ってください。現在の制限については [Windows ベータ](/ja/docs/windows-beta/) を参照してください。 + +## Enter、Tab、Backspace が 2 回入力される + +古いターミナルでは、アプリケーションが Kitty キーボードイベント報告を有効にすると、Enter、Tab、Backspace のリリースをプレスと同じバイトとして送ることがあります。送信後は Herdr から重複したバイトを区別できません。 + +上流の修正を含むバージョンへ外側のターミナルを更新してください: + +| ターミナル | 修正済みの最小バージョン | +| --- | --- | +| kitty | 0.33.0 | +| foot | 1.20.0 | +| Alacritty | 0.15.0 | + +長期サポート版 Linux ディストリビューションの古いターミナルパッケージで特に発生します。確認済みの境界キャプチャと上流参照は [Herdr issue #1116](https://github.com/ogulcancelik/herdr/issues/1116) を参照してください。現在のターミナルでも発生する場合は、正確なバージョンと Herdr の外でも発生するかを報告してください。 + +## Option+Left または Option+Right を押すと `;3D` または `;3C` が入力される + +ターミナルは一般に、Alt+Left と Alt+Right を標準の修飾付き矢印シーケンス `ESC[1;3D` と `ESC[1;3C` として送信します。macOS では、まず外側のターミナルで Option を Alt として扱うよう設定する必要があります。シェルがこれらのシーケンスを割り当てていない場合、zsh は残りの文字を `;3D` または `;3C` と表示することがあります。これは kitty、Alacritty、その他のターミナルで発生する可能性があります。Herdr と tmux はどちらも元の修飾付き矢印入力を保持します。 + +すべてのターミナルとネストしたシェルで修飾付き矢印を単語移動として使う場合は、zsh のバインドを明示的に追加します: + +```zsh +bindkey $'\e[1;3D' backward-word +bindkey $'\e[1;3C' forward-word +``` + +kitty の外では動作するのに Herdr 内では動作しないことがあります。これは kitty の自動 zsh インテグレーションが、kitty が直接起動したシェルにだけこれらのバインドを追加し、ターミナルマルチプレクサーが起動したシェルには追加しないためです。kitty の[手動シェルインテグレーション手順](https://sw.kovidgoyal.net/kitty/shell-integration/#manual-shell-integration)に従うか、シェルへ届く前に `kitty.conf` でキーを割り当ててください: + +```text +map alt+left send_text all \x1bb +map alt+right send_text all \x1bf +``` + +ターミナルアプリケーションが Alt+Left と Alt+Right を直接使用する場合があるため、Herdr は修飾付き矢印を意図的に書き換えません。調査の詳細は [Herdr issue #1370](https://github.com/ogulcancelik/herdr/issues/1370) を参照してください。 + +## Herdr を更新したのに実行中のセッションが古い + +バイナリを更新しても、互換性のある実行中サーバーは自動で置き換わらないことがあります。`herdr status` を確認してください。更新済みサーバーを起動するには、セッションを停止して Herdr を再起動します: + +```bash +herdr server stop +herdr +``` + +サーバーを停止するとペインのプロセスも終了します。名前付きセッションでは `herdr session stop <name>` を使います。更新機能、パッケージマネージャー、ライブハンドオフについては[インストール](/ja/docs/install/#update)を参照してください。 + +## `herdr` コマンドが見つからない + +ターミナルを再起動して環境を読み込み直し、Herdr のインストール先が `PATH` に含まれていることを確認してください。パッケージマネージャー経由のインストールは、そのパッケージマネージャーから更新して公開する必要があります。[インストール](/ja/docs/install/#verify)を参照してください。 + +## 直接キーバインドが動作しない + +OS または外側のターミナルが Herdr に届く前にキーを処理している可能性があります。そのレイヤーでキーを解放するか、別のバインドを選んでください。既知の競合と安全なデフォルトは[キーボード](/ja/docs/keyboard/#going-prefix-free)を参照してください。 + +## リモートアタッチで認証できない + +まず `ssh <host>` で通常の OpenSSH 接続が動作することを確認してください。非対話シェル、CI、モバイルターミナルでパスフレーズ付きキーを使う場合は、リモートアタッチの前にキーを `ssh-agent` へ読み込んでください。[永続化とリモートアクセス](/ja/docs/persistence-remote/#remote-attach-over-ssh)を参照してください。 + +## 診断ログを探す + +Herdr のログはデフォルトで `~/.config/herdr/` にあります: + +```text +herdr.log +herdr-client.log +herdr-server.log +``` + +詳細なログには `HERDR_LOG=herdr=debug` を設定します。問題を報告するときは現在のログとローテーション済みログを含めてください。[設定](/ja/docs/configuration/#logs)を参照してください。 diff --git a/docs/preview/website/src/content/docs/ja/windows-beta.mdx b/docs/preview/website/src/content/docs/ja/windows-beta.mdx new file mode 100644 index 00000000..ee0f8567 --- /dev/null +++ b/docs/preview/website/src/content/docs/ja/windows-beta.mdx @@ -0,0 +1,124 @@ +--- +title: Windows ベータ +description: ネイティブ Windows 対応の状況、サポートされるワークフロー、既知の制限。 +--- + +ネイティブ Windows 対応は実験的ベータです。 + +Windows 上の Herdr は、Herdr が本来前提としていた Unix の PTY モデルではなく、ConPTY と Windows のプロセス/ランタイム挙動を使います。Herdr の機能には Windows にきれいに対応付くものもあれば、そうでないものもあります。このプレビューは、すべての Linux/macOS 機能が Windows で完全にサポートされることを約束するものではありません。 + +ベータの目的は実際の使用から学ぶことです: インストールの成功率、ペインの信頼性、エージェントのワークフロー、バグの量、不足している機能、そして Windows ユーザーが Herdr から十分な価値を得られているかどうか。そのフィードバックに基づき、Windows 対応は安定版に昇格するか、成熟するまでプレビュー限定のままか、メンテナンスコストに見合わなければ縮小される可能性があります。 + +ネイティブ Windows ベータビルドは PowerShell でインストールします: + +```powershell +powershell -ExecutionPolicy Bypass -c "irm https://herdr.dev/install.ps1 | iex" +``` + +Windows ベータビルドはプレビューチャンネルでのみ提供されます。Windows では Herdr が設定を変更せずにデフォルトでプレビューを使います。インストーラーはリリースを `%USERPROFILE%\.herdr\packages\standalone\releases` に保存し、`%LOCALAPPDATA%\Programs\Herdr\bin` を現在のリリースに向け、実行中のプロセスがアップデートを妨げないよう少数の古いリリースを保持します。 + +内部ベータテスト向けに、`HERDR_MANIFEST_URL` でインストーラーを Herdr の安定版/プレビューマニフェストではなくカスタムマニフェストに向けられます。 + +## ベータでサポート + +| 機能 | 状況 | +| --- | --- | +| ローカル永続セッション | ベータ | +| ConPTY によるネイティブペイン | ベータ | +| Windows Terminal / PowerShell アプリからのアタッチ | ベータ | +| `cmd.exe` ペイン | ベータ | +| 起動時 cwd とワークスペースラベル | ベータ | +| ペイン起動時の cwd | ベータ | +| エージェントコマンドの発見 | ベータ | +| エージェントのセルフレポートインテグレーション | ベータ | +| エージェントのプロセスツリー検出 | ベータ | +| 既知の cwd からの Git/worktree 検出 | ベータ | +| プラグイン | プレビュー | +| ペイン画面履歴 | ベータ | +| ネストされた起動のオーバーライド | ベータ | + +Windows のエージェントプロセス検出は、ペインのシェルの子孫プロセスをスキャンし、直接のエージェントと一般的なコマンドラッパーを認識します。Codex、Claude などのエージェントには有用ですが、Unix のフォアグラウンドプロセスグループ検出と同じものではありません。 + +プラグインは、プレビューとしてマニフェストのプラットフォームに `windows` をサポートします。GitHub インストール、ローカルリンク、ビルドコマンド、アクション、イベント、プラグインペインは Windows ではベストエフォートです。コマンドは argv 形式で Windows 互換である必要があります。`npm`、`bun`、`node` のような Node パッケージの shim は `PATH` にあれば動作するはずですが、`sh` や Bash を使う Unix 専用の例には Windows 向けの代替が必要です。プラットフォームフィルターは、未サポートのビルドコマンドをスキップし、未サポートのアクションやペインには `platform_unsupported` を返します。 + +## 部分的なサポート + +| 機能 | 状況 | +| --- | --- | +| シェルで `cd` した後のライブ cwd | 部分的 | +| シェルインテグレーション/OSC7 によるライブ cwd | ベータ | +| エージェントへのクリップボード画像貼り付け | 未検証 | +| CJK IME の変換候補位置 | 部分的 | +| Kitty graphics のレンダリング | 未検証 | +| ホストカーソル描画 | 部分的 | + +Herdr はペインを正しいディレクトリで起動でき、Herdr を起動したディレクトリから最初のワークスペースを作成できます。起動後の PowerShell のディレクトリ変更は別問題です: Herdr が調べられるプロセスのフィールドは、その後の論理的な `cd` の変化を確実には追跡しません。ライブ cwd の報告には Herdr インテグレーションかプロンプトのシェルインテグレーションを使ってください。 + +Windows Terminal は特定のエージェント向けに画像貼り付け経路をサポートしているかもしれませんが、Herdr 自身のクリップボード画像リーダーはまだ Windows に配線されていません。Windows のクリップボードブリッジが実装・テストされるまで、`alt+v` の画像貼り付けは未検証として扱ってください。リモートクリップボードの画像ブリッジは別機能で、引き続き Unix/macOS の `herdr --remote` に紐づいています。 + +Kitty graphics は実験的なままで、まだ Windows でのサポートを謳っていません。Windows Terminal での画像レンダリングを特にテストしているのでない限り、`experimental.kitty_graphics = false` のままにしてください。 + +## 既知の注意点 + +### カーソル描画 + +Herdr はネイティブ Windows のペインに ConPTY を使用します。現在の Windows のターミナルカーソル処理では、マルチプレクサーが画面を再描画している途中のカーソル位置が表示されることがあります。そのため、出力中にネイティブカーソルがちらついたり、ジャンプしたり、古い位置に一瞬残ったりします。この挙動は他のネイティブ Windows ターミナルマルチプレクサーや、VT カーソル位置を直接連続更新するテストでも再現するため、ネイティブカーソルの挙動を維持したまま Herdr 側で完全に解消することはできません。 + +視覚的な安定性を優先するため、デフォルトの `host_cursor = "auto"` はネイティブ Windows と WSL で Herdr のカーソルを端末セルの内容として描画します。それ以外の Linux と macOS クライアントでは、引き続きネイティブの端末カーソルを使います。Windows の描画カーソルは安定していて点滅しませんが、外側ターミナルのネイティブな点滅、形状、カーソル色は反映されません。 + +描画カーソルは、Windows が IME の変換中テキストや候補ウィンドウの位置決めに使う実際のカーソルではありません。そのため、韓国語、日本語、中国語 IME の UI が誤った位置に表示されることがあります。この問題が発生する場合は、外側ターミナルのカーソルを有効にしてください: + +```toml +[ui] +host_cursor = "native" +``` + +ネイティブモードでは IME の基準位置が正しくなりますが、出力中にカーソルのちらつき、ジャンプ、古い位置の残像が再び発生することがあります。これは現在の Windows ベータにおける互換性上のトレードオフです。 + +### キーボードとマウス + +Windows のターミナルは、修飾キー付きのキーをすべて同じ形で報告するわけではありません。Herdr は Windows Terminal と Windows 上の Alacritty ではマウス報告と `ctrl+j` を保持しますが、`shift+enter` は外側ターミナルが修飾付き Enter として区別して報告する場合にだけ動作します。Windows またはターミナルがそれを通常の Enter として報告する場合、Herdr は通常の Enter を転送します。 + +古い Windows 10 ビルドのシステム ConPTY は、Kimi や Pi などのエージェントが使用する Kitty キーボードプロトコルのシーケンスを失うため、プレビューパッケージには Microsoft の最新のアプリローカル ConPTY ランタイムが含まれます。バンドル版ランタイムとの互換性問題を診断する場合に限り、Herdr を起動する前に `HERDR_WINDOWS_CONPTY=system` を設定してください。 + +## コピーと貼り付け + +Herdr のペインテキストコピーは Windows ベータで動作します。ペイン内でテキストをドラッグ選択すると Herdr 経由でコピーされます。 + +テキストの貼り付けには Windows Terminal で `ctrl+shift+v` を使ってください。複数行テキストの貼り付けはブラケット付きなので、シェルやエージェントのプロンプトは各行を個別に送信せず、ひとつの貼り付けとして受け取ります。`shift` を押しながら右クリックすると、クリックを Herdr に送らずに外側ターミナルの貼り付けアクションを使えます。 + +## Windows ベータで未サポート + +| 機能 | 状況 | +| --- | --- | +| ダイレクトターミナルアタッチ | 未サポート | +| Windows バイナリからの `herdr --remote` | 未サポート | +| ライブサーバーハンドオフ | 未サポート | +| Unix ファイルディスクリプタのハンドオフ | 未サポート | +| Unix フォアグラウンドプロセスグループ | 未サポート | +| リモートクリップボード画像ブリッジ | 未サポート | +| プレフィックスによる入力ソース切り替え | 未サポート | +| 署名済みバイナリ / SmartScreen 回避 | 未サポート | + +Windows からのリモート作業は、サーバーに SSH してそこで `herdr` を実行してください: + +```powershell +ssh you@server +herdr +``` + +このモードでは Herdr はリモートホスト上で動きます。ネイティブ Windows の `herdr --remote` はベータの範囲外です。 + +Windows のアップデートは Windows インストーラー経由で行われ、バージョン付きインストールジャンクションを更新します。アップデート後は実行中の Herdr セッションを再起動してください。ライブハンドオフは Unix 専用です。 + +## Windows ベータの問題を報告する + +以下を含めてください: + +- Herdr のバージョン。 +- Windows のバージョン。 +- ターミナルアプリ。 +- シェル (PowerShell、cmd など)。 +- 名前付き `HERDR_SESSION` を使っていたかどうか。 +- 関連する Herdr のログ。 +- 正確な再現手順。 diff --git a/docs/preview/website/src/content/docs/keyboard.mdx b/docs/preview/website/src/content/docs/keyboard.mdx new file mode 100644 index 00000000..22283518 --- /dev/null +++ b/docs/preview/website/src/content/docs/keyboard.mdx @@ -0,0 +1,112 @@ +--- +title: Keyboard +description: What the prefix is, which bindings to learn first, and how to go prefix-free. +--- + +:::tip[Coming from tmux or zellij?] +You already know this model. Jump to the [keybinding reference](/docs/configuration/#keybindings) for the full default keymap and config syntax. +::: + +Herdr is mouse-native. You can click panes, tabs, workspaces, and agents, drag split borders, and use right-click menus without learning a single keybinding. Keyboard control is an optional layer, not a requirement. + +## What the prefix is + +A terminal multiplexer sits between your terminal and the programs running inside it. Those programs already use most key combinations: `ctrl+c` interrupts, `ctrl+r` searches history, editors claim nearly everything else. If Herdr grabbed common keys directly, it would break the programs inside it. + +The prefix solves this. Press the prefix key, default `ctrl+b`, and the next keypress goes to Herdr instead of your terminal. `prefix+c` means: press `ctrl+b`, release, then press `c`. One reserved key instead of dozens. + +Press `prefix+?` at any time to see every active binding. Press `/` in the keybind help to filter actions and shortcuts; use Backspace to edit the filter or `ctrl+u` to clear it. + +## Learn these five first + +| Action | Key | +| --- | --- | +| New tab | `prefix+c` | +| Split right / down | `prefix+v` / `prefix+minus` | +| Move between panes | `prefix+h/j/k/l` | +| Workspace navigation | `prefix+w` | +| Detach, leave everything running | `prefix+q` | + +These cover most daily movement. Everything else can stay on the mouse. + +## The rest, by task + +Panes: + +| Action | Key | +| --- | --- | +| Zoom the focused pane | `prefix+z` | +| Close pane | `prefix+x` | +| Swap panes | `prefix+shift+h/j/k/l` | +| Resize mode | `prefix+r` | +| Copy mode | `prefix+[` | + +Tabs: + +| Action | Key | +| --- | --- | +| Next / previous tab | `prefix+n` / `prefix+p` | +| Jump to tab 1–9 | `prefix+1..9` | +| Rename tab | `prefix+shift+t` | +| Close tab | `prefix+shift+x` | + +Workspaces and session: + +| Action | Key | +| --- | --- | +| New workspace | `prefix+shift+n` | +| Rename workspace | `prefix+shift+w` | +| Close workspace | `prefix+shift+d` | +| Goto picker | `prefix+g` | +| Toggle sidebar | `prefix+b` | + +The full keymap and the binding syntax live in the [keybinding reference](/docs/configuration/#keybindings). + +## Copy mode + +Press `prefix+[` to enter copy mode for the focused pane. Use `h/j/k/l`, tmux-style `w/b/e`, `{`/`}`, `PageUp`/`PageDown`, `ctrl+b`/`ctrl+f`, and `ctrl+u`/`ctrl+d` to move. Press `/` or `?` for forward or backward literal search, then `n` or `N` to repeat in the same or opposite direction. Search is case-insensitive unless the query contains an uppercase letter. Use `v` or Space to start a selection, `y` or Enter to copy it, and `q` or Esc to leave without copying. Esc clears an active selection or search before exiting. Copy mode does not pause the pane process: output remains live, follows at the bottom, and stays pinned when you navigate into history. The configured prefix keeps its normal meaning in copy mode; with the default prefix, `ctrl+b` enters prefix mode instead of paging up, so use a different prefix if you want `ctrl+b` for copy-mode page-up. Mouse drag-select copies without entering copy mode at all. + +## Change anything + +Every binding is configurable, including the prefix itself: + +```toml +[keys] +prefix = "ctrl+a" +``` + +## Going prefix-free + +You can bind Herdr actions to direct chords that need no prefix at all. The hard part is knowing which chords are safe, because terminals, shells, and desktop environments already own most of the keyboard. + +Any chord works as a binding: `ctrl+j`, `alt+k`, whatever fits your hands. But a chord has to survive three layers before Herdr sees it: your operating system, your outer terminal (Ghostty, iTerm2, and others ship their own defaults), and the programs running inside the pane. `ctrl+j` reaches Herdr fine, but shells and editors treat it as enter. `alt+k` is free on Linux, but macOS composes it into a special character in most terminals. If you pick chords from these families, double-check them against your own terminal and OS shortcuts. + +We mapped the default keybindings of Ghostty, iTerm2, Terminal.app, kitty, WezTerm, Alacritty, Warp, Windows Terminal, GNOME Terminal, and Konsole, plus the global shortcuts of GNOME and KDE. One modifier family is almost untouched everywhere: `ctrl+alt`. Terminals leave it free, it is not affected by the macOS option-key composing behavior that blocks plain `alt` chords, and it transmits even in terminals without a modern keyboard protocol. It is the safe default recommendation; the choice stays yours. + +This setup keeps the prefix bindings working and adds direct chords on top: + +```toml +[keys] +focus_pane_left = ["prefix+h", "ctrl+alt+h"] +focus_pane_down = ["prefix+j", "ctrl+alt+j"] +focus_pane_up = ["prefix+k", "ctrl+alt+k"] +focus_pane_right = ["prefix+l", "ctrl+alt+l"] +previous_tab = ["prefix+p", "ctrl+alt+["] +next_tab = ["prefix+n", "ctrl+alt+]"] +new_tab = ["prefix+c", "ctrl+alt+c"] +split_vertical = ["prefix+v", "ctrl+alt+d"] +split_horizontal = ["prefix+minus", "ctrl+alt+shift+d"] +zoom = ["prefix+z", "ctrl+alt+z"] +``` + +A few `ctrl+alt` chords are taken elsewhere. Avoid these: + +| Chord | Owned by | +| --- | --- | +| `ctrl+alt+arrows` | GNOME workspace switching, Ghostty and Konsole defaults | +| `ctrl+alt+t` | "Launch terminal" on Ubuntu and Fedora | +| `ctrl+alt+l` / `ctrl+alt+a` | KDE lock screen / attention window | +| `ctrl+alt+s` / `ctrl+alt+u` | Konsole | +| `ctrl+alt+f1..f12` | Linux virtual console switching | + +If a direct chord does nothing, your terminal or desktop environment consumed it before Herdr could see it. Rebind either side: free the chord in the terminal's settings, or pick another chord in Herdr. diff --git a/docs/preview/website/src/content/docs/marketplace.mdx b/docs/preview/website/src/content/docs/marketplace.mdx new file mode 100644 index 00000000..09a27b2d --- /dev/null +++ b/docs/preview/website/src/content/docs/marketplace.mdx @@ -0,0 +1,53 @@ +--- +title: Marketplace +description: Discover community Herdr plugins on GitHub, and get your own plugin listed. +--- + +The Herdr plugin marketplace is a discoverable index of community plugins. +Browse it at [herdr.dev/plugins](/plugins/). It is an automatic index of public +GitHub repositories, not a reviewed catalog. + +## Browse plugins + +The [marketplace](/plugins/) lists every public repository tagged with the +GitHub topic `herdr-plugin`. Search by name, owner, description, or language, +and sort by popularity, recent activity, or newest. Each listing links straight +to its source repository on GitHub. + +Discovery is automatic and unreviewed. A listing means a repository tagged +itself, not that Herdr vetted it, so the +[trust guidance](/docs/plugins/#trust-and-security) applies before you install +anything. + +## Install a plugin + +The marketplace adds discovery on top of installation; it does not replace it. +Install any plugin straight from GitHub: + +```bash +herdr plugin install owner/repo[/subdir...] +``` + +Publish a normal public GitHub repository with a `herdr-plugin.toml` manifest at +its root, or in a subdirectory, and that command works. See +[Plugins](/docs/plugins/) for the manifest and authoring reference. + +## Get your plugin listed + +Add the GitHub topic `herdr-plugin` to a public repository. That topic is the +only signal the index uses, so tagging a public plugin is all it takes. The +index refreshes automatically every 30 minutes, so a newly tagged repository +shows up shortly after, and one that drops the topic disappears on the next +refresh. + +## What a listing shows + +Each card shows GitHub repository metadata: the repository name and owner, its +description, star count, primary language, and the time it was last pushed, with +a link back to the source. The index reads this from GitHub's repository search, +so keeping your repository description and topics accurate is what makes your +listing useful. + +The index does not parse `herdr-plugin.toml` yet, so manifest fields like the +plugin `id`, declared `platforms`, and `min_herdr_version` are not shown in v1. +Forks and archived repositories are excluded from the list. diff --git a/docs/preview/website/src/content/docs/persistence-remote.mdx b/docs/preview/website/src/content/docs/persistence-remote.mdx new file mode 100644 index 00000000..a365ac88 --- /dev/null +++ b/docs/preview/website/src/content/docs/persistence-remote.mdx @@ -0,0 +1,165 @@ +--- +title: Persistence and remote access +description: Detach from Herdr, reattach later, use named sessions, and connect over SSH. +--- + +Herdr keeps panes running in a background server. Your terminal client can detach and reconnect later. + +For the local, SSH, and `herdr --remote` workflows, see [How to work with Herdr](/docs/how-to-work/). + +## Detach and reattach + +Detach the client with `ctrl+b q`; panes and agents keep running. Reattach by running `herdr` again. Stop the session and its panes with `herdr server stop`. + +When Herdr starts again after a full server stop, it restores the saved session shape. For what survives detach, server restart, screen history replay, native agent session restore, and live handoff, see [Session state and restore](/docs/session-state/). + +## Named sessions + +Use named sessions when you want independent Herdr servers. + +```bash +herdr session list +herdr session attach work +herdr session attach side-project +herdr session stop work +herdr session delete side-project +``` + +A named session has its own panes, tabs, workspaces, sockets, and runtime state. It still shares the same global config file. + +Use `--json` for scripts: + +```bash +herdr session list --json +herdr session stop work --json +herdr session delete side-project --json +``` + +## Remote attach over SSH + +There are two remote modes; [How to work with Herdr](/docs/how-to-work/) compares them. SSH to the server and run `herdr` there for the tmux-style path. Or attach through SSH from your local machine: + +```bash +herdr --remote workbox +herdr --remote ssh://you@server:2222 +``` + +In this mode your local Herdr is a thin client. It connects over SSH, starts or attaches to the remote Herdr server, and streams the UI back to your local terminal. Because the client runs locally, Herdr can bridge local desktop features such as image clipboard paste into the remote session by copying the image to a remote temp file and pasting that path. + +By default, `herdr --remote` uses your local Herdr keybindings for that attach. This keeps local muscle memory even when the remote server has different config. The local keybindings are a snapshot from attach time; detach and reattach after editing local keybindings. Use `--remote-keybindings server` when you want the remote server config instead. Local custom command keybindings are not sent, because those commands would run on the remote host. + +For repeat targets, use your SSH config: + +```text +Host workbox + HostName server.example.com + User you + Port 2222 +``` + +Then attach with: + +```bash +herdr --remote workbox +``` + +Remote attach supports Linux and macOS hosts on x86_64 and aarch64. Herdr checks the remote platform, prefers a matching `herdr` already on the remote `PATH`, then checks common direct, Homebrew, mise, and Nix profile install paths. If no matching binary exists, interactive runs prompt to install one to `~/.local/bin/herdr`; non-interactive runs fail instead of modifying the host. If `~/.local/bin` is not on the remote `PATH`, Herdr warns after install. + +Native Windows `herdr --remote` is not part of the Windows beta. From Windows, SSH into the server and run `herdr` there. + +By default, `herdr --remote` runs remote setup and the bridge through a temporary SSH config that includes your SSH config first, then adds fallback keepalive settings and a private per-attach control socket for connection reuse. Existing user keepalive settings win. Set `[remote].manage_ssh_config = false` to use plain `ssh` without Herdr's generated config or control socket. + +Remote attach uses your normal OpenSSH authentication. If the target uses a passphrase-protected key in a non-interactive shell, script, CI job, or mobile terminal that cannot show the passphrase prompt, load the key into ssh-agent first: + +```bash +ssh-add +herdr --remote workbox +``` + +For any remote authentication failure, verify plain SSH access first with `ssh workbox`, then run `herdr --remote workbox` again. + +By default, remote attach uses the normal restart/stop flow if it needs to replace or restart a running remote server. To opt into experimental live handoff for a supported running remote server, pass `--handoff`: + +```bash +herdr --remote workbox --handoff +``` + +If you SSH into the server first and run `herdr` there, Herdr runs entirely on the server. That mode is useful and simple, but it cannot access your local desktop clipboard beyond normal terminal text paste. + +When your local and remote platforms match, Herdr can copy the current local binary for direct installs. For Homebrew, mise, and Nix installs, or when the platforms differ, it downloads the matching release asset for the current client version from `https://herdr.dev/latest.json`. + +For local builds or custom binaries, set `HERDR_REMOTE_BINARY` to a local file path before running remote attach. + +```bash +HERDR_REMOTE_BINARY=target/release/herdr herdr --remote workbox +``` + +## Remote named sessions + +Use `--session` with `--remote` to attach to a named session on the remote host: + +```bash +herdr --remote workbox --session agents +``` + +## Direct terminal attach + +Full Herdr attach opens the whole workspace UI. Direct attach opens one server-owned terminal in your current terminal. + +Direct terminal attach is Unix-only in the Windows beta. + +Attach by agent target: + +```bash +herdr agent attach reviewer +``` + +Attach by terminal ID: + +```bash +herdr terminal attach term_abc123 +``` + +Direct attach streams the current rendered terminal state, then live ANSI frames. Input goes straight to that terminal. + +Detach with `ctrl+b q`. Send a literal `ctrl+b` with `ctrl+b ctrl+b`. + +Only one writable direct attach client owns input and resize for a terminal. Use `--takeover` to replace an existing owner: + +```bash +herdr terminal attach term_abc123 --takeover +``` + +For third-party bridges that only need rendered terminal bytes, use a read-only +terminal session observer: + +```bash +herdr terminal session observe w1:p1 --cols 120 --rows 40 +``` + +It prints newline-delimited JSON `terminal.frame` records with base64 ANSI +bytes, then a `terminal.closed` record when the server closes the stream. +Multiple observers can watch the same terminal without taking input, resize, +scroll, or takeover ownership. + +For an interactive bridge, use a writable terminal session controller: + +```bash +herdr terminal session control w1:p1 --takeover --cols 120 --rows 40 +``` + +Control mode prints the same newline-delimited frame records and reads +newline-delimited JSON commands on stdin. `terminal.input` sends text or +base64 bytes, `terminal.resize` changes the controller viewport, +`terminal.scroll` scrolls the attached viewport, and `terminal.release` closes +the controller. Only one controller owns input and resize at a time. + +## Single-process escape hatch + +Use `--no-session` to run Herdr without the background server/client split: + +```bash +herdr --no-session +``` + +This is mainly an escape hatch for debugging or compatibility. The default persistent session mode is the normal path. diff --git a/docs/preview/website/src/content/docs/plugins.mdx b/docs/preview/website/src/content/docs/plugins.mdx new file mode 100644 index 00000000..1d93bedc --- /dev/null +++ b/docs/preview/website/src/content/docs/plugins.mdx @@ -0,0 +1,372 @@ +--- +title: Plugins +description: Author local Herdr plugins with manifest actions, event hooks, and panes. +--- + +Herdr plugins are shareable, executable workflow packages. A plugin can be a +Bash script, JavaScript app, Lua script, Rust binary, or any other argv command +your machine can run. Herdr owns the host surface: installation, manifest +validation, keybindings, terminal panes, events, invocation context, and socket +access. The plugin owns its implementation language, dependencies, files, and +durable state. + +Plugins exist so Herdr can stay lean. The core stays focused on terminal +workspaces, panes, agents, and a stable CLI/socket API. Plugins turn that +existing extension surface into reusable workflows that people can build, +install, and share without adding every workflow to Herdr itself. + +A plugin is not an SDK integration. It is a directory with a +`herdr-plugin.toml` manifest and commands Herdr can launch. Herdr validates the +manifest, injects runtime context, starts the declared commands, and records +logs. The commands call back into Herdr through the CLI or socket when they need +to do more work. + +There is no separate plugin SDK or restricted command set. The entire Herdr CLI +is the plugin API: every command in the [CLI reference](/docs/cli-reference/) is +available to a plugin, and anything you can run as `herdr ...` yourself a plugin +can run too. Most plugins should call Herdr through `HERDR_BIN_PATH`, which +points at the running Herdr binary. That keeps plugins portable across Unix +sockets and Windows named pipes. Use the [socket API](/docs/socket-api/) when +you want to send raw JSON requests yourself. + +Runtime action registration and native non-terminal plugin UI are not part of +plugin v1. Actions, event hooks, panes, and link handlers are all declared in +the manifest. + +## Trust and security + +A plugin is ordinary code that runs on your machine. When you install or link +one, its build and runtime commands run as your user, with your environment, and +can call the full Herdr CLI — the same as any extension you add to an editor, +shell, or coding agent. That openness is the point, and a little judgment keeps +it safe. + +Install plugins from authors and repositories you trust, and skim what a new one +does first: the `herdr-plugin.toml` manifest and the scripts or binaries it runs. +`herdr plugin install` shows a preview of the source and the commands it will run +in interactive terminals, so you can review before confirming. Use `--yes` for +sources you already trust, and pin `--ref` when you want a specific revision. + +Herdr validates the manifest and keeps each plugin's config and state in its own +directory, but it does not review or sandbox what a plugin does. Third-party +plugins come from their authors, not from Herdr, so they are yours to vet and run +at your own discretion. + +## Manifest + +The manifest is the contract between Herdr and the plugin. It declares package +metadata, supported platforms, optional build commands, and the entrypoints +Herdr can run. + +```toml +id = "example.layout" +name = "Layout" +version = "0.1.0" +min_herdr_version = "0.7.0" +description = "Apply project layouts" +platforms = ["linux", "macos", "windows"] + +[[build]] +command = ["npm", "ci"] + +[[build]] +command = ["npm", "run", "build"] +platforms = ["linux", "macos"] + +[[startup]] +command = ["node", "dist/restore.js"] + +[[actions]] +id = "apply" +title = "Apply layout" +contexts = ["workspace"] +command = ["node", "dist/apply.js"] + +[[events]] +on = "worktree.created" +command = ["herdr", "workspace", "list"] + +[[panes]] +id = "board" +title = "Project board" +placement = "overlay" +command = ["herdr-board"] + +[[link_handlers]] +id = "github-issue" +title = "Open GitHub issue" +pattern = "^https://github\\.com/[^/]+/[^/]+/(issues|pull)/[0-9]+$" +action = "apply" +``` + +Top-level `id`, `name`, `version`, and `min_herdr_version` are required. +Set `min_herdr_version` to the oldest Herdr version that supports the plugin +APIs, event names, and manifest fields your plugin uses. Herdr refuses to link +or install a plugin when its minimum version is newer than the current binary. +`description` is optional. Plugin ids may use ASCII letters, digits, dot, +colon, underscore, and hyphen. + +Action ids, pane ids, and link handler ids are local ids inside the plugin. +They may use ASCII letters, digits, colon, underscore, and hyphen, but not +dots. Each id type must be unique inside a plugin. Herdr qualifies action ids +as `plugin.id.action` when it needs a globally unique name. + +Use `platforms = ["linux", "macos", "windows"]` to declare where the plugin +can run. Build commands, startup hooks, actions, event hooks, panes, and link +handlers can also declare their own `platforms`; item-level platforms override +the top-level list. Local plugins without top-level `platforms` link with a +warning. + +`command` values are argv arrays. Herdr does not run them through a shell, so +there is no shell expansion unless your command starts a shell itself. Put +language-specific behavior in your script or binary. + +## First plugin + +Start with a directory that contains `herdr-plugin.toml` and one executable +script or program: + +```text +my-plugin/ + herdr-plugin.toml + index.js +``` + +```toml +id = "example.workspace-tools" +name = "Workspace Tools" +version = "0.1.0" +min_herdr_version = "0.7.0" +description = "Small workspace helpers" +platforms = ["linux", "macos", "windows"] + +[[actions]] +id = "list-workspaces" +title = "List workspaces" +contexts = ["workspace"] +command = ["node", "index.js"] +``` + +Inside the command, call back into Herdr with `HERDR_BIN_PATH`: + +```js +const { spawnSync } = require("node:child_process"); + +const herdr = process.env.HERDR_BIN_PATH ?? "herdr"; +const result = spawnSync(herdr, ["workspace", "list"], { + encoding: "utf8", + stdio: ["ignore", "pipe", "pipe"], +}); + +process.stdout.write(result.stdout); +process.stderr.write(result.stderr); +process.exit(result.status ?? 1); +``` + +This example uses Node, but nothing about plugins requires Node. The manifest +could launch Bash, PowerShell, Python, Rust, Go, Lua, Bun, or any other command +available on the user's machine. + +## Install and link + +Install an example plugin: + +```bash +herdr plugin install ogulcancelik/herdr-plugin-examples/agent-telegram-notify +herdr plugin config-dir examples.agent-telegram-notify +herdr plugin list +herdr plugin action list --plugin examples.agent-telegram-notify +``` + +When you are authoring a local plugin, link the working directory instead: + +```bash +herdr plugin link /path/to/plugin +herdr plugin config-dir example.layout +herdr plugin action list --plugin example.layout +herdr plugin action invoke example.layout.apply +herdr plugin pane open --plugin example.layout --entrypoint board +herdr plugin log list --plugin example.layout +``` + +`plugin install` accepts GitHub shorthand only, such as +`owner/repo/subdir`. It clones with `git`, shows a preview in interactive +terminals, runs supported build commands, then stores the checkout under +Herdr-managed plugin data and registers it. Use `--yes` for noninteractive +installs. Reinstalling a GitHub-managed plugin replaces that managed checkout. +Installed and linked plugins, including their enabled state, are global to the +current user and available in every Herdr session. Both `plugin install` and +`plugin link` can register plugins while no Herdr server is running. Plugins +installed only in a named session on Herdr 0.7.3 must be installed or linked +again. Existing plugin config and state remain in place. +Installing over a locally linked plugin is refused; unlink or uninstall the +local plugin first. `plugin install` and `plugin link` create the plugin's +config and state directories, and `plugin config-dir <id>` prints the config +directory for setup docs and shell scripts. + +`plugin uninstall <id-or-source>` unregisters the plugin. For GitHub-managed +installs it also removes the managed checkout, and it accepts either the plugin +id or the same `owner/repo[/subdir...]` shorthand used by install. +`plugin unlink <id>` only unregisters a plugin and leaves files alone, which is +useful for local development. There is no separate `plugin update` in v1; +reinstall from GitHub to refresh a managed plugin. + +The example cookbook repo is `ogulcancelik/herdr-plugin-examples`. It contains +separate example plugins in subdirectories, including `agent-telegram-notify`, +`github-link-preview`, and `dev-layout-bootstrap`. These are examples to copy, +not maintained official plugins. + +## Build commands + +Build commands run during GitHub `plugin install` after confirmation and before +Herdr registers the plugin. If a build command fails, install aborts and the +plugin is not registered. `plugin link` does not run build commands; local +authors build their working tree themselves. Build commands may generate files, +but changing `herdr-plugin.toml` after the install preview aborts install. Build +failures show the plugin id, build index, working directory, command, exit +status or spawn error, and capped stdout/stderr without interpreting tool output. + +Build commands are plain argv commands too, but they do not receive runtime +plugin context or Herdr socket env. Plugin authors should document required +system tools such as `cargo`, `npm`, `bun`, or `lua`; Herdr reports build +failures but does not install missing toolchains. + +## Startup hooks + +`[[startup]]` commands run once for each enabled plugin after Herdr restores the +session and its API socket is ready. They run again when a new server takes over +during live handoff, but not when a client attaches, config reloads, or a plugin +is linked or enabled. Herdr starts them asynchronously and records their +completion in the normal plugin command log. A startup failure does not stop the +server. + +Startup hooks are one-shot initialization commands, not supervised daemons. A +hook should restore plugin-owned state, call any required Herdr APIs, and exit. +For example, a plugin can save a declarative Agent view under +`HERDR_PLUGIN_STATE_DIR`, then read and reapply that view from its startup hook. + +Startup hooks receive the normal runtime plugin environment and +`HERDR_PLUGIN_EVENT=startup`. The install preview lists every startup command so +users can review code that will run automatically. + +## Commands and environment + +Runtime commands run with the plugin directory as their working directory. Herdr +injects `HERDR_SOCKET_PATH`, `HERDR_BIN_PATH`, `HERDR_ENV=1`, +`HERDR_PLUGIN_ID`, `HERDR_PLUGIN_ROOT`, `HERDR_PLUGIN_CONFIG_DIR`, +`HERDR_PLUGIN_STATE_DIR`, `HERDR_PLUGIN_CONTEXT_JSON`, and any available +`HERDR_WORKSPACE_ID`, `HERDR_TAB_ID`, and `HERDR_PANE_ID`. Action commands also +receive `HERDR_PLUGIN_ACTION_ID`; startup and event hooks receive +`HERDR_PLUGIN_EVENT` (`startup` for startup hooks), event hooks additionally +receive `HERDR_PLUGIN_EVENT_JSON`, and pane commands receive +`HERDR_PLUGIN_ENTRYPOINT_ID`. + +`HERDR_PLUGIN_ROOT` is the installed or linked plugin directory. Do not store +user credentials or durable state there, because GitHub-installed plugin roots +are managed source checkouts. Put user-editable config such as `.env` files +under `HERDR_PLUGIN_CONFIG_DIR`, and put local runtime state under +`HERDR_PLUGIN_STATE_DIR`. Herdr creates those directories and seeds +`HERDR_PLUGIN_CONFIG_DIR` from the legacy plugin config locations when present, +but it does not validate, sync, or delete their contents. The plugin owns the +file format and lifecycle. + +`HERDR_PLUGIN_CONTEXT_JSON` can include workspace, tab, focused pane, worktree, +agent, selected text, clicked URL, and link handler fields when they are +available for that invocation. Shell plugins can read the individual env vars +for common ids, or parse the context JSON for the full shape. + +Use `HERDR_BIN_PATH` when a plugin needs to call Herdr portably from Node, +PowerShell, Bash, or another runtime. The raw socket transport behind +`HERDR_SOCKET_PATH` is OS-specific: Unix clients connect to a Unix socket path, +while Windows clients connect to a named pipe. CLI calls through +`HERDR_BIN_PATH` avoid that transport difference. See the +[CLI reference](/docs/cli-reference/) for available commands and +[socket API](/docs/socket-api/) for raw request shapes. + +## Panes + +Manifest pane `placement` defaults to `overlay`, which opens a temporary zoomed +overlay over the active pane and restores the previous focus and zoom when it +closes. A `plugin.pane.open` request can override the manifest placement with +`overlay`, `popup`, `split`, `tab`, or `zoomed`. + +`placement = "popup"` opens a session-modal terminal popup without changing the +tiled layout. It accepts optional `width` and `height` fields in the manifest or +open request; omit them for the default half-size popup, use numbers for outer +terminal-cell dimensions, or use strings like `"80%"` for a percentage of the +terminal area. It receives all terminal input, including Escape, and closes +when the command exits or a `popup.close` request is sent. Dimensions smaller +than the popup minimum are clamped. + +Declare the placement directly on a plugin pane entrypoint when the pane should +always be transient: + +```toml +[[panes]] +id = "picker" +title = "Picker" +platforms = ["linux", "macos"] +placement = "popup" +width = "80%" +height = 20 +command = ["sh", "picker.sh"] +``` + +Split, tab, zoomed, and overlay plugin panes are normal Herdr panes after they +open. Plugins can call standard pane APIs such as `pane.move`, `pane.swap`, +`pane.resize`, and `pane.zoom` through the socket or CLI; Herdr keeps plugin +pane ownership attached to the underlying pane when it moves across tabs or +workspaces. A popup is a singleton session resource rather than a Herdr pane: +it has no pane ID, does not change plugin focus context, emits no pane lifecycle +events, and does not participate in pane, layout, persistence, or agent APIs. +Its process does not receive `HERDR_PANE_ID`; the underlying tiled pane remains +available through `HERDR_PLUGIN_CONTEXT_JSON`. +Opening a popup returns `ui_busy` while Settings, Copy mode, or another Herdr +modal is active, and `plugin.pane.open` returns an `ok` result after launch. + +On Windows, build commands, action commands, and event commands resolve common +`PATHEXT` shims such as `npm.cmd`, `bun.cmd`, and `pnpm.cmd` when the bare +command is on `PATH`. Pane commands use Herdr's normal Windows pane launcher and +must still be valid Windows argv commands. + +## Keybindings + +Bind a key to an installed plugin action: + +```toml +[[keys.command]] +key = "prefix+l" +type = "plugin_action" +command = "example.layout.apply" +description = "apply layout" +``` + +## Link handlers + +Use `[[link_handlers]]` to route modified clicks on matching terminal URLs to a +plugin action instead of opening the URL in the browser. The modified-click +modifier is Control on every platform, including macOS, because captured +terminal mouse reports do not expose Command/Super separately from a plain +click. `pattern` is a Rust regular expression matched against the clicked URL, +and `action` must +name an action declared by the same plugin. Link handler actions receive +`invocation_source = "link_click"`, `clicked_url`, and `link_handler_id` in +`HERDR_PLUGIN_CONTEXT_JSON`; shell plugins can also read +`HERDR_PLUGIN_CLICKED_URL` and `HERDR_PLUGIN_LINK_HANDLER_ID`. Handlers are +checked in manifest order inside each plugin. + +## Storage + +There is no Herdr-managed plugin storage API in v1. Plugins that need durable +state should own their files or database. + +## Marketplace + +Community plugins are discoverable in the [marketplace](/plugins/), an automatic +index of public GitHub repositories tagged with the topic `herdr-plugin`. +Plugins stay ordinary GitHub repositories: publish one with `herdr-plugin.toml`, +then share `herdr plugin install owner/repo[/subdir]`. + +To get a plugin listed, add the GitHub topic `herdr-plugin` to its public +repository. The index refreshes every 30 minutes. See +[Marketplace](/docs/marketplace/) for how discovery works. diff --git a/docs/preview/website/src/content/docs/quick-start.mdx b/docs/preview/website/src/content/docs/quick-start.mdx new file mode 100644 index 00000000..a8f95e7c --- /dev/null +++ b/docs/preview/website/src/content/docs/quick-start.mdx @@ -0,0 +1,69 @@ +--- +title: Quick start +description: Create your first Herdr workspace and run agents in persistent terminal panes. +--- + +If Herdr is not installed yet, see [Install](/docs/install/). Then start Herdr from any project directory: + +```bash +herdr +``` + +Herdr launches or attaches to your default background session. You do not manage sockets. If you detach, agents keep running. + +## Create a workspace + +When a session has no workspaces, Herdr opens one automatically. A workspace is a project-level container for tabs, panes, and agents. Give each active project its own workspace; this keeps agent state readable in the sidebar. + +## Use the mouse + +Herdr is mouse-native, so start by clicking. Click panes, tabs, workspaces, and agents to focus them. Drag split borders to resize. Right-click for context menus, including splitting panes and creating tabs. Drag-select text to copy it to your clipboard; double-click a token to copy it directly. Copying does not require Ctrl+C. + +Ctrl-click opens pane links when your terminal sends the modified click to Herdr. This works for OSC 8 hyperlinks and visible `http://` or `https://` URLs. On macOS, use Ctrl-click for Herdr-handled pane links while mouse capture is enabled; Cmd-click is only available through the terminal-native bypass path, such as Shift-Cmd-click or `ui.mouse_capture = false`. + +If you configure `ui.right_click_passthrough_modifier`, that modifier plus right-click sends right-click, hold, and drag gestures to mouse-reporting pane apps. + +## Run an agent + +Start your coding agent in a pane: + +```bash +claude +``` + +Or `codex`, `pi`, `opencode`, or any other [supported agent](/docs/agents/). Herdr detects it automatically. The sidebar shows whether each agent is `working`, `blocked`, `done`, or `idle` — across every workspace, so you always know which project needs you. + +## Keyboard control + +Keyboard control is optional; the mouse covers everything. Press `ctrl+b` to enter prefix mode, then press an action key. + +Common actions: + +| Action | Key | +| --- | --- | +| Split right | `prefix+v` | +| Split down | `prefix+minus` | +| New tab | `prefix+c` | +| Next / previous tab | `prefix+n` / `prefix+p` | +| Workspace navigation | `prefix+w` | +| New workspace | `prefix+shift+n` | +| Detach client | `prefix+q` | + +New to the prefix idea? [Keyboard](/docs/keyboard/) explains what it is, why multiplexers use one, and how to go prefix-free. Press `prefix+?` inside Herdr to see every active binding, and `prefix+[` to copy from the keyboard in copy mode. + +## Detach and come back + +Press `prefix+q` or simply close your terminal window. The Herdr server and every agent keep running. Run `herdr` again to reattach to the same session. + +To actually end the session and stop its panes: + +```bash +herdr server stop +``` + +## Where next + +- [Concepts](/docs/concepts/) — the workspace, tab, pane, and agent model in two minutes. +- [How to work with Herdr](/docs/how-to-work/) — local, SSH, phone, and `herdr --remote` workflows. +- [Agents](/docs/agents/) — supported agents, detection, and integrations that improve state accuracy. +- [Configuration](/docs/configuration/) — keybindings, themes, notifications, and everything else. diff --git a/docs/preview/website/src/content/docs/session-state.mdx b/docs/preview/website/src/content/docs/session-state.mdx new file mode 100644 index 00000000..87987b2d --- /dev/null +++ b/docs/preview/website/src/content/docs/session-state.mdx @@ -0,0 +1,108 @@ +--- +title: Session state and restore +description: Understand what Herdr keeps live, restores after restart, replays from history, resumes through agent integrations, and hands off during updates. +--- + +Herdr has several state paths. They solve different problems. + +## What survives + +| Case | Processes keep running | Layout returns | Recent screen returns | Agent conversation resumes | +| --- | --- | --- | --- | --- | +| Detach and reattach | Yes | Yes | Yes, from the live terminal | Yes, because the process never stopped | +| Server restart | No | Yes | Only with pane screen history | Only with native agent session restore | +| Update without `--handoff` | Compatible servers keep running; restart-required servers may need stop/restart | Yes after restart | Only with pane screen history | Only with native agent session restore | +| Update with `--handoff` | Best effort for supported running servers | Yes | Yes, from the live terminal if handoff succeeds | Yes, because the process keeps running if handoff succeeds | + +The sections below explain each path. + +## Live persistence + +Normal detach keeps the Herdr server running. Panes, shells, agents, servers, tests, and command processes keep running inside that server. + +Detach the client with `ctrl+b q`. Reattach later: + +```bash +herdr +``` + +This is the strongest persistence path because the original processes never stop. + +## Snapshot restore + +If the Herdr server stops and starts again, the original pane processes are gone. Herdr restores the saved session shape: workspaces, tabs, panes, cwd, layout, and focus. + +Snapshot restore does not preserve running shells, servers, tests, or arbitrary processes. Panes that cannot use a stronger restore path come back as new shells in their saved directories. + +## Pane screen history replay + +Pane screen history restores recent terminal contents after a full server restart. It restores what Herdr can show, not the old process. + +This is off by default because pane output can include secrets, tokens, prompts, and command output. Enable it from Settings > Experiments > pane screen history or with: + +```toml +[experimental] +pane_history = true +``` + +When enabled, Herdr stores saved pane history in `session-history.json` next to `session.json`. Treat the Herdr config/session directory like terminal history. + +## Native agent session restore + +Some agents can resume their own conversation sessions. Herdr can use official integration-reported session references to restart supported agent panes after a Herdr server restart. + +This is enabled by default. Disable it with: + +```toml +[session] +resume_agents_on_restore = false +``` + +Herdr only resumes panes that reported a native session reference through a current official Herdr integration. + +After a client attaches and provides terminal size and theme context, Herdr resumes eligible restored agent panes across workspaces and tabs without waiting for each pane to be focused. + +Native session restore requires these Herdr integration versions or newer: + +| Agent | Minimum Herdr integration version | Resume command | +| --- | --- | --- | +| Pi | `2` | `pi --session <path-or-id>` | +| OMP | `3` | `omp --resume=<path-or-id>` | +| Claude Code | `6` | `claude --resume <id>` | +| Codex | `5` | `codex resume <id>` | +| Cursor Agent CLI | `1` | `cursor-agent --resume <id>` | +| Grok CLI | `1` | `grok --resume <id>` | +| GitHub Copilot CLI | `2` | `copilot --resume=<id>` | +| Devin CLI | `2` | `devin --resume <id>` | +| Droid | `2` | `droid --resume <id>` | +| Kimi Code CLI | `3` | `kimi --session <id>` | +| Qoder CLI | `2` | `qodercli --resume <id>` | +| OpenCode | `5` | `opencode --session <id>` | +| Kilo Code CLI | `1` | `kilo --session <id>` | +| Hermes Agent | `2` | `hermes --resume <id>` | +| MastraCode | `1` | `mastracode --thread <id>` | + +Run `herdr integration status` to check installed integration versions. Reinstall outdated integrations with `herdr integration install <agent>`. + +Unsupported, missing, invalid, duplicated, or stale session references restore as normal shells in the saved pane directory. + +If native agent session restore applies to a pane, Herdr resumes the agent session instead of replaying saved pane history for that pane. + +## Live handoff + +Live handoff is for update and remote attach flows that need to replace a running Herdr server. It asks the old server to transfer live panes to the new server, so pane processes can keep running across the server replacement. + +This is different from snapshot restore, pane history replay, and native agent session restore. Handoff tries to keep the current processes alive. The others reconstruct state after the old server has already stopped. + +Handoff protects long-lived server-owned session state: pane PTYs and processes, agent identity and durable metadata, and plugin/session state needed by the replacement server. It does not preserve transient coordination across the replacement boundary. In-flight CLI or API requests, waits, subscription streams, client sockets, and pane-to-pane messages may be interrupted; clients should reconnect and retry them. + +Live handoff is experimental and opt-in: + +```bash +herdr update --handoff +herdr --remote workbox --handoff +``` + +Plain `herdr update` and plain `herdr --remote workbox` use the normal restart/stop flow by default. + +`herdr update --handoff` only applies to installs managed by Herdr's own updater. Homebrew, mise, and Nix installs are updated through their package managers, so `herdr update` is disabled there and cannot perform live handoff. diff --git a/docs/preview/website/src/content/docs/socket-api.mdx b/docs/preview/website/src/content/docs/socket-api.mdx new file mode 100644 index 00000000..0e9dd71b --- /dev/null +++ b/docs/preview/website/src/content/docs/socket-api.mdx @@ -0,0 +1,887 @@ +--- +title: Socket API +description: Control a running Herdr server from scripts, tools, and coding agents. +--- + +Herdr exposes a local socket API for scripts and agents that need to inspect or control a running session. + +Most automation should start with the CLI wrappers. Use the raw socket API only when you need direct request/response control or long-lived event subscriptions. + +## Choose an integration layer + +| Layer | Use it for | +| --- | --- | +| Agent skill | Teaching a coding agent how to use Herdr from inside a pane. | +| CLI wrappers | Shell scripts, simple orchestration, and human debugging. | +| Raw socket API | Custom tools, protocol clients, and event subscribers. | + +The layers share the same control surface. + +## Schema + +The installed CLI can print the socket protocol schema bundled with that Herdr +binary: + +```bash +herdr api schema +herdr api schema --json +herdr api schema --output herdr-api.schema.json +``` + +Plain `herdr api schema` prints a short summary. `--json` prints the full JSON +Schema document for tools, and `--output PATH` writes that document to a file. +The schema covers raw requests, success responses, error responses, emitted +events, and subscription events. + +## What you can control + +The socket API can: + +- create, list, focus, rename, and close workspaces +- create, list, focus, rename, and close tabs +- list, inspect, split, swap, focus, resize, rename, read, close, and send input to panes +- list, inspect, read, prompt, wait on, rename, focus, start, and attach agents through CLI helpers +- report custom agent state from hooks and plugins +- subscribe to events and wait for output or state changes +- install and uninstall built-in integrations +- stop the server and reload config + +## CLI examples + +Create a workspace: + +```bash +herdr workspace create --cwd ~/project --label api +``` + +Create a tab: + +```bash +herdr tab create --label logs +``` + +Split a pane and run a command: + +```bash +herdr pane split w1:p1 --direction right +herdr pane run w1:p2 "npm test" +``` + +Inspect and rearrange panes: + +```bash +herdr pane layout --current +herdr pane neighbor --direction right --current +herdr pane resize --direction right --amount 0.1 --current +herdr pane swap --direction right --current +herdr pane zoom --on --current +herdr pane split w1:p1 --direction right --ratio 0.333 +``` + +Wait for an agent: + +```bash +herdr agent wait w1:p1 --until done +``` + +Read pane output: + +```bash +herdr pane read w1:p2 --source recent --lines 50 +``` + +## Raw methods + +Raw socket method names use dot notation: + +| Area | Methods | +| --- | --- | +| Server | `ping`, `server.stop`, `server.reload_config`, `server.agent_manifests`, `server.reload_agent_manifests` | +| Notification | `notification.show` | +| Client | `client.window_title.set`, `client.window_title.clear` | +| Session | `session.snapshot` | +| Workspace | `workspace.create`, `workspace.list`, `workspace.get`, `workspace.focus`, `workspace.rename`, `workspace.move`, `workspace.move_block`, `workspace.report_metadata`, `workspace.close` | +| Worktree | `worktree.list`, `worktree.create`, `worktree.open`, `worktree.remove` | +| Tab | `tab.create`, `tab.list`, `tab.get`, `tab.focus`, `tab.rename`, `tab.move`, `tab.close` | +| Pane | `pane.split`, `pane.swap`, `pane.move`, `pane.zoom`, `pane.layout`, `pane.process_info`, `pane.neighbor`, `pane.edges`, `pane.focus_direction`, `pane.resize`, `pane.list`, `pane.current`, `pane.get`, `pane.rename`, `pane.send_text`, `pane.send_keys`, `pane.send_input`, `pane.read`, `pane.graphics.info`, `pane.graphics.set`, `pane.graphics.clear`, `pane.graphics.stream`, `pane.report_agent`, `pane.report_agent_session`, `pane.report_metadata`, `pane.clear_agent_authority`, `pane.release_agent`, `pane.close`, `pane.wait_for_output` | +| Popup | `popup.close` | +| Layout | `layout.export`, `layout.apply`, `layout.set_split_ratio` | +| Agent | `agent.list`, `agent.get`, `agent.read`, `agent.explain`, `agent.send_keys`, `agent.prompt`, `agent.wait`, `agent.rename`, `agent.focus`, `agent.start`, `agent.view.set`, `agent.view.clear` | +| Events | `events.subscribe`, `events.wait` | +| Integrations | `integration.install`, `integration.uninstall` | +| Plugins | `plugin.link`, `plugin.list`, `plugin.unlink`, `plugin.enable`, `plugin.disable`, `plugin.action.list`, `plugin.action.invoke`, `plugin.log.list`, `plugin.pane.open`, `plugin.pane.focus`, `plugin.pane.close` | + +`agent.wait` is server-owned and event-driven. It pins the resolved pane occupant so a replacement cannot satisfy the wait. `agent.prompt` accepts an optional `wait` object with `until` and `timeout_ms`; this submits the prompt and starts the wait in one request, avoiding a race between separate calls. + +`workspace.move_block` atomically moves the ordered `workspace_ids` before `before_workspace_id`; omit the anchor to move the block to the end. The ids must be unique and the anchor cannot be part of the block. The response contains the authoritative ordered workspace list. + +`session.snapshot` returns a one-time bootstrap snapshot for clients that keep +their own local runtime cache. The response includes version/protocol metadata, +focused workspace/tab/pane ids, workspace records, tab records, pane records, +tab layout snapshots, and agent records. It is not a subscription; after reading +it, subscribe to resource events and update the local cache from those events. +Call `session.snapshot` again after reconnecting or when the local cache may be +stale. Attached worktree provenance is included on workspace records. Full repo +worktree discovery remains `worktree.list`. + +From the CLI, `herdr api snapshot` prints the live `session.snapshot` response +as JSON for clients and agents that want a simple bootstrap command. + +Pane control methods use public pane ids such as `w1:p1`. Methods whose +schema makes `pane_id` optional use the server's active focused pane when it is +omitted. `pane.move` always requires the source `pane_id`. + +`pane.send_keys` and `pane.send_input.keys` accept Herdr key-combo strings: +plain printable keys, special keys like `enter` and `esc`, modifier chords +like `ctrl+h`, `control+j`, `alt+x`, and `shift+tab`, function keys like +`f1`, and named punctuation like `minus` and `plus`. They do not accept +`prefix+` binding strings. + +```json +{"id":"req_current","method":"pane.current","params":{"caller_pane_id":"w1:p1"}} +{"id":"req_layout","method":"pane.layout","params":{"pane_id":"w1:p1"}} +{"id":"req_neighbor","method":"pane.neighbor","params":{"pane_id":"w1:p1","direction":"right"}} +{"id":"req_edges","method":"pane.edges","params":{"pane_id":"w1:p1"}} +{"id":"req_focus","method":"pane.focus_direction","params":{"direction":"right"}} +{"id":"req_resize","method":"pane.resize","params":{"pane_id":"w1:p1","direction":"right","amount":0.1}} +{"id":"req_zoom","method":"pane.zoom","params":{"pane_id":"w1:p1","mode":"toggle"}} +{"id":"req_split","method":"pane.split","params":{"direction":"right","ratio":0.333,"env":{"HERDR_ROLE":"tests"}}} +{"id":"req_process","method":"pane.process_info","params":{"pane_id":"w1:p1"}} +``` + +`pane.current` returns a single `PaneInfo`. When `caller_pane_id` is present, +Herdr returns that pane. When it is omitted, Herdr returns the active focused +pane. + +`PaneInfo` includes `scroll` when terminal scroll metrics are available: + +```json +{ + "offset_from_bottom": 12, + "max_offset_from_bottom": 240, + "viewport_rows": 30 +} +``` + +Clients can treat `offset_from_bottom == 0` as at-bottom state. + +### Experimental pane graphics + +Pane graphics let a plugin place image data over a pane. They are available +only when `[experimental].kitty_graphics = true`; otherwise every pane graphics +method returns `feature_disabled`. Calling `pane.graphics.info` returns the +attached client's cell width and height in pixels without creating a graphics +layer. `pane.graphics.set` accepts `png`, `rgb`, or `rgba` data in `data_base64`, +and `pane.graphics.clear` removes the layer. + +```json +{"id":"graphics_info","method":"pane.graphics.info","params":{"pane_id":"w1:p1"}} +{"id":"graphics_set","method":"pane.graphics.set","params":{"pane_id":"w1:p1","format":"png","image_width":800,"image_height":600,"data_base64":"...","placement":{"viewport_col":0,"viewport_row":0,"grid_cols":80,"grid_rows":30}}} +{"id":"graphics_clear","method":"pane.graphics.clear","params":{"pane_id":"w1:p1"}} +``` + +For repeated frames, open a dedicated socket with `pane.graphics.stream`. After +Herdr replies with `ok`, send one JSON header and then exactly `data_length` raw +bytes per frame. A stream owns that pane's graphics layer until the socket +closes; concurrent set, clear, or stream requests return `stream_conflict`. + +```json +{"id":"graphics_stream","method":"pane.graphics.stream","params":{"pane_id":"w1:p1"}} +{"format":"png","image_width":800,"image_height":600,"data_length":12345,"placement":{"viewport_col":0,"viewport_row":0,"grid_cols":80,"grid_rows":30}} +``` + +`pane.layout` returns the tab layout snapshot with `workspace_id`, `tab_id`, +`zoomed`, outer `area`, `focused_pane_id`, pane rects, and split rects/ratios. +`pane.neighbor` and `pane.edges` include that same layout snapshot so clients +can make the next decision without private layout state. + +`pane.process_info` returns the pane's shell pid, foreground process group id +when available, and foreground processes with pid, name, argv/cmdline, and cwd +when the platform exposes them. + +`layout.export` returns a portable tab layout tree. Omit `tab_id` and `pane_id` +to export the active tab, pass `tab_id` to export that tab, or pass `pane_id` to +export the tab containing that pane. + +```json +{"id":"req_export","method":"layout.export","params":{"tab_id":"w1:t1"}} +``` + +The response includes `workspace_id`, `tab_id`, `zoomed`, `focused_pane_id`, and +`root`. `root` is a BSP tree of `pane` and `split` nodes. Pane nodes can include +`pane_id`, `label`, `cwd`, and argv `command`. Split nodes use +`direction` (`right` or `down`), `ratio`, `first`, and `second`. + +`layout.apply` creates a fresh tab from a declarative tree. If `tab_id` is +provided, Herdr creates the replacement tab first and then closes the old tab. +This restores structure, labels, cwd, env, and optional argv commands; it does +not preserve live PTYs, scrollback, or running processes. + +```json +{ + "id": "req_apply", + "method": "layout.apply", + "params": { + "workspace_id": "wabc", + "tab_label": "dev", + "focus": true, + "root": { + "type": "split", + "direction": "right", + "ratio": 0.65, + "first": { + "type": "pane", + "label": "editor", + "cwd": "/repo" + }, + "second": { + "type": "pane", + "label": "tests", + "cwd": "/repo", + "command": ["sh", "-c", "just test"], + "env": { "HERDR_ROLE": "tests" } + } + } + } +} +``` + +`layout.set_split_ratio` updates an existing split in a tab layout. The response +is `type: "layout_split_ratio_set"` with the updated portable `layout`. + +```json +{"id":"req_ratio","method":"layout.set_split_ratio","params":{"tab_id":"w1:t1","path":[],"ratio":0.6}} +``` + +Process-launching methods accept an `env` object. Herdr applies those key/value +pairs to the newly launched process only. Herdr also injects `HERDR_SOCKET_PATH`, +`HERDR_ENV=1`, `HERDR_WORKSPACE_ID`, `HERDR_TAB_ID`, and `HERDR_PANE_ID` into +managed pane processes. Herdr-managed variables are authoritative when they +conflict with caller-provided env. + +`pane.swap` supports directional and explicit forms: + +```json +{"id":"req_swap_dir","method":"pane.swap","params":{"pane_id":"w1:p1","direction":"right"}} +{"id":"req_swap_explicit","method":"pane.swap","params":{"source_pane_id":"w1:p1","target_pane_id":"w1:p2"}} +``` + +Swap is same-tab only. It preserves split shape, split ratios, pane ids, and +running processes. The response is `type: "pane_swap"` with `changed`, optional +`reason`, `source_pane_id`, optional `target_pane_id`, `focused_pane_id`, and +`layout`. Reason values are `no_neighbor`, `same_pane`, `not_found`, and +`cross_tab`. When a tab is zoomed, swap keeps zoom active and mutates the hidden +full-tab layout. + +`pane.move` moves a running pane to a different tab, a new tab, or a new +workspace: + +```json +{"id":"req_move_tab","method":"pane.move","params":{"pane_id":"w1:p2","destination":{"type":"tab","tab_id":"w1:t2","target_pane_id":"w1:p3","split":"right","ratio":0.5},"focus":true}} +{"id":"req_move_new_tab","method":"pane.move","params":{"pane_id":"w1:p2","destination":{"type":"new_tab","workspace_id":"w1","label":"logs"},"focus":true}} +{"id":"req_move_new_workspace","method":"pane.move","params":{"pane_id":"w1:p2","destination":{"type":"new_workspace","label":"logs","tab_label":"main"},"focus":true}} +``` + +Existing-tab moves require `split: "right" | "down"`. `target_pane_id` is +optional and defaults to the target tab's focused pane. Same-tab layout changes +remain `pane.swap`; moving to the source tab returns `changed: false` with +`reason: "same_tab"`. Moves involving a zoomed source or target tab return +`changed: false` with `reason: "zoomed_tab"`. + +The response is `type: "pane_move"` with `changed`, optional `reason`, +`previous_pane_id`, `previous_workspace_id`, `previous_tab_id`, the moved +`pane`, optional `source_layout`, `target_layout`, optional created workspace or +tab records, optional closed workspace or tab ids, and `focused_pane_id`. +Cross-workspace moves keep the internal pane and terminal alive but assign a new +public pane id in the destination workspace. Subscribers can listen for +`pane.moved`; Herdr does not emit fake pane close/create events for the moved +terminal process. + +`pane.zoom` toggles, enables, or disables zoom for the target pane's tab: + +```json +{"id":"req_zoom_toggle","method":"pane.zoom","params":{"pane_id":"w1:p1"}} +{"id":"req_zoom_on","method":"pane.zoom","params":{"pane_id":"w1:p1","mode":"on"}} +{"id":"req_zoom_off","method":"pane.zoom","params":{"pane_id":"w1:p1","mode":"off"}} +``` + +Omitting `pane_id` targets the server's active focused pane. The response is +`type: "pane_zoom"` with `changed`, `zoom_changed`, `focus_changed`, optional +`reason`, `pane_id`, `focused_pane_id`, `zoomed`, and `layout`. `changed` is +true when either zoom state or focus changed. Reason values are `single_pane`, +`already_zoomed`, and `already_unzoomed`. + +The CLI wrapper for `notification.show` is: + +```bash +herdr notification show "build failed" --body "api workspace" --position top-left --sound request +``` + +Show a user notification through the configured toast delivery: + +```json +{"id":"req_notify","method":"notification.show","params":{"title":"build failed","body":"api workspace","position":"top-left","sound":"request"}} +``` + +`title` is required and must contain visible text after control characters and repeated whitespace are removed. `body` is optional. Herdr collapses newlines, tabs, carriage returns, and repeated whitespace into spaces, then trims notification text to 80 characters for `title` and 240 characters for `body`. An empty sanitized `title` returns `invalid_params`. `position` is optional and applies only when `ui.toast.delivery = "herdr"`; desktop positions are relative to the full Herdr frame, and omitted positions use `ui.toast.herdr.position`. Terminal, system, and off delivery ignore `position`. `sound` is optional and can be `none`, `done`, or `request`; it defaults to `none` and plays only when the notification is shown. + +The response reports whether anything was shown: + +```json +{"id":"req_notify","result":{"type":"notification_show","shown":true,"reason":"shown"}} +``` + +Possible reasons are `shown`, `disabled`, `rate_limited`, `no_foreground_client`, and `busy`. `disabled` means `ui.toast.delivery = "off"`. `busy` means an existing in-app toast was not replaced. Terminal and system delivery are best-effort through the current foreground attached Herdr client. + +Set or clear the foreground client's outer terminal window title: + +```json +{"id":"req_title","method":"client.window_title.set","params":{"title":"herdr api"}} +{"id":"req_title_clear","method":"client.window_title.clear","params":{}} +``` + +`client.window_title.clear` restores Herdr's default title. The response is +`type: "client_window_title"` with `changed` and reason `set`, `cleared`, or +`no_foreground_client`. + +Worktree methods manage Git checkouts as Herdr workspaces. `worktree.create` creates a checkout and returns the new `workspace`, `tab`, `root_pane`, and `worktree` records. If the requested branch already exists locally, it checks out that branch; otherwise it creates the branch from the requested base or `HEAD`. `worktree.open` opens an existing checkout or returns the already-open workspace. `worktree.remove` runs `git worktree remove` against a linked child workspace and never deletes the branch. + +Create a worktree from a source workspace: + +```json +{"id":"req_1","method":"worktree.create","params":{"workspace_id":"w1","branch":"worktree/api","focus":false}} +``` + +Open an existing checkout: + +```json +{"id":"req_2","method":"worktree.open","params":{"workspace_id":"w1","branch":"worktree/api","focus":true}} +``` + +Remove a linked checkout: + +```json +{"id":"req_3","method":"worktree.remove","params":{"workspace_id":"2","force":false}} +``` + +Use at most one of `workspace_id` or `cwd` for `worktree.list`, `worktree.create`, and `worktree.open`; omit both to use the active workspace. Use exactly one of `path` or `branch` for `worktree.open`. Raw socket `cwd` and `path` values must be absolute; the CLI expands relative `--cwd` and `--path` values before sending requests. Workspace responses include optional `worktree` provenance when a workspace belongs to a Herdr worktree group. Worktree commands can emit `workspace.updated` when an existing workspace gains or changes worktree provenance. + +Worktree commands also emit lifecycle events. `worktree.create` emits `workspace.created`, `tab.created`, `pane.created`, and `worktree.created`. `worktree.open` emits `worktree.opened`, and it also emits workspace/tab/pane creation events when it opens a new Herdr workspace. `worktree.remove` emits `worktree.removed`; if the linked workspace is still open, it also emits `workspace.closed`. + +## Agent view queries + +`agent.view.set` installs one transient declarative projection for the built-in +Agents view. The projection is reevaluated whenever agent facts or current UI +context change. It controls the expanded and collapsed sidebar, mobile Agents +list, mouse targets, indexed focus, and next/previous Agent navigation. It does +not change `agent.list`, notifications, detection, or global attention counts. + +Show agents in the currently presented Space or agents needing attention +elsewhere, then order by attention and most recent state transition: + +```json +{ + "id": "view_set", + "method": "agent.view.set", + "params": { + "source": "plugin:example.agent-views", + "label": "focus", + "filter": { + "op": "any", + "filters": [ + { + "op": "eq", + "field": "workspace_id", + "value": {"context": "current_workspace_id"} + }, + { + "op": "in", + "field": "status", + "values": ["blocked", "done"] + } + ] + }, + "sort": [ + {"field": "attention", "order": "desc"}, + {"field": "state_change_seq", "order": "desc"} + ] + } +} +``` + +Filter nodes use `op` values `all`, `any`, `not`, `eq`, `in`, or `exists`. +Built-in filter fields are `status`, `workspace_id`, `tab_id`, `pane_id`, +`agent`, `seen`, and `state_change_seq`. Use `{"token":"name"}` as a field to +filter plugin-reported pane metadata. Values are strings, booleans, unsigned +numbers, or a context object. Context values are `current_workspace_id` and +`current_tab_id`, and may only be compared to the matching ID field. Effective +status values are `idle`, `working`, `blocked`, `done`, and `unknown`; `done` +means idle and not yet seen. + +Sort fields are `workspace_order`, `tab_order`, `pane_order`, `attention`, +`status`, `agent`, `seen`, `state_change_seq`, or `{"token":"name"}`. Sorts are +stable, evaluated in order, and accept `asc` or `desc`. Missing values stay +after present values. When `sort` is omitted, the existing +`ui.agent_panel_sort` policy remains active. A custom sort temporarily replaces +that policy without rewriting config. + +`source` identifies the owner. Plugins use `plugin:<HERDR_PLUGIN_ID>`; Herdr +rejects plugin-owned sets when that plugin is missing or disabled. Other callers +may use their own non-`plugin:` source. A successful set atomically replaces the +previous view. The view lasts until it is cleared, replaced, its owning plugin +is disabled, unlinked, or uninstalled, or the server exits. Plugins that want +durable behavior should save the query under `HERDR_PLUGIN_STATE_DIR` and +reapply it from a `[[startup]]` hook. + +Clear unconditionally, or only when the named source still owns the view: + +```json +{"id":"view_clear","method":"agent.view.clear","params":{}} +{"id":"view_clear_owned","method":"agent.view.clear","params":{"source":"plugin:example.agent-views"}} +``` + +A source mismatch leaves the active view unchanged. Set and clear responses use +`type: "agent_view"` and report `active`, `source`, and optional `label`. + +## Plugin APIs + +The plugin API is an early host surface for executable workflow tools. A plugin +is a package with a `herdr-plugin.toml` manifest. The manifest declares +startup hooks, shareable actions, event hooks, terminal pane entrypoints, and +link handlers. Startup hooks run once after restore when the API is ready. +Actions and panes are manifest-only; runtime action registration and runtime +argv pane creation are not part of v1. + +Installed and linked plugins persist across restarts. Herdr writes a +`plugins.json` registry file alongside `session.json` on `plugin.link`, +`plugin.unlink`, `plugin.enable`, and `plugin.disable`. The +`herdr plugin install` and `herdr plugin link` CLIs also write the same registry +when Herdr is not running, then startup loads it automatically. On startup, +Herdr re-reads each +manifest from its original path; if the file is missing or unparseable the entry +is kept with a `warnings` field so `plugin.list` surfaces it. + +Event hook `on` values are validated against the known Herdr event names at link time. An unrecognised name is not an error — the link still succeeds — but the returned plugin info includes a warning (e.g. `"unknown event 'worktree.craeted'"`). Check the `warnings` field in the `plugin.link` and `plugin.list` responses. + +Link a local plugin manifest: + +```json +{"id":"req_plugin_link","method":"plugin.link","params":{"path":"/path/to/plugin","enabled":true}} +``` + +`plugin.link` also accepts optional `source` metadata. The CLI uses this when it installs from GitHub so `plugin.list` can show origin, requested ref, resolved commit, and managed checkout path: + +```json +{"id":"req_plugin_link","method":"plugin.link","params":{"path":"/managed/plugin/herdr-plugin.toml","enabled":true,"source":{"kind":"github","owner":"ogulcancelik","repo":"herdr-plugin-examples","subdir":"worktree-bootstrap","requested_ref":"main","resolved_commit":"abc123","managed_path":"/data/plugins/github/<managed-checkout>","installed_unix_ms":1780000000000}}} +``` + +The path can be a plugin directory containing `herdr-plugin.toml` or a direct +manifest path. The manifest shape is: + +```toml +id = "example.worktree-bootstrap" +name = "Worktree Bootstrap" +version = "0.1.0" +min_herdr_version = "0.7.0" +description = "Prepare new worktrees" +platforms = ["linux", "macos", "windows"] + +[[build]] +command = ["bun", "install"] + +[[actions]] +id = "bootstrap" +title = "Bootstrap worktree" +contexts = ["workspace"] +command = ["bun", "run", "bootstrap.ts"] + +[[events]] +on = "worktree.created" +command = ["bun", "run", "bootstrap.ts"] + +[[panes]] +id = "board" +title = "Worktree board" +placement = "overlay" +command = ["bun", "run", "board.ts"] + +[[link_handlers]] +id = "github-issue" +title = "Open GitHub issue" +pattern = "^https://github\\.com/[^/]+/[^/]+/(issues|pull)/[0-9]+$" +action = "bootstrap" +``` + +`min_herdr_version` is required. The server refuses to link a plugin when the +field is missing, invalid, or newer than the running Herdr binary. + +Declare `platforms` at the top level with the OS identifiers (`linux`, `macos`, +`windows`) your plugin supports. Omitting `platforms` is allowed for local +development — `plugin.link` succeeds but the response includes a warning. +Individual build commands, actions, event hooks, panes, and link handlers can +declare their own `platforms` to override the plugin-level list; if omitted they +inherit from the plugin. Invoking an action or opening a pane whose effective +platforms do not include the current OS returns a `platform_unsupported` error. + +List, enable, disable, or unlink linked plugins: + +```json +{"id":"req_plugin_list","method":"plugin.list","params":{}} +{"id":"req_plugin_disable","method":"plugin.disable","params":{"plugin_id":"example.worktree-bootstrap"}} +{"id":"req_plugin_enable","method":"plugin.enable","params":{"plugin_id":"example.worktree-bootstrap"}} +{"id":"req_plugin_unlink","method":"plugin.unlink","params":{"plugin_id":"example.worktree-bootstrap"}} +``` + +Actions are resolved from the linked manifest. `plugin.action.list` returns all actions across installed plugins; pass `plugin_id` to filter. + +```json +{"id":"req_plugin_actions","method":"plugin.action.list","params":{}} +{"id":"req_plugin_actions_filtered","method":"plugin.action.list","params":{"plugin_id":"example.worktree-bootstrap"}} +``` + +`plugin.action.list` returns each action's effective `platforms` after applying +plugin-level inheritance. + +Invoke an action by its qualified id or bare action id: + +```json +{"id":"req_plugin_invoke","method":"plugin.action.invoke","params":{"action_id":"example.worktree-bootstrap.bootstrap","context":{"invocation_source":"keybinding"}}} +``` + +`plugin.action.invoke` resolves the manifest action, starts the manifest +command, and returns the Herdr-built invocation context plus the started command +log record. Missing context fields are filled from the active workspace, tab, +focused pane, worktree provenance, and request id. Invoking an action from a +disabled plugin returns a `plugin_disabled` error. + +Herdr injects `HERDR_SOCKET_PATH`, `HERDR_BIN_PATH`, `HERDR_ENV=1`, +`HERDR_PLUGIN_ID`, `HERDR_PLUGIN_ROOT`, `HERDR_PLUGIN_CONFIG_DIR`, +`HERDR_PLUGIN_STATE_DIR`, `HERDR_PLUGIN_CONTEXT_JSON`, and available +`HERDR_WORKSPACE_ID`, `HERDR_TAB_ID`, and `HERDR_PANE_ID` values. Action +commands also receive `HERDR_PLUGIN_ACTION_ID`; event hooks receive +`HERDR_PLUGIN_EVENT` and `HERDR_PLUGIN_EVENT_JSON`; pane commands receive +`HERDR_PLUGIN_ENTRYPOINT_ID`. + +List recent action and event command logs: + +```json +{"id":"req_plugin_logs","method":"plugin.log.list","params":{"plugin_id":"example.worktree-bootstrap","limit":20}} +``` + +Event hooks run for enabled installed plugins when Herdr emits a matching event +name such as `worktree.created`. + +There is no Herdr-managed plugin storage API in v1. `HERDR_PLUGIN_CONFIG_DIR` +and `HERDR_PLUGIN_STATE_DIR` are path discovery only; plugins own their files, +schemas, migrations, and cleanup. + +Open a managed terminal UI: + +```json +{"id":"req_plugin_pane","method":"plugin.pane.open","params":{"plugin_id":"example.board","entrypoint":"board","placement":"zoomed","target_pane_id":"w1:p1","env":{"HERDR_ROLE":"board"},"focus":true}} +``` + +`plugin.pane.open` requires an installed, enabled, platform-compatible plugin, +then launches the requested manifest `[[panes]]` entrypoint as an argv-backed +terminal pane. Manifest pane `placement` defaults to `overlay`; request +`placement` overrides the manifest with `overlay`, `popup`, `split`, `tab`, or +`zoomed`. Overlay and popup placements use the active tiled pane as launch +context. Popup terminals are session-modal and do not change the tab layout; +optional `width` and `height` fields set their outer size as terminal cells or +percentages such as `"80%"`. Omitted dimensions default to half the terminal +size, with too-small values clamped to the popup minimum. A popup has no pane +ID, remains outside all `pane.*` and agent APIs, emits no pane lifecycle events, +leaves plugin focus context on the underlying tiled pane, and does not export +`HERDR_PANE_ID` to its process. Popup launch returns `ok`; `popup.close` closes +the active popup and returns +`popup_not_open` when none exists. Split and zoomed panes target an existing +pane; tab panes can target a workspace. Split, tab, zoomed, and overlay panes +behave like normal Herdr panes, and `plugin.pane.focus` and `plugin.pane.close` +continue to operate on those panes. + +## Socket transport + +Herdr uses newline-delimited JSON over a local socket. On Unix, that socket is a +Unix domain socket. On Windows, it is a named pipe. + +Send one request per line: + +```json +{"id":"req_1","method":"ping","params":{}} +``` + +A successful response includes the same `id`: + +```json +{"id":"req_1","result":{"type":"pong"}} +``` + +Event subscriptions keep the connection open after the initial response. + +## Socket paths + +The default socket lives under your Herdr config directory. + +Named sessions have separate sockets: + +```text +~/.config/herdr/herdr.sock +~/.config/herdr/sessions/<name>/herdr.sock +``` + +Resolution order: + +1. explicit CLI `--session <name>` +2. `HERDR_SOCKET_PATH` +3. `HERDR_SESSION=<name>` +4. default session socket + +Use `HERDR_SOCKET_PATH` only for low-level overrides. + +For plugins, prefer invoking `HERDR_BIN_PATH` and the CLI wrappers when you need +portable Windows behavior. Raw socket clients are responsible for using the +platform-native local socket form. + +## Agent state reporting + +Integrations report agent state with `pane.report_agent`. + +```json +{ + "id": "req_1", + "method": "pane.report_agent", + "params": { + "pane_id": "w1:p1", + "source": "custom:docs", + "agent": "docs-bot", + "state": "working", + "message": "building docs" + } +} +``` + +`state` is semantic. It affects waits, notifications, and rollups. Report display-only values separately through metadata. + +Session-only official integrations report native session references with `pane.report_agent_session`. State-reporting integrations can still include native session references in `pane.report_agent`. State-independent session reports do not affect waits, notifications, or rollups. + +```json +{ + "id": "req_2", + "method": "pane.report_agent_session", + "params": { + "pane_id": "w1:p1", + "source": "herdr:codex", + "agent": "codex", + "agent_session_id": "..." + } +} +``` + +`pane.get`, `pane.list`, `agent.get`, and `agent.list` expose a read-only `agent_session` object when Herdr has a stored native session reference: + +```json +{ + "agent_session": { + "source": "herdr:codex", + "agent": "codex", + "kind": "id", + "value": "..." + } +} +``` + +If no native session reference is stored, the field is omitted. + +`pane.get`, `pane.list`, `agent.get`, and `agent.list` also expose `foreground_cwd` when Herdr can resolve the cwd of the process currently controlling the pane PTY. The existing `cwd` field remains the pane/workspace cwd used for labels, follow-cwd behavior, and restored session state. + +`PaneInfo` and `AgentInfo` expose optional `terminal_title` and `terminal_title_stripped` fields. `terminal_title` is the latest OSC 0/2 title after safety normalization. `terminal_title_stripped` removes one recognized leading activity or spinner glyph and following whitespace. These server-owned values are ephemeral across a cold restart and are independent of the metadata `title` and semantic agent state. + +Use `pane.report_metadata` when a user hook wants to customize presentation without taking over lifecycle state from a Herdr integration. + +```json +{ + "id": "req_2", + "method": "pane.report_metadata", + "params": { + "pane_id": "w1:p1", + "source": "user:claude-title", + "agent": "claude", + "title": "Refactor auth middleware", + "display_agent": "Claude: auth", + "state_labels": { + "working": "refactoring auth", + "idle": "ready", + "done": "review ready" + }, + "tokens": { + "summary": "refactor auth", + "model": "opus" + }, + "ttl_ms": 3600000 + } +} +``` + +Metadata reports are display-only. Valid metadata can override the pane title, displayed agent name, visible state labels, and arbitrary named tokens. `working`, `blocked`, `idle`, waits, notifications, and rollups still come from semantic state. Native session restore comes from stored official session references. `agent` is an optional guard for presentation fields against the authoritative agent label; `applies_to_source` similarly guards presentation fields against the active lifecycle authority source. These guards do not apply to token patches: token reporters own clearing and TTL refresh. Use `display_agent` to change the visible name. `state_labels` keys must be `idle`, `working`, `blocked`, `done`, or `unknown`. + +Token maps are per-resource patches. A string sets a key, JSON `null` clears it, and omitted keys remain unchanged. The latest accepted update wins. Optional TTL applies independently to token keys updated by that report. Pane tokens are exposed by pane and agent get/list responses and can be rendered as `$name` in Agent sidebar rows. A report may mention at most 16 token keys, and a pane or workspace may retain at most 32 keys. Token names are 1–32 ASCII letters, digits, underscores, or hyphens. + +Workspace tokens use the same contract: + +```json +{"id":"req_3","method":"workspace.report_metadata","params":{"workspace_id":"w1","source":"user:jj","tokens":{"jj_status":"2 changes","old":null},"ttl_ms":5000}} +``` + +Workspace get/list responses expose the resulting `tokens` map, and Space sidebar rows can render values such as `$jj_status`. Changes and TTL expiry emit `workspace.metadata_updated` with the latest workspace snapshot. This metadata event is available to API subscribers but does not invoke plugin event hooks. + +Presentation text is normalized before storage. Herdr trims surrounding whitespace, removes control characters, and caps `title`, `display_agent`, each state label, and token values at 80 characters. Empty normalized token values clear that key. + +`source` and `applies_to_source` are source identifiers. They must be 80 characters or fewer and may contain only ASCII letters, digits, colon, dot, underscore, and hyphen. + +Use `ttl_ms` for short-lived metadata. It must be between `1` and `86400000` milliseconds. Omit `ttl_ms` for metadata that should stay until replaced, cleared, or the pane/workspace closes. Presentation fields retain their existing source-scoped expiry behavior; each token updated by the call receives its own deadline. Token metadata is not restored after a server restart. + +Use `seq` when a hook may send updates out of order. For the same `source`, reports with a sequence number less than or equal to the last accepted sequence are accepted by the API but ignored by the pane state. A pane or workspace accepts sequenced token reports from at most 32 distinct sources during its lifetime; clearing or expiry does not release those source slots. + +## Event subscriptions + +Subscribe to events when you need a long-lived stream: + +```json +{ + "id": "sub_1", + "method": "events.subscribe", + "params": { + "subscriptions": [ + { "type": "pane.agent_status_changed", "pane_id": "w1:p1", "agent_status": "blocked" } + ] + } +} +``` + +The first response acknowledges the subscription. Later lines are pushed events. + +Workspace event subscriptions include `workspace.created`, `workspace.updated`, `workspace.metadata_updated`, `workspace.renamed`, `workspace.moved`, `workspace.reordered`, `workspace.closed`, and `workspace.focused`. `workspace.metadata_updated` reports token changes and TTL expiry without invoking plugin event hooks. Other workspace events describe Herdr UI/runtime lifecycle. `workspace.created` includes optional `workspace.worktree` provenance when the workspace belongs to a worktree group. `workspace.moved` includes the moved `workspace_id`, requested `insert_index`, and updated ordered `workspaces` list. `workspace.reordered` includes the atomically moved `workspace_ids`, optional `before_workspace_id`, and authoritative ordered `workspaces` list. `workspace.closed` includes a final `workspace` snapshot when Herdr can still identify it before removal. +Tab event subscriptions include `tab.created`, `tab.closed`, `tab.focused`, +`tab.renamed`, and `tab.moved`. `tab.moved` includes the moved `tab_id`, +`workspace_id`, requested `insert_index`, and updated ordered `tabs` list for +that workspace. +Pane event subscriptions include `pane.created`, `pane.updated`, `pane.closed`, `pane.focused`, +`pane.moved`, `pane.exited`, `pane.agent_detected`, +`pane.output_matched`, `pane.agent_status_changed`, and `pane.scroll_changed`. +Terminal-title changes can emit `pane.updated`, but spinner-only raw-title changes do not emit it when `terminal_title_stripped` is unchanged. +`pane.scroll_changed` is scoped to one `pane_id` and emits `pane_id`, +`workspace_id`, and the current `scroll` metrics whenever Herdr observes a +changed scroll snapshot. +Layout event subscriptions include `layout.updated`. The event carries the +updated `PaneLayoutSnapshot` for one tab. Clients that bootstrap with +`session.snapshot` should replace the cached layout with the same +`workspace_id` and `tab_id`. + +Worktree event subscriptions include `worktree.created`, `worktree.opened`, and `worktree.removed`. Worktree events describe Git checkout lifecycle. `worktree.created` includes the opened `workspace` and created `worktree`. `worktree.opened` includes the target `workspace`, opened `worktree`, and `already_open`. `worktree.removed` includes the `workspace_id`, removed `worktree`, and `forced`. + +Use `events.subscribe` for lifecycle events. Dedicated wait helpers are documented separately when a one-shot wait is supported. + +## Reading panes + +Use `pane.read` through the CLI unless you are writing a protocol client. + +```bash +herdr pane read w1:p1 --source visible --lines 80 +herdr pane read w1:p1 --source recent --lines 120 +herdr pane read w1:p1 --source recent-unwrapped --lines 120 +herdr pane read w1:p1 --source detection +``` + +`recent-unwrapped` is useful for logs because it ignores soft wrapping. +`detection` returns the bottom-buffer snapshot used by agent screen detection. + +## Waiting for state + +Use waits to coordinate agents and scripts. + +```bash +herdr agent wait w1:p1 --until done +herdr agent wait w1:p1 --until blocked +``` + +Agent waits observe semantic state, not arbitrary command completion. + +## Response shapes + +Successful responses look like this: + +```json +{ + "id": "req_1", + "result": { + "type": "pane_info", + "pane": { + "pane_id": "w1:p1", + "terminal_id": "term_abc123", + "workspace_id": "w1", + "tab_id": "w1:t1", + "focused": true, + "agent_status": "working", + "revision": 42 + } + } +} +``` + +`server.agent_manifests` returns the active agent detection manifest sources and remote update diagnostics without reloading rules: + +```json +{ + "id": "req_1", + "result": { + "type": "agent_manifest_status", + "last_check_unix": 1781043522, + "last_result": "checked", + "manifests": [ + { + "agent": "cursor", + "source": "/home/me/.config/herdr/agent-detection/cursor.toml", + "source_kind": "local override", + "active_version": "2026.06.10.1", + "cached_remote_version": "2026.06.10.1", + "local_override_shadowing_remote": true, + "remote_update_result": "current" + } + ] + } +} +``` + +Fields such as `last_check_unix`, `last_result`, `active_version`, `cached_remote_version`, `remote_update_result`, `remote_update_error`, `remote_last_checked_unix`, and `warning` are omitted when not available. `server.reload_agent_manifests` returns `agent_manifest_reload` with the same `manifests` item shape after reloading the in-memory rule cache. + +`agent.explain` evaluates the target pane's detection snapshot in the running server using the server's active manifest cache: + +```json +{ + "id": "req_2", + "method": "agent.explain", + "params": { "target": "w1:p1" } +} +``` + +The response contains the same explain object printed by `herdr agent explain --json`, including the final state, manifest source and version, matched rule, evaluated rule evidence, skip-state reason, idle fallback reason, and `screen_detection_skip_reason` when a full lifecycle hook authority makes screen rules non-authoritative. + +Clients need a running server that supports `agent.explain`; after upgrading Herdr, restart or live-handoff the server before relying on this method. + +Errors look like this: + +```json +{ + "id": "req_1", + "error": { + "code": "not_found", + "message": "pane not found" + } +} +``` + +## Protocol stability + +Herdr has a protocol version for client/server compatibility. Protocol changes are reviewed with release compatibility in mind. + +Check the server protocol with `ping` or `herdr status` before depending on new behavior. Handle unknown fields gracefully. diff --git a/docs/preview/website/src/content/docs/troubleshooting.mdx b/docs/preview/website/src/content/docs/troubleshooting.mdx new file mode 100644 index 00000000..e0df4974 --- /dev/null +++ b/docs/preview/website/src/content/docs/troubleshooting.mdx @@ -0,0 +1,95 @@ +--- +title: Troubleshooting +description: Diagnose common installation, terminal input, session, keybinding, and remote access problems. +--- + +Start with the versions and session status: + +```bash +herdr -V +herdr status +``` + +Also record your operating system, outer terminal name and version, whether the session is local or remote, and whether tmux is involved. + +## The CJK IME window is misplaced or the cursor flickers on Windows + +Herdr defaults to a cell-drawn cursor on native Windows and WSL because native cursors can flicker, jump, or show stale positions while ConPTY-based multiplexers repaint the screen. A cell-drawn cursor cannot provide the native cursor anchor used by Korean, Japanese, and Chinese IME composition UI. + +If the IME composition or candidate window appears at the wrong location, enable the native cursor: + +```toml +[ui] +host_cursor = "native" +``` + +Native mode restores the IME anchor but may reintroduce occasional cursor movement artifacts during active output. Return to the visually stable cursor with `host_cursor = "drawn"`, or remove the setting to use the Windows default. See [Windows beta](/docs/windows-beta/) for the current limitation. + +## Enter, Tab, or Backspace fires twice + +Older terminal versions can emit the release of Enter, Tab, and Backspace as the same bytes as the press when an application enables Kitty keyboard event reporting. Herdr cannot distinguish those duplicate bytes after the terminal sends them. + +Update the outer terminal to a version containing its upstream fix: + +| Terminal | Minimum fixed version | +| --- | --- | +| kitty | 0.33.0 | +| foot | 1.20.0 | +| Alacritty | 0.15.0 | + +This is especially common with older terminal packages from long-term-support Linux distributions. See [Herdr issue #1116](https://github.com/ogulcancelik/herdr/issues/1116) for the confirmed boundary captures and upstream references. If the problem remains on a current terminal version, report the exact terminal version and whether it also happens outside Herdr. + +## Option+Left or Option+Right inserts `;3D` or `;3C` + +Terminals commonly send Alt+Left and Alt+Right as the standard modified-arrow sequences `ESC[1;3D` and `ESC[1;3C`. On macOS, the outer terminal must first be configured to treat Option as Alt. If the shell does not bind these sequences, zsh may display their remaining characters as `;3D` or `;3C`. This can happen with kitty, Alacritty, and other terminals; Herdr and tmux both preserve the original modified-arrow input. + +Add explicit zsh bindings if you want modified arrows to perform word navigation in every terminal and nested shell: + +```zsh +bindkey $'\e[1;3D' backward-word +bindkey $'\e[1;3C' forward-word +``` + +Kitty can appear to work differently outside Herdr because its automatic zsh integration adds these bindings only to shells started directly by kitty, not shells created by terminal multiplexers. Follow kitty's [manual shell integration instructions](https://sw.kovidgoyal.net/kitty/shell-integration/#manual-shell-integration), or map the keys in `kitty.conf` before they reach the shell: + +```text +map alt+left send_text all \x1bb +map alt+right send_text all \x1bf +``` + +Herdr deliberately does not rewrite modified arrows because terminal applications may use Alt+Left and Alt+Right directly. See [Herdr issue #1370](https://github.com/ogulcancelik/herdr/issues/1370) for the investigation. + +## Herdr updated, but the running session is still old + +Updating the binary does not always replace a compatible server that is already running. Check `herdr status`. To start the updated server, stop the session and launch Herdr again: + +```bash +herdr server stop +herdr +``` + +Stopping a server exits its pane processes. Named sessions use `herdr session stop <name>`. See [Install Herdr](/docs/install/#update) for updater, package-manager, and live-handoff behavior. + +## The `herdr` command is not found + +Restart the terminal so it reloads its environment, then confirm the Herdr install directory is on `PATH`. Package-manager installs must be updated and exposed through that package manager. See [Install Herdr](/docs/install/#verify). + +## A direct keybinding does nothing + +The operating system or outer terminal may consume the chord before Herdr receives it. Free the chord in that layer or choose another binding. See [Keyboard](/docs/keyboard/#going-prefix-free) for known conflicts and safe defaults. + +## Remote attach cannot authenticate + +First confirm that normal OpenSSH works with `ssh <host>`. For a passphrase-protected key in a non-interactive shell, CI job, or mobile terminal, load the key into `ssh-agent` before starting remote attach. See [Persistence and remote access](/docs/persistence-remote/#remote-attach-over-ssh). + +## Find diagnostic logs + +Herdr logs live in `~/.config/herdr/` by default: + +```text +herdr.log +herdr-client.log +herdr-server.log +``` + +Set `HERDR_LOG=herdr=debug` for more detail. Include the current log and rotated siblings when reporting a problem. See [Configuration](/docs/configuration/#logs). diff --git a/docs/preview/website/src/content/docs/windows-beta.mdx b/docs/preview/website/src/content/docs/windows-beta.mdx new file mode 100644 index 00000000..6533ca6f --- /dev/null +++ b/docs/preview/website/src/content/docs/windows-beta.mdx @@ -0,0 +1,124 @@ +--- +title: Windows beta +description: Native Windows support status, supported workflows, and known limitations. +--- + +Native Windows support is experimental beta. + +Herdr on Windows uses ConPTY and Windows process/runtime behavior instead of the Unix PTY model Herdr was originally built around. Some Herdr features map cleanly to Windows, and some do not. This preview is not a commitment that every Linux/macOS feature will become fully supported on Windows. + +The goal of the beta is to learn from real use: install success, pane reliability, agent workflows, bug volume, missing features, and whether Windows users are getting enough value from Herdr. Based on that feedback, Windows support may graduate to stable, stay preview-only while it matures, or be reduced if the maintenance cost is not justified. + +Install native Windows beta builds with PowerShell: + +```powershell +powershell -ExecutionPolicy Bypass -c "irm https://herdr.dev/install.ps1 | iex" +``` + +Windows beta builds ship only through the preview channel. Herdr defaults to preview on Windows without changing your config. The installer stores releases under `%USERPROFILE%\.herdr\packages\standalone\releases`, points `%LOCALAPPDATA%\Programs\Herdr\bin` at the current release, and keeps a small number of older releases so running processes do not block updates. + +For internal beta testing, `HERDR_MANIFEST_URL` can point the installer at a custom manifest instead of Herdr's stable or preview manifest. + +## Supported in beta + +| Capability | Status | +| --- | --- | +| Local persistent sessions | beta | +| Native panes through ConPTY | beta | +| Windows Terminal / PowerShell app attach | beta | +| `cmd.exe` panes | beta | +| Startup cwd and workspace labels | beta | +| Pane launch cwd | beta | +| Agent command discovery | beta | +| Agent self-report integrations | beta | +| Agent process-tree detection | beta | +| Git/worktree detection from known cwd | beta | +| Plugins | preview | +| Pane screen history | beta | +| Nested launch override | beta | + +Windows agent process detection scans descendants of the pane shell and recognizes direct agents plus common command wrappers. It is useful for Codex, Claude, and similar agents, but it is not the same as Unix foreground process-group detection. + +Plugins support `windows` as a manifest platform in preview. GitHub install, local link, build commands, actions, events, and plugin panes are best effort on Windows. Commands are argv commands and must be Windows-compatible; Node package shims such as `npm`, `bun`, and `node` are expected to work when they are on `PATH`, while Unix-only examples using `sh` or Bash need Windows-specific alternatives. Platform filters skip unsupported build commands and return `platform_unsupported` for unsupported actions or panes. + +## Partial support + +| Capability | Status | +| --- | --- | +| Live cwd after shell `cd` | partial | +| Live cwd via shell integration/OSC7 | beta | +| Clipboard image paste to agents | unverified | +| CJK IME composition anchoring | partial | +| Kitty graphics rendering | unverified | +| Host cursor rendering | partial | + +Herdr can launch panes in the right directory and can create the initial workspace from the directory where you started Herdr. PowerShell directory changes after startup are different: the process field Herdr can inspect does not reliably track later logical `cd` changes. Use Herdr integrations or prompt shell integration for live cwd reporting. + +Windows Terminal may support image paste paths for specific agents, but Herdr's own clipboard-image reader is not wired on Windows yet. Treat `alt+v` image paste as unverified until the Windows clipboard bridge is implemented and tested. Remote clipboard image bridging is separate and remains tied to Unix/macOS `herdr --remote`. + +Kitty graphics remains experimental and is not claimed as Windows-supported yet. Leave `experimental.kitty_graphics = false` unless you are specifically testing image rendering in Windows Terminal. + +## Known caveats + +### Cursor rendering + +Herdr relies on ConPTY for native Windows panes. The current Windows terminal cursor path can expose intermediate cursor positions while a multiplexer repaints the screen. A native cursor may flicker, jump, or briefly remain at an old position during active output. This behavior also reproduces in other native Windows terminal multiplexers and with direct VT cursor-position stress tests, so Herdr cannot eliminate it while preserving native cursor behavior. + +To prioritize visual stability, the default `host_cursor = "auto"` draws Herdr's cursor as terminal cell content on native Windows and WSL. Other Linux and macOS clients continue to use the native terminal cursor. The drawn Windows cursor is steady and non-blinking, but it does not provide the outer terminal's native blink, shape, or cursor color. + +A drawn cursor is not the cursor that Windows uses to position IME composition and candidate UI. Korean, Japanese, or Chinese IME UI may therefore appear at the wrong location. If this affects you, opt back into the outer terminal cursor: + +```toml +[ui] +host_cursor = "native" +``` + +Native mode restores the IME anchor, but it can reintroduce occasional cursor flicker, jumps, or stale cursor positions during active output. This is currently a compatibility trade-off in the Windows beta. + +### Keyboard and mouse + +Windows terminals do not all report modified keys in the same shape. Herdr preserves mouse reporting and `ctrl+j` in Windows Terminal and Alacritty on Windows, but `shift+enter` only works when the outer terminal reports it as a distinct modified Enter key. If Windows or the terminal reports it as plain Enter, Herdr forwards plain Enter. + +Preview packages include Microsoft's current app-local ConPTY runtime because the system ConPTY on older Windows 10 builds drops Kitty keyboard protocol sequences used by agents such as Kimi and Pi. Set `HERDR_WINDOWS_CONPTY=system` before starting Herdr only when diagnosing a compatibility problem with the bundled runtime. + +## Copy and paste + +Herdr's pane text copy works on Windows beta. Drag-select text inside a pane to copy through Herdr. + +For text paste, use `ctrl+shift+v` in Windows Terminal. Multiline text paste is bracketed so shells and agent prompts receive it as one paste instead of submitting each line separately. Hold `shift` and right-click to use the outer terminal paste action instead of sending the click through Herdr. + +## Not supported on Windows beta + +| Capability | Status | +| --- | --- | +| Direct terminal attach | unsupported | +| `herdr --remote` from the Windows binary | unsupported | +| Live server handoff | unsupported | +| Unix file-descriptor handoff | unsupported | +| Unix foreground process groups | unsupported | +| Remote clipboard image bridge | unsupported | +| Prefix input-source switching | unsupported | +| Signed binary / SmartScreen avoidance | unsupported | + +For remote work from Windows, SSH into the server and run `herdr` there: + +```powershell +ssh you@server +herdr +``` + +That mode runs Herdr on the remote host. Native Windows `herdr --remote` is not part of the beta. + +Windows updates run through the Windows installer and update the versioned install junction. Restart running Herdr sessions after updating. Live handoff is Unix-only. + +## Reporting Windows beta issues + +Include: + +- Herdr version. +- Windows version. +- Terminal app. +- Shell, such as PowerShell or cmd. +- Whether you used a named `HERDR_SESSION`. +- Relevant Herdr logs. +- Exact steps to reproduce. diff --git a/docs/preview/website/src/content/docs/zh-cn/agent-automation.mdx b/docs/preview/website/src/content/docs/zh-cn/agent-automation.mdx new file mode 100644 index 00000000..c8ec6ce9 --- /dev/null +++ b/docs/preview/website/src/content/docs/zh-cn/agent-automation.mdx @@ -0,0 +1,123 @@ +--- +title: 智能体自动化 +description: 使用 Herdr 的布局、窗格和智能体原语,从脚本或其他智能体协调编码智能体。 +--- + +Herdr 可以作为编码智能体的自动化层。脚本可以控制智能体,一个智能体也可以给其他智能体分配工作、检查状态并收集结果。关键是为任务选择正确的原语。 + +## 三种原语 + +| 原语 | 职责 | +| --- | --- | +| 布局(`workspace`、`tab` 和窗格拓扑) | 创建和组织终端位置。 | +| 窗格 | 控制原始终端:运行命令、发送输入、读取输出和等待输出。 | +| 智能体 | 按名称或窗格以及生命周期状态控制已识别的编码智能体。 | + +无论是否包含智能体,窗格都可以存在。智能体是 Herdr 在窗格中识别出的当前进程。因此,`agent start` 需要一个现有 shell 窗格,绝不会创建、拆分或移动布局。 + +创建工作区时也会创建第一个标签页和根窗格;创建标签页时会创建它的根窗格。第一个进程应使用返回的窗格 ID,只有布局需要另一个终端时才进行拆分。 + +创建命令输出 JSON。请从响应中读取 ID,不要猜测: + +```bash +created=$(herdr workspace create --cwd ~/project --label api --no-focus) +pane_id=$(printf '%s\n' "$created" | jq -r '.result.root_pane.pane_id') + +split=$(herdr pane split "$pane_id" --direction right --no-focus) +review_pane=$(printf '%s\n' "$split" | jq -r '.result.pane.pane_id') +``` + +`workspace create` 返回 `.result.workspace`、`.result.tab` 和 `.result.root_pane`;`tab create` 返回 `.result.tab` 和 `.result.root_pane`;`pane split` 把新窗格返回为 `.result.pane`。 + +把窗格移动到另一个工作区会改变带工作区前缀的窗格 ID。执行 `pane move` 后,请继续使用 `.result.move_result.pane.pane_id`;旧值保留在 `.result.move_result.previous_pane_id`。运行中的进程会保留启动时的 Herdr 环境,但旧的 `HERDR_PANE_ID` 会继续作为该终端的别名,所以 `--current` 仍可安全使用。移动后发起的新命令仍可通过智能体名称解析,但已经进行中的等待会以 `agent_not_running` 结束。 + +对于 shell、测试、服务器、CI 监视器和其他普通终端进程,使用窗格命令。当 Herdr 需要理解正在运行的是哪个智能体,或它处于 `working`、`blocked`、`done`、`idle`、`unknown` 中的哪个状态时,使用智能体命令。 + +## 智能体身份与启动 + +`w1:p2` 这样的窗格 ID 标识终端位置。`reviewer` 这样的智能体名称是该窗格中当前智能体的便捷别名。名称必须匹配 `[a-z][a-z0-9_-]{0,31}`,并在实时智能体中唯一。智能体退出、被释放或被替换时,别名会被清除;它不会永久重命名窗格。 + +智能体命令既可以接受唯一的实时名称,也可以接受当前托管该智能体的窗格 ID。 + +可用的 shell 窗格必须停在交互式 shell 提示符,由 shell 自身占用前台,没有正在前台运行的命令、编辑器或智能体。调用 `agent start` 前先让窗格回到提示符。 + +`--kind` 选择受支持的智能体及其标准可执行文件。支持的 kind 是 `pi`、`claude`、`codex`、`gemini`、`cursor`、`devin`、`agy`、`cline`、`omp`、`mastracode`、`opencode`、`copilot`、`kimi`、`kiro`、`droid`、`amp`、`grok`、`hermes`、`kilo`、`qodercli` 和 `maki`。`--` 后的参数会原样传给该可执行文件。 + +`agent start` 只有在 Herdr 于同一终端检测到预期智能体,并确认它可接受交互输入后才返回。默认等待启动 30 秒;`--timeout` 必须大于 3000 且不超过 300000 毫秒。 + +```bash +herdr agent start reviewer --kind codex --pane "$review_pane" -- -m gpt-5.4 +``` + +手动启动的智能体也会被自动检测,可以用窗格 ID 指定。当需要一个稳定、易读的目标时,给它命名: + +```bash +herdr agent get w1:p2 +herdr agent rename w1:p2 reviewer +``` + +## 选择控制界面 + +| 目标 | 命令 | +| --- | --- | +| 运行并提交 shell 命令 | `pane run` | +| 发送不带 Enter 的纯文本 | `pane send-text` | +| 发送终端按键或修饰键组合 | `pane send-keys` | +| 等待文本或正则表达式 | `pane wait-output` | +| 在现有窗格中启动受支持的智能体 | `agent start` | +| 提交提示,并可选择等待 | `agent prompt` | +| 向智能体交互界面发送按键 | `agent send-keys` | +| 等待智能体生命周期状态 | `agent wait` | + +`agent prompt` 会提交文本和编码后的 Enter,并遵循终端当前的 bracketed paste 模式。即使智能体正在 working 也可以提交。使用 `agent send-keys` 进行 `esc`、`up`、`enter`、`ctrl+c` 等交互;`escape` 也是 `esc` 的别名。只有在明确需要原始终端控制时才使用窗格输入命令。 + +窗格输入直接指定终端,不关心当前进程。智能体输入会解析实时智能体;如果该智能体已不再控制此窗格,操作会被拒绝。 + +`agent prompt --wait` 会立即提交提示。智能体从非 working 状态开始时,Herdr 首先要求在五秒内观察到生命周期变化。如果状态序列没有前进,它会返回 `agent_prompt_stalled`,而不是无限等待;调用方设置的 `--timeout` 不超过五秒时,仍返回普通的 `timeout` 错误。观察到活动后,它会等待请求的稳定状态。它不会跟踪单独的轮次。如果智能体已经处于 working,当前轮次的完成可能满足等待。独立的 `agent wait` 会观察当前智能体;如果状态已经匹配,就会立即返回。两者默认匹配 `idle`、`done` 或 `blocked`。可以重复使用 `--until` 接受多个精确状态,例如 `--until idle --until done`;需要 `unknown` 时请明确使用 `--until unknown`。在 `agent prompt` 中,`--until` 必须与 `--wait` 一起使用。 + +`idle` 表示智能体正等待输入,且其标签页已在聚焦的 Herdr 界面中显示。`done` 是相同的底层 idle 状态,但用于后台工作完成后,直到该标签页获得焦点或 `pane focus` / `agent focus` 指向它。仅通过 CLI 读取不会把它标记为已查看。`blocked` 表示 Herdr 识别到了审批或提问界面。`unknown` 表示智能体存在,但 Herdr 无法可靠判断其生命周期;它不代表工作成功完成。区别重要时,请指定精确的 `--until` 状态。 + +`pane wait-output` 不解释智能体生命周期。它轮询选定的终端快照并立即进行第一次搜索,所以已经存在的文本也会匹配。默认来源名是 `recent`;匹配时会把最近 80 个已渲染终端行作为未折行输出处理。`--lines` 可以修改这个行数限制;`--regex` 使用 Rust 正则表达式语法并逐行匹配。 + +在 CLI 中,`pane read` 和 `agent read` 都直接打印终端文本。默认输出去除 ANSI 转义的 UTF-8 文本;来源包含样式时,使用 `--format ansi` 或 `--ansi` 保留终端转义。`detection` 来源始终是纯文本。对 recent 类来源,`--lines N` 会在可选的取消折行之前选择最后 N 个已渲染终端行;省略时默认读取 80 行。对 `visible` 和 `detection`,省略 `--lines` 会返回完整快照,指定时保留按换行分隔的最后 N 行。socket API 在 `.result.read.text` 返回文本。 + +## 已知注意事项:备用屏幕输出 + +Claude Code 和 OpenCode 等全屏智能体可能会在终端的备用屏幕中绘制。备用屏幕中的行不会进入 Herdr 的主机回滚缓冲区。`--lines` 只能请求窗格当前屏幕和主机回滚缓冲区中现有的更多行,不能生成缺失的历史。如果增大 `--lines` 后仍没有返回更多回复文本,该窗格很可能正在使用备用屏幕,且那些回复行已不再保留。字体较大或窗格较小时更容易遇到此限制。 + +可以要求智能体简洁回复、扩大窗格或使用较小字体,也可以使用智能体自身的记录和滚动控件。在智能体内部滚动后,使用 `--source visible` 读取它当前绘制的页面。 + +在这次读取失败后,可以让智能体把完整回复以 Markdown 格式写入临时目录,只回复文件路径,然后直接读取该文件。仅将此方法用作后备方案;不要在初始提示中要求文件输出。 + +成功的 `agent start`、`agent prompt` 和 `agent wait` 会在 `.result.agent` 返回当前智能体。`pane wait-output` 返回 `.result.pane_id`、`.result.matched_line` 以及位于 `.result.read` 的匹配快照。 + +等待命令没有默认超时,可能无限等待。超时或服务器错误会把 JSON 错误写到 stderr 并以状态 1 退出;CLI 语法错误以状态 2 退出。 + +## 示例 + +启动辅助智能体,分配工作,并等待该工作结束: + +```bash +split=$(herdr pane split --current --direction right --no-focus) +review_pane=$(printf '%s\n' "$split" | jq -r '.result.pane.pane_id') +herdr agent start reviewer --kind codex --pane "$review_pane" -- -m gpt-5.4 +herdr agent prompt reviewer "Review the current diff" --wait --timeout 120000 +herdr agent read reviewer --source recent-unwrapped --lines 120 +``` + +等待智能体请求输入,检查内容,再操作其交互界面: + +```bash +herdr agent wait reviewer --until blocked --timeout 120000 +herdr agent read reviewer --source recent-unwrapped --lines 80 +herdr agent send-keys reviewer esc +``` + +运行普通进程并等待输出,而不把它当作智能体: + +```bash +herdr pane run w1:p3 "just test --watch" +herdr pane wait-output w1:p3 --regex "passed|failed" --timeout 120000 +``` + +完整命令和选项列表见 [CLI 参考](/zh-cn/docs/cli-reference/)。Shell 补全也能以交互方式显示同一命令树。 diff --git a/docs/preview/website/src/content/docs/zh-cn/agent-skill.mdx b/docs/preview/website/src/content/docs/zh-cn/agent-skill.mdx new file mode 100644 index 00000000..f905f347 --- /dev/null +++ b/docs/preview/website/src/content/docs/zh-cn/agent-skill.mdx @@ -0,0 +1,65 @@ +--- +title: 智能体技能文件 +description: 为 Claude Code 或其他编程智能体安装 Herdr 使用说明。 +--- + +Herdr 提供了一个可复用的智能体技能文件: [`SKILL.md`](https://github.com/ogulcancelik/herdr/blob/master/SKILL.md)。 + +把这个文件安装到任何支持可复用技能或自定义指令的编程智能体中。这份技能教智能体如何在 Herdr 窗格内部控制 Herdr。 + +Herdr 还提供了另一份用途不同的指南: [`herdr.dev/agent-guide.md`](https://herdr.dev/agent-guide.md),用于让智能体帮助人类学习、安装或排查 Herdr。技能是给操作 Herdr 的智能体用的;指南是给教人类的智能体用的。 + +## 这份技能做什么 + +这份技能告诉智能体: 当 `HERDR_ENV=1` 已设置时,使用 `herdr` CLI。这意味着智能体运行在 Herdr 管理的窗格内,可以安全地与本地 Herdr socket 通信。 + +安装技能后,智能体可以: + +- 查看工作区、标签页、窗格和相邻的智能体 +- 分割窗格并在不抢占焦点的情况下运行命令 +- 读取窗格输出和最近的日志 +- 等待服务器、测试或另一个智能体完成 +- 在相邻窗格中启动辅助智能体 + +这份技能不是独立的应用或服务,它只是一份给智能体看的 markdown 指令文件。 + +## 安装 + +用 `npx skills` 安装技能: + +```bash +npx skills add ogulcancelik/herdr --skill herdr -g +``` + +`-g` 参数为受支持的智能体全局安装。省略 `-g` 则安装到当前项目。 + +以仓库中的副本作为手动兜底和事实来源: + +```text +https://github.com/ogulcancelik/herdr/blob/master/SKILL.md +``` + +对于有技能系统的智能体,把这个文件安装为名为 `herdr` 的技能。对于没有技能系统的智能体,把文件内容粘贴到智能体的项目指令或用户指令中。 + +安装完成后,在 Herdr 内启动智能体: + +```bash +herdr +claude +``` + +也可以在 Herdr 窗格里使用任何其他编程智能体。重点是智能体进程要运行在 Herdr 内部,这样 `HERDR_ENV=1` 才可用。 + +## 安全规则 + +这份技能以一条护栏开头: 如果 `HERDR_ENV=1` 未设置,智能体应当停下来,并说明自己没有运行在 Herdr 管理的窗格内。 + +这可以防止 Herdr 外部的智能体试图控制一个不属于它的会话。 + +## 面向智能体的参考 + +完整的命令指南就在技能文件本身。它涵盖窗格 ID、`pane split`、`pane run`、`pane read`、`pane wait-output`、`agent wait`、工作区和标签页命令,以及协作配方。 + +在这里阅读源文件: + +[在 GitHub 上打开 `SKILL.md` →](https://github.com/ogulcancelik/herdr/blob/master/SKILL.md) diff --git a/docs/preview/website/src/content/docs/zh-cn/agents.mdx b/docs/preview/website/src/content/docs/zh-cn/agents.mdx new file mode 100644 index 00000000..fb478081 --- /dev/null +++ b/docs/preview/website/src/content/docs/zh-cn/agents.mdx @@ -0,0 +1,154 @@ +--- +title: 智能体 +description: 了解 Herdr 能检测什么、智能体状态如何工作,以及集成如何改进它。 +--- + +Herdr 为同时运行多个编程智能体而生。每个智能体都待在一个真实的终端窗格里,shell、日志、提示符和运行中的进程都完好无损。Herdr 跟踪哪些窗格里有智能体,把它们的状态汇总到标签页和工作区,让你直接跳到需要关注的窗格,而不是手动轮询每个终端。 + +要从脚本或另一个智能体协调它们,请参阅[智能体自动化](/zh-cn/docs/agent-automation/)。 + +## 受支持的智能体 + +常见编程智能体开箱即用地支持自动检测。重要的区别不在于 Herdr 能不能看到某个智能体,而在于允许哪个信号来决定 `idle`、`working` 和 `blocked`。 + +| 智能体 | 状态权威 | 集成角色 | +| --- | --- | --- | +| Pi | 安装后为生命周期钩子;否则为屏幕清单 | 状态与会话 | +| OMP | 安装后为生命周期钩子 | 状态 | +| GitHub Copilot CLI | 屏幕清单 | 会话 | +| Devin CLI | 屏幕清单 | 会话 | +| Kimi Code CLI | 安装后为生命周期钩子;否则为屏幕清单 | 状态与会话 | +| Hermes Agent | 安装后为生命周期钩子;否则为屏幕清单 | 状态与会话 | +| Qoder CLI | 屏幕清单 | 会话 | +| Droid | 屏幕清单 | 会话 | +| OpenCode | 安装后为生命周期插件;否则为屏幕清单 | 状态与会话 | +| Kilo Code CLI | 安装后为生命周期插件;否则为屏幕清单 | 状态与会话 | +| MastraCode | 安装后为生命周期钩子 | 状态与会话 | +| Claude Code | 屏幕清单 | 会话 | +| Codex | 屏幕清单 | 会话 | +| Cursor Agent CLI | 屏幕清单 | 会话 | +| Amp | 屏幕清单 | 无 | +| Grok CLI | 屏幕清单 | 无 | +| Antigravity CLI | 屏幕清单 | 无 | +| Kiro CLI | 屏幕清单 | 无 | +| Maki | 屏幕清单 | 无 | + +可检测但测试较少: Gemini CLI 和 Cline。不受支持的智能体仍然可以作为普通终端进程正常运行,只是在你添加集成或通过 socket API 上报状态之前,可能得不到丰富的状态。 + +## 状态权威 + +Herdr 首先检测每个窗格的前台进程。之后,每个窗格有且只有一个状态权威。 + +对于具备完整生命周期钩子的智能体,当集成已安装并在为运行中的窗格主动上报时,集成就是权威。Herdr 用这些钩子上报来决定 `idle`、`working`、`blocked` 和会话身份。对同一个生命周期权威,它不会再并行运行屏幕清单兜底。这样可以避免出现两个相互竞争的事实来源。 + +对于没有完整生命周期钩子的智能体,Herdr 识别前台进程,并读取实时的底部缓冲区屏幕快照。它对该快照评估 TOML 清单,来判定 `idle`、`working` 和 `blocked`。对于会发出这些信号的智能体,清单也可以把终端标题和进度 (OSC) 序列作为检测证据;当这些证据不存在时,屏幕规则独自承担检测。 + +屏幕快照来自窗格缓冲区最近的底部,而不是滚动后的视口。即使你在 Herdr 里向上翻页,检测仍然跟随底部的实时智能体 UI。 + +Claude Code、Codex、GitHub Copilot CLI、Droid、Qoder CLI 和 Cursor Agent CLI 的集成有意不作为生命周期权威。它们为恢复提供原生会话身份,但它们的钩子并不覆盖整个生命周期,可能漏掉权限审批结果、Esc 中断或其他状态转换。对这些智能体,Herdr 仍然使用屏幕清单检测。 + +## 虚拟机与沙箱包装器 + +在 Linux 和 macOS 上,宿主可见的包装器可能会向 Herdr 隐藏真实的智能体进程。在包装器命令上设置 `HERDR_AGENT=<agent>`,告诉 Herdr 应使用哪个已有智能体的屏幕清单。例如,在 Linux 上运行 `HERDR_AGENT=claude fence -- claude`,或在 macOS 上运行 `HERDR_AGENT=claude nono run --profile claude-code -- claude`。这个提示的作用范围只限于该前台进程;仅在 VM 或容器内部设置时 Herdr 无法看到它,而且除非所有继承的前台进程都应被视为该智能体,否则不要全局 export。 + +## blocked 状态 + +对屏幕清单类智能体,blocked 检测刻意从严。只有当实时底部缓冲区快照匹配已知可见的审批、提问或权限 UI 时,Herdr 才标记 `blocked`。对已知智能体,如果没有任何清单规则匹配,Herdr 会回退到 `idle`,并在 explain 输出中把该回退标记为 `default_known_agent_idle_fallback`。 + +这意味着不常见的新智能体提示可能一开始显示为 `idle` 而不是 `blocked`,直到 Herdr 学会那种屏幕形态。这些交互不会让 Herdr 发送输入或执行破坏性操作;它们只影响可见状态和等待。 + +## 检测清单 + +内置清单打包在 Herdr 内部。Herdr 还会向 herdr.dev 检查远程清单更新,并自动应用有效的按智能体规则更新,不需要重启 Herdr。远程清单存放在 Herdr 的状态目录中。设置 `[update] manifest_check = false` 可以禁用后台远程清单检查。 + +本地覆盖可以从平台配置目录替换远程或内置清单: + +```text +~/.config/herdr/agent-detection/<agent>.toml +``` + +本地覆盖始终优先。没有本地覆盖时,Herdr 在缓存的远程清单和运行中二进制文件内置的清单之间,选择更新且兼容的那个。在调试构建上,同一个配置助手可能使用 `herdr-dev` 之类的开发目录。无效的覆盖文件会被忽略并给出警告,Herdr 会对该智能体回退到缓存的远程或内置清单。 + +远程清单只为 Herdr 已经知道如何识别的智能体修补检测规则。添加全新的智能体仍然需要更新 Herdr 二进制文件,以获得进程检测、标签和集成行为。 + +运行中的服务器在启动时把生效的清单加载进内存。自动的远程清单更新会在写入新规则后重新加载该内存缓存。运行 `herdr server update-agent-manifests` 可以立即拉取远程清单更新并重载运行中的服务器。手动编辑本地覆盖后,重启 Herdr 或运行 `herdr server reload-agent-manifests` 把文件应用到运行中的服务器。 + +当某个窗格显示了错误状态时,用 `herdr agent explain`: + +```bash +herdr agent explain <target> +herdr agent explain --file screen.txt --agent codex --json +``` + +实时 explain 由运行中的服务器评估,因此反映的是生效的清单缓存。explain 输出包括: 智能体、最终状态、屏幕检测是否被完整生命周期权威跳过、清单来源和版本、缓存的远程版本、本地覆盖的遮蔽情况、远程更新状态、匹配的规则、可见证据标志、已评估规则的匹配器和区域证据、转写查看器的跳过更新原因,以及没有规则匹配时的 idle 回退原因。 + +Herdr 可以在 tmux 作为外层终端环境时运行。智能体检测不会检查在 Herdr 窗格内启动的 tmux 会话。如果某个 shell 框架在 Herdr 内自动进入 tmux,Herdr 看到的窗格进程就是 `tmux`,而不是它背后的智能体。 + +## 状态汇总 + +侧边栏会把状态向上汇总。 + +一个 blocked 的智能体会让它的窗格、标签页和工作区看起来是 blocked。一个 working 的智能体会让工作区看起来处于活跃状态。一个 done 的智能体在你查看之前会一直保持可见。 + +这就是 Herdr 的主要工作流: 启动多个智能体,让它们并行工作,用侧边栏看哪个项目需要决策、哪个还在运行、哪个已经可以审阅。 + +## 直接集成 + +为你使用的每个智能体安装集成;它让 Herdr 获得钩子或插件上报,而不是只靠屏幕检测: + +```bash +herdr integration install claude +herdr integration status +``` + +每个受支持的智能体都有自己的集成名称和行为。按智能体的细节和完整安装列表见[集成](/zh-cn/docs/integrations/)。 + +## 自定义智能体标签 + +你可以重命名智能体目标的显示名: + +```bash +herdr agent rename w1:p1 reviewer +herdr agent rename reviewer --clear +``` + +目标使用唯一的实时智能体名称,或当前承载该智能体的窗格 ID。终端 ID 和单独的智能体 kind 标签不能作为目标。 + +## 自定义状态标签 + +集成只把生命周期状态作为语义状态上报。展示自定义应通过窗格元数据令牌单独添加。 + +```bash +herdr pane report-agent w1:p1 \ + --source custom:indexer \ + --agent docs-bot \ + --state working + +herdr pane report-metadata w1:p1 \ + --source custom:indexer-display \ + --token summary=indexing +``` + +`state` 控制等待、通知和汇总。`summary` 令牌只影响展示,可在智能体侧边栏行中写成 `$summary`。 + +智能体侧边栏行也可以选择使用 `terminal_title` 或 `terminal_title_stripped`;两者都不在默认行中。前者显示经过安全规范化的最新 OSC 0/2 终端标题,后者会移除开头一个已识别的活动或旋转指示符字形及其后的空白。这些值由 Herdr 服务器所有,冷重启后不会保留,并且独立于元数据标题和语义智能体状态。因此,如果移除后的文本不变,旋转动画可以改变原始标题而不触发窗格更新。 + +## 直接附加到智能体 + +把当前终端附加到某一个智能体终端,而不是完整的 Herdr UI: + +```bash +herdr agent attach reviewer +``` + +用 `ctrl+b q` 分离。用 `ctrl+b ctrl+b` 发送字面的 `ctrl+b`。 + +用鼠标滚轮或普通的 page up/page down 滚动。正常输入会跳回底部。 + +如果另一个直接附加客户端已经拥有输入,用 `--takeover`: + +```bash +herdr agent attach reviewer --takeover +``` + +想对非智能体终端获得相同的直接附加行为时,用 `herdr terminal attach <terminal_id>`。 diff --git a/docs/preview/website/src/content/docs/zh-cn/cli-reference.mdx b/docs/preview/website/src/content/docs/zh-cn/cli-reference.mdx new file mode 100644 index 00000000..03831dc7 --- /dev/null +++ b/docs/preview/website/src/content/docs/zh-cn/cli-reference.mdx @@ -0,0 +1,444 @@ +--- +title: CLI 参考 +description: 用于会话、工作区、标签页、窗格、通知、智能体、等待、集成和状态的 Herdr 命令。 +--- + +Herdr 的 CLI 通过与集成和智能体相同的本地 socket API 与运行中的服务器通信。 + +大多数命令输出 JSON 响应。需要确定性自动化时,从脚本中使用它们。 + +## 启动与状态 + +```bash +herdr # 启动或连接默认会话 +herdr --session work # 启动或连接命名会话 +herdr --remote workbox # 通过 SSH 连接,使用本地按键绑定 +herdr --remote workbox --remote-keybindings server +herdr --remote workbox --handoff +herdr --no-session # 单进程逃生舱 +herdr --default-config # 打印默认配置 +herdr update # 从配置的通道下载并安装 +herdr update --handoff # 对受支持的运行中服务器启用实时交接 +herdr completion zsh # 生成 zsh 补全脚本 +herdr channel show # 打印 stable 或 preview +herdr channel set preview # 启用预览构建 +herdr channel set stable # 把 Linux/macOS 直接安装切回稳定版 +herdr --version # 打印版本 +``` + +状态命令: + +```bash +herdr status +herdr status server +herdr status client +``` + +API schema 命令: + +```bash +herdr api schema +herdr api schema --json +herdr api schema --output herdr-api.schema.json +``` + +`herdr api schema` 会打印安装的二进制中包含的 socket 协议 schema 简短摘要。需要完整 JSON Schema 文档时使用 `--json`;要写入文件则使用 `--output PATH`。 + +## Shell 补全 + +```bash +herdr completion zsh +herdr completions zsh +herdr completion bash +herdr completion fish +herdr completion powershell +herdr completion elvish +``` + +`completion` 会把脚本打印到 stdout。`completions` 是别名。临时 zsh 会话可以直接加载脚本: + +```bash +source <(herdr completion zsh) +``` + +持久 zsh 设置中,把生成的 `_herdr` 函数写到 `compinit` 运行前已经在 `fpath` 上的位置: + +```bash +mkdir -p ~/.zfunc +herdr completion zsh > ~/.zfunc/_herdr +``` + +然后确认 `.zshrc` 包含: + +```zsh +fpath=(~/.zfunc $fpath) +autoload -Uz compinit +compinit +``` + +## 服务器 + +```bash +herdr server +herdr server stop +herdr server reload-config +herdr server agent-manifests [--json] +herdr server update-agent-manifests [--json] +herdr server reload-agent-manifests +``` + +`herdr server` 显式运行无界面服务器,适合被监管或服务式的部署。`reload-config` 在不重启窗格的情况下应用可重载设置。`agent-manifests` 显示生效的智能体检测清单来源、缓存的远程版本和最近的远程更新结果。`update-agent-manifests` 立即拉取远程清单更新,重载到运行中的服务器,并打印更新后的清单状态;要原始状态响应就加 `--json`。`reload-agent-manifests` 在编辑本地覆盖后,把智能体检测清单重载到运行中的服务器。 + +## 通知 + +```bash +herdr notification show <title> [--body TEXT] [--position top-left|top-right|bottom-left|bottom-right] [--sound none|done|request] +``` + +`notification show` 使用配置的 `[ui.toast]` 投递方式。`--position` 只影响 Herdr 应用内的 toast。`--sound` 默认为 `none`;`done` 和 `request` 只在通知实际显示时播放已有的完成音和需要关注音。 + +## 会话 + +```bash +herdr session list [--json] +herdr session attach <name> +herdr session stop <name> [--json] +herdr session delete <name> [--json] +``` + +需要显式停止默认会话时,把会话名写成 `default`。 + +## 工作区 + +```bash +herdr workspace list +herdr workspace create [--cwd PATH] [--label TEXT] [--env KEY=VALUE] [--focus] [--no-focus] +herdr workspace get <workspace_id> +herdr workspace focus <workspace_id> +herdr workspace rename <workspace_id> <label> +herdr workspace report-metadata <workspace_id> --source ID [--token NAME=VALUE] [--clear-token NAME] [--seq N] [--ttl-ms N] +herdr workspace close <workspace_id> +``` + +不抢占焦点地创建工作区: + +```bash +herdr workspace create --cwd ~/project --label api --no-focus +``` + +工作区是顶层项目或工作上下文。创建工作区也会创建第一个标签页和根窗格。JSON 响应中的 `.result.workspace.workspace_id`、`.result.tab.tab_id` 和 `.result.root_pane.pane_id` 是对应 ID。 + +## Worktree + +```bash +herdr worktree list [--workspace ID | --cwd PATH] [--json] +herdr worktree create [--workspace ID | --cwd PATH] [--branch NAME] [--base REF] [--path PATH] [--label TEXT] [--focus] [--no-focus] [--json] +herdr worktree open [--workspace ID | --cwd PATH] (--path PATH | --branch NAME) [--label TEXT] [--focus] [--no-focus] [--json] +herdr worktree remove --workspace ID [--force] [--json] +``` + +worktree 是带有 Git 检出来源信息的普通 Herdr 工作区。`worktree create` 创建一个 Git worktree 检出,作为工作区打开,并与父仓库工作区分到一组。如果 `--branch` 指向已有的本地分支,Herdr 检出它;否则从 `--base` 或 `HEAD` 创建分支。没有 `--path` 时,Herdr 在 `<worktrees.directory>/<repo>/<branch-slug>` 下创建检出。 + +`workspace close` 只关闭 Herdr 状态。`worktree remove` 是显式的检出删除路径;它运行 `git worktree remove`,从不删除分支,并在 Git 拒绝脏检出时要求 `--force`。 + +## 标签页 + +```bash +herdr tab list [--workspace <workspace_id>] +herdr tab create [--workspace <workspace_id>] [--cwd PATH] [--label TEXT] [--env KEY=VALUE] [--focus] [--no-focus] +herdr tab get <tab_id> +herdr tab focus <tab_id> +herdr tab rename <tab_id> <label> +herdr tab close <tab_id> +``` + +标签页是工作区内的另一个终端布局。不指定 `--workspace` 时,`tab create` 使用活动工作区;如果没有活动工作区则失败。JSON 响应中的 `.result.tab.tab_id` 和 `.result.root_pane.pane_id` 是对应 ID。 + +创建工作区、标签页和拆分窗格默认不改变焦点。`--focus` 选择新布局;`--no-focus` 显式表达默认行为。不指定 `--cwd` 时,新终端遵循 `terminal.new_cwd` 设置,默认跟随来源窗格或工作区。每个 `--env KEY=VALUE` 在新的根 shell 中添加或替换一个环境变量。 + +## 窗格 + +```bash +herdr pane list [--workspace <workspace_id>] +herdr pane current [--pane ID|--current] +herdr pane get <pane_id> +herdr pane layout [--pane ID|--current] +herdr pane process-info [--pane ID|--current] +herdr pane neighbor --direction left|right|up|down [--pane ID|--current] +herdr pane edges [--pane ID|--current] +herdr pane focus --direction left|right|up|down [--pane ID|--current] +herdr pane resize --direction left|right|up|down [--amount FLOAT] [--pane ID|--current] +herdr pane zoom [<pane_id>|--pane ID|--current] [--toggle|--on|--off] +herdr pane rename <pane_id> <label>|--clear +herdr pane split [<pane_id>|--pane ID|--current] --direction right|down [--ratio FLOAT] [--cwd PATH] [--env KEY=VALUE] [--focus] [--no-focus] +herdr pane swap --direction left|right|up|down [--pane ID|--current] +herdr pane swap --source-pane ID --target-pane ID +herdr pane move <pane_id> --tab <tab_id> --split right|down [--target-pane ID] [--ratio FLOAT] [--focus|--no-focus] +herdr pane move <pane_id> --new-tab [--workspace ID] [--label TEXT] [--focus|--no-focus] +herdr pane move <pane_id> --new-workspace [--label TEXT] [--tab-label TEXT] [--focus|--no-focus] +herdr pane close <pane_id> +``` + +接受 `--current` 的窗格命令在 Herdr 窗格内运行时使用调用方的 `HERDR_PANE_ID`。对 `pane split` 而言,显式窗格 ID 或 `--pane ID` 拆分该窗格,`--current` 拆分调用方窗格,省略目标则使用 UI 当前聚焦的窗格。拆分响应中的新窗格 ID 位于 `.result.pane.pane_id`。 + +执行 `pane move` 后,后续命令应使用 `.result.move_result.pane.pane_id`。跨工作区移动会改变带工作区前缀的窗格 ID;旧值保留在 `.result.move_result.previous_pane_id`。运行中进程的 `HERDR_PANE_ID`、`HERDR_TAB_ID` 和 `HERDR_WORKSPACE_ID` 保持启动时的值,但 Herdr 会把旧窗格 ID 保留为该终端的别名,所以使用 `--current` 的窗格命令仍能解析它。实时智能体名称也会跟随终端。 + +读取输出: + +```bash +herdr pane read <pane_id> [--source visible|recent|recent-unwrapped|detection] [--lines N] [--format text|ansi] [--ansi] [--raw] +herdr pane read <pane_id> --source visible --ansi +herdr pane read <pane_id> --source recent-unwrapped --lines 120 +``` + +`pane read` 直接打印 UTF-8 终端文本。默认去除 ANSI 转义;来源包含样式时,使用 `--format ansi` 或 `--ansi` 保留。`detection` 来源始终是纯文本。对 recent 类来源,`--lines N` 会在可选的取消折行之前选择最后 N 个已渲染终端行;省略时默认读取 80 行。对 `visible` 和 `detection`,省略 `--lines` 会返回完整快照,指定时保留按换行分隔的最后 N 行。`agent read` 使用相同的输出和行数行为。 + +发送输入: + +```bash +herdr pane send-text <pane_id> <text> +herdr pane send-keys <pane_id> <key> [key ...] +herdr pane run <pane_id> <command> +``` + +`<key>` 使用 Herdr 的组合键语法: `a` 这类普通可打印键,`enter`、`tab`、`esc`、`backspace`、`left`、`right`、`up`、`down` 这类特殊键,`ctrl+h`、`control+j`、`alt+x`、`shift+tab` 这类修饰组合键,`f1` 这类功能键,以及 `minus`、`plus`、`backtick` 这类命名标点。旧式的 `C-c` 和 `c-c` 作为 `ctrl+c` 的别名被接受。 + +标准拼写是 `esc`;也接受 `escape`。 + +`pane run` 会遵循当前的 bracketed paste 模式,并把文本和 Enter 作为一个原子操作提交。发送命令时优先用它,而不是 `send-text` 加 `send-keys Enter`;单独的发送操作仍是底层操作,不会提交命令。 + +从自定义钩子上报智能体状态: + +```bash +herdr pane report-agent <pane_id> \ + --source ID \ + --agent LABEL \ + --state idle|working|blocked|unknown \ + [--message TEXT] \ + [--seq N] \ + [--agent-session-id ID] \ + [--agent-session-path PATH] + +herdr pane report-agent-session <pane_id> \ + --source ID \ + --agent LABEL \ + [--seq N] \ + [--agent-session-id ID] \ + [--agent-session-path PATH] \ + [--session-start-source SOURCE] + +herdr pane release-agent <pane_id> \ + --source ID \ + --agent LABEL \ + [--seq N] +``` + +`report-agent-session` 只更新原生会话身份,不上报生命周期状态。`release-agent` 在智能体进程退出时结束该来源的生命周期权威。 + +当官方集成上报了原生会话引用时,`pane get`、`pane list`、`agent get` 和 `agent list` 会包含一个只读的 `agent_session` 对象。没有存储原生会话引用时,该字段被省略。 + +当 Herdr 能解析控制窗格的前台进程的 cwd 时,这些命令会包含 `foreground_cwd`。已有的 `cwd` 字段仍然是用于标签和 follow-cwd 行为的窗格/工作区 cwd。 + +上报仅用于展示的窗格元数据,而不接管语义状态: + +```bash +herdr pane report-metadata <pane_id> \ + --source ID \ + [--agent LABEL] \ + [--applies-to-source ID] \ + [--title TEXT|--clear-title] \ + [--display-agent TEXT|--clear-display-agent] \ + [--state-label STATUS=TEXT] \ + [--clear-state-labels] \ + [--token NAME=VALUE] \ + [--clear-token NAME] \ + [--seq N] \ + [--ttl-ms N] +``` + +`STATUS` 是 `idle`、`working`、`blocked`、`done` 或 `unknown` 之一。`--agent` 和 `--applies-to-source` 只守卫 `--title`、`--display-agent` 和 `--state-label`,不守卫令牌补丁。上报方负责清除令牌或刷新 TTL。用 `--display-agent` 修改可见名称。 + +元数据文本在存储前会被规范化。Herdr 去掉首尾空白、移除控制字符,并把 `--title`、`--display-agent`、每个 `--state-label` 和令牌值截断到 80 个字符。规范化后为空的令牌值会清除该键。 + +`--token` 设置一个命名展示值,`--clear-token` 删除一个值。未提及的令牌保持不变。窗格令牌可在智能体侧边栏行中写成 `$name`;工作区令牌可用于空间行。TTL 分别应用于该次调用更新的每个令牌键。 + +`--source` 和 `--applies-to-source` 必须不超过 80 个字符,并且只能包含 ASCII 字母、数字、冒号、点、下划线和连字符。`--ttl-ms` 让元数据自动过期,取值必须在 `1` 到 `86400000` 毫秒之间。想让元数据一直保留到被替换、清除或窗格或工作区关闭时,省略它。`--seq` 让 Herdr 忽略来自同一 `--source` 的过期上报;过期上报会被 API 接受,但被窗格状态忽略。每个窗格或工作区在其生命周期内最多接受来自 32 个不同来源的带序号令牌上报,清除或过期不会释放这些来源名额。 + +## 智能体 + +窗格与智能体的模型和完整协调示例见[智能体自动化](/zh-cn/docs/agent-automation/)。 + +```bash +herdr agent list +herdr agent get <target> +herdr agent read <target> [--source visible|recent|recent-unwrapped|detection] [--lines N] [--format text|ansi] [--ansi] +herdr agent send-keys <target> <key> [key ...] +herdr agent prompt <target> <text> [--wait] [--until STATUS]... [--timeout MS] +herdr agent rename <target> <name>|--clear +herdr agent focus <target> +herdr agent wait <target> [--until STATUS]... [--timeout MS] +herdr agent attach <target> [--takeover] +herdr agent start <name> --kind KIND --pane ID [--timeout MS] [-- <agent-args...>] +herdr agent explain <target> [--json|--verbose] +herdr agent explain --file PATH --agent LABEL [--json|--verbose] +``` + +智能体目标只能是唯一的实时智能体名称,或当前承载该智能体的窗格 ID。终端 ID 和单独的智能体 kind 标签不能作为目标。通过 `agent start` 启动的智能体必须有名称;手动启动的智能体保持未命名,通过窗格 ID 寻址。 + +`agent start` 会在现有可用 shell 窗格中启动智能体:交互式 shell 必须占用前台,不能有正在前台运行的命令、编辑器或智能体。拓扑必须单独创建。名称在实时智能体中必须唯一,并匹配 `[a-z][a-z0-9_-]{0,31}`。支持的 kind 是 `pi`、`claude`、`codex`、`gemini`、`cursor`、`devin`、`agy`、`cline`、`omp`、`mastracode`、`opencode`、`copilot`、`kimi`、`kiro`、`droid`、`amp`、`grok`、`hermes`、`kilo`、`qodercli` 和 `maki`。名称属于当前窗格占用者,在该智能体退出、release 或被替换时清除;短暂的检测不确定不会清除它。 + +start 只有在预期智能体占用同一终端并可接受交互输入后才返回。默认启动超时是 30000 毫秒;显式值必须大于 3000 且不超过 300000。 + +`agent prompt` 遵循当前的 bracketed paste 模式,即使智能体处于 working 也会原子地提交文本和编码后的 Enter。使用 `--wait` 时,从非 working 状态发送的提示必须在五秒内产生可观察的生命周期变化,否则 Herdr 返回 `agent_prompt_stalled`;调用方超时不超过五秒时,仍返回普通的 `timeout` 错误。观察到活动后,它会等待请求的稳定状态。它不会跟踪单独的轮次。如果智能体已经处于 working,当前轮次的完成可能满足等待。`--until` 用于缩小匹配状态,不带 `--wait` 时会被拒绝。独立的 `agent wait` 在当前状态匹配时立即返回。两者默认匹配 `idle`、`done` 或 `blocked`;需要 `unknown` 时请明确使用 `--until unknown`。 + +`idle` 表示智能体在等待输入,且其标签页已在聚焦的 Herdr 界面中显示。`done` 是相同的底层 idle 状态,用于未查看的后台工作完成后。聚焦该标签页或用 `pane focus` / `agent focus` 指向它会标记为已查看;仅通过 CLI 读取不会改变它。`blocked` 表示 Herdr 识别到审批或提问界面。`unknown` 表示智能体存在但无法可靠分类,不代表工作成功。 + +`agent send-keys` 发送 `enter`、`up`、`esc`、`ctrl+c` 等逻辑按键。Herdr 会在写入任何字节前验证所有按键。`agent read` 读取解析出的终端流,`agent rename` 为已检测到的智能体命名。 + +`agent explain` 请求运行中的服务器对屏幕检测所用的同一份底部缓冲区检测快照进行分类,因此实时输出反映服务器生效的清单缓存。因为它使用 `agent.explain` socket 方法,升级 Herdr 后,请先重启或交接到更新后的服务器,再使用实时 explain。用 `--file PATH --agent LABEL` 可以改为在本地解释一份保存的样本。默认输出显示智能体、最终状态、清单来源和版本、匹配的规则及其区域证据,以及任何回退、跳过或警告原因。加 `--verbose` 可以看到可见证据标志、缓存的远程版本、本地覆盖的遮蔽情况、远程更新状态,以及带匹配器和区域证据的完整已评估规则列表。提交问题报告或写测试时加 `--json`。 + +普通终端、服务器、测试、shell 或底层终端控制,请使用 `pane send-text`、`pane send-keys`、`pane run` 和 `terminal attach`。想带回车提交命令时用 `pane run`。 + +## 直接终端附加 + +```bash +herdr terminal attach <terminal_id> [--takeover] +herdr terminal title set <title> +herdr terminal title clear +``` + +从直接附加中用 `ctrl+b q` 分离。用 `ctrl+b ctrl+b` 发送字面的 `ctrl+b`。 +`terminal title clear` 恢复 Herdr 默认的外层终端窗口标题。 + +## 输出等待 + +等待窗格中的输出: + +```bash +herdr pane wait-output <pane_id> (--match <text> | --regex <pattern>) [--source visible|recent|recent-unwrapped] [--lines N] [--timeout MS] [--raw] +``` + +普通命令和服务器用 `pane wait-output`。编程智能体用 `agent wait`。 + +`pane wait-output` 会立即检查所选快照,所以已经存在的输出也能匹配。默认来源名是 `recent`;匹配时会把最近 80 个已渲染终端行作为未折行输出处理。`--lines` 可以修改这个行数限制。`--match` 在单行中查找字面子串;`--regex` 使用 Rust 正则表达式语法并逐行匹配。 + +`pane wait-output`、`agent wait` 和 `agent prompt --wait` 在省略 `--timeout` 时会无限等待。超时或服务器错误以 JSON 写到 stderr,退出状态为 1。CLI 用法错误的退出状态为 2。 + +## 集成 + +```bash +herdr integration install pi +herdr integration install omp +herdr integration install claude +herdr integration install codex +herdr integration install copilot +herdr integration install devin +herdr integration install droid +herdr integration install kimi +herdr integration install opencode +herdr integration install kilo +herdr integration install hermes +herdr integration install mastracode +herdr integration install qodercli +herdr integration install cursor +herdr integration uninstall pi +herdr integration uninstall omp +herdr integration uninstall claude +herdr integration uninstall codex +herdr integration uninstall copilot +herdr integration uninstall devin +herdr integration uninstall droid +herdr integration uninstall kimi +herdr integration uninstall opencode +herdr integration uninstall kilo +herdr integration uninstall hermes +herdr integration uninstall mastracode +herdr integration uninstall qodercli +herdr integration uninstall cursor +herdr integration status [--outdated-only] +``` + +## 插件 + +插件命令用于安装和运行本地可执行的工作流插件。插件是清单加进程外命令;Herdr 负责宿主侧,插件负责自己的实现语言。 + +安装、列出和移除插件: + +```bash +herdr plugin install <owner>/<repo>[/subdir...] [--ref REF] [--yes] +herdr plugin list [--plugin ID] [--json] +herdr plugin uninstall <plugin_id|owner/repo[/subdir...]> +herdr plugin enable <plugin_id> +herdr plugin disable <plugin_id> +``` + +`plugin install` 只接受 GitHub 简写,比如 `ogulcancelik/herdr-plugin-examples/worktree-bootstrap`。它使用 `git`,在交互式终端显示信任预览,运行受支持的清单构建命令,并把 GitHub 安装保存在 Herdr 管理的目录中。非交互式安装用 `--yes`。重新安装 GitHub 管理的插件会替换该托管检出。不允许覆盖安装到本地链接的插件之上。插件清单必须声明 `min_herdr_version`;当插件要求更新的 Herdr 二进制时,install 和 link 会失败。`plugin list` 默认是人类可读的;要原始 API 响应就传 `--json`。 + +本地开发: + +```bash +herdr plugin link <path> [--disabled] +herdr plugin unlink <plugin_id> +``` + +`plugin link` 接受包含 `herdr-plugin.toml` 的插件目录,或直接指向清单的路径。从本地检出编写或测试插件时,它仍然是正确的命令。`plugin unlink` 注销插件、不动文件。`plugin uninstall` 注销插件,并同时删除 Herdr 管理的 GitHub 检出文件。对 GitHub 安装,uninstall 既接受插件 id,也接受与 install 相同的 `owner/repo[/subdir...]` 简写。动作、事件钩子、窗格和链接处理器在清单中声明;运行时动作注册不在 v1 范围内。 + +配置目录: + +```bash +herdr plugin config-dir <plugin_id> +``` + +`plugin config-dir` 打印插件的配置目录,需要时会创建它 (旧版插件配置位置存在时会从那里初始化)。在安装文档和 shell 脚本中用它给用户指出一个稳定路径,用于存放 `.env` 等用户可编辑配置,与托管的插件检出分开。 + +动作: + +```bash +herdr plugin action list [--plugin ID] +herdr plugin action invoke <action_id> [--plugin ID] +``` + +`plugin action invoke` 为一个已安装、已启用、平台兼容的插件动作启动清单命令,并在 JSON 响应中打印已启动命令的日志记录。当多个插件使用相同的动作 id 时,使用限定的动作 id (`plugin.id.action`)。本地动作 id 不能包含点,所以即使插件 id 包含点,限定 id 也不会有歧义。 + +日志: + +```bash +herdr plugin log list [--plugin ID] [--limit N] +``` + +托管终端窗格: + +```bash +herdr plugin pane open --plugin ID --entrypoint ID [--placement overlay|popup|split|tab|zoomed] [--width SIZE] [--height SIZE] [--workspace ID] [--target-pane PANE] [--direction right|down] [--cwd PATH] [--env KEY=VALUE] [--focus|--no-focus] +herdr plugin pane focus <pane_id> +herdr plugin pane close <pane_id> +``` + +`plugin pane open` 要求插件已链接、已启用并与当前平台兼容。它把清单声明的 `[[panes]]` 命令作为 Herdr 管理的终端窗格启动。清单的默认值是 `overlay`,在活动窗格上方打开一个临时的缩放覆盖层。它也可以作为分割、新标签页、缩放窗格,或不改变标签页布局的会话级模态 `popup` 打开。`--width` 和 `--height` 以终端单元格数或 `80%` 这样的百分比设置弹窗外层尺寸;省略时默认为终端大小的一半,过小的值会限制为弹窗最小尺寸。弹窗不是 Herdr 窗格,不会收到 `HERDR_PANE_ID`,也不参与 pane 或智能体 API。非终端的原生插件窗格是之后的能力面。 + +`--env KEY=VALUE` 可以在启动进程的命令上重复使用,只作用于新启动的进程。当与调用方提供的环境变量冲突时,`HERDR_SOCKET_PATH`、`HERDR_BIN_PATH`、`HERDR_ENV`、`HERDR_WORKSPACE_ID`、`HERDR_TAB_ID`、`HERDR_PANE_ID`、`HERDR_PLUGIN_ID`、`HERDR_PLUGIN_ROOT`、`HERDR_PLUGIN_CONFIG_DIR`、`HERDR_PLUGIN_STATE_DIR`、`HERDR_PLUGIN_ENTRYPOINT_ID` 和 `HERDR_PLUGIN_CONTEXT_JSON` 等 Herdr 管理的变量保持权威。 + +## 读取来源 + +| 来源 | 含义 | +| --- | --- | +| `visible` | 当前渲染的屏幕。最适合 UI 反馈循环。 | +| `recent` | 带终端折行的最近回滚内容。 | +| `recent-unwrapped` | 不带软折行的最近回滚内容。最适合日志。 | +| `detection` | 智能体屏幕检测使用的底部缓冲区快照。 | + +此表描述读取时的含义。仅对 `pane wait-output` 而言,`recent` 和 `recent-unwrapped` 都搜索 recent 的未折行快照,默认写法仍是 `recent`。 + +## 环境变量 + +| 变量 | 用途 | +| --- | --- | +| `HERDR_CONFIG_PATH` | 覆盖配置文件路径。 | +| `HERDR_SESSION` | 为 CLI 命令选择命名会话。 | +| `HERDR_SOCKET_PATH` | 底层 socket 路径覆盖。 | +| `HERDR_ENV` | 在 Herdr 管理的窗格进程内设为 `1`。 | +| `HERDR_PANE_ID` | 运行中窗格进程的公开窗格 id。 | +| `HERDR_TAB_ID` | 运行中窗格进程的公开标签页 id。 | +| `HERDR_WORKSPACE_ID` | 运行中窗格进程的公开工作区 id。 | +| `HERDR_LOG` | 设置日志过滤,例如 `HERDR_LOG=herdr=debug`。 | +| `HERDR_DISABLE_SOUND` | 即使启用了声音通知也禁用声音播放。 | diff --git a/docs/preview/website/src/content/docs/zh-cn/concepts.mdx b/docs/preview/website/src/content/docs/zh-cn/concepts.mdx new file mode 100644 index 00000000..ab950416 --- /dev/null +++ b/docs/preview/website/src/content/docs/zh-cn/concepts.mdx @@ -0,0 +1,85 @@ +--- +title: 核心概念 +description: 理解 Herdr 的工作区、标签页、窗格、智能体、会话和模式。 +--- + +Herdr 是一个终端工作区管理器。它让真实的终端进程持续运行,并在其之上添加结构。 + +## 工作区 + +工作区是最顶层的项目容器。为每个仓库、任务或调查使用一个工作区。 + +工作区拥有标签页和窗格。它在侧边栏中的状态由内部的智能体汇总而来,让你一眼看出哪个项目需要关注。 + +## 标签页 + +标签页是工作区内的一种布局。用标签页来分隔不同视图,比如 `agents`、`logs`、`server` 或 `review`。 + +标签页可以通过 CLI 和 socket API 寻址。 + +## 窗格 + +窗格是一个真实的终端。Herdr 渲染终端输出,把输入传回进程,并在客户端分离后保留窗格。 + +窗格可以向右或向下分割。它们可以手动重命名、通过 CLI 读取、接收输入,以及被关闭。 + +## 鼠标 UI + +Herdr 是鼠标原生的。你可以点击窗格、标签页、工作区和智能体,可以拖动分割边框、选择文本、使用右键菜单。下文的所有操作都可以用鼠标完成;按键绑定是可选的一层。 + +如果你偏好纯键盘操作,或者不想让 Herdr 捕获鼠标输入,可以禁用鼠标捕获: + +```toml +[ui] +mouse_capture = false +``` + +## 智能体 + +智能体是 Herdr 在窗格内识别出的进程。Herdr 通过前台进程、屏幕清单和可选的集成来检测智能体。 + +智能体状态如下: + +| 状态 | 含义 | +| --- | --- | +| `blocked` | 智能体需要输入、审批或决策。 | +| `working` | 智能体正在运行。 | +| `done` | 智能体已完成,你还没有查看。 | +| `idle` | 智能体已完成或在等待,并且已被查看过。 | +| `unknown` | Herdr 无法有把握地判断状态。 | + +## 会话 + +会话是一个持久的 Herdr 服务器命名空间。默认的 `herdr` 命令连接到默认会话。 + +命名会话是彼此独立的运行时命名空间: + +```bash +herdr session list +herdr session attach work +herdr session attach side-project +``` + +优先使用工作区。当你需要完全独立的窗格、socket 和持久化运行时状态时,才使用命名会话。 + +## 客户端与服务器 + +默认情况下,Herdr 以一个后台服务器加一个或多个已连接客户端的方式运行。 + +服务器拥有窗格和进程状态。客户端是连接到该服务器的终端 UI。 + +用 `ctrl+b q` 分离客户端。服务器和智能体会继续运行。 + +如果想结束会话并停止其中的窗格,停止服务器: + +```bash +herdr server stop +``` + +## 模式 + +Herdr 有终端模式、前缀模式和导航模式。 + +终端模式把按键发送给聚焦的窗格。前缀模式在按下前缀键后等待一个 Herdr 动作。导航模式是常驻的工作区导航界面。 + +按下前缀键 (默认 `ctrl+b`),再按一个动作键,比如 `c` 新建标签页,`w` 打开工作区导航。第一次接触前缀键的概念?参见[键盘](/zh-cn/docs/keyboard/)。 diff --git a/docs/preview/website/src/content/docs/zh-cn/config-reference.mdx b/docs/preview/website/src/content/docs/zh-cn/config-reference.mdx new file mode 100644 index 00000000..7f70afa2 --- /dev/null +++ b/docs/preview/website/src/content/docs/zh-cn/config-reference.mdx @@ -0,0 +1,26 @@ +--- +title: 配置参考 +description: Herdr 的所有规范 config.toml 键,包含类型、默认值与允许的取值。 +--- + +import ConfigReference from '../../../components/ConfigReference.astro'; + +Herdr 从 `config.toml` 读取的所有规范键都在这里平铺列出,并支持筛选。 +配置的分步指南与各选项的背景说明见[配置](/zh-cn/docs/configuration/)。 + +随时可以打印带注释的完整默认配置: + +```bash +herdr --default-config +``` + +自定义命令绑定(`[[keys.command]]`)是用户自定义的表,因此不在此处逐键列出, +见[自定义命令按键绑定](/zh-cn/docs/configuration/#自定义命令按键绑定)。 + +各键的描述目前为英文。 + +<ConfigReference + placeholder="筛选键(例如 sidebar、prefix、sound…)" + noMatches="没有匹配的键。" + keyCountLabel="个键" +/> diff --git a/docs/preview/website/src/content/docs/zh-cn/configuration.mdx b/docs/preview/website/src/content/docs/zh-cn/configuration.mdx new file mode 100644 index 00000000..8b8f6c4d --- /dev/null +++ b/docs/preview/website/src/content/docs/zh-cn/configuration.mdx @@ -0,0 +1,466 @@ +--- +title: 配置 +description: 配置 Herdr 的按键绑定、主题、侧边栏行为、通知和高级选项。 +--- + +Herdr 无需配置文件即可使用。当你想自定义按键、主题、侧边栏布局、通知或高级行为时,再添加配置文件。 + +在查找某项设置或按键绑定?请在[配置参考](/docs/config-reference/)中搜索,其中列出了每个键、类型、默认值和允许值。本页重点介绍设置方法、常用配置方案,以及需要比参考表格更详细说明的配置结构。 + +## 配置文件 + +Herdr 从以下位置读取配置: + +```text +Linux and macOS: ~/.config/herdr/config.toml +Windows: %APPDATA%\herdr\config.toml +``` + +运行 `herdr --help` 可查看系统解析后的配置路径。 + +打印完整的默认配置: + +```bash +herdr --default-config +``` + +如果想从完整配置开始,可以将其保存为你的配置文件: + +```bash +herdr --default-config > ~/.config/herdr/config.toml +``` + +如果配置值无效,Herdr 会回退到安全的默认值,并显示启动警告。 + +当 `onboarding` 缺失或为 true 时,Herdr 会显示首次运行设置。继续完成引导会写入 `onboarding = false`,并打开设置中的集成标签页。完成设置后,如果想跳过此流程,请设置该值。 + +```toml +onboarding = false +``` + +## 重载配置 + +编辑 `config.toml` 后,重载正在运行的服务器: + +```bash +herdr server reload-config +``` + +你也可以打开 Herdr 的全局菜单并选择 `reload config`。 + +重载会在不重启窗格的情况下应用大多数 UI 设置。仅启动时生效的设置仍需重启。 + +## 终端默认值 + +设置 Herdr 创建新交互式窗格时使用的可执行文件: + +```toml +[terminal] +default_shell = "nu" +``` + +未设置或值为空时,Herdr 会依次使用 `$SHELL`、Unix 上的 `/bin/sh` 或 Windows 上的 PowerShell。该值是可执行文件名或路径,而不是 shell 命令行。现有窗格会保留当前 shell,直到重新创建。自定义命令按键绑定中的字符串在 Unix 上通过 `/bin/sh -c` 执行窗格命令,通过 `/bin/sh -lc` 执行分离式命令;在 Windows 上则通过 `cmd.exe /d /c` 执行。 + +设置 Herdr 如何启动新建交互式窗格的 shell: + +```toml +[terminal] +shell_mode = "auto" +``` + +`shell_mode = "auto"` 会在 macOS 上启动登录 shell,让 `/usr/libexec/path_helper` 等仅登录时生效的 PATH 设置和 Homebrew shell 初始化在新窗格中运行。在其他平台上,它会保持现有的非登录 shell 行为。使用 `"login"` 强制以登录 shell 启动,或使用 `"non_login"` 禁用登录 shell。命令窗格、分离式自定义命令按键绑定和显式 argv 启动仍使用现有的命令执行路径。 + +设置新窗格、标签页和工作区的工作目录策略: + +```toml +[terminal] +new_cwd = "follow" +``` + +`new_cwd = "follow"` 保持默认行为,并继承来源窗格或工作区。没有来源工作区时,Herdr 从 `$HOME` 启动。使用 `"home"` 可始终从 `$HOME` 启动,使用 `"current"` 可采用 Herdr 的进程目录,也可以指定 `"~/Projects"` 这样的固定路径。CLI 或 socket API 显式提供的 `--cwd` 值仍然优先。 + +## Worktree + +设置 Herdr 从侧边栏创建 Git worktree 检出时使用的根目录: + +```toml +[worktrees] +directory = "~/.herdr/worktrees" +``` + +Herdr 会在 `<directory>/<repo>/<branch-slug>` 下创建检出。若要使用同级目录风格的检出,请将其设置为 `~/Projects/herdr-worktrees` 这样的目录。应用配置时,相对路径会解析为绝对路径。 + +Git 工作区行提供 worktree 操作。`New worktree` 会创建检出:如果输入的分支已存在,则检出该本地分支,否则创建该分支;随后将其作为新的 Herdr 工作区打开,并归组到来源工作区下。`Open worktree...` 会列出该仓库现有的 Git worktree 检出;选择已打开的检出会聚焦它,选择尚未打开的检出会在同一组中打开它。 + +归组的 worktree 仍像普通 Herdr 工作区一样工作:可以聚焦、重命名和关闭,也可以拥有自己的标签页和窗格。父行是原始工作区。关闭父行会关闭整个 Herdr 组,但不会删除检出目录或分支。 + +删除 worktree 检出需要显式操作。在归组的子工作区上使用 `Delete worktree checkout...` 来运行 `git worktree remove`。Herdr 会先请求 Git 安全删除。如果 Git 因检出中存在已修改或未跟踪的文件而拒绝,Herdr 会在强制删除前再次确认。分支不会被删除。 + +## 远程连接 + +远程连接默认使用临时保活设置和连接复用兜底来管理 SSH 连接。 + +```toml +[remote] +manage_ssh_config = true +``` + +启用后,`herdr --remote` 会写入一份私有的临时 SSH 配置:先包含你的 `~/.ssh/config` 和 `/etc/ssh/ssh_config`,再添加兜底的 `ServerAliveInterval` 和 `ServerAliveCountMax` 值。你自己的保活设置优先。Herdr 还会为每次远程连接使用私有的 OpenSSH control socket,以复用首次通过身份验证的连接。设置 `manage_ssh_config = false` 可通过普通 `ssh` 进行远程连接,不使用 Herdr 生成的配置或 control socket。 + +## 按键绑定 + +有关前缀键的引导式介绍和经过验证的免前缀配置,请参阅[键盘](/docs/keyboard/)。 + +Herdr 提供类似 tmux 的前缀模式。默认前缀是 `ctrl+b`。按键绑定字符串是显式的:`prefix+n` 表示先按配置的前缀,再按 `n`;`ctrl+alt+n` 则是终端模式下的直接快捷键。 + +一个简短的按键绑定覆盖如下: + +```toml +[keys] +prefix = "ctrl+b" +goto = "prefix+g" +new_tab = "prefix+c" +next_tab = "prefix+n" +previous_tab = "prefix+p" +focus_pane_left = "prefix+h" +navigate_workspace_down = "j" +navigate_pane_down = "ctrl+j" +split_horizontal = "prefix+minus" +``` + +默认键位以前缀为主,因此 Herdr 不会抢占 shell、编辑器、tmux 或终端应用的输入。在[配置参考](/docs/config-reference/)中搜索 `keys.` 可查看每个动作及其默认绑定。应用内帮助面板可通过 `prefix+?` 打开,其中会显示当前生效的绑定。 + +当一个动作需要多个快捷键时,绑定也可以是数组: + +```toml +[keys] +next_tab = ["prefix+n", "ctrl+alt+]"] +``` + +可选动作默认不设置。使用 `prefix+` 可获得前缀模式行为;当你确实需要直接快捷键时,请使用显式的修饰组合键。 + +按键字符串支持普通按键、`ctrl+a`、`shift+n`、`alt+1`、`cmd+k` 等修饰组合键,以及 `enter`、`tab`、`esc`、`left`、`right`、`up`、`down` 等特殊键。也支持 `minus`、`comma`、`ampersand`、`plus`、`backtick` 等命名标点。直接绑定 `n` 这样的普通可打印键并不安全,因为它会拦截输入;除非你有意设置直接绑定,否则请使用 `prefix+n`。`navigate_workspace_*` 和 `navigate_pane_*` 字段仅在导航模式中生效,可以使用 `j` 或 `k` 等普通按键;它们不得使用 `prefix+`、`esc`、`enter`、`tab`、`shift+tab`、`left`、`right`,也不得使用无修饰键的 `1` 到 `9`。左右方向键是向左和向右导航窗格的永久别名。这些导航模式快捷键独立于 `focus_pane_down = "prefix+j"` 等通用动作绑定;两者使用同一按键时,打开导航模式后,导航模式快捷键优先。Alt、Cmd/Super 以及带修饰键的标点取决于你的终端和 tmux 设置。 + +如果你已有旧的自定义按键绑定,并想使用新的默认值,请运行 `herdr config reset-keys`。Herdr 会备份 `config.toml`,移除 `[keys]` 和 `[[keys.command]]`,并在重启或执行 `herdr server reload-config` 后使用内置的 v2 默认值。 + +## 索引跳转 + +索引式按键绑定在普通按键绑定字段中使用 `1..9`: + +```toml +[keys] +switch_tab = "prefix+1..9" +switch_workspace = "prefix+shift+1..9" +focus_agent = "prefix+alt+1..9" +``` + +旧版 `[keys.indexed]` 表仍会出于兼容性而解析,但新配置应优先使用显式的动作字段。 + +## 自定义命令按键绑定 + +自定义命令使用相同的按键绑定语法。 + +```toml +[[keys.command]] +key = "prefix+alt+g" +type = "popup" +command = "lazygit" +description = "run lazygit" +width = "80%" +height = "80%" +``` + +`type = "popup"` 会打开一个会话级模态弹窗,而不改变标签页布局。 +弹窗会接收包括 Escape 在内的所有终端输入,直到命令退出。 +`width` 和 `height` 是可选的;省略时默认为终端大小的一半,数字表示终端单元格数,`"80%"` 这样的字符串表示终端区域的百分比。 +尺寸包含弹窗边框,过小的值会限制为弹窗最小尺寸。 +弹窗命令不会收到 `HERDR_PANE_ID`;可使用 `HERDR_ACTIVE_PANE_ID` 引用底层平铺窗格。 + +在 Unix 和 macOS 上,还可以打开一个临时终端,而无需添加分割或标签页: + +```toml +[[keys.command]] +key = "prefix+t" +type = "popup" +command = "exec \"${SHELL:-sh}\"" +description = "open scratch terminal" +width = "80%" +height = "80%" +``` + +在 Windows 上,请改用 `command = "powershell.exe -NoLogo"` 之类的 shell 命令。 +退出 shell 即可关闭弹窗并恢复平铺终端视图。 + +`type = "pane"` 会打开一个临时窗格,并在命令退出时关闭它。 + +`type = "shell"` 会在后台分离运行。 + +`type = "plugin_action"` 会调用已安装插件的动作 id。当动作 id 并非全局唯一时,请使用限定 id: + +```toml +[[keys.command]] +key = "prefix+l" +type = "plugin_action" +command = "example.layout.apply" +description = "apply layout" +``` + +可以提供可选的 `description`。指定后,该描述会在按键绑定帮助面板(通过 `prefix+?` 打开)中显示,替代默认的 `'custom command'` 标签。 + +在相应值可用时,自定义命令会收到 `HERDR_SOCKET_PATH`、`HERDR_BIN_PATH`、`HERDR_ACTIVE_WORKSPACE_ID`、`HERDR_ACTIVE_TAB_ID`、`HERDR_ACTIVE_PANE_ID` 和 `HERDR_ACTIVE_PANE_CWD`。当 Herdr 能检测到聚焦窗格的工作目录时,shell 命令会从该目录运行。 + +在 Windows 上,自定义命令字符串使用 `cmd.exe /d /c`,因此环境变量应采用 `%HERDR_BIN_PATH%` 语法。若要运行 PowerShell 语法,请显式调用它,例如 `powershell.exe -NoProfile -Command "..."`。 + +## 主题 + +选择内置主题: + +```toml +[theme] +name = "catppuccin" +``` + +在[配置参考](/docs/config-reference/)中搜索 `theme.name` 可查看所有内置主题。如果想让 Herdr UI 颜色跟随宿主终端的 ANSI 调色板,请使用 `terminal`。 + +要让 Herdr 在宿主终端报告明暗外观变化时自动切换自身的 UI 主题,请启用主题自动切换: + +```toml +[theme] +name = "catppuccin" +auto_switch = true +light_name = "catppuccin-latte" +dark_name = "catppuccin" +``` + +`auto_switch` 默认为 `false`,因此现有主题配置会保持手动行为。如果省略 `light_name` 或 `dark_name`,且配置的 `name` 存在对应的内置姊妹主题,Herdr 会使用该主题,例如 `tokyo-night`/`tokyo-night-day` 或 `gruvbox`/`gruvbox-light`。在设置中手动选择主题会禁用 `auto_switch`。 + +你可以覆盖单个颜色: + +```toml +[theme.custom] +panel_bg = "reset" +accent = "#a6e3a1" +green = "#a6e3a1" +blue = "#89b4fa" +red = "#f38ba8" +yellow = "#f9e2af" +``` + +颜色值支持十六进制、命名颜色、`rgb(r,g,b)`,以及 `reset`、`default`、`none`、`transparent` 等重置别名。 + +## UI 与侧边栏 + +侧边栏是 Herdr 的主仪表盘。在[配置参考](/docs/config-reference/)中搜索 `ui.`,可查看尺寸、折叠模式、Agent 面板排序、鼠标行为、窗格边框和其他显示设置。 + +### 侧边栏行布局 + +展开的桌面侧边栏会将 `rows` 中的每个内层数组渲染为一行。以下是完整的默认布局: + +```toml +[ui.sidebar.agents] +row_gap = 0 +rows = [ + ["state_icon", "workspace", "tab"], + ["agent"], +] + +[ui.sidebar.spaces] +row_gap = 0 +rows = [ + ["state_icon", "workspace"], + ["branch", "git_status"], +] +``` + +Agent 行支持以下内置 token: + +- `state_icon` — 智能体语义状态的彩色图标。 +- `state_text` — `idle`、`working`、`blocked`、`done` 或 `unknown`;如果报告中包含显示标签,也会一并显示。 +- `workspace` — 工作区名称。 +- `tab` — 标签页名称(如有)。 +- `pane` — 窗格名称(如有)。 +- `agent` — 检测到或报告的智能体显示名称。 +- `terminal_title` — 经过安全规范化的最新 OSC 0/2 终端标题。 +- `terminal_title_stripped` — 从终端标题开头移除一个可识别的活动或旋转指示符字形及其后空白后的结果。 +- `$name` — 名为 `name` 的自定义窗格元数据。 + +Space 行支持以下内置 token: + +- `state_icon` — Space 汇总后智能体状态的彩色图标。 +- `state_text` — Space 汇总后智能体状态的文本。 +- `workspace` — 工作区名称。 +- `branch` — Git 分支(如有)。 +- `git_status` — 非零时显示 Git ahead 和 behind 数量。 +- `$name` — 名为 `name` 的自定义工作区元数据。 + +token 会按配置顺序渲染。Herdr 通常使用 ` · ` 分隔相邻值,并在 `state_icon` 后使用一个空格。缺失值及其分隔符会消失;当一行中的所有 token 都没有值时,该行会消失。每个布局最多可包含 16 行,每行最多可包含 16 个 token。 + +也可以用内联样式表定义 token: + +```toml +[ui.sidebar.agents] +rows = [ + ["state_icon", { token = "workspace", bold = false }, "tab"], + [{ token = "$summary", fg = "#89b4fa", bold = true, dim = false }], +] +``` + +`fg` 只接受严格的 `#RGB` 或 `#RRGGBB`,`bold` 和 `dim` 接受布尔值。省略字段会保留上下文样式;显式 `false` 会移除对应修饰。样式只作用于当前出现位置。为 `git_status` 设置 `fg` 后,ahead 和 behind 会使用同一种颜色,而不是默认的绿色和红色。token 样式不会改变分隔符或行背景。 + +`row_gap` 分别控制 Agent 和 Space 面板中条目之间的空白终端行数。默认值为 `0`,会紧密排列条目;设为 `1` 可恢复之前的间距。它不会在 `rows` 声明的内容行之间添加间距。连续缩进的 worktree 子项仍会作为一个 Space 组紧密排列。 + +在 `rows_by_agent` 下为已知智能体覆盖完整的 Agent 布局: + +```toml +[ui.sidebar.agents] +rows = [ + ["state_icon", "agent", "state_text"], + ["workspace", "tab"], +] + +[ui.sidebar.agents.rows_by_agent] +claude = [ + ["state_icon", "agent", "state_text"], + ["terminal_title_stripped"], + ["workspace", "tab"], +] +``` + +覆盖项会替换 `rows`,而不是扩展它。覆盖项的键是区分大小写的规范智能体 ID,例如 `claude`、`codex` 和 `pi`。不接受 `claude-code` 等检测别名。没有覆盖项的智能体(包括自定义报告的智能体)会使用 `rows`。 + +自定义 `$name` token 是动态值,而不是字面文本。先将 token 添加到布局,再通过脚本或插件报告其值: + +```toml +[ui.sidebar.agents] +rows = [ + ["state_icon", "agent", "$model"], + ["$summary"], + ["workspace", "tab"], +] +``` + +```bash +herdr pane report-metadata <pane_id> \ + --source my-agent-hook \ + --token model=opus \ + --token summary="reviewing authentication" +``` + +自定义 Space token 可用同样的方式通过 `herdr workspace report-metadata` 报告。未报告的自定义 token 会直接消失。 + +元数据上报方只提供值;样式由本地侧边栏配置控制。有关限制、清除、顺序和过期值的信息,请参阅 [CLI 参考:报告元数据](/docs/cli-reference/#panes)。 + +侧边栏行设置仅影响展开的桌面侧边栏。折叠视图和移动视图仍使用紧凑布局。 + +## 通知 + +当后台智能体完成任务或需要输入时,Herdr 可以通知你: + +```toml +[ui.toast] +delivery = "herdr" +delay_seconds = 1 + +[ui.toast.herdr] +position = "bottom-right" +``` + +选择 `herdr` 可使用应用内 toast,选择 `terminal` 可使用适合 SSH 场景的外层终端通知,选择 `system` 可使用本地操作系统通知服务,选择 `off` 可禁用弹出通知。Herdr 会抑制活动标签页的弹出通知。在[配置参考](/docs/config-reference/)中搜索 `ui.toast`,可查看位置、延迟行为和剪贴板反馈设置。 + +## 声音 + +声音通知通过本地 Herdr 客户端播放。自定义声音必须是 mp3 文件;相对路径从配置文件所在目录解析。 + +```toml +[ui.sound] +path = "sounds/notification.mp3" +done_path = "sounds/done.mp3" +request_path = "sounds/request.mp3" +``` + +`path` 为所有声音通知设置同一种声音。`done_path` 和 `request_path` 只覆盖完成和需要输入时的声音。 + +按智能体覆盖声音设置时,可使用 `default`、`on` 或 `off`。键应使用检测到的智能体标签,例如 `claude`、`codex`、`devin` 或 `droid`。Droid 默认静音。 + +```toml +[ui.sound.agents] +droid = "off" +claude = "on" +``` + +## 高级配置 + +在[配置参考](/docs/config-reference/)中搜索回滚缓冲区限制、嵌套启动以及其他高级或实验性设置。启用窗格屏幕历史前,请参阅[会话状态与恢复](/docs/session-state/);该指南说明了保存窗格内容带来的安全取舍。 + +## Kitty graphics + +面向已连接本地客户端的 Kitty graphics 渲染是一项实验性功能,默认禁用: + +```toml +[experimental] +kitty_graphics = true +``` + +仅在测试终端图像行为时启用。 + +## 智能体会话恢复 + +默认情况下,Herdr 会在服务器重启后恢复受支持的智能体对话: + +```toml +[session] +resume_agents_on_restore = true +``` + +只有通过官方集成获得有效原生会话引用的窗格才能恢复;其他窗格会作为普通 shell 恢复。有关受支持的智能体和持久化行为,请参阅[会话状态与恢复](/docs/session-state/)。 + +## IME 光标跟踪 + +在 macOS 上,隐藏硬件光标的 AI 智能体 TUI 可能导致原生输入法候选窗口无法跟随聚焦窗格。使用以下设置可为这些窗格显示光标锚点: + +```toml +[experimental] +reveal_hidden_cursor_for_cjk_ime = true +cjk_ime_agents = ["claude", "pi", "codex"] +``` + +限制 `cjk_ime_agents` 可以避免在无关应用中额外显示硬件光标。在[配置参考](/docs/config-reference/)中搜索这些键,可查看接受的智能体名称和光标形状。 + +## 前缀输入源切换 + +在 macOS 和 Windows 上,当前缀命令和由前缀启动的模式处于活动状态时,Herdr 可以临时切换到支持 ASCII 的输入源: + +```toml +[experimental] +switch_ascii_input_source_in_prefix = true +``` + +在 macOS 上,会切换到当前支持 ASCII 的键盘布局;在 Windows 上,会将 IME 切换到英文(ASCII)输入。返回终端输入或进入文本字段时,Herdr 会恢复之前的输入源。此设置在其他平台上无效。 + +:::caution[Windows 上仅支持韩语 IME] +目前 Windows 上的支持仅限于韩语 IME。使用其他语言的 IME 时,此设置不会改变输入源。 +::: + +## 环境变量 + +| 变量 | 用途 | +| --- | --- | +| `HERDR_CONFIG_PATH` | 覆盖配置文件路径。 | +| `HERDR_SESSION` | 为 CLI 命令选择命名会话。 | +| `HERDR_SOCKET_PATH` | 覆盖底层 socket 路径。 | +| `HERDR_LOG` | 设置日志过滤,例如 `HERDR_LOG=herdr=debug`。 | +| `HERDR_DISABLE_SOUND` | 即使 `[ui.sound] enabled = true` 也禁用声音播放。 | + +## 日志 + +日志有助于诊断启动警告、集成状态或 socket API 行为。 + +常见日志文件: + +```text +~/.config/herdr/herdr.log +~/.config/herdr/herdr-client.log +~/.config/herdr/herdr-server.log +``` + +日志会自动轮转。报告问题时,请附上当前日志和轮转后的同级日志文件。 diff --git a/docs/preview/website/src/content/docs/zh-cn/how-to-work.mdx b/docs/preview/website/src/content/docs/zh-cn/how-to-work.mdx new file mode 100644 index 00000000..0df1b87f --- /dev/null +++ b/docs/preview/website/src/content/docs/zh-cn/how-to-work.mdx @@ -0,0 +1,103 @@ +--- +title: 使用 Herdr 的工作方式 +description: 在本地、SSH 内或通过远程连接运行 Herdr。 +--- + +在工作所在的地方运行 Herdr,从你所在的任何地方连接。 + +Herdr 由一个后台会话服务器和一个或多个终端客户端组成。窗格在服务器中持续运行。客户端负责连接、分离和渲染会话。 + +## 本地工作 + +在项目目录中启动 Herdr: + +```bash +herdr +``` + +Herdr 会自动启动或连接到本地后台会话。你不需要管理 socket。在窗格里照常运行 shell、服务器、测试和智能体。 + +用 `ctrl+b q` 分离客户端。窗格会继续运行。 + +之后重新连接: + +```bash +herdr +``` + +如果想结束会话并停止其中的窗格,停止服务器: + +```bash +herdr server stop +``` + +## 通过普通 SSH 的远程工作 + +SSH 到有代码和凭据的机器,然后在那里运行 Herdr: + +```bash +ssh you@server +herdr +``` + +这和终端复用器的用法一样。你的 shell 在远程,Herdr 服务器在远程,智能体和窗格都运行在远程机器上。用 `ctrl+b q` 分离并断开连接,之后再 SSH 回去,重新运行 `herdr`。 + +当你本来就工作在 SSH shell 里、在手机或平板的 SSH 客户端上,或者想要最简单的配置时,用这条路径。 + +## 用手机工作 + +你不需要 Herdr 手机应用或 Web 仪表盘。在手机上装任意一个 SSH 客户端,连到智能体所在的机器,在那里启动 Herdr: + +```bash +ssh you@server +herdr +``` + +同一个持久的 Herdr 会话就会在手机终端中打开。TUI 会适应窄屏,所以你可以在不离开 SSH 的情况下检查智能体、切换工作区、查看窗格。 + +在 iPhone 上,[moshi](https://getmoshi.app/) 这类应用表现很好。 + +<div class="mobile-doc-shots"> + <figure> + <img src="/assets/mobile-agent-session-v2.jpeg" alt="手机上通过 SSH 的 Herdr 智能体会话" loading="lazy" /> + <figcaption>SSH 上的智能体会话</figcaption> + </figure> + <figure> + <img src="/assets/mobile-switch-menu-v2.jpeg" alt="手机上 Herdr 的响应式切换菜单" loading="lazy" /> + <figcaption>响应式切换菜单</figcaption> + </figure> +</div> + +## 从本地终端远程工作 + +不先打开 shell,直接通过 SSH 连接: + +```bash +herdr --remote workbox +herdr --remote ssh://you@server:2222 +``` + +你本地的 Herdr 充当瘦客户端。它通过 SSH 连接,启动或连接远程 Herdr 服务器,并把 UI 流式传回你的本地终端。 + +想让远程会话用起来像本地时,用这条路径。客户端运行在你的机器上,所以图像剪贴板粘贴等本地桌面功能可以桥接到远程服务器。如果你先 SSH 再在服务器上运行 `herdr`,Herdr 就完全运行在那台服务器上,无法读取你本地桌面的剪贴板。 + +对于经常连接的目标,把主机写进 SSH 配置: + +```text +Host workbox + HostName server.example.com + User you + Port 2222 +``` + +然后这样连接: + +```bash +herdr --remote workbox +``` + +## 该用哪条路径 + +本地工作用 `herdr`。想让 Herdr 在远程 shell 上表现得像 tmux,或者在用手机 SSH 客户端时,用 `ssh you@server` 再 `herdr`。想要一个连接远程会话的本地瘦客户端 (包括本地剪贴板图像粘贴桥接) 时,用 `herdr --remote <host>`。 + +关于远程引导细节、命名远程会话、自定义二进制、直接终端附加和 `--no-session`,参见[持久化与远程访问](/zh-cn/docs/persistence-remote/)。 diff --git a/docs/preview/website/src/content/docs/zh-cn/index.mdx b/docs/preview/website/src/content/docs/zh-cn/index.mdx new file mode 100644 index 00000000..b62a2b02 --- /dev/null +++ b/docs/preview/website/src/content/docs/zh-cn/index.mdx @@ -0,0 +1,77 @@ +--- +title: Herdr 文档 +description: 面向 AI 编程智能体的终端工作区管理器。 +template: splash +hero: + tagline: "安装、学习和配置 Herdr。从适合你的路径开始 — 无需任何终端复用器经验。" + image: + file: ../../../../public/assets/logo.svg + actions: + - text: 安装 Herdr + link: /zh-cn/docs/install/ + - text: 快速开始 + link: /zh-cn/docs/quick-start/ + variant: secondary +--- + +import { Card, CardGrid } from '@astrojs/starlight/components'; + +## 选择你的路径 + +<CardGrid> + <Card title="第一次接触终端复用器?"> + 上手不需要学习快捷键。Herdr 以鼠标为先: 点击窗格、拖动边框、通过右键菜单分割和切换。 + + [快速开始 →](/zh-cn/docs/quick-start/) + </Card> + <Card title="从 tmux 或 zellij 迁移过来?"> + 这套模型你已经很熟悉了。前缀键是 `ctrl+b`,窗格会持久保留,分离和重新连接的行为都符合你的预期。 + + [核心概念 →](/zh-cn/docs/concepts/) · [按键绑定 →](/zh-cn/docs/configuration/#按键绑定) + </Card> +</CardGrid> + +## 或者让你的智能体来介绍 + +已经在用 AI 编程智能体?让它来帮你完成入门。粘贴这个提示词: + +```text +Help me understand and set up Herdr. Read https://herdr.dev/agent-guide.md first, then walk me through it step by step. +``` + +这份指南会教你的智能体 Herdr 的概念、安装、配置和常见问题的修复方法,让它的回答保持准确,而不是即兴发挥。 + +## 核心指南 + +<CardGrid> + <Card title="智能体"> + 了解受支持的智能体、检测行为、集成、自定义标签和直接附加。 + + [理解智能体 →](/zh-cn/docs/agents/) + </Card> + <Card title="会话状态"> + 理解分离、重启恢复、窗格历史回放、智能体原生会话恢复和实时交接。 + + [对比状态路径 →](/zh-cn/docs/session-state/) + </Card> + <Card title="配置"> + 配置按键绑定、主题、侧边栏行为、通知、回滚缓冲和高级选项。 + + [配置 Herdr →](/zh-cn/docs/configuration/) + </Card> + <Card title="API"> + 通过 CLI 和本地 socket API,从脚本、工具和智能体控制 Herdr。 + + [阅读 API 指南 →](/zh-cn/docs/socket-api/) + </Card> + <Card title="插件"> + 编写本地可执行的工作流插件,带有清单声明的动作和事件钩子。 + + [编写插件 →](/zh-cn/docs/plugins/) + </Card> + <Card title="插件市场"> + 现在就可以从 GitHub 分享插件,为仓库打上主题标签,插件市场上线时即可收录。 + + [发布插件 →](/zh-cn/docs/marketplace/) + </Card> +</CardGrid> diff --git a/docs/preview/website/src/content/docs/zh-cn/install.mdx b/docs/preview/website/src/content/docs/zh-cn/install.mdx new file mode 100644 index 00000000..3a66ab3b --- /dev/null +++ b/docs/preview/website/src/content/docs/zh-cn/install.mdx @@ -0,0 +1,153 @@ +--- +title: 安装 Herdr +description: 在 Linux、macOS 和 Windows 测试版上安装、更新和验证 Herdr。 +--- + +Herdr 为 Linux 和 macOS 提供稳定版二进制文件。Windows 原生支持是仅限预览版的测试版。 + +## 安装 + +在 Linux 或 macOS 上运行: + +```bash +curl -fsSL https://herdr.dev/install.sh | sh +``` + +在 Windows 预览测试版上,安装预览通道: + +```powershell +powershell -ExecutionPolicy Bypass -c "irm https://herdr.dev/install.ps1 | iex" +``` + +安装器会下载适合你平台的发布二进制文件并放到 PATH 上。在 Windows 上,Herdr 默认使用预览通道,且不会修改你的配置。安装器使用带版本号的安装文件夹,并更新一个 `current` 联接点,因此更新时不需要覆盖正在运行的 `herdr.exe`。 + +## 用 Homebrew 安装 + +如果你已经在用 Homebrew: + +```bash +brew install herdr +``` + +## 用 mise 安装 + +如果你已经在用 mise: + +```bash +mise use -g herdr +``` + +如果 mise 报 `herdr not found in mise tool registry`,请更新 mise 后重试。旧版 mise 早于 Herdr 的注册表条目;`mise use -g github:ogulcancelik/herdr` 可以作为临时兜底。 + +## 用 Nix 安装 + +如果你已经在用 Nix,Herdr 提供了一个从源码构建的 flake: + +```bash +nix run github:ogulcancelik/herdr/v0.x.y +nix build github:ogulcancelik/herdr/v0.x.y +nix profile install github:ogulcancelik/herdr/v0.x.y +``` + +把 `v0.x.y` 替换为最新的发布标签。省略标签可以跟踪 `master`,但常规安装建议使用发布标签。 + +flake 还提供一个开发 shell: + +```bash +nix develop github:ogulcancelik/herdr +``` + +更新时使用你安装 Herdr 的同一套 Nix 工作流。对于 profile 安装,列出 profile 条目并升级 Herdr 条目: + +```bash +nix profile list +nix profile upgrade <index-or-name> +``` + +如果 Herdr 是你自己 flake 的一个 input,更新该 input 并重建你的系统、Home Manager 或开发环境: + +```bash +nix flake update herdr +``` + +## 手动下载 + +也可以从 [GitHub releases](https://github.com/ogulcancelik/herdr/releases) 下载二进制文件。 + +选择匹配你系统的产物: + +| 系统 | 产物 | +| --- | --- | +| Linux x86_64 | `herdr-linux-x86_64` | +| Linux aarch64 | `herdr-linux-aarch64` | +| macOS Intel | `herdr-macos-x86_64` | +| macOS Apple silicon | `herdr-macos-aarch64` | + +在 Linux 或 macOS 上,赋予可执行权限并移动到 PATH 上的某个位置。 + +```bash +chmod +x herdr-linux-x86_64 +mv herdr-linux-x86_64 ~/.local/bin/herdr +``` + +### Windows 测试版下载 + +在 Windows 原生支持处于测试阶段期间,Windows 二进制文件只在预览版发布中提供。常规测试请使用上面的预览安装器,或从预览的 GitHub 预发布中下载 Windows 产物: + +| 系统 | 产物 | +| --- | --- | +| Windows x86_64 测试版 | `herdr-windows-x86_64.zip` | + +压缩包包含 `herdr.exe` 及其应用本地 ConPTY 运行时。请保留完整的解压目录,不要只复制 `herdr.exe`。 + +## 验证 + +启动 Herdr: + +```bash +herdr +``` + +如果 shell 找不到 `herdr`,重启终端,或检查安装目录是否在 PATH 上。 + +## 更新 + +Herdr 会检查新版本并在应用内通知你。也可以手动更新: + +```bash +herdr update +``` + +`herdr update` 适用于由 Herdr 自带安装器管理的安装。Homebrew、mise 和 Nix 安装请改用那些包管理器更新。 + +在 Linux 和 macOS 上,Herdr 默认使用稳定更新通道。要启用来自 `master` 的预览构建,设置通道: + +```bash +herdr channel set preview +``` + +用同样的方式把 Linux 和 macOS 的直接安装切回稳定版: + +```bash +herdr channel set stable +``` + +对于直接安装,切换通道时也会检查该通道并安装其最新二进制文件。如果该更新失败,运行 `herdr update` 从配置的通道重试。 + +预览构建是从当前开发分支手动发布的 GitHub 预发布。当你想在下一个稳定版之前拿到修复时很有用,但它们可能出现回归。Homebrew、mise 和 Nix 安装不使用预览通道。 + +Windows 测试版构建目前仅限预览通道。在稳定的 Windows 发布可用之前,Windows 上的 `herdr channel set stable` 会被拒绝。 + +默认情况下,`herdr update` 安装新二进制文件,并不打扰兼容的运行中会话。如果某次更新改变了 Herdr 的客户端/服务器协议,Herdr 会在安装后询问是否停止旧服务器。要使用新版本,请停止旧服务器。停止会退出窗格进程。对于默认会话,运行 `herdr server stop`,再运行 `herdr`。对于命名会话,运行 `herdr session stop <name>`,再运行 `herdr session attach <name>`。 + +要对受支持的运行中会话启用实验性的实时服务器交接,运行: + +```bash +herdr update --handoff +``` + +实时交接不适用于 Homebrew、mise 或 Nix 的包管理器更新。这些安装先用包管理器更新,然后在准备好使用新服务器时重启那个 Herdr 会话。如果运行中的会话仍在使用旧服务器,用 `herdr server stop` 或 `herdr session stop <name>` 停止它,再重新运行 Herdr。 + +## 系统要求 + +Herdr 稳定版支持 Linux 和 macOS。Windows 原生构建是仅限预览版的测试版发布;受支持的工作流和已知限制见 [Windows 测试版](/zh-cn/docs/windows-beta/)。 diff --git a/docs/preview/website/src/content/docs/zh-cn/integrations.mdx b/docs/preview/website/src/content/docs/zh-cn/integrations.mdx new file mode 100644 index 00000000..c49f61db --- /dev/null +++ b/docs/preview/website/src/content/docs/zh-cn/integrations.mdx @@ -0,0 +1,299 @@ +--- +title: 集成 +description: 为 Pi、OMP、Claude Code、Codex、GitHub Copilot CLI、Devin CLI、Droid、Kimi Code CLI、OpenCode、Kilo Code CLI、Hermes Agent、Qoder CLI、Cursor Agent CLI 和 MastraCode 安装 Herdr 集成。 +--- + +Herdr 自动检测受支持的智能体。官方集成可以额外提供用于恢复的原生会话身份、生命周期状态上报,或两者兼有。 + +当你想要 Claude Code/Codex/Copilot/Devin 式钩子的智能体原生会话恢复、Pi/OMP/Kimi/OpenCode/Kilo/Hermes/MastraCode 式钩子或插件的直接生命周期上报,或两者都要时,使用集成。完整的状态权威模型见[智能体](/zh-cn/docs/agents/)。 + +## 安装集成 + +在 Herdr 中打开设置,用集成标签页为 `PATH` 上发现的智能体安装推荐集成,或手动运行命令: + +```bash +herdr integration install pi +herdr integration install omp +herdr integration install claude +herdr integration install codex +herdr integration install copilot +herdr integration install devin +herdr integration install droid +herdr integration install kimi +herdr integration install opencode +herdr integration install kilo +herdr integration install hermes +herdr integration install mastracode +herdr integration install qodercli +herdr integration install cursor +``` + +## 卸载集成 + +```bash +herdr integration uninstall pi +herdr integration uninstall omp +herdr integration uninstall claude +herdr integration uninstall codex +herdr integration uninstall copilot +herdr integration uninstall devin +herdr integration uninstall droid +herdr integration uninstall kimi +herdr integration uninstall opencode +herdr integration uninstall kilo +herdr integration uninstall hermes +herdr integration uninstall mastracode +herdr integration uninstall qodercli +herdr integration uninstall cursor +``` + +## Herdr 如何使用集成 + +Herdr 以两种不同方式使用集成: + +| 集成类型 | 智能体 | 效果 | +| --- | --- | --- | +| 生命周期权威 | Pi、OMP、Kimi Code CLI、OpenCode、Kilo Code CLI、Hermes Agent、MastraCode | 已安装且在为该窗格主动上报时,由钩子或插件事件决定 `idle`、`working` 和 `blocked`。对同一个生命周期权威,Herdr 不再使用屏幕清单兜底。 | +| 会话身份 | Claude Code、Codex、GitHub Copilot CLI、Devin CLI、Droid、Qoder CLI、Cursor Agent CLI | 集成上报用于恢复的原生会话引用。状态仍来自 Herdr 的屏幕清单检测。 | + +自定义 socket 集成在定义了原生终端 UI 中不可见的状态时,也可以上报状态。 + +一些集成会上报智能体的原生会话引用。除非被 `[session] resume_agents_on_restore = false` 禁用,Herdr 会在服务器重启后使用官方会话引用恢复 Claude Code、Codex、Devin CLI、Droid、Kimi Code CLI、Qoder CLI、Cursor Agent CLI、GitHub Copilot CLI、Pi、OMP、Hermes Agent、OpenCode、Kilo Code CLI 和 MastraCode 的窗格。 + +原生会话恢复需要最新的 Herdr 集成: Pi 集成版本 `2`、OMP 版本 `3`、Claude Code 版本 `6`、Codex 版本 `5`、GitHub Copilot CLI 版本 `2`、Devin CLI 版本 `2`、Droid 版本 `2`、Kimi Code CLI 版本 `3`、Qoder CLI 版本 `2`、Cursor Agent CLI 版本 `1`、OpenCode 版本 `5`、Kilo Code CLI 版本 `1`、Hermes Agent 版本 `2`、MastraCode 版本 `1`。用 `herdr integration status` 查看已安装版本。 + +## Pi + +安装 Pi 集成: + +```bash +herdr integration install pi +``` + +Herdr 把内置扩展写入: + +```text +~/.pi/agent/extensions/herdr-agent-state.ts +``` + +如果设置了 `PI_CODING_AGENT_DIR`,Herdr 会改为写入 `$PI_CODING_AGENT_DIR/extensions/herdr-agent-state.ts`。如果 Pi agent 目录已经存在,Herdr 会创建 extensions 目录。卸载只删除那个扩展文件。 + +## OMP + +安装 OMP 集成: + +```bash +herdr integration install omp +``` + +Herdr 把内置扩展写入: + +```text +~/.omp/agent/extensions/herdr-omp-agent-state.ts +``` + +如果设置了 `PI_CODING_AGENT_DIR`,Herdr 会改为写入 `$PI_CODING_AGENT_DIR/extensions/herdr-omp-agent-state.ts`。如果 OMP agent 目录已经存在,Herdr 会创建 extensions 目录。卸载只删除那个扩展文件。 + +OMP 集成通过 Herdr 的 socket API 上报智能体标签 `omp`、生命周期状态和原生会话身份。它不需要对 `omp` 可执行文件做原生进程检测,并且 Herdr 可以在服务器重启后用 `omp --resume=<session>` 恢复 OMP 窗格。 + +## Claude Code + +安装 Claude Code 钩子: + +```bash +herdr integration install claude +``` + +该钩子在会话启动时把 Claude Code 的会话身份上报给本地 Herdr socket。Claude Code 的状态来自 Herdr 的屏幕清单检测。 + +Herdr 默认使用 `~/.claude`,设置了 `CLAUDE_CONFIG_DIR` 时使用后者。Claude 配置目录必须已经存在。安装会写入 `hooks/herdr-agent-state.sh`,并向 `settings.json` 添加 Herdr 钩子条目。卸载会移除匹配的钩子条目并删除钩子脚本。 + +## Codex + +安装 Codex 钩子: + +```bash +herdr integration install codex +``` + +Codex 钩子通过与其他集成相同的本地 socket API 上报会话身份。Codex 的状态来自 Herdr 的屏幕清单检测。 + +Herdr 默认使用 `~/.codex`,设置了 `CODEX_HOME` 时使用后者。Codex 配置目录必须已经存在。安装会写入 `herdr-agent-state.sh`、更新 `hooks.json`,并确保 `config.toml` 中有 `[features] hooks = true`。如果存在已弃用的顶层 `codex_hooks` 标志,它也会一并移除。卸载会从 `hooks.json` 中移除 Herdr 条目并删除钩子脚本,但不改动 `config.toml`。 + +## GitHub Copilot CLI + +安装 GitHub Copilot CLI 钩子: + +```bash +herdr integration install copilot +``` + +Copilot 钩子通过与其他集成相同的本地 socket API 上报会话身份。Copilot 的状态来自 Herdr 的屏幕清单检测。 + +Herdr 默认使用 `~/.copilot`,设置了 `COPILOT_HOME` 时使用后者。Copilot 配置目录必须已经存在。安装会写入 `hooks/herdr-agent-state.sh`,并向 `settings.json` 添加一个 `SessionStart` 钩子条目。卸载会从 `settings.json` 中移除 Herdr 条目并删除钩子脚本。 + +在 Copilot 发出携带会话信息的事件后,Herdr 可以用上报的会话 id 通过 `copilot --resume=<id>` 恢复该窗格。 + +## Devin CLI + +安装 Devin CLI 钩子: + +```bash +herdr integration install devin +``` + +该钩子从 Devin 的会话、提示、工具使用、权限和停止事件中上报原生会话身份。Devin 的状态仍来自 Herdr 的屏幕清单和 OSC 检测,因为 Devin 钩子不会在每次权限取消或用户中断后都发出可靠的状态转换。 + +Herdr 默认使用 `~/.config/devin`,设置了 `XDG_CONFIG_HOME` 时使用 `$XDG_CONFIG_HOME/devin`。Devin 配置目录必须已经存在。安装会写入 `herdr-agent-state.sh`,并向 `config.json` 添加 Herdr 钩子条目。钩子在 Devin 运行期间刷新会话引用。卸载会从 `config.json` 中移除 Herdr 条目并删除钩子脚本。 + +Herdr 用 `devin --resume <id>` 恢复保存的 Devin 会话。无论钩子是否安装,屏幕清单检测始终是状态权威。 + +## Kimi Code CLI + +安装 Kimi Code CLI 钩子: + +```bash +herdr integration install kimi +``` + +该钩子向 Herdr 上报 Kimi 的会话身份和生命周期状态,用于原生恢复和权威的 `idle`、`working`、`blocked` 状态。需要 Kimi Code CLI `0.14.0` 或更新版本。 + +Herdr 默认使用 `~/.kimi-code`,设置了 `KIMI_CODE_HOME` 时使用后者。Kimi Code 配置目录必须已经存在。安装会写入 `hooks/herdr-agent-state.sh`,并在 `config.toml` 中追加 Herdr 管理的 `[[hooks]]` 条目。卸载会移除 Herdr 管理的配置块并删除钩子脚本。 + +Herdr 用 `kimi --session <id>` 恢复保存的 Kimi 会话。 + +## Droid + +安装 Droid 钩子: + +```bash +herdr integration install droid +``` + +Droid 钩子通过与其他集成相同的本地 socket API 上报会话身份。生命周期状态仍来自 Herdr 的屏幕清单检测,因为 Droid 钩子没有覆盖所有生命周期转换。 + +Herdr 的 Droid 钩子使用 `~/.factory`。Factory 配置目录必须已经存在。安装会写入 `hooks/herdr-agent-state.sh`,向 `settings.json` 添加 Herdr 的 `SessionStart` 钩子条目,并在 `hooks.json` 中存在旧的 Herdr Droid 钩子条目时将其移除。卸载会从两个配置文件中移除 Herdr 条目并删除钩子脚本。 + +在 Droid 发出会话启动事件后,Herdr 可以用上报的会话 id 通过 `droid --resume <id>` 恢复该窗格。 + +## OpenCode + +安装 OpenCode 插件: + +```bash +herdr integration install opencode +``` + +Herdr 把插件写入 `~/.config/opencode/plugins/herdr-agent-state.js`。OpenCode 配置目录必须已经存在。卸载只删除那个插件文件。 + +该插件在 OpenCode 运行于 Herdr 窗格内时上报生命周期状态和会话身份。在 OpenCode 发出携带会话信息的事件后,Herdr 可以用上报的会话 id 通过 `opencode --session <id>` 恢复该窗格。插件未安装时,屏幕清单检测仍然可用。 + +## Kilo Code CLI + +安装 Kilo Code CLI 插件: + +```bash +herdr integration install kilo +``` + +Herdr 把插件写入 `~/.config/kilo/plugin/herdr-agent-state.js`。Kilo 配置目录必须已经存在。卸载只删除那个插件文件。 + +该插件在 Kilo 运行于 Herdr 窗格内时上报生命周期状态和会话身份。在 Kilo 发出携带会话信息的事件后,Herdr 可以用上报的会话 id 通过 `kilo --session <id>` 恢复该窗格。插件未安装时,屏幕清单检测仍然可用。 + +## Hermes Agent + +安装 Hermes Agent 插件: + +```bash +herdr integration install hermes +``` + +Herdr 写入 `~/.hermes/plugins/herdr-agent-state/`,并在 `~/.hermes/config.yaml` 中启用 `herdr-agent-state`。Hermes 配置目录必须已经存在。安装后请重启 Hermes 以加载插件。卸载会删除插件目录,并从 `plugins.enabled` 中移除 `herdr-agent-state`。 + +该插件在 Hermes 运行于 Herdr 窗格内时上报生命周期、工具、审批状态和会话 id。Herdr 可以用上报的会话 id 通过 `hermes --resume <id>` 恢复该窗格。插件未安装时,屏幕清单检测仍然可用。 + +## Qoder CLI + +安装 Qoder CLI 钩子: + +```bash +herdr integration install qodercli +``` + +该钩子向 Herdr 上报 Qoder CLI 的会话身份,用于原生恢复。生命周期状态仍来自 Herdr 的屏幕清单检测,因为 Qoder 钩子没有覆盖所有生命周期转换。 + +Herdr 默认使用 `~/.qoder`,设置了 `QODER_CONFIG_DIR` 时使用后者。Qoder 配置目录必须已经存在。安装会写入 `hooks/herdr-agent-state.sh`,并向 `settings.json` 添加 Herdr 钩子条目。卸载会移除匹配的钩子条目并删除钩子脚本。 + +Herdr 用 `qodercli --resume <id>` 恢复保存的 Qoder CLI 会话。 + +钩子未安装时,屏幕清单检测仍然可用。 + +## Cursor Agent CLI + +安装 Cursor Agent CLI 钩子: + +```bash +herdr integration install cursor +``` + +该钩子在 Cursor Agent CLI 运行于 Herdr 窗格内时,通过 Cursor 的 `sessionStart` 钩子上报会话身份。Cursor 的状态来自 Herdr 的屏幕清单检测。 + +Herdr 默认使用 `~/.cursor`,设置了 `CURSOR_CONFIG_DIR` 时使用后者。Cursor 配置目录必须已经存在。安装会写入 `herdr-agent-state.sh`,并向 `hooks.json` 添加 Herdr 的 `sessionStart` 条目。卸载会移除匹配的钩子条目并删除钩子脚本。 + +在 Cursor 发出会话启动事件后,Herdr 可以用上报的会话 id 通过 `cursor-agent --resume <id>` 恢复该窗格。Herdr 恢复窗格时,`cursor-agent` 命令必须在 `PATH` 上;Herdr 不会启动通用的 `agent` 命令。 + +## MastraCode + +安装 MastraCode 钩子: + +```bash +herdr integration install mastracode +``` + +该钩子向 Herdr 上报 MastraCode 生命周期状态和线程身份,用于权威的 `idle`、`working`、`blocked` 状态和原生恢复。MastraCode 没有屏幕清单兜底;当 MastraCode 在 Herdr 窗格内运行时,状态来自该钩子。 + +Herdr 使用 `~/.mastracode`。安装会写入 `hooks/herdr-agent-state.sh`,并把 Herdr 命令条目添加到 `hooks.json`;目录不存在时会创建。卸载会删除匹配的钩子条目和钩子脚本。 + +Herdr 用 `mastracode --thread <id>` 恢复保存的 MastraCode 线程。 + +## 自定义状态标签 + +集成只把生命周期状态作为语义状态上报。例如,上报智能体为 `working` 时,不在生命周期报告中添加展示字段。 + +```bash +herdr pane report-agent w1:p1 \ + --source custom:docs \ + --agent docs-bot \ + --state working +``` + +与 Herdr 管理的集成并行运行的用户钩子,应该使用元数据而不是 `report-agent`。元数据只改变展示,不会夺走集成对 `idle`、`working`、`blocked` 或会话恢复的权威。`--agent` 和 `--applies-to-source` 只守卫展示字段 (`--title`、`--display-agent` 和 `--state-label`)。令牌补丁始终生效;上报方负责清除或刷新 TTL。`--display-agent` 修改显示名称。 + +```bash +herdr pane report-metadata "$HERDR_PANE_ID" \ + --source user:claude-title \ + --agent claude \ + --title "Refactor auth middleware" \ + --display-agent "Claude: auth" \ + --token summary="refactor auth" \ + --state-label working="refactoring auth" \ + --ttl-ms 3600000 +``` + +令牌和状态标签只影响视觉。等待、通知和工作区汇总仍使用语义状态。 + +## 调试集成状态 + +列出已知的智能体: + +```bash +herdr agent list +``` + +需要验证 Herdr 能看到什么时,读取窗格: + +```bash +herdr pane read w1:p1 --source recent --lines 50 +``` + +如果集成状态看起来不对,先确认智能体运行在 Herdr 内部,并且相关的钩子或插件是为同一个用户账号安装的。 diff --git a/docs/preview/website/src/content/docs/zh-cn/keyboard.mdx b/docs/preview/website/src/content/docs/zh-cn/keyboard.mdx new file mode 100644 index 00000000..d7b0d297 --- /dev/null +++ b/docs/preview/website/src/content/docs/zh-cn/keyboard.mdx @@ -0,0 +1,112 @@ +--- +title: 键盘 +description: 前缀键是什么、应该先学哪些绑定,以及如何做到不用前缀键。 +--- + +:::tip[从 tmux 或 zellij 迁移过来?] +这套模型你已经很熟悉了。直接跳到[按键绑定参考](/zh-cn/docs/configuration/#按键绑定)查看完整的默认键位和配置语法。 +::: + +Herdr 是鼠标原生的。你可以点击窗格、标签页、工作区和智能体,拖动分割边框,使用右键菜单,一个按键绑定都不用学。键盘操作是可选的一层,不是必需项。 + +## 前缀键是什么 + +终端复用器位于终端和其中运行的程序之间。这些程序已经占用了大多数按键组合: `ctrl+c` 中断,`ctrl+r` 搜索历史,编辑器几乎占用了剩下的一切。如果 Herdr 直接抢占常用按键,就会破坏其中运行的程序。 + +前缀键解决了这个问题。按下前缀键 (默认 `ctrl+b`),下一次按键会发给 Herdr 而不是终端。`prefix+c` 的意思是: 按下 `ctrl+b`,松开,再按 `c`。只保留一个键,而不是几十个。 + +随时按 `prefix+?` 可以查看所有生效的绑定。在快捷键帮助中按 `/` 即可筛选操作和快捷键;使用 Backspace 编辑筛选内容,或按 `ctrl+u` 清除。 + +## 先学这五个 + +| 动作 | 按键 | +| --- | --- | +| 新建标签页 | `prefix+c` | +| 向右 / 向下分割 | `prefix+v` / `prefix+minus` | +| 在窗格间移动 | `prefix+h/j/k/l` | +| 工作区导航 | `prefix+w` | +| 分离并让一切继续运行 | `prefix+q` | + +这些覆盖了日常的大部分移动操作。其余的都可以继续用鼠标。 + +## 其余的,按任务分 + +窗格: + +| 动作 | 按键 | +| --- | --- | +| 缩放聚焦的窗格 | `prefix+z` | +| 关闭窗格 | `prefix+x` | +| 交换窗格 | `prefix+shift+h/j/k/l` | +| 调整大小模式 | `prefix+r` | +| 复制模式 | `prefix+[` | + +标签页: + +| 动作 | 按键 | +| --- | --- | +| 下一个 / 上一个标签页 | `prefix+n` / `prefix+p` | +| 跳转到标签页 1–9 | `prefix+1..9` | +| 重命名标签页 | `prefix+shift+t` | +| 关闭标签页 | `prefix+shift+x` | + +工作区与会话: + +| 动作 | 按键 | +| --- | --- | +| 新建工作区 | `prefix+shift+n` | +| 重命名工作区 | `prefix+shift+w` | +| 关闭工作区 | `prefix+shift+d` | +| Goto 选择器 | `prefix+g` | +| 切换侧边栏 | `prefix+b` | + +完整键位和绑定语法见[按键绑定参考](/zh-cn/docs/configuration/#按键绑定)。 + +## 复制模式 + +按 `prefix+[` 让聚焦的窗格进入复制模式。用 `h/j/k/l`、tmux 风格的 `w/b/e` 和 `{`/`}` 移动。按 `/` 或 `?` 向前或向后进行文本搜索,再用 `n` 或 `N` 按相同或相反方向重复搜索。查询包含大写字母时区分大小写,否则不区分。用 `v` 或空格开始选择,用 `y` 或回车复制,用 `q` 或 Esc 不复制直接退出。Esc 会先清除当前选择或搜索,然后才退出。复制模式不会暂停窗格进程:停留在底部时继续跟随输出,进入历史记录后保持当前位置。鼠标拖选可以直接复制,完全不用进入复制模式。 + +## 一切都可以改 + +每个绑定都可以配置,包括前缀键本身: + +```toml +[keys] +prefix = "ctrl+a" +``` + +## 不用前缀键 + +你可以把 Herdr 动作绑定到完全不需要前缀的直接组合键。难点在于知道哪些组合键是安全的,因为终端、shell 和桌面环境已经占用了键盘的大部分。 + +任何组合键都可以做绑定: `ctrl+j`、`alt+k`,顺手就行。但一个组合键要经过三层才能到达 Herdr: 操作系统、外层终端 (Ghostty、iTerm2 等都带有自己的默认键位),以及窗格内运行的程序。`ctrl+j` 能顺利到达 Herdr,但 shell 和编辑器把它当作回车。`alt+k` 在 Linux 上是空闲的,但在 macOS 的大多数终端里会被组合成特殊字符。如果你从这些键系里选组合键,请对照自己的终端和系统快捷键再确认一遍。 + +我们梳理了 Ghostty、iTerm2、Terminal.app、kitty、WezTerm、Alacritty、Warp、Windows Terminal、GNOME Terminal 和 Konsole 的默认按键绑定,以及 GNOME 和 KDE 的全局快捷键。有一个修饰键系几乎在所有地方都没被占用: `ctrl+alt`。终端把它留空,它不受 macOS option 键组合行为 (会挡掉纯 `alt` 组合键) 的影响,而且即使在没有现代键盘协议的终端里也能正常传输。它是安全的默认推荐;选择权在你。 + +下面的配置保留前缀绑定,并在其上叠加直接组合键: + +```toml +[keys] +focus_pane_left = ["prefix+h", "ctrl+alt+h"] +focus_pane_down = ["prefix+j", "ctrl+alt+j"] +focus_pane_up = ["prefix+k", "ctrl+alt+k"] +focus_pane_right = ["prefix+l", "ctrl+alt+l"] +previous_tab = ["prefix+p", "ctrl+alt+["] +next_tab = ["prefix+n", "ctrl+alt+]"] +new_tab = ["prefix+c", "ctrl+alt+c"] +split_vertical = ["prefix+v", "ctrl+alt+d"] +split_horizontal = ["prefix+minus", "ctrl+alt+shift+d"] +zoom = ["prefix+z", "ctrl+alt+z"] +``` + +少数 `ctrl+alt` 组合键已被其他地方占用。请避开这些: + +| 组合键 | 占用者 | +| --- | --- | +| `ctrl+alt+arrows` | GNOME 工作区切换,Ghostty 和 Konsole 默认键位 | +| `ctrl+alt+t` | Ubuntu 和 Fedora 的“启动终端” | +| `ctrl+alt+l` / `ctrl+alt+a` | KDE 锁屏 / 注意力窗口 | +| `ctrl+alt+s` / `ctrl+alt+u` | Konsole | +| `ctrl+alt+f1..f12` | Linux 虚拟控制台切换 | + +如果某个直接组合键没有任何反应,说明终端或桌面环境在 Herdr 看到它之前就把它消费掉了。两边任选其一重新配置: 在终端设置里释放这个组合键,或者在 Herdr 里换一个。 diff --git a/docs/preview/website/src/content/docs/zh-cn/marketplace.mdx b/docs/preview/website/src/content/docs/zh-cn/marketplace.mdx new file mode 100644 index 00000000..350cc0d5 --- /dev/null +++ b/docs/preview/website/src/content/docs/zh-cn/marketplace.mdx @@ -0,0 +1,44 @@ +--- +title: 插件市场 +description: 在 GitHub 上发现社区 Herdr 插件,并让你自己的插件被收录。 +--- + +Herdr 插件市场是一个可供发现的社区插件索引。 +访问 [herdr.dev/plugins](/plugins/) 浏览。它是公开 GitHub 仓库的自动索引, +而不是经过审核的目录。 + +## 浏览插件 + +[插件市场](/plugins/)会列出所有打了 GitHub 主题标签 `herdr-plugin` 的公开仓库。 +你可以按名称、作者、描述或语言搜索,并按热度、最近活跃度或最新排序。 +每个条目都直接链接到它在 GitHub 上的源码仓库。 + +收录是自动且未经审核的。被列出只说明仓库给自己打了标签,并不代表 Herdr +审查过它,所以在安装任何插件之前,请先阅读[信任指南](/zh-cn/docs/plugins/#信任与安全)。 + +## 安装插件 + +插件市场是在安装之上增加的发现渠道,它不会取代安装。任何插件都可以直接从 +GitHub 安装: + +```bash +herdr plugin install owner/repo[/subdir...] +``` + +发布一个普通的公开 GitHub 仓库,在根目录或子目录放一个 `herdr-plugin.toml` +清单,这条命令就能用了。清单和编写参考见[插件](/zh-cn/docs/plugins/)。 + +## 让你的插件被收录 + +给公开仓库添加 GitHub 主题标签 `herdr-plugin`。索引只使用这一个信号, +所以给公开插件打上标签就够了。索引每 30 分钟自动刷新,新打标签的仓库很快 +就会出现,去掉标签的仓库会在下一次刷新时消失。 + +## 条目会展示什么 + +每张卡片展示 GitHub 仓库的元数据: 仓库名和作者、描述、star 数、主要语言、 +最后 push 时间,以及指回源码的链接。索引从 GitHub 的仓库搜索读取这些信息, +所以保持仓库描述和主题标签的准确,是让条目有用的关键。 + +索引目前还不会解析 `herdr-plugin.toml`,所以插件 `id`、声明的 `platforms`、 +`min_herdr_version` 等清单字段在 v1 中不会展示。Fork 和已归档的仓库会被排除。 diff --git a/docs/preview/website/src/content/docs/zh-cn/persistence-remote.mdx b/docs/preview/website/src/content/docs/zh-cn/persistence-remote.mdx new file mode 100644 index 00000000..a81a8469 --- /dev/null +++ b/docs/preview/website/src/content/docs/zh-cn/persistence-remote.mdx @@ -0,0 +1,132 @@ +--- +title: 持久化与远程访问 +description: 从 Herdr 分离、稍后重新连接、使用命名会话,以及通过 SSH 连接。 +--- + +Herdr 让窗格在后台服务器中持续运行。你的终端客户端可以分离,稍后再重新连接。 + +关于本地、SSH 和 `herdr --remote` 工作流,参见[使用 Herdr 的工作方式](/zh-cn/docs/how-to-work/)。 + +## 分离与重新连接 + +用 `ctrl+b q` 分离客户端;窗格和智能体继续运行。再次运行 `herdr` 即可重新连接。用 `herdr server stop` 停止会话及其窗格。 + +在服务器完全停止后再次启动时,Herdr 会恢复保存的会话形态。关于分离、服务器重启、屏幕历史回放、智能体原生会话恢复和实时交接各自能保留什么,参见[会话状态与恢复](/zh-cn/docs/session-state/)。 + +## 命名会话 + +需要相互独立的 Herdr 服务器时,使用命名会话。 + +```bash +herdr session list +herdr session attach work +herdr session attach side-project +herdr session stop work +herdr session delete side-project +``` + +命名会话拥有自己的窗格、标签页、工作区、socket 和运行时状态。它仍然共享同一个全局配置文件。 + +脚本中使用 `--json`: + +```bash +herdr session list --json +herdr session stop work --json +herdr session delete side-project --json +``` + +## 通过 SSH 远程连接 + +远程模式有两种;[使用 Herdr 的工作方式](/zh-cn/docs/how-to-work/)对它们做了比较。tmux 风格的路径是 SSH 到服务器并在那里运行 `herdr`。另一种是从本地机器通过 SSH 连接: + +```bash +herdr --remote workbox +herdr --remote ssh://you@server:2222 +``` + +这种模式下,你本地的 Herdr 是瘦客户端。它通过 SSH 连接,启动或连接远程 Herdr 服务器,并把 UI 流式传回本地终端。因为客户端在本地运行,Herdr 可以把图像剪贴板粘贴等本地桌面功能桥接到远程会话: 把图像复制到远程临时文件,再粘贴该路径。 + +默认情况下,`herdr --remote` 在这次连接中使用你本地的 Herdr 按键绑定。即使远程服务器的配置不同,也能保持本地的肌肉记忆。本地按键绑定是连接时的快照;编辑本地按键绑定后请分离再重连。想改用远程服务器配置时,使用 `--remote-keybindings server`。本地的自定义命令按键绑定不会被发送,因为那些命令会在远程主机上执行。 + +对于经常连接的目标,使用你的 SSH 配置: + +```text +Host workbox + HostName server.example.com + User you + Port 2222 +``` + +然后这样连接: + +```bash +herdr --remote workbox +``` + +远程连接支持 x86_64 和 aarch64 的 Linux 与 macOS 主机。Herdr 会检查远程平台,优先使用远程 `PATH` 上已有的匹配 `herdr`,然后检查 `~/.local/bin/herdr`。如果没有匹配的二进制文件,交互式运行会询问是否安装到 `~/.local/bin/herdr`;非交互式运行则直接失败,不会修改主机。如果 `~/.local/bin` 不在远程 `PATH` 上,Herdr 会在安装后发出警告。 + +Windows 原生的 `herdr --remote` 不在 Windows 测试版范围内。在 Windows 上请 SSH 到服务器并在那里运行 `herdr`。 + +默认情况下,`herdr --remote` 通过一个临时 SSH 配置运行桥接: 先包含你的 SSH 配置,再补充兜底的保活设置。已有的用户保活设置优先。设置 `[remote].manage_ssh_config = false` 可以不用 Herdr 生成的桥接配置,而使用普通 `ssh`。 + +默认情况下,如果需要替换或重启运行中的远程服务器,远程连接使用常规的重启/停止流程。要对受支持的运行中远程服务器启用实验性实时交接,加上 `--handoff`: + +```bash +herdr --remote workbox --handoff +``` + +如果你先 SSH 到服务器再在那里运行 `herdr`,Herdr 就完全运行在服务器上。这种模式简单实用,但除了普通的终端文本粘贴之外,它无法访问你本地桌面的剪贴板。 + +当本地和远程平台一致时,对于直接安装,Herdr 可以直接复制当前本地二进制文件。对于 Homebrew、mise 和 Nix 安装,或平台不一致时,它会从 `https://herdr.dev/latest.json` 下载与当前客户端版本匹配的发布产物。 + +对于本地构建或自定义二进制文件,在远程连接前设置 `HERDR_REMOTE_BINARY` 指向本地文件路径。 + +```bash +HERDR_REMOTE_BINARY=target/release/herdr herdr --remote workbox +``` + +## 远程命名会话 + +用 `--session` 搭配 `--remote` 连接远程主机上的命名会话: + +```bash +herdr --remote workbox --session agents +``` + +## 直接终端附加 + +完整的 Herdr 连接会打开整个工作区 UI。直接附加则在你当前的终端中打开一个由服务器拥有的终端。 + +在 Windows 测试版中,直接终端附加仅限 Unix。 + +按智能体目标附加: + +```bash +herdr agent attach reviewer +``` + +按终端 ID 附加: + +```bash +herdr terminal attach term_abc123 +``` + +直接附加先流式传输当前渲染的终端状态,然后是实时 ANSI 帧。输入直接进入该终端。 + +用 `ctrl+b q` 分离。用 `ctrl+b ctrl+b` 发送字面的 `ctrl+b`。 + +一个终端只能有一个可写的直接附加客户端拥有输入和调整尺寸的权限。用 `--takeover` 替换现有的所有者: + +```bash +herdr terminal attach term_abc123 --takeover +``` + +## 单进程逃生舱 + +用 `--no-session` 在没有后台服务器/客户端分离的情况下运行 Herdr: + +```bash +herdr --no-session +``` + +这主要是调试或兼容性的逃生舱。默认的持久会话模式才是常规路径。 diff --git a/docs/preview/website/src/content/docs/zh-cn/plugins.mdx b/docs/preview/website/src/content/docs/zh-cn/plugins.mdx new file mode 100644 index 00000000..306bda53 --- /dev/null +++ b/docs/preview/website/src/content/docs/zh-cn/plugins.mdx @@ -0,0 +1,328 @@ +--- +title: 插件 +description: 编写带有清单动作、事件钩子和窗格的本地 Herdr 插件。 +--- + +Herdr 插件是可分享、可执行的工作流包。插件可以是 Bash 脚本、JavaScript +应用、Lua 脚本、Rust 二进制,或你机器上能运行的任何 argv 命令。Herdr +负责宿主侧: 安装、清单校验、按键绑定、终端窗格、事件、调用上下文和 +socket 访问。插件负责自己的实现语言、依赖、文件和持久状态。 + +插件的存在是为了让 Herdr 保持精简。核心继续专注于终端工作区、窗格、 +智能体和稳定的 CLI/socket API。插件把这套已有的扩展面变成可复用的 +工作流,让大家可以构建、安装和分享,而不必把每种工作流都塞进 Herdr 本体。 + +插件不是 SDK 集成。它是一个带 `herdr-plugin.toml` 清单和 Herdr 可启动 +命令的目录。Herdr 校验清单、注入运行时上下文、启动声明的命令并记录日志。 +命令在需要做更多工作时,通过 CLI 或 socket 回调 Herdr。 + +没有单独的插件 SDK,也没有受限的命令集。整个 Herdr CLI 就是插件 API: +[CLI 参考](/zh-cn/docs/cli-reference/)中的每条命令插件都能用,你自己能以 +`herdr ...` 运行的任何东西,插件也能运行。大多数插件应通过指向运行中 +Herdr 二进制的 `HERDR_BIN_PATH` 调用 Herdr,这样插件在 Unix socket 和 +Windows 命名管道之间保持可移植。想自己发送原始 JSON 请求时,使用 +[socket API](/zh-cn/docs/socket-api/)。 + +运行时动作注册和非终端的原生插件 UI 不在插件 v1 范围内。动作、事件钩子、 +窗格和链接处理器都在清单中声明。 + +## 信任与安全 + +插件是运行在你机器上的普通代码。安装或链接一个插件时,它的构建和运行时 +命令以你的用户身份、在你的环境中执行,并且可以调用完整的 Herdr CLI — +和你给编辑器、shell 或编程智能体添加的任何扩展一样。这种开放性正是设计 +初衷,加上一点判断力就能保持安全。 + +从你信任的作者和仓库安装插件,并先大致看看新插件做什么: `herdr-plugin.toml` +清单,以及它运行的脚本或二进制。`herdr plugin install` 在交互式终端中会 +展示来源和将要运行的命令的预览,你可以在确认前审查。对已经信任的来源用 +`--yes`,想固定某个特定版本时用 `--ref`。 + +Herdr 校验清单,并把每个插件的配置和状态放在各自的目录中,但它不会审查 +或沙箱化插件的行为。第三方插件来自它们的作者,而不是 Herdr,审查与运行 +由你自行决定。 + +## 清单 + +清单是 Herdr 和插件之间的契约。它声明包元数据、支持的平台、可选的构建 +命令,以及 Herdr 可以运行的入口点。 + +```toml +id = "example.layout" +name = "Layout" +version = "0.1.0" +min_herdr_version = "0.7.0" +description = "Apply project layouts" +platforms = ["linux", "macos", "windows"] + +[[build]] +command = ["npm", "ci"] + +[[build]] +command = ["npm", "run", "build"] +platforms = ["linux", "macos"] + +[[startup]] +command = ["node", "dist/restore.js"] + +[[actions]] +id = "apply" +title = "Apply layout" +contexts = ["workspace"] +command = ["node", "dist/apply.js"] + +[[events]] +on = "worktree.created" +command = ["herdr", "workspace", "list"] + +[[panes]] +id = "board" +title = "Project board" +placement = "overlay" +command = ["herdr-board"] + +[[link_handlers]] +id = "github-issue" +title = "Open GitHub issue" +pattern = "^https://github\\.com/[^/]+/[^/]+/(issues|pull)/[0-9]+$" +action = "apply" +``` + +顶层的 `id`、`name`、`version` 和 `min_herdr_version` 是必填项。 +把 `min_herdr_version` 设为支持你插件所用的插件 API、事件名和清单字段的 +最老 Herdr 版本。当插件的最低版本比当前二进制更新时,Herdr 会拒绝链接 +或安装。`description` 可选。插件 id 可以使用 ASCII 字母、数字、点、 +冒号、下划线和连字符。 + +动作 id、窗格 id 和链接处理器 id 是插件内部的本地 id。它们可以使用 +ASCII 字母、数字、冒号、下划线和连字符,但不能用点。每种 id 在插件内 +必须唯一。当需要全局唯一名称时,Herdr 会把动作 id 限定为 +`plugin.id.action` 的形式。 + +用 `platforms = ["linux", "macos", "windows"]` 声明插件可以运行的平台。 +构建命令、启动钩子、动作、事件钩子、窗格和链接处理器也可以声明自己的 +`platforms`;条目级的 platforms 覆盖顶层列表。没有顶层 `platforms` 的本地 +插件在链接时会给出警告。 + +`command` 的值是 argv 数组。Herdr 不会通过 shell 运行它们,所以除非你的 +命令自己启动 shell,否则没有 shell 展开。语言相关的行为放到你的脚本或 +二进制里。 + +## 第一个插件 + +从一个包含 `herdr-plugin.toml` 和一个可执行脚本或程序的目录开始: + +```text +my-plugin/ + herdr-plugin.toml + index.js +``` + +```toml +id = "example.workspace-tools" +name = "Workspace Tools" +version = "0.1.0" +min_herdr_version = "0.7.0" +description = "Small workspace helpers" +platforms = ["linux", "macos", "windows"] + +[[actions]] +id = "list-workspaces" +title = "List workspaces" +contexts = ["workspace"] +command = ["node", "index.js"] +``` + +在命令内部通过 `HERDR_BIN_PATH` 回调 Herdr: + +```js +const { spawnSync } = require("node:child_process"); + +const herdr = process.env.HERDR_BIN_PATH ?? "herdr"; +const result = spawnSync(herdr, ["workspace", "list"], { + encoding: "utf8", + stdio: ["ignore", "pipe", "pipe"], +}); + +process.stdout.write(result.stdout); +process.stderr.write(result.stderr); +process.exit(result.status ?? 1); +``` + +这个示例用了 Node,但插件本身完全不要求 Node。清单可以启动 Bash、 +PowerShell、Python、Rust、Go、Lua、Bun 或用户机器上可用的任何其他命令。 + +## 安装与链接 + +安装一个示例插件: + +```bash +herdr plugin install ogulcancelik/herdr-plugin-examples/agent-telegram-notify +herdr plugin config-dir examples.agent-telegram-notify +herdr plugin list +herdr plugin action list --plugin examples.agent-telegram-notify +``` + +在本地编写插件时,改为链接工作目录: + +```bash +herdr plugin link /path/to/plugin +herdr plugin config-dir example.layout +herdr plugin action list --plugin example.layout +herdr plugin action invoke example.layout.apply +herdr plugin pane open --plugin example.layout --entrypoint board +herdr plugin log list --plugin example.layout +``` + +`plugin install` 只接受 GitHub 简写,比如 `owner/repo/subdir`。它用 `git` +克隆,在交互式终端展示预览,运行受支持的构建命令,然后把检出保存到 +Herdr 管理的插件数据下并注册。非交互式安装用 `--yes`。重新安装 GitHub +管理的插件会替换该托管检出。已安装和已链接的插件及其启用状态对当前用户 +全局生效,可在所有 Herdr 会话中使用。即使 Herdr 服务器没有运行,也可以通过 +`plugin install` 和 `plugin link` 注册插件。仅安装在 Herdr 0.7.3 命名会话中的 +插件必须重新 install 或 link;现有的插件配置和状态会保留。 +不允许覆盖安装到本地链接的插件之上;请先 unlink 或 uninstall 本地插件。 +`plugin install` 和 `plugin link` 会创建 +插件的配置和状态目录,`plugin config-dir <id>` 打印配置目录,方便安装 +文档和 shell 脚本使用。 + +`plugin uninstall <id-or-source>` 注销插件。对 GitHub 管理的安装,它还会 +删除托管检出,并且既接受插件 id,也接受与 install 相同的 +`owner/repo[/subdir...]` 简写。`plugin unlink <id>` 只注销插件、不动文件, +对本地开发很有用。v1 没有单独的 `plugin update`;要刷新托管插件,请从 +GitHub 重新安装。 + +示例菜谱仓库是 `ogulcancelik/herdr-plugin-examples`。它在子目录中包含 +多个独立示例插件,包括 `agent-telegram-notify`、`github-link-preview` 和 +`dev-layout-bootstrap`。这些是供复制的示例,不是持续维护的官方插件。 + +## 构建命令 + +构建命令在 GitHub `plugin install` 过程中运行,时机在确认之后、Herdr +注册插件之前。如果构建命令失败,安装中止,插件不会被注册。`plugin link` +不运行构建命令;本地作者自己构建工作树。构建命令可以生成文件,但在 +安装预览之后修改 `herdr-plugin.toml` 会导致安装中止。构建失败时会显示 +插件 id、构建序号、工作目录、命令、退出状态或 spawn 错误,以及截断后的 +stdout/stderr,不会解读工具输出。 + +构建命令同样是普通的 argv 命令,但它们不会收到运行时插件上下文或 +Herdr socket 环境变量。插件作者应在文档中说明所需的系统工具,比如 +`cargo`、`npm`、`bun` 或 `lua`;Herdr 报告构建失败,但不会安装缺失的 +工具链。 + +## 启动钩子 + +Herdr 恢复会话且 API socket 就绪后,会为每个已启用插件运行一次 +`[[startup]]` 命令。实时交接由新服务器接管时会再次运行,但客户端连接、 +配置重载以及插件 link 或 enable 时不会运行。Herdr 异步启动这些命令,并在 +普通插件命令日志中记录完成情况。启动钩子失败不会停止服务器。 + +启动钩子是一次性初始化命令,不是受监督的守护进程。钩子应恢复插件自己的 +状态、调用所需的 Herdr API,然后退出。例如,插件可以把声明式 Agent 视图保存到 +`HERDR_PLUGIN_STATE_DIR`,再由启动钩子读取并重新应用。 + +启动钩子会收到普通运行时插件环境和 `HERDR_PLUGIN_EVENT=startup`。安装预览会 +列出所有启动命令,让用户检查将自动运行的代码。 + +## 命令与环境 + +运行时命令以插件目录为工作目录执行。Herdr 注入 `HERDR_SOCKET_PATH`、 +`HERDR_BIN_PATH`、`HERDR_ENV=1`、`HERDR_PLUGIN_ID`、`HERDR_PLUGIN_ROOT`、 +`HERDR_PLUGIN_CONFIG_DIR`、`HERDR_PLUGIN_STATE_DIR`、 +`HERDR_PLUGIN_CONTEXT_JSON`,以及可用时的 `HERDR_WORKSPACE_ID`、 +`HERDR_TAB_ID` 和 `HERDR_PANE_ID`。动作命令还会收到 +`HERDR_PLUGIN_ACTION_ID`;启动钩子和事件钩子收到 `HERDR_PLUGIN_EVENT` +(启动钩子中的值为 `startup`),事件钩子还会收到 +`HERDR_PLUGIN_EVENT_JSON`;窗格命令收到 `HERDR_PLUGIN_ENTRYPOINT_ID`。 + +`HERDR_PLUGIN_ROOT` 是已安装或已链接的插件目录。不要把用户凭据或持久 +状态放在那里,因为 GitHub 安装的插件根目录是托管的源码检出。把 `.env` +这类用户可编辑的配置放在 `HERDR_PLUGIN_CONFIG_DIR` 下,把本地运行时 +状态放在 `HERDR_PLUGIN_STATE_DIR` 下。Herdr 会创建这些目录,并在旧版 +插件配置位置存在时把内容初始化到 `HERDR_PLUGIN_CONFIG_DIR`,但不会校验、 +同步或删除其中的内容。文件格式和生命周期归插件所有。 + +`HERDR_PLUGIN_CONTEXT_JSON` 在本次调用可用时,可以包含工作区、标签页、 +聚焦窗格、worktree、智能体、选中文本、点击的 URL 和链接处理器字段。 +shell 插件可以从各个环境变量读取常用 id,或解析上下文 JSON 获取完整结构。 + +当插件需要从 Node、PowerShell、Bash 或其他运行时可移植地调用 Herdr 时, +使用 `HERDR_BIN_PATH`。`HERDR_SOCKET_PATH` 背后的原始 socket 传输是 +操作系统相关的: Unix 客户端连接 Unix socket 路径,Windows 客户端连接 +命名管道。通过 `HERDR_BIN_PATH` 的 CLI 调用可以避开这种传输差异。可用 +命令见 [CLI 参考](/zh-cn/docs/cli-reference/),原始请求结构见 +[socket API](/zh-cn/docs/socket-api/)。 + +## 窗格 + +清单中窗格的 `placement` 默认为 `overlay`,它在活动窗格上方打开一个 +临时的缩放覆盖层,关闭时恢复之前的焦点和缩放。`plugin.pane.open` 请求 +可以用 `overlay`、`popup`、`split`、`tab` 或 `zoomed` 覆盖清单的 placement。 + +`placement = "popup"` 会打开一个会话级模态终端弹窗,而不改变平铺布局。 +可以在清单或 open 请求中指定可选的 `width` 和 `height`;省略时默认为终端大小的一半,数字表示外层终端单元格数,`"80%"` 这样的字符串表示终端区域的百分比。 +弹窗会接收包括 Escape 在内的所有终端输入,并在命令退出或发送 `popup.close` 请求时关闭。 +小于弹窗最小尺寸的值会限制为最小值。 + +如果某个插件窗格应始终是临时的,可直接在入口点上声明 placement: + +```toml +[[panes]] +id = "picker" +title = "Picker" +platforms = ["linux", "macos"] +placement = "popup" +width = "80%" +height = 20 +command = ["sh", "picker.sh"] +``` + +split、tab、zoomed 和 overlay 插件窗格打开后就是普通的 Herdr 窗格。插件可以通过 socket 或 CLI 调用 +`pane.move`、`pane.swap`、`pane.resize`、`pane.zoom` 等标准窗格 API; +窗格跨标签页或工作区移动时,Herdr 会让插件窗格的所有权跟随底层窗格。 +弹窗不是 Herdr 窗格,而是会话级单例资源:它没有窗格 id,不会改变插件焦点上下文,不会发出窗格生命周期事件,也不参与 pane、layout、持久化或智能体 API。 +其进程不会收到 `HERDR_PANE_ID`;底层平铺窗格仍可通过 `HERDR_PLUGIN_CONTEXT_JSON` 获取。 +在 Settings、复制模式或其他 Herdr 模态界面打开时尝试打开弹窗会返回 `ui_busy`;启动成功后,`plugin.pane.open` 返回 `ok`。 + +在 Windows 上,构建命令、动作命令和事件命令会在裸命令位于 `PATH` 上时 +解析常见的 `PATHEXT` shim,比如 `npm.cmd`、`bun.cmd` 和 `pnpm.cmd`。 +窗格命令使用 Herdr 常规的 Windows 窗格启动器,仍然必须是有效的 Windows +argv 命令。 + +## 按键绑定 + +把某个键绑定到已安装的插件动作: + +```toml +[[keys.command]] +key = "prefix+l" +type = "plugin_action" +command = "example.layout.apply" +description = "apply layout" +``` + +## 链接处理器 + +用 `[[link_handlers]]` 把对匹配终端 URL 的修饰键点击路由到插件动作, +而不是在浏览器中打开 URL。修饰键点击的修饰键在所有平台上都是 Control, +包括 macOS,因为被捕获的终端鼠标上报无法把 Command/Super 和普通点击 +区分开。`pattern` 是对被点击 URL 匹配的 Rust 正则表达式,`action` 必须 +指向同一插件声明的动作。链接处理器动作在 `HERDR_PLUGIN_CONTEXT_JSON` +中收到 `invocation_source = "link_click"`、`clicked_url` 和 +`link_handler_id`;shell 插件也可以读取 `HERDR_PLUGIN_CLICKED_URL` 和 +`HERDR_PLUGIN_LINK_HANDLER_ID`。每个插件内的处理器按清单顺序检查。 + +## 存储 + +v1 没有 Herdr 管理的插件存储 API。需要持久状态的插件应自己管理文件或 +数据库。 + +## 插件市场 + +社区插件可以在[插件市场](/plugins/)中发现,它是打了 `herdr-plugin` 主题 +标签的公开 GitHub 仓库的自动索引。插件仍然是普通的 GitHub 仓库: 发布一个 +带 `herdr-plugin.toml` 的仓库,然后分享 +`herdr plugin install owner/repo[/subdir]`。 + +要让插件被收录,给它的公开仓库添加 GitHub 主题标签 `herdr-plugin`。索引 +每 30 分钟刷新一次。发现机制的工作方式见[插件市场](/zh-cn/docs/marketplace/)。 diff --git a/docs/preview/website/src/content/docs/zh-cn/quick-start.mdx b/docs/preview/website/src/content/docs/zh-cn/quick-start.mdx new file mode 100644 index 00000000..3f695d92 --- /dev/null +++ b/docs/preview/website/src/content/docs/zh-cn/quick-start.mdx @@ -0,0 +1,69 @@ +--- +title: 快速开始 +description: 创建你的第一个 Herdr 工作区,在持久的终端窗格中运行智能体。 +--- + +如果还没有安装 Herdr,请参见[安装](/zh-cn/docs/install/)。然后在任意项目目录中启动 Herdr: + +```bash +herdr +``` + +Herdr 会启动或连接到你的默认后台会话。你不需要管理 socket。即使分离,智能体也会继续运行。 + +## 创建工作区 + +当会话中没有工作区时,Herdr 会自动打开一个。工作区是项目级别的容器,容纳标签页、窗格和智能体。给每个活跃项目一个独立的工作区,这样侧边栏里的智能体状态才清晰可读。 + +## 使用鼠标 + +Herdr 是鼠标原生的,所以从点击开始。点击窗格、标签页、工作区和智能体来聚焦它们。拖动分割边框来调整大小。右键打开上下文菜单,包括分割窗格和创建标签页。拖选文本即可复制到剪贴板;双击一个词直接复制它。复制不需要 Ctrl+C。 + +当终端把带修饰键的点击传给 Herdr 时,Ctrl+点击可以打开窗格内的链接。这对 OSC 8 超链接和可见的 `http://` 或 `https://` URL 有效。在 macOS 上,鼠标捕获开启时,请用 Ctrl+点击来触发 Herdr 处理的窗格链接;Cmd+点击只能通过终端原生的绕过路径使用,比如 Shift+Cmd+点击或 `ui.mouse_capture = false`。 + +如果配置了 `ui.right_click_passthrough_modifier`,该修饰键加右键会把右键点击、按住和拖动手势发送给启用鼠标上报的窗格应用。 + +## 运行智能体 + +在窗格里启动你的编程智能体: + +```bash +claude +``` + +也可以是 `codex`、`pi`、`opencode` 或任何其他[受支持的智能体](/zh-cn/docs/agents/)。Herdr 会自动检测。侧边栏会显示每个智能体处于 `working`、`blocked`、`done` 还是 `idle` — 并且跨所有工作区显示,所以你总能知道哪个项目需要你。 + +## 键盘操作 + +键盘操作是可选的;鼠标可以完成一切。按 `ctrl+b` 进入前缀模式,然后按一个动作键。 + +常用动作: + +| 动作 | 按键 | +| --- | --- | +| 向右分割 | `prefix+v` | +| 向下分割 | `prefix+minus` | +| 新建标签页 | `prefix+c` | +| 下一个 / 上一个标签页 | `prefix+n` / `prefix+p` | +| 工作区导航 | `prefix+w` | +| 新建工作区 | `prefix+shift+n` | +| 分离客户端 | `prefix+q` | + +第一次接触前缀键的概念?[键盘](/zh-cn/docs/keyboard/)会解释它是什么、为什么终端复用器要用它,以及如何做到不用前缀键。在 Herdr 里按 `prefix+?` 查看所有生效的绑定,按 `prefix+[` 进入复制模式用键盘复制。 + +## 分离与回来 + +按 `prefix+q`,或者直接关掉终端窗口。Herdr 服务器和所有智能体会继续运行。再次运行 `herdr` 即可重新连接到同一个会话。 + +要真正结束会话并停止其中的窗格: + +```bash +herdr server stop +``` + +## 接下来 + +- [核心概念](/zh-cn/docs/concepts/) — 两分钟了解工作区、标签页、窗格和智能体模型。 +- [使用 Herdr 的工作方式](/zh-cn/docs/how-to-work/) — 本地、SSH、手机和 `herdr --remote` 工作流。 +- [智能体](/zh-cn/docs/agents/) — 受支持的智能体、检测,以及提升状态准确度的集成。 +- [配置](/zh-cn/docs/configuration/) — 按键绑定、主题、通知,以及其他一切。 diff --git a/docs/preview/website/src/content/docs/zh-cn/session-state.mdx b/docs/preview/website/src/content/docs/zh-cn/session-state.mdx new file mode 100644 index 00000000..cf796d0c --- /dev/null +++ b/docs/preview/website/src/content/docs/zh-cn/session-state.mdx @@ -0,0 +1,107 @@ +--- +title: 会话状态与恢复 +description: 理解 Herdr 保持哪些实时状态、重启后恢复什么、从历史回放什么、通过智能体集成恢复什么会话,以及更新时如何交接。 +--- + +Herdr 有多条状态路径,它们解决的是不同的问题。 + +## 什么能保留下来 + +| 场景 | 进程继续运行 | 布局恢复 | 最近的屏幕内容恢复 | 智能体对话恢复 | +| --- | --- | --- | --- | --- | +| 分离并重新连接 | 是 | 是 | 是,来自实时终端 | 是,因为进程从未停止 | +| 服务器重启 | 否 | 是 | 仅在开启窗格屏幕历史时 | 仅在有智能体原生会话恢复时 | +| 不带 `--handoff` 的更新 | 兼容的服务器继续运行;需要重启的服务器可能要停止/重启 | 重启后恢复 | 仅在开启窗格屏幕历史时 | 仅在有智能体原生会话恢复时 | +| 带 `--handoff` 的更新 | 对受支持的运行中服务器尽力而为 | 是 | 是,交接成功时来自实时终端 | 是,交接成功时进程保持运行 | + +下面的小节逐一解释这些路径。 + +## 实时持久化 + +普通分离会让 Herdr 服务器继续运行。窗格、shell、智能体、服务器、测试和命令进程都在该服务器中继续运行。 + +用 `ctrl+b q` 分离客户端。之后重新连接: + +```bash +herdr +``` + +这是最强的持久化路径,因为原始进程从未停止。 + +## 快照恢复 + +如果 Herdr 服务器停止后再启动,原来的窗格进程已经不在了。Herdr 会恢复保存的会话形态: 工作区、标签页、窗格、cwd、布局和焦点。 + +快照恢复不会保留运行中的 shell、服务器、测试或任意进程。无法使用更强恢复路径的窗格,会在各自保存的目录中作为新 shell 回来。 + +## 窗格屏幕历史回放 + +窗格屏幕历史在服务器完全重启后恢复最近的终端内容。它恢复的是 Herdr 能展示的内容,而不是原来的进程。 + +它默认关闭,因为窗格输出可能包含密钥、令牌、提示词和命令输出。可以在 Settings > Experiments > pane screen history 中开启,或者: + +```toml +[experimental] +pane_history = true +``` + +开启后,Herdr 把保存的窗格历史存放在 `session.json` 旁边的 `session-history.json` 中。请像对待终端历史一样对待 Herdr 的配置/会话目录。 + +## 智能体原生会话恢复 + +一些智能体可以恢复它们自己的对话会话。Herdr 可以使用官方集成上报的会话引用,在 Herdr 服务器重启后重新启动受支持的智能体窗格。 + +这默认开启。关闭方法: + +```toml +[session] +resume_agents_on_restore = false +``` + +Herdr 只会恢复那些通过当前官方 Herdr 集成上报了原生会话引用的窗格。 + +当客户端连接并提供终端尺寸和主题上下文后,Herdr 会跨工作区和标签页恢复符合条件的智能体窗格,不需要等每个窗格被聚焦。 + +智能体原生会话恢复需要以下版本或更新的 Herdr 集成: + +| 智能体 | 最低 Herdr 集成版本 | 恢复命令 | +| --- | --- | --- | +| Pi | `2` | `pi --session <path-or-id>` | +| OMP | `3` | `omp --resume=<path-or-id>` | +| Claude Code | `6` | `claude --resume <id>` | +| Codex | `5` | `codex resume <id>` | +| Cursor Agent CLI | `1` | `cursor-agent --resume <id>` | +| GitHub Copilot CLI | `2` | `copilot --resume=<id>` | +| Devin CLI | `2` | `devin --resume <id>` | +| Droid | `2` | `droid --resume <id>` | +| Kimi Code CLI | `3` | `kimi --session <id>` | +| Qoder CLI | `2` | `qodercli --resume <id>` | +| OpenCode | `5` | `opencode --session <id>` | +| Kilo Code CLI | `1` | `kilo --session <id>` | +| Hermes Agent | `2` | `hermes --resume <id>` | +| MastraCode | `1` | `mastracode --thread <id>` | + +运行 `herdr integration status` 查看已安装的集成版本。用 `herdr integration install <agent>` 重新安装过期的集成。 + +不受支持、缺失、无效、重复或过期的会话引用,会在保存的窗格目录中作为普通 shell 恢复。 + +如果某个窗格适用智能体原生会话恢复,Herdr 会对该窗格恢复智能体会话,而不是回放保存的窗格历史。 + +## 实时交接 + +实时交接用于需要替换运行中 Herdr 服务器的更新和远程连接流程。它请求旧服务器把实时窗格转移给新服务器,让窗格进程跨服务器替换继续运行。 + +这与快照恢复、窗格历史回放和智能体原生会话恢复不同。交接尝试让当前进程活下去,其他路径则是在旧服务器已经停止之后重建状态。 + +交接保护的是由服务器拥有的长期会话状态:窗格 PTY 和进程、智能体身份与持久元数据,以及替换服务器所需的插件/会话状态。它不会跨替换边界保留临时协调状态。进行中的 CLI 或 API 请求、等待、订阅流、客户端套接字和窗格间消息可能会中断;客户端应重新连接并重试。 + +实时交接是实验性功能,需要主动开启: + +```bash +herdr update --handoff +herdr --remote workbox --handoff +``` + +普通的 `herdr update` 和普通的 `herdr --remote workbox` 默认使用常规的重启/停止流程。 + +`herdr update --handoff` 只适用于由 Herdr 自带更新器管理的安装。Homebrew、mise 和 Nix 安装通过各自的包管理器更新,因此那些安装中 `herdr update` 被禁用,无法执行实时交接。 diff --git a/docs/preview/website/src/content/docs/zh-cn/socket-api.mdx b/docs/preview/website/src/content/docs/zh-cn/socket-api.mdx new file mode 100644 index 00000000..269e6a3d --- /dev/null +++ b/docs/preview/website/src/content/docs/zh-cn/socket-api.mdx @@ -0,0 +1,748 @@ +--- +title: Socket API +description: 从脚本、工具和编程智能体控制运行中的 Herdr 服务器。 +--- + +Herdr 为需要检查或控制运行中会话的脚本和智能体提供了一个本地 socket API。 + +大多数自动化应从 CLI 包装命令开始。只有在需要直接的请求/响应控制或长期事件订阅时,才使用原始 socket API。 + +## 选择集成层 + +| 层 | 用途 | +| --- | --- | +| 智能体技能 | 教编程智能体如何在窗格内使用 Herdr。 | +| CLI 包装 | Shell 脚本、简单编排和人工调试。 | +| 原始 socket API | 自定义工具、协议客户端和事件订阅者。 | + +这些层共享同一套控制面。 + +## Schema + +安装的 CLI 可以打印该 Herdr 二进制中包含的 socket 协议 schema: + +```bash +herdr api schema +herdr api schema --json +herdr api schema --output herdr-api.schema.json +``` + +普通的 `herdr api schema` 打印简短摘要。`--json` 打印供工具使用的完整 JSON Schema,`--output PATH` 将该文档写入文件。schema 覆盖原始请求、成功响应、错误响应、发出的事件和订阅事件。 + +## 你能控制什么 + +socket API 可以: + +- 创建、列出、聚焦、重命名和关闭工作区 +- 创建、列出、聚焦、重命名和关闭标签页 +- 列出、检查、分割、交换、聚焦、调整、重命名、读取、关闭窗格并向其发送输入 +- 通过 CLI 辅助命令列出、检查、读取、提示、等待、重命名、聚焦、启动和附加智能体 +- 从钩子和插件上报自定义智能体状态 +- 订阅事件并等待输出或状态变化 +- 安装和卸载内置集成 +- 停止服务器并重载配置 + +## CLI 示例 + +创建工作区: + +```bash +herdr workspace create --cwd ~/project --label api +``` + +创建标签页: + +```bash +herdr tab create --label logs +``` + +分割窗格并运行命令: + +```bash +herdr pane split w1:p1 --direction right +herdr pane run w1:p2 "npm test" +``` + +检查并重排窗格: + +```bash +herdr pane layout --current +herdr pane neighbor --direction right --current +herdr pane resize --direction right --amount 0.1 --current +herdr pane swap --direction right --current +herdr pane zoom --on --current +herdr pane split w1:p1 --direction right --ratio 0.333 +``` + +等待智能体: + +```bash +herdr agent wait w1:p1 --until done +``` + +读取窗格输出: + +```bash +herdr pane read w1:p2 --source recent --lines 50 +``` + +## 原始方法 + +原始 socket 方法名使用点号记法: + +| 领域 | 方法 | +| --- | --- | +| 服务器 | `ping`、`server.stop`、`server.reload_config`、`server.agent_manifests`、`server.reload_agent_manifests` | +| 通知 | `notification.show` | +| 客户端 | `client.window_title.set`、`client.window_title.clear` | +| 会话 | `session.snapshot` | +| 工作区 | `workspace.create`、`workspace.list`、`workspace.get`、`workspace.focus`、`workspace.rename`、`workspace.move`、`workspace.move_block`、`workspace.report_metadata`、`workspace.close` | +| Worktree | `worktree.list`、`worktree.create`、`worktree.open`、`worktree.remove` | +| 标签页 | `tab.create`、`tab.list`、`tab.get`、`tab.focus`、`tab.rename`、`tab.move`、`tab.close` | +| 窗格 | `pane.split`、`pane.swap`、`pane.move`、`pane.zoom`、`pane.layout`、`pane.process_info`、`pane.neighbor`、`pane.edges`、`pane.focus_direction`、`pane.resize`、`pane.list`、`pane.current`、`pane.get`、`pane.rename`、`pane.send_text`、`pane.send_keys`、`pane.send_input`、`pane.read`、`pane.graphics.info`、`pane.graphics.set`、`pane.graphics.clear`、`pane.graphics.stream`、`pane.report_agent`、`pane.report_agent_session`、`pane.report_metadata`、`pane.clear_agent_authority`、`pane.release_agent`、`pane.close`、`pane.wait_for_output` | +| 弹窗 | `popup.close` | +| 布局 | `layout.export`、`layout.apply`、`layout.set_split_ratio` | +| 智能体 | `agent.list`、`agent.get`、`agent.read`、`agent.explain`、`agent.send_keys`、`agent.prompt`、`agent.wait`、`agent.rename`、`agent.focus`、`agent.start` | +| 事件 | `events.subscribe`、`events.wait` | +| 集成 | `integration.install`、`integration.uninstall` | +| 插件 | `plugin.link`、`plugin.list`、`plugin.unlink`、`plugin.enable`、`plugin.disable`、`plugin.action.list`、`plugin.action.invoke`、`plugin.log.list`、`plugin.pane.open`、`plugin.pane.focus`、`plugin.pane.close` | + +`agent.wait` 由服务器拥有并由事件驱动。它会固定到已解析的窗格占用者,因此替换后的智能体不能满足该等待。`agent.prompt` 接受可选的 `wait` 对象,其中包含 `until` 和 `timeout_ms`;这样可在一个请求中提交提示并开始等待,避免两个独立调用之间的竞态。 + +`workspace.move_block` 会将有序的 `workspace_ids` 原子地移动到 `before_workspace_id` 之前;省略锚点则将该块移动到末尾。id 必须唯一,且锚点不能属于被移动的块。响应包含服务器确认的有序工作区列表。 + +`session.snapshot` 为维护本地运行时缓存的客户端返回一次性引导快照。响应包含版本/协议元数据、当前聚焦的工作区/标签页/窗格 id、工作区记录、标签页记录、窗格记录、标签页布局快照和智能体记录。它不是订阅;读取后应订阅资源事件,并根据事件更新本地缓存。重新连接后或本地缓存可能已过期时,再次调用 `session.snapshot`。工作区记录也包含关联 worktree 的来源信息。完整的仓库 worktree 发现仍使用 `worktree.list`。 + +CLI 的 `herdr api snapshot` 会把当前 `session.snapshot` 响应输出为 JSON,让客户端和智能体可以简单地完成引导。 + +窗格控制方法使用 `w1:p1` 这类公开窗格 id。schema 中 `pane_id` 可选的方法,在省略它时使用服务器当前聚焦的活动窗格。`pane.move` 总是要求来源 `pane_id`。 + +`pane.send_keys` 和 `pane.send_input.keys` 接受 Herdr 组合键字符串: 普通可打印键、`enter` 和 `esc` 这类特殊键、`ctrl+h`、`control+j`、`alt+x`、`shift+tab` 这类修饰组合键、`f1` 这类功能键,以及 `minus` 和 `plus` 这类命名标点。它们不接受 `prefix+` 绑定字符串。 + +```json +{"id":"req_current","method":"pane.current","params":{"caller_pane_id":"w1:p1"}} +{"id":"req_layout","method":"pane.layout","params":{"pane_id":"w1:p1"}} +{"id":"req_neighbor","method":"pane.neighbor","params":{"pane_id":"w1:p1","direction":"right"}} +{"id":"req_edges","method":"pane.edges","params":{"pane_id":"w1:p1"}} +{"id":"req_focus","method":"pane.focus_direction","params":{"direction":"right"}} +{"id":"req_resize","method":"pane.resize","params":{"pane_id":"w1:p1","direction":"right","amount":0.1}} +{"id":"req_zoom","method":"pane.zoom","params":{"pane_id":"w1:p1","mode":"toggle"}} +{"id":"req_split","method":"pane.split","params":{"direction":"right","ratio":0.333,"env":{"HERDR_ROLE":"tests"}}} +{"id":"req_process","method":"pane.process_info","params":{"pane_id":"w1:p1"}} +``` + +`pane.current` 返回单个 `PaneInfo`。带有 `caller_pane_id` 时,Herdr 返回那个窗格。省略时,Herdr 返回当前聚焦的活动窗格。 + +终端滚动指标可用时,`PaneInfo` 包含 `scroll`: + +```json +{ + "offset_from_bottom": 12, + "max_offset_from_bottom": 240, + "viewport_rows": 30 +} +``` + +客户端可以把 `offset_from_bottom == 0` 视为位于底部。 + +### 实验性窗格图形 + +窗格图形允许插件在窗格上放置图像数据。它们只在 `[experimental].kitty_graphics = true` 时可用;否则所有窗格图形方法都返回 `feature_disabled`。调用 `pane.graphics.info` 会返回所连接客户端的单元格像素宽度和高度,但不会创建图形层。`pane.graphics.set` 接受 `data_base64` 中的 `png`、`rgb` 或 `rgba` 数据,`pane.graphics.clear` 删除该图层。 + +```json +{"id":"graphics_info","method":"pane.graphics.info","params":{"pane_id":"w1:p1"}} +{"id":"graphics_set","method":"pane.graphics.set","params":{"pane_id":"w1:p1","format":"png","image_width":800,"image_height":600,"data_base64":"...","placement":{"viewport_col":0,"viewport_row":0,"grid_cols":80,"grid_rows":30}}} +{"id":"graphics_clear","method":"pane.graphics.clear","params":{"pane_id":"w1:p1"}} +``` + +要重复发送帧,用 `pane.graphics.stream` 打开专用 socket。Herdr 回复 `ok` 后,每一帧发送一个 JSON 头,再发送恰好 `data_length` 字节的原始数据。socket 关闭前,该流拥有对应窗格的图形层;并发 set、clear 或 stream 请求返回 `stream_conflict`。 + +```json +{"id":"graphics_stream","method":"pane.graphics.stream","params":{"pane_id":"w1:p1"}} +{"format":"png","image_width":800,"image_height":600,"data_length":12345,"placement":{"viewport_col":0,"viewport_row":0,"grid_cols":80,"grid_rows":30}} +``` + +`pane.layout` 返回标签页布局快照,包含 `workspace_id`、`tab_id`、`zoomed`、外层 `area`、`focused_pane_id`、窗格矩形和分割矩形/比例。`pane.neighbor` 和 `pane.edges` 也包含同一份布局快照,让客户端不需要私有布局状态就能做出下一步决策。 + +`pane.process_info` 返回窗格的 shell pid、可用时的前台进程组 id,以及平台暴露时带有 pid、名称、argv/cmdline 和 cwd 的前台进程。 + +`layout.export` 返回可移植的标签页布局树。省略 `tab_id` 和 `pane_id` 导出活动标签页,传 `tab_id` 导出该标签页,或传 `pane_id` 导出包含该窗格的标签页。 + +```json +{"id":"req_export","method":"layout.export","params":{"tab_id":"w1:t1"}} +``` + +响应包含 `workspace_id`、`tab_id`、`zoomed`、`focused_pane_id` 和 `root`。`root` 是由 `pane` 和 `split` 节点组成的 BSP 树。窗格节点可以包含 `pane_id`、`label`、`cwd` 和 argv `command`。分割节点使用 `direction` (`right` 或 `down`)、`ratio`、`first` 和 `second`。 + +`layout.apply` 从声明式的树创建一个新标签页。提供 `tab_id` 时,Herdr 先创建替代标签页,再关闭旧标签页。它会恢复结构、标签、cwd、env 和可选的 argv 命令;不会保留活跃的 PTY、回滚内容或运行中的进程。 + +```json +{ + "id": "req_apply", + "method": "layout.apply", + "params": { + "workspace_id": "wabc", + "tab_label": "dev", + "focus": true, + "root": { + "type": "split", + "direction": "right", + "ratio": 0.65, + "first": { + "type": "pane", + "label": "editor", + "cwd": "/repo" + }, + "second": { + "type": "pane", + "label": "tests", + "cwd": "/repo", + "command": ["sh", "-c", "just test"], + "env": { "HERDR_ROLE": "tests" } + } + } + } +} +``` + +`layout.set_split_ratio` 更新标签页布局中已有分割的比例。响应是 `type: "layout_split_ratio_set"`,并包含更新后的可移植 `layout`。 + +```json +{"id":"req_ratio","method":"layout.set_split_ratio","params":{"tab_id":"w1:t1","path":[],"ratio":0.6}} +``` + +启动进程的方法接受一个 `env` 对象。Herdr 只把这些键值对应用到新启动的进程。Herdr 还向受管窗格进程注入 `HERDR_SOCKET_PATH`、`HERDR_ENV=1`、`HERDR_WORKSPACE_ID`、`HERDR_TAB_ID` 和 `HERDR_PANE_ID`。与调用方提供的环境变量冲突时,Herdr 管理的变量保持权威。 + +`pane.swap` 支持按方向和显式两种形式: + +```json +{"id":"req_swap_dir","method":"pane.swap","params":{"pane_id":"w1:p1","direction":"right"}} +{"id":"req_swap_explicit","method":"pane.swap","params":{"source_pane_id":"w1:p1","target_pane_id":"w1:p2"}} +``` + +交换仅限同一标签页。它保留分割形状、分割比例、窗格 id 和运行中的进程。响应是 `type: "pane_swap"`,带 `changed`、可选的 `reason`、`source_pane_id`、可选的 `target_pane_id`、`focused_pane_id` 和 `layout`。reason 的取值有 `no_neighbor`、`same_pane`、`not_found` 和 `cross_tab`。标签页处于缩放状态时,交换保持缩放,并修改隐藏的整页布局。 + +`pane.move` 把运行中的窗格移动到另一个标签页、新标签页或新工作区: + +```json +{"id":"req_move_tab","method":"pane.move","params":{"pane_id":"w1:p2","destination":{"type":"tab","tab_id":"w1:t2","target_pane_id":"w1:p3","split":"right","ratio":0.5},"focus":true}} +{"id":"req_move_new_tab","method":"pane.move","params":{"pane_id":"w1:p2","destination":{"type":"new_tab","workspace_id":"w1","label":"logs"},"focus":true}} +{"id":"req_move_new_workspace","method":"pane.move","params":{"pane_id":"w1:p2","destination":{"type":"new_workspace","label":"logs","tab_label":"main"},"focus":true}} +``` + +移动到已有标签页需要 `split: "right" | "down"`。`target_pane_id` 可选,默认是目标标签页的聚焦窗格。同一标签页内的布局变化仍然用 `pane.swap`;移动到来源标签页返回 `changed: false` 和 `reason: "same_tab"`。涉及缩放状态的来源或目标标签页的移动返回 `changed: false` 和 `reason: "zoomed_tab"`。 + +响应是 `type: "pane_move"`,带 `changed`、可选的 `reason`、`previous_pane_id`、`previous_workspace_id`、`previous_tab_id`、被移动的 `pane`、可选的 `source_layout`、`target_layout`、可选的新建工作区或标签页记录、可选的已关闭工作区或标签页 id,以及 `focused_pane_id`。跨工作区移动保持内部窗格和终端存活,但在目标工作区分配新的公开窗格 id。订阅者可以监听 `pane.moved`;Herdr 不会为被移动的终端进程发出假的窗格关闭/创建事件。 + +`pane.zoom` 切换、启用或禁用目标窗格所在标签页的缩放: + +```json +{"id":"req_zoom_toggle","method":"pane.zoom","params":{"pane_id":"w1:p1"}} +{"id":"req_zoom_on","method":"pane.zoom","params":{"pane_id":"w1:p1","mode":"on"}} +{"id":"req_zoom_off","method":"pane.zoom","params":{"pane_id":"w1:p1","mode":"off"}} +``` + +省略 `pane_id` 时,目标是服务器当前聚焦的活动窗格。响应是 `type: "pane_zoom"`,带 `changed`、`zoom_changed`、`focus_changed`、可选的 `reason`、`pane_id`、`focused_pane_id`、`zoomed` 和 `layout`。缩放状态或焦点任一发生变化时,`changed` 为 true。reason 的取值有 `single_pane`、`already_zoomed` 和 `already_unzoomed`。 + +`notification.show` 的 CLI 包装是: + +```bash +herdr notification show "build failed" --body "api workspace" --position top-left --sound request +``` + +通过配置的 toast 投递方式显示用户通知: + +```json +{"id":"req_notify","method":"notification.show","params":{"title":"build failed","body":"api workspace","position":"top-left","sound":"request"}} +``` + +`title` 必填,并且在移除控制字符和重复空白后必须仍有可见文本。`body` 可选。Herdr 把换行、制表符、回车和重复空白折叠为空格,然后把通知文本截断: `title` 80 个字符,`body` 240 个字符。净化后为空的 `title` 返回 `invalid_params`。`position` 可选,只在 `ui.toast.delivery = "herdr"` 时生效;桌面位置相对于完整的 Herdr 画面,省略时使用 `ui.toast.herdr.position`。terminal、system 和 off 投递忽略 `position`。`sound` 可选,取值 `none`、`done` 或 `request`;默认 `none`,且只在通知实际显示时播放。 + +响应会报告是否有内容被显示: + +```json +{"id":"req_notify","result":{"type":"notification_show","shown":true,"reason":"shown"}} +``` + +可能的 reason 有 `shown`、`disabled`、`rate_limited`、`no_foreground_client` 和 `busy`。`disabled` 表示 `ui.toast.delivery = "off"`。`busy` 表示已有的应用内 toast 未被替换。terminal 和 system 投递是通过当前前台连接的 Herdr 客户端尽力而为的。 + +设置或清除前台客户端的外层终端窗口标题: + +```json +{"id":"req_title","method":"client.window_title.set","params":{"title":"herdr api"}} +{"id":"req_title_clear","method":"client.window_title.clear","params":{}} +``` + +`client.window_title.clear` 恢复 Herdr 的默认标题。响应是 `type: "client_window_title"`,带 `changed` 和 `set`、`cleared` 或 `no_foreground_client` 之一的 reason。 + +Worktree 方法把 Git 检出作为 Herdr 工作区管理。`worktree.create` 创建检出,并返回新的 `workspace`、`tab`、`root_pane` 和 `worktree` 记录。请求的分支已在本地存在时检出它;否则从请求的 base 或 `HEAD` 创建分支。`worktree.open` 打开已有检出,或返回已打开的工作区。`worktree.remove` 对关联的子工作区运行 `git worktree remove`,从不删除分支。 + +从来源工作区创建 worktree: + +```json +{"id":"req_1","method":"worktree.create","params":{"workspace_id":"w1","branch":"worktree/api","focus":false}} +``` + +打开已有检出: + +```json +{"id":"req_2","method":"worktree.open","params":{"workspace_id":"w1","branch":"worktree/api","focus":true}} +``` + +移除关联的检出: + +```json +{"id":"req_3","method":"worktree.remove","params":{"workspace_id":"2","force":false}} +``` + +`worktree.list`、`worktree.create` 和 `worktree.open` 中,`workspace_id` 和 `cwd` 最多用一个;两个都省略则使用活动工作区。`worktree.open` 中,`path` 和 `branch` 恰好用一个。原始 socket 的 `cwd` 和 `path` 值必须是绝对路径;CLI 在发送请求前会展开相对的 `--cwd` 和 `--path` 值。当工作区属于某个 Herdr worktree 组时,工作区响应包含可选的 `worktree` 来源信息。当已有工作区获得或改变 worktree 来源信息时,worktree 命令可能发出 `workspace.updated`。 + +Worktree 命令也发出生命周期事件。`worktree.create` 发出 `workspace.created`、`tab.created`、`pane.created` 和 `worktree.created`。`worktree.open` 发出 `worktree.opened`,并在打开新的 Herdr 工作区时同时发出工作区/标签页/窗格创建事件。`worktree.remove` 发出 `worktree.removed`;如果关联的工作区仍然打开,还会发出 `workspace.closed`。 + +## 智能体视图查询 + +`agent.view.set` 为内置 Agents 视图设置一个临时的声明式投影。智能体信息或当前 +UI 上下文变化时,该投影会重新求值。它控制展开和折叠的侧边栏、移动端 Agents +列表、鼠标目标、编号聚焦以及上一个/下一个智能体的导航顺序。它不会改变 +`agent.list`、通知、检测或全局待处理计数。 + +下面的示例显示当前呈现的 Space 中的智能体,以及其他位置需要处理的智能体, +然后按待处理优先级和最近状态转换排序: + +```json +{ + "id": "view_set", + "method": "agent.view.set", + "params": { + "source": "plugin:example.agent-views", + "label": "focus", + "filter": { + "op": "any", + "filters": [ + { + "op": "eq", + "field": "workspace_id", + "value": {"context": "current_workspace_id"} + }, + { + "op": "in", + "field": "status", + "values": ["blocked", "done"] + } + ] + }, + "sort": [ + {"field": "attention", "order": "desc"}, + {"field": "state_change_seq", "order": "desc"} + ] + } +} +``` + +过滤节点的 `op` 可以是 `all`、`any`、`not`、`eq`、`in` 或 `exists`。内置 +过滤字段是 `status`、`workspace_id`、`tab_id`、`pane_id`、`agent`、`seen` +和 `state_change_seq`。要过滤插件上报的窗格元数据,使用 `{"token":"name"}` +作为字段。值可以是字符串、布尔值、无符号整数或上下文对象。上下文值包括 +`current_workspace_id` 和 `current_tab_id`,并且只能与对应的 ID 字段比较。 +有效 status 值为 `idle`、`working`、`blocked`、`done` 和 `unknown`;`done` +表示 idle 且尚未查看。 + +排序字段包括 `workspace_order`、`tab_order`、`pane_order`、`attention`、 +`status`、`agent`、`seen`、`state_change_seq` 或 `{"token":"name"}`。排序是 +稳定的,按声明顺序求值,并接受 `asc` 或 `desc`。缺失值排在存在值之后。省略 +`sort` 时,现有 `ui.agent_panel_sort` 策略继续生效。自定义排序会临时替换该 +策略,但不会重写配置。 + +`source` 标识所有者。插件使用 `plugin:<HERDR_PLUGIN_ID>`;当所属插件不存在 +或被禁用时,Herdr 会拒绝设置。其他调用方可以使用自己的非 `plugin:` source。 +设置成功会原子替换之前的视图。该视图会持续到被 clear、被替换、所属插件被 +disable、unlink 或 uninstall,或者服务器退出。需要持久行为的插件应把查询保存到 +`HERDR_PLUGIN_STATE_DIR`,并从 `[[startup]]` 钩子重新应用。 + +可以无条件 clear,也可以只在指定 source 仍拥有视图时 clear: + +```json +{"id":"view_clear","method":"agent.view.clear","params":{}} +{"id":"view_clear_owned","method":"agent.view.clear","params":{"source":"plugin:example.agent-views"}} +``` + +source 不匹配时,活动视图保持不变。set 和 clear 响应使用 +`type: "agent_view"`,并报告 `active`、`source` 和可选的 `label`。 + +## 插件 API + +插件 API 是面向可执行工作流工具的早期宿主面。插件是带 `herdr-plugin.toml` 清单的包。清单声明可分享的动作、事件钩子、终端窗格入口点和链接处理器。动作和窗格仅限清单声明;运行时动作注册和运行时 argv 窗格创建不在 v1 范围内。 + +安装和链接的插件跨重启持久化。在 `plugin.link`、`plugin.unlink`、`plugin.enable` 和 `plugin.disable` 时,Herdr 在 `session.json` 旁写入一个 `plugins.json` 注册表文件。Herdr 不在运行时,`herdr plugin install` CLI 也写同一个注册表,然后启动时自动加载。启动时,Herdr 从原始路径重新读取每个清单;文件缺失或无法解析时,条目会带着 `warnings` 字段保留,`plugin.list` 会将其展示出来。 + +事件钩子的 `on` 值在链接时会对照已知的 Herdr 事件名校验。无法识别的名称不算错误 — 链接仍会成功 — 但返回的插件信息会包含警告 (例如 `"unknown event 'worktree.craeted'"`)。检查 `plugin.link` 和 `plugin.list` 响应中的 `warnings` 字段。 + +链接本地插件清单: + +```json +{"id":"req_plugin_link","method":"plugin.link","params":{"path":"/path/to/plugin","enabled":true}} +``` + +`plugin.link` 也接受可选的 `source` 元数据。CLI 从 GitHub 安装时使用它,让 `plugin.list` 能显示来源、请求的 ref、解析的 commit 和托管检出路径: + +```json +{"id":"req_plugin_link","method":"plugin.link","params":{"path":"/managed/plugin/herdr-plugin.toml","enabled":true,"source":{"kind":"github","owner":"ogulcancelik","repo":"herdr-plugin-examples","subdir":"worktree-bootstrap","requested_ref":"main","resolved_commit":"abc123","managed_path":"/data/plugins/github/<managed-checkout>","installed_unix_ms":1780000000000}}} +``` + +路径可以是包含 `herdr-plugin.toml` 的插件目录,或直接指向清单的路径。清单结构如下: + +```toml +id = "example.worktree-bootstrap" +name = "Worktree Bootstrap" +version = "0.1.0" +min_herdr_version = "0.7.0" +description = "Prepare new worktrees" +platforms = ["linux", "macos", "windows"] + +[[build]] +command = ["bun", "install"] + +[[actions]] +id = "bootstrap" +title = "Bootstrap worktree" +contexts = ["workspace"] +command = ["bun", "run", "bootstrap.ts"] + +[[events]] +on = "worktree.created" +command = ["bun", "run", "bootstrap.ts"] + +[[panes]] +id = "board" +title = "Worktree board" +placement = "overlay" +command = ["bun", "run", "board.ts"] + +[[link_handlers]] +id = "github-issue" +title = "Open GitHub issue" +pattern = "^https://github\\.com/[^/]+/[^/]+/(issues|pull)/[0-9]+$" +action = "bootstrap" +``` + +`min_herdr_version` 是必填项。字段缺失、无效,或比运行中的 Herdr 二进制更新时,服务器拒绝链接插件。 + +在顶层用你的插件支持的操作系统标识 (`linux`、`macos`、`windows`) 声明 `platforms`。本地开发允许省略 `platforms` — `plugin.link` 会成功,但响应包含警告。单个构建命令、动作、事件钩子、窗格和链接处理器可以声明自己的 `platforms` 来覆盖插件级列表;省略时从插件继承。调用有效 platforms 不包含当前操作系统的动作或打开这样的窗格,会返回 `platform_unsupported` 错误。 + +列出、启用、禁用或取消链接插件: + +```json +{"id":"req_plugin_list","method":"plugin.list","params":{}} +{"id":"req_plugin_disable","method":"plugin.disable","params":{"plugin_id":"example.worktree-bootstrap"}} +{"id":"req_plugin_enable","method":"plugin.enable","params":{"plugin_id":"example.worktree-bootstrap"}} +{"id":"req_plugin_unlink","method":"plugin.unlink","params":{"plugin_id":"example.worktree-bootstrap"}} +``` + +动作从链接的清单解析。`plugin.action.list` 返回所有已安装插件的全部动作;传 `plugin_id` 过滤。 + +```json +{"id":"req_plugin_actions","method":"plugin.action.list","params":{}} +{"id":"req_plugin_actions_filtered","method":"plugin.action.list","params":{"plugin_id":"example.worktree-bootstrap"}} +``` + +`plugin.action.list` 返回应用插件级继承后每个动作的有效 `platforms`。 + +用限定 id 或裸动作 id 调用动作: + +```json +{"id":"req_plugin_invoke","method":"plugin.action.invoke","params":{"action_id":"example.worktree-bootstrap.bootstrap","context":{"invocation_source":"keybinding"}}} +``` + +`plugin.action.invoke` 解析清单动作,启动清单命令,并返回 Herdr 构建的调用上下文和已启动命令的日志记录。缺失的上下文字段会从活动工作区、标签页、聚焦窗格、worktree 来源信息和请求 id 补全。调用被禁用插件的动作返回 `plugin_disabled` 错误。 + +Herdr 注入 `HERDR_SOCKET_PATH`、`HERDR_BIN_PATH`、`HERDR_ENV=1`、`HERDR_PLUGIN_ID`、`HERDR_PLUGIN_ROOT`、`HERDR_PLUGIN_CONFIG_DIR`、`HERDR_PLUGIN_STATE_DIR`、`HERDR_PLUGIN_CONTEXT_JSON`,以及可用的 `HERDR_WORKSPACE_ID`、`HERDR_TAB_ID` 和 `HERDR_PANE_ID` 值。动作命令还收到 `HERDR_PLUGIN_ACTION_ID`;事件钩子收到 `HERDR_PLUGIN_EVENT` 和 `HERDR_PLUGIN_EVENT_JSON`;窗格命令收到 `HERDR_PLUGIN_ENTRYPOINT_ID`。 + +列出最近的动作和事件命令日志: + +```json +{"id":"req_plugin_logs","method":"plugin.log.list","params":{"plugin_id":"example.worktree-bootstrap","limit":20}} +``` + +当 Herdr 发出匹配的事件名 (比如 `worktree.created`) 时,事件钩子为已启用的已安装插件运行。 + +v1 没有 Herdr 管理的插件存储 API。`HERDR_PLUGIN_CONFIG_DIR` 和 `HERDR_PLUGIN_STATE_DIR` 只提供路径发现;文件、schema、迁移和清理归插件所有。 + +打开托管终端 UI: + +```json +{"id":"req_plugin_pane","method":"plugin.pane.open","params":{"plugin_id":"example.board","entrypoint":"board","placement":"zoomed","target_pane_id":"w1:p1","env":{"HERDR_ROLE":"board"},"focus":true}} +``` + +`plugin.pane.open` 要求已安装、已启用、平台兼容的插件,然后把请求的清单 `[[panes]]` 入口点作为 argv 支撑的终端窗格启动。清单窗格的 `placement` 默认为 `overlay`;请求的 `placement` 可以用 `overlay`、`popup`、`split`、`tab` 或 `zoomed` 覆盖清单。overlay 和 popup 使用活动平铺窗格作为启动上下文。弹窗终端是会话级模态界面,不会改变标签页布局;可选的 `width` 和 `height` 以终端单元格数或 `"80%"` 这样的百分比设置外层尺寸。省略时默认为终端大小的一半,过小的值会限制为弹窗最小尺寸。弹窗没有窗格 id,不属于任何 `pane.*` 或智能体 API,不会发出窗格生命周期事件,会把插件焦点上下文保留在底层平铺窗格上,也不会向其进程导出 `HERDR_PANE_ID`。弹窗启动返回 `ok`;`popup.close` 关闭活动弹窗,没有弹窗时返回 `popup_not_open`。split 和 zoomed 针对已有窗格,tab 可以针对工作区。split、tab、zoomed 和 overlay 打开后表现得像普通 Herdr 窗格,`plugin.pane.focus` 和 `plugin.pane.close` 仍可操作这些窗格。 + +## Socket 传输 + +Herdr 在本地 socket 上使用换行分隔的 JSON。在 Unix 上,那个 socket 是 Unix 域 socket。在 Windows 上,是命名管道。 + +每行发送一个请求: + +```json +{"id":"req_1","method":"ping","params":{}} +``` + +成功响应包含相同的 `id`: + +```json +{"id":"req_1","result":{"type":"pong"}} +``` + +事件订阅在初始响应之后保持连接打开。 + +## Socket 路径 + +默认 socket 位于你的 Herdr 配置目录下。 + +命名会话有各自独立的 socket: + +```text +~/.config/herdr/herdr.sock +~/.config/herdr/sessions/<name>/herdr.sock +``` + +解析顺序: + +1. 显式的 CLI `--session <name>` +2. `HERDR_SOCKET_PATH` +3. `HERDR_SESSION=<name>` +4. 默认会话 socket + +`HERDR_SOCKET_PATH` 只用于底层覆盖。 + +对插件来说,需要可移植的 Windows 行为时,优先调用 `HERDR_BIN_PATH` 和 CLI 包装命令。原始 socket 客户端要自己负责使用平台原生的本地 socket 形式。 + +## 智能体状态上报 + +集成用 `pane.report_agent` 上报智能体状态。 + +```json +{ + "id": "req_1", + "method": "pane.report_agent", + "params": { + "pane_id": "w1:p1", + "source": "custom:docs", + "agent": "docs-bot", + "state": "working", + "message": "building docs" + } +} +``` + +`state` 是语义性的。它影响等待、通知和汇总。只影响展示的值应通过元数据单独上报。 + +仅提供会话的官方集成用 `pane.report_agent_session` 上报原生会话引用。上报状态的集成仍然可以在 `pane.report_agent` 中包含原生会话引用。与状态无关的会话上报不影响等待、通知或汇总。 + +```json +{ + "id": "req_2", + "method": "pane.report_agent_session", + "params": { + "pane_id": "w1:p1", + "source": "herdr:codex", + "agent": "codex", + "agent_session_id": "..." + } +} +``` + +Herdr 存有原生会话引用时,`pane.get`、`pane.list`、`agent.get` 和 `agent.list` 暴露一个只读的 `agent_session` 对象: + +```json +{ + "agent_session": { + "source": "herdr:codex", + "agent": "codex", + "kind": "id", + "value": "..." + } +} +``` + +没有存储原生会话引用时,该字段被省略。 + +当 Herdr 能解析当前控制窗格 PTY 的进程的 cwd 时,`pane.get`、`pane.list`、`agent.get` 和 `agent.list` 也暴露 `foreground_cwd`。已有的 `cwd` 字段仍然是用于标签、follow-cwd 行为和恢复会话状态的窗格/工作区 cwd。 + +`PaneInfo` 和 `AgentInfo` 暴露可选的 `terminal_title` 和 `terminal_title_stripped` 字段。`terminal_title` 是经过安全规范化的最新 OSC 0/2 标题。`terminal_title_stripped` 会移除开头一个已识别的活动或旋转指示符字形及其后的空白。这些值归服务器所有,冷重启后不会保留,并且独立于元数据 `title` 和语义智能体状态。 + +当用户钩子想自定义展示、又不从 Herdr 集成接管生命周期状态时,使用 `pane.report_metadata`。 + +```json +{ + "id": "req_2", + "method": "pane.report_metadata", + "params": { + "pane_id": "w1:p1", + "source": "user:claude-title", + "agent": "claude", + "title": "Refactor auth middleware", + "display_agent": "Claude: auth", + "state_labels": { + "working": "refactoring auth", + "idle": "ready", + "done": "review ready" + }, + "tokens": { + "summary": "refactor auth", + "model": "opus" + }, + "ttl_ms": 3600000 + } +} +``` + +元数据上报只影响展示。有效的元数据可以覆盖窗格标题、显示的智能体名称、可见的状态标签和任意命名令牌。`working`、`blocked`、`idle`、等待、通知和汇总仍来自语义状态。原生会话恢复来自存储的官方会话引用。`agent` 是展示字段针对权威智能体标签的可选守卫;`applies_to_source` 同样保护展示字段,使其只适用于活动生命周期权威来源。这些守卫不作用于令牌补丁;令牌上报方负责清除和刷新 TTL。用 `display_agent` 修改可见名称。`state_labels` 的键必须是 `idle`、`working`、`blocked`、`done` 或 `unknown`。 + +令牌映射是针对每个资源的补丁。字符串设置键,JSON `null` 清除键,未提及的键保持不变。最后接受的更新生效。可选 TTL 分别应用于该次上报更新的每个令牌键。窗格令牌会在 pane 和 agent 的 get/list 响应中公开,并可在智能体侧边栏行中写成 `$name`。单次上报最多可包含 16 个令牌键,每个窗格或工作区最多保留 32 个键。令牌名称必须由 1–32 个 ASCII 字母、数字、下划线或连字符组成。 + +工作区令牌使用相同约定: + +```json +{"id":"req_3","method":"workspace.report_metadata","params":{"workspace_id":"w1","source":"user:jj","tokens":{"jj_status":"2 changes","old":null},"ttl_ms":5000}} +``` + +workspace 的 get/list 响应会公开生成的 `tokens` 映射,空间侧边栏行可显示 `$jj_status` 这类值。变更和 TTL 到期会发出包含最新工作区快照的 `workspace.metadata_updated`。该元数据事件会发送给 API 订阅者,但不会调用插件事件钩子。 + +展示文本在存储前被规范化。Herdr 去掉首尾空白、移除控制字符,并把 `title`、`display_agent`、每个状态标签和令牌值截断到 80 个字符。规范化后为空的令牌值会清除该键。 + +`source` 和 `applies_to_source` 是来源标识符。它们必须不超过 80 个字符,且只能包含 ASCII 字母、数字、冒号、点、下划线和连字符。 + +短期元数据用 `ttl_ms`。取值必须在 `1` 到 `86400000` 毫秒之间。想让元数据保留到被替换、清除或窗格或工作区关闭时,省略 `ttl_ms`。展示字段继续按来源过期;该次调用更新的每个令牌都有独立期限。令牌元数据不会在服务器重启后恢复。 + +钩子可能乱序发送更新时,使用 `seq`。对同一 `source`,序号小于等于最后接受序号的上报会被 API 接受,但被窗格状态忽略。每个窗格或工作区在其生命周期内最多接受来自 32 个不同来源的带序号令牌上报;清除或过期不会释放这些来源名额。 + +## 事件订阅 + +需要长期数据流时订阅事件: + +```json +{ + "id": "sub_1", + "method": "events.subscribe", + "params": { + "subscriptions": [ + { "type": "pane.agent_status_changed", "pane_id": "w1:p1", "agent_status": "blocked" } + ] + } +} +``` + +第一个响应确认订阅。之后的行是推送的事件。 + +工作区事件订阅包括 `workspace.created`、`workspace.updated`、`workspace.metadata_updated`、`workspace.renamed`、`workspace.moved`、`workspace.reordered`、`workspace.closed` 和 `workspace.focused`。`workspace.metadata_updated` 报告令牌变更和 TTL 到期,但不会调用插件事件钩子。其他工作区事件描述 Herdr UI/运行时的生命周期。当工作区属于 worktree 组时,`workspace.created` 包含可选的 `workspace.worktree` 来源信息。`workspace.moved` 包含被移动的 `workspace_id`、请求的 `insert_index` 和更新后的有序 `workspaces` 列表。`workspace.reordered` 包含原子移动的 `workspace_ids`、可选的 `before_workspace_id` 和服务器确认的有序 `workspaces` 列表。在移除前 Herdr 仍能识别时,`workspace.closed` 包含最终的 `workspace` 快照。 +标签页事件订阅包括 `tab.created`、`tab.closed`、`tab.focused`、`tab.renamed` 和 `tab.moved`。`tab.moved` 包含被移动的 `tab_id`、`workspace_id`、请求的 `insert_index` 和该工作区更新后的有序 `tabs` 列表。 +窗格事件订阅包括 `pane.created`、`pane.updated`、`pane.closed`、`pane.focused`、`pane.moved`、`pane.exited`、`pane.agent_detected`、`pane.output_matched`、`pane.agent_status_changed` 和 `pane.scroll_changed`。终端标题变化可能发出 `pane.updated`,但如果原始标题只有旋转指示符发生变化且 `terminal_title_stripped` 不变,则不会发出。`pane.scroll_changed` 只针对一个 `pane_id`,每当 Herdr 观察到滚动快照变化时,都会发出 `pane_id`、`workspace_id` 和当前 `scroll` 指标。 +布局事件订阅包括 `layout.updated`。该事件携带一个标签页更新后的 `PaneLayoutSnapshot`。使用 `session.snapshot` 引导的客户端应替换具有相同 `workspace_id` 和 `tab_id` 的缓存布局。 + +Worktree 事件订阅包括 `worktree.created`、`worktree.opened` 和 `worktree.removed`。Worktree 事件描述 Git 检出的生命周期。`worktree.created` 包含打开的 `workspace` 和创建的 `worktree`。`worktree.opened` 包含目标 `workspace`、打开的 `worktree` 和 `already_open`。`worktree.removed` 包含 `workspace_id`、被移除的 `worktree` 和 `forced`。 + +生命周期事件用 `events.subscribe`。支持一次性等待时,专门的等待辅助命令会单独在文档中说明。 + +## 读取窗格 + +除非你在编写协议客户端,否则通过 CLI 使用 `pane.read`。 + +```bash +herdr pane read w1:p1 --source visible --lines 80 +herdr pane read w1:p1 --source recent --lines 120 +herdr pane read w1:p1 --source recent-unwrapped --lines 120 +herdr pane read w1:p1 --source detection +``` + +`recent-unwrapped` 对日志很有用,因为它忽略软折行。 +`detection` 返回智能体屏幕检测使用的底部缓冲区快照。 + +## 等待状态 + +用等待来协调智能体和脚本。 + +```bash +herdr agent wait w1:p1 --until done +herdr agent wait w1:p1 --until blocked +``` + +智能体等待观察的是语义状态,不是任意命令的完成。 + +## 响应结构 + +成功响应长这样: + +```json +{ + "id": "req_1", + "result": { + "type": "pane_info", + "pane": { + "pane_id": "w1:p1", + "terminal_id": "term_abc123", + "workspace_id": "w1", + "tab_id": "w1:t1", + "focused": true, + "agent_status": "working", + "revision": 42 + } + } +} +``` + +`server.agent_manifests` 返回生效的智能体检测清单来源和远程更新诊断信息,不重载规则: + +```json +{ + "id": "req_1", + "result": { + "type": "agent_manifest_status", + "last_check_unix": 1781043522, + "last_result": "checked", + "manifests": [ + { + "agent": "cursor", + "source": "/home/me/.config/herdr/agent-detection/cursor.toml", + "source_kind": "local override", + "active_version": "2026.06.10.1", + "cached_remote_version": "2026.06.10.1", + "local_override_shadowing_remote": true, + "remote_update_result": "current" + } + ] + } +} +``` + +`last_check_unix`、`last_result`、`active_version`、`cached_remote_version`、`remote_update_result`、`remote_update_error`、`remote_last_checked_unix` 和 `warning` 这类字段在不可用时被省略。`server.reload_agent_manifests` 在重载内存中的规则缓存后,返回带相同 `manifests` 条目结构的 `agent_manifest_reload`。 + +`agent.explain` 使用服务器生效的清单缓存,在运行中的服务器上评估目标窗格的检测快照: + +```json +{ + "id": "req_2", + "method": "agent.explain", + "params": { "target": "w1:p1" } +} +``` + +响应包含与 `herdr agent explain --json` 打印的相同的 explain 对象,包括最终状态、清单来源和版本、匹配的规则、已评估规则的证据、跳过状态原因、idle 回退原因,以及当完整生命周期钩子权威使屏幕规则不再权威时的 `screen_detection_skip_reason`。 + +客户端需要一个支持 `agent.explain` 的运行中服务器;升级 Herdr 后,请先重启或实时交接服务器,再依赖此方法。 + +错误长这样: + +```json +{ + "id": "req_1", + "error": { + "code": "not_found", + "message": "pane not found" + } +} +``` + +## 协议稳定性 + +Herdr 有一个用于客户端/服务器兼容性的协议版本。协议变更会在考虑发布兼容性的前提下进行评审。 + +在依赖新行为之前,用 `ping` 或 `herdr status` 检查服务器协议。对未知字段做宽容处理。 diff --git a/docs/preview/website/src/content/docs/zh-cn/troubleshooting.mdx b/docs/preview/website/src/content/docs/zh-cn/troubleshooting.mdx new file mode 100644 index 00000000..60a41153 --- /dev/null +++ b/docs/preview/website/src/content/docs/zh-cn/troubleshooting.mdx @@ -0,0 +1,95 @@ +--- +title: 故障排除 +description: 诊断常见的安装、终端输入、会话、快捷键和远程连接问题。 +--- + +先检查版本和会话状态: + +```bash +herdr -V +herdr status +``` + +同时记录操作系统、外层终端名称和版本、会话是本地还是远程,以及是否使用了 tmux。 + +## Windows 上的 CJK 输入法窗口位置错误或光标闪烁 + +Herdr 在原生 Windows 和 WSL 上默认使用单元格绘制光标,因为 ConPTY 多路复用器重绘屏幕时,原生光标可能闪烁、跳动或显示旧位置。单元格绘制光标无法提供韩语、日语和中文输入法预编辑文本及候选窗口所需的原生光标定位。 + +如果输入法预编辑文本或候选窗口出现在错误位置,请启用原生光标: + +```toml +[ui] +host_cursor = "native" +``` + +原生模式会恢复输入法定位,但在持续输出期间可能再次出现偶发的光标移动异常。要恢复视觉稳定的光标,请设置 `host_cursor = "drawn"`,或删除该设置以使用 Windows 默认值。当前限制详见 [Windows 测试版](/zh-cn/docs/windows-beta/)。 + +## Enter、Tab 或 Backspace 触发两次 + +旧版终端在应用启用 Kitty 键盘事件报告后,可能把 Enter、Tab 和 Backspace 的释放事件发送为与按下事件相同的字节。终端发送后,Herdr 无法区分这些重复字节。 + +请把外层终端更新到包含上游修复的版本: + +| 终端 | 最低修复版本 | +| --- | --- | +| kitty | 0.33.0 | +| foot | 1.20.0 | +| Alacritty | 0.15.0 | + +长期支持版 Linux 发行版中的旧终端软件包尤其容易出现此问题。已确认的边界捕获和上游链接见 [Herdr issue #1116](https://github.com/ogulcancelik/herdr/issues/1116)。如果当前版本仍有问题,请报告准确的终端版本,以及该问题是否也会在 Herdr 外出现。 + +## 按 Option+Left 或 Option+Right 时输入 `;3D` 或 `;3C` + +终端通常把 Alt+Left 和 Alt+Right 发送为标准的带修饰键方向键序列 `ESC[1;3D` 和 `ESC[1;3C`。在 macOS 上,需要先把外层终端配置为将 Option 视为 Alt。如果 shell 没有绑定这些序列,zsh 可能会把剩余字符显示为 `;3D` 或 `;3C`。kitty、Alacritty 和其他终端都可能出现这种情况;Herdr 和 tmux 都会保留原始的带修饰键方向键输入。 + +如果希望带修饰键的方向键在所有终端和嵌套 shell 中都执行按单词移动,请添加明确的 zsh 绑定: + +```zsh +bindkey $'\e[1;3D' backward-word +bindkey $'\e[1;3C' forward-word +``` + +kitty 在 Herdr 外可能表现不同,因为它的自动 zsh 集成只会为 kitty 直接启动的 shell 添加这些绑定,而不会为终端多路复用器创建的 shell 添加。请按照 kitty 的[手动 shell 集成说明](https://sw.kovidgoyal.net/kitty/shell-integration/#manual-shell-integration),或在按键到达 shell 前通过 `kitty.conf` 映射它们: + +```text +map alt+left send_text all \x1bb +map alt+right send_text all \x1bf +``` + +终端应用可能会直接使用 Alt+Left 和 Alt+Right,因此 Herdr 不会改写带修饰键的方向键。调查详情见 [Herdr issue #1370](https://github.com/ogulcancelik/herdr/issues/1370)。 + +## Herdr 已更新,但运行中的会话仍是旧版本 + +更新二进制文件不一定会替换已经运行且兼容的服务器。先检查 `herdr status`。要启动更新后的服务器,请停止会话并重新启动 Herdr: + +```bash +herdr server stop +herdr +``` + +停止服务器会结束窗格进程。命名会话使用 `herdr session stop <name>`。更新器、软件包管理器和实时移交行为见[安装 Herdr](/zh-cn/docs/install/#update)。 + +## 找不到 `herdr` 命令 + +重启终端以重新加载环境,然后确认 Herdr 安装目录位于 `PATH` 中。通过软件包管理器安装的 Herdr 必须通过该管理器更新并加入环境。见[安装 Herdr](/zh-cn/docs/install/#verify)。 + +## 直接快捷键没有反应 + +操作系统或外层终端可能在 Herdr 收到按键前就拦截了组合键。请在对应层释放该组合键,或改用其他绑定。已知冲突和安全默认值见[键盘](/zh-cn/docs/keyboard/#going-prefix-free)。 + +## 远程连接无法认证 + +先用 `ssh <host>` 确认普通 OpenSSH 连接正常。若在非交互 shell、CI 或移动终端中使用带密码的密钥,请在远程连接前把密钥载入 `ssh-agent`。见[持久化和远程访问](/zh-cn/docs/persistence-remote/#remote-attach-over-ssh)。 + +## 查找诊断日志 + +Herdr 日志默认位于 `~/.config/herdr/`: + +```text +herdr.log +herdr-client.log +herdr-server.log +``` + +设置 `HERDR_LOG=herdr=debug` 可获得更多细节。报告问题时请附上当前日志和轮转后的日志。见[配置](/zh-cn/docs/configuration/#logs)。 diff --git a/docs/preview/website/src/content/docs/zh-cn/windows-beta.mdx b/docs/preview/website/src/content/docs/zh-cn/windows-beta.mdx new file mode 100644 index 00000000..781a6f96 --- /dev/null +++ b/docs/preview/website/src/content/docs/zh-cn/windows-beta.mdx @@ -0,0 +1,124 @@ +--- +title: Windows 测试版 +description: Windows 原生支持的现状、受支持的工作流和已知限制。 +--- + +Windows 原生支持是实验性的测试版。 + +Windows 上的 Herdr 使用 ConPTY 和 Windows 的进程/运行时行为,而不是 Herdr 最初围绕构建的 Unix PTY 模型。有些 Herdr 功能可以干净地映射到 Windows,有些则不行。这个预览并不承诺每个 Linux/macOS 功能都会在 Windows 上得到完整支持。 + +测试版的目标是从真实使用中学习: 安装成功率、窗格可靠性、智能体工作流、bug 数量、缺失的功能,以及 Windows 用户是否从 Herdr 获得了足够的价值。基于这些反馈,Windows 支持可能升级为稳定版,可能在成熟前保持仅预览,也可能在维护成本不合理时被缩减。 + +用 PowerShell 安装 Windows 原生测试版构建: + +```powershell +powershell -ExecutionPolicy Bypass -c "irm https://herdr.dev/install.ps1 | iex" +``` + +Windows 测试版构建只通过预览通道发布。在 Windows 上,Herdr 默认使用预览通道,且不会修改你的配置。安装器将发布版本保存在 `%USERPROFILE%\.herdr\packages\standalone\releases` 下,让 `%LOCALAPPDATA%\Programs\Herdr\bin` 指向当前版本,并保留少量旧版本,以免运行中的进程阻塞更新。 + +对于内部测试,`HERDR_MANIFEST_URL` 可以让安装器指向自定义清单,而不是 Herdr 的稳定或预览清单。 + +## 测试版支持 + +| 能力 | 状态 | +| --- | --- | +| 本地持久会话 | 测试版 | +| 通过 ConPTY 的原生窗格 | 测试版 | +| Windows Terminal / PowerShell 应用连接 | 测试版 | +| `cmd.exe` 窗格 | 测试版 | +| 启动 cwd 和工作区标签 | 测试版 | +| 窗格启动 cwd | 测试版 | +| 智能体命令发现 | 测试版 | +| 智能体自报告集成 | 测试版 | +| 智能体进程树检测 | 测试版 | +| 从已知 cwd 检测 Git/worktree | 测试版 | +| 插件 | 预览 | +| 窗格屏幕历史 | 测试版 | +| 嵌套启动覆盖 | 测试版 | + +Windows 智能体进程检测会扫描窗格 shell 的后代进程,识别直接运行的智能体和常见的命令包装器。它对 Codex、Claude 等智能体很有用,但和 Unix 前台进程组检测不是一回事。 + +插件以预览状态支持在清单中声明 `windows` 平台。GitHub 安装、本地链接、构建命令、动作、事件和插件窗格在 Windows 上尽力而为。命令是 argv 命令,必须兼容 Windows;`npm`、`bun`、`node` 之类的 Node 包 shim 只要在 `PATH` 上就应该能用,而使用 `sh` 或 Bash 的 Unix 专用示例需要 Windows 专用的替代方案。平台过滤器会跳过不支持的构建命令,并对不支持的动作或窗格返回 `platform_unsupported`。 + +## 部分支持 + +| 能力 | 状态 | +| --- | --- | +| shell `cd` 之后的实时 cwd | 部分支持 | +| 通过 shell 集成/OSC7 的实时 cwd | 测试版 | +| 向智能体粘贴剪贴板图像 | 未验证 | +| CJK 输入法候选窗口定位 | 部分支持 | +| Kitty graphics 渲染 | 未验证 | +| 主机光标渲染 | 部分支持 | + +Herdr 可以在正确的目录中启动窗格,并能从你启动 Herdr 的目录创建初始工作区。启动之后 PowerShell 的目录变化则不同: Herdr 能检查的进程字段无法可靠跟踪后续的逻辑 `cd` 变化。实时 cwd 上报请使用 Herdr 集成或提示符 shell 集成。 + +Windows Terminal 可能为特定智能体支持图像粘贴路径,但 Herdr 自己的剪贴板图像读取器尚未在 Windows 上接通。在 Windows 剪贴板桥实现并测试之前,请把 `alt+v` 图像粘贴视为未验证。远程剪贴板图像桥是独立功能,仍然绑定于 Unix/macOS 的 `herdr --remote`。 + +Kitty graphics 仍是实验性功能,尚未宣称支持 Windows。除非你专门在 Windows Terminal 中测试图像渲染,否则保持 `experimental.kitty_graphics = false`。 + +## 已知注意事项 + +### 光标渲染 + +Herdr 的原生 Windows 窗格依赖 ConPTY。当前 Windows 终端的光标处理可能会在多路复用器重绘屏幕时显示中间光标位置。因此,在持续输出期间,原生光标可能闪烁、跳动,或短暂留在旧位置。这个现象也能在其他 Windows 原生终端多路复用器以及直接连续更新 VT 光标位置的压力测试中复现,所以 Herdr 无法在保留原生光标行为的同时彻底消除它。 + +为了优先保证视觉稳定性,默认的 `host_cursor = "auto"` 会在原生 Windows 和 WSL 上把 Herdr 光标绘制为终端单元格内容。其他 Linux 和 macOS 客户端仍使用原生终端光标。Windows 上的绘制光标稳定且不闪烁,但不会使用外层终端的原生闪烁、形状或光标颜色。 + +绘制光标并不是 Windows 用来定位输入法预编辑文本和候选窗口的真实光标。因此,韩语、日语或中文输入法 UI 可能显示在错误位置。如果遇到这个问题,请改用外层终端光标: + +```toml +[ui] +host_cursor = "native" +``` + +原生模式会恢复输入法定位,但在持续输出期间可能再次出现偶发的光标闪烁、跳动或旧位置残影。这是当前 Windows 测试版中的兼容性取舍。 + +### 键盘和鼠标 + +Windows 终端并不都以相同形式报告带修饰键的按键。Herdr 在 Windows Terminal 和 Windows 上的 Alacritty 中保留鼠标报告和 `ctrl+j`,但 `shift+enter` 只有在外层终端把它报告为独立的修饰 Enter 键时才有效。如果 Windows 或终端把它报告为普通 Enter,Herdr 会转发普通 Enter。 + +旧版 Windows 10 的系统 ConPTY 会丢弃 Kimi 和 Pi 等智能体使用的 Kitty 键盘协议序列,因此预览包包含 Microsoft 当前的应用本地 ConPTY 运行时。仅在诊断捆绑运行时的兼容性问题时,才应在启动 Herdr 前设置 `HERDR_WINDOWS_CONPTY=system`。 + +## 复制与粘贴 + +Herdr 的窗格文本复制在 Windows 测试版上可用。在窗格内拖选文本即可通过 Herdr 复制。 + +文本粘贴请在 Windows Terminal 中使用 `ctrl+shift+v`。多行文本粘贴是带括号的 (bracketed),因此 shell 和智能体提示符会把它作为一次粘贴接收,而不是逐行提交。按住 `shift` 再右键,可以使用外层终端的粘贴操作,而不把点击发给 Herdr。 + +## Windows 测试版不支持 + +| 能力 | 状态 | +| --- | --- | +| 直接终端附加 | 不支持 | +| Windows 二进制的 `herdr --remote` | 不支持 | +| 实时服务器交接 | 不支持 | +| Unix 文件描述符交接 | 不支持 | +| Unix 前台进程组 | 不支持 | +| 远程剪贴板图像桥 | 不支持 | +| 前缀输入法切换 | 不支持 | +| 签名二进制 / 规避 SmartScreen | 不支持 | + +在 Windows 上进行远程工作,请 SSH 到服务器并在那里运行 `herdr`: + +```powershell +ssh you@server +herdr +``` + +这种模式下 Herdr 运行在远程主机上。Windows 原生的 `herdr --remote` 不在测试版范围内。 + +Windows 更新通过 Windows 安装器进行,并更新带版本号的安装联接点。更新后请重启运行中的 Herdr 会话。实时交接仅限 Unix。 + +## 报告 Windows 测试版问题 + +请包含: + +- Herdr 版本。 +- Windows 版本。 +- 终端应用。 +- Shell,比如 PowerShell 或 cmd。 +- 是否使用了命名的 `HERDR_SESSION`。 +- 相关的 Herdr 日志。 +- 精确的复现步骤。 diff --git a/docs/preview/website/src/data/config-reference.json b/docs/preview/website/src/data/config-reference.json new file mode 100644 index 00000000..4ad61f01 --- /dev/null +++ b/docs/preview/website/src/data/config-reference.json @@ -0,0 +1,1121 @@ +{ + "sections": [ + { + "id": "general", + "title": "General", + "keys": [ + { + "key": "onboarding", + "type": "boolean", + "default": "unset", + "description": "Show first-run setup on startup. Missing or true shows onboarding; continuing from onboarding writes onboarding = false." + } + ] + }, + { + "id": "theme", + "title": "Theme", + "keys": [ + { + "key": "theme.name", + "type": "string", + "default": "\"catppuccin\"", + "description": "Built-in theme name." + }, + { + "key": "theme.auto_switch", + "type": "boolean", + "default": "false", + "description": "Follow host terminal light/dark appearance and switch between theme names." + }, + { + "key": "theme.dark_name", + "type": "string", + "default": "unset", + "description": "Theme name used when `auto_switch` selects a dark appearance." + }, + { + "key": "theme.light_name", + "type": "string", + "default": "unset", + "description": "Theme name used when `auto_switch` selects a light appearance." + }, + { + "key": "theme.custom.accent", + "type": "color", + "default": "unset", + "description": "Override the accent color token on top of the base theme. Accepts hex, named colors, rgb(r,g,b), or reset aliases." + }, + { + "key": "theme.custom.panel_bg", + "type": "color", + "default": "unset", + "description": "Override the panel_bg color token on top of the base theme. Accepts hex, named colors, rgb(r,g,b), or reset aliases." + }, + { + "key": "theme.custom.surface0", + "type": "color", + "default": "unset", + "description": "Override the surface0 color token on top of the base theme. Accepts hex, named colors, rgb(r,g,b), or reset aliases." + }, + { + "key": "theme.custom.surface1", + "type": "color", + "default": "unset", + "description": "Override the surface1 color token on top of the base theme. Accepts hex, named colors, rgb(r,g,b), or reset aliases." + }, + { + "key": "theme.custom.surface_dim", + "type": "color", + "default": "unset", + "description": "Override the surface_dim color token on top of the base theme. Accepts hex, named colors, rgb(r,g,b), or reset aliases." + }, + { + "key": "theme.custom.overlay0", + "type": "color", + "default": "unset", + "description": "Override the overlay0 color token on top of the base theme. Accepts hex, named colors, rgb(r,g,b), or reset aliases." + }, + { + "key": "theme.custom.overlay1", + "type": "color", + "default": "unset", + "description": "Override the overlay1 color token on top of the base theme. Accepts hex, named colors, rgb(r,g,b), or reset aliases." + }, + { + "key": "theme.custom.text", + "type": "color", + "default": "unset", + "description": "Override the text color token on top of the base theme. Accepts hex, named colors, rgb(r,g,b), or reset aliases." + }, + { + "key": "theme.custom.subtext0", + "type": "color", + "default": "unset", + "description": "Override the subtext0 color token on top of the base theme. Accepts hex, named colors, rgb(r,g,b), or reset aliases." + }, + { + "key": "theme.custom.mauve", + "type": "color", + "default": "unset", + "description": "Override the mauve color token on top of the base theme. Accepts hex, named colors, rgb(r,g,b), or reset aliases." + }, + { + "key": "theme.custom.green", + "type": "color", + "default": "unset", + "description": "Override the green color token on top of the base theme. Accepts hex, named colors, rgb(r,g,b), or reset aliases." + }, + { + "key": "theme.custom.yellow", + "type": "color", + "default": "unset", + "description": "Override the yellow color token on top of the base theme. Accepts hex, named colors, rgb(r,g,b), or reset aliases." + }, + { + "key": "theme.custom.red", + "type": "color", + "default": "unset", + "description": "Override the red color token on top of the base theme. Accepts hex, named colors, rgb(r,g,b), or reset aliases." + }, + { + "key": "theme.custom.blue", + "type": "color", + "default": "unset", + "description": "Override the blue color token on top of the base theme. Accepts hex, named colors, rgb(r,g,b), or reset aliases." + }, + { + "key": "theme.custom.teal", + "type": "color", + "default": "unset", + "description": "Override the teal color token on top of the base theme. Accepts hex, named colors, rgb(r,g,b), or reset aliases." + }, + { + "key": "theme.custom.peach", + "type": "color", + "default": "unset", + "description": "Override the peach color token on top of the base theme. Accepts hex, named colors, rgb(r,g,b), or reset aliases." + } + ] + }, + { + "id": "terminal", + "title": "Terminal", + "keys": [ + { + "key": "terminal.default_shell", + "type": "string", + "default": "\"\"", + "description": "Executable used for new interactive panes. Empty means SHELL, then /bin/sh." + }, + { + "key": "terminal.shell_mode", + "type": "enum", + "default": "\"auto\"", + "description": "Startup mode for new interactive pane shells.", + "values": [ + "auto", + "login", + "non_login" + ] + }, + { + "key": "terminal.new_cwd", + "type": "enum", + "default": "\"follow\"", + "description": "CWD policy for new interactive panes, tabs, and workspaces.", + "values": [ + "follow", + "home", + "current", + "path" + ] + } + ] + }, + { + "id": "update", + "title": "Updates", + "keys": [ + { + "key": "update.channel", + "type": "enum", + "default": "\"stable\" (\"preview\" on Windows)", + "description": "Update channel used by background version checks and herdr update. Homebrew, mise, and Nix installs ignore the preview channel.", + "values": [ + "stable", + "preview" + ] + }, + { + "key": "update.version_check", + "type": "boolean", + "default": "true", + "description": "Check herdr.dev for new Herdr versions in the background." + }, + { + "key": "update.manifest_check", + "type": "boolean", + "default": "true", + "description": "Check herdr.dev for remote agent-detection manifest updates in the background. Bundled manifests and local overrides still apply." + } + ] + }, + { + "id": "keys", + "title": "Keybindings", + "keys": [ + { + "key": "keys.prefix", + "type": "string", + "default": "\"ctrl+b\"", + "description": "Prefix key to enter prefix mode (e.g. \"ctrl+b\", \"f12\", \"esc\")." + }, + { + "key": "keys.help", + "type": "keybinding", + "default": "\"prefix+?\"", + "description": "Open keybinding help." + }, + { + "key": "keys.settings", + "type": "keybinding", + "default": "\"prefix+s\"", + "description": "Open settings." + }, + { + "key": "keys.new_workspace", + "type": "keybinding", + "default": "\"prefix+shift+n\"", + "description": "Create a new workspace." + }, + { + "key": "keys.new_worktree", + "type": "keybinding", + "default": "\"prefix+shift+g\"", + "description": "Create a Git worktree from the selected workspace." + }, + { + "key": "keys.open_worktree", + "type": "keybinding", + "default": "unset", + "description": "Open an existing Git worktree from the selected workspace. Unset by default." + }, + { + "key": "keys.remove_worktree", + "type": "keybinding", + "default": "unset", + "description": "Delete the selected managed worktree checkout after confirmation. Unset by default." + }, + { + "key": "keys.rename_workspace", + "type": "keybinding", + "default": "\"prefix+shift+w\"", + "description": "Rename the selected workspace." + }, + { + "key": "keys.close_workspace", + "type": "keybinding", + "default": "\"prefix+shift+d\"", + "description": "Close the selected workspace." + }, + { + "key": "keys.workspace_picker", + "type": "keybinding", + "default": "\"prefix+w\"", + "description": "Open the workspace navigation surface." + }, + { + "key": "keys.goto", + "type": "keybinding", + "default": "\"prefix+g\"", + "description": "Open the session navigator." + }, + { + "key": "keys.navigate_workspace_up", + "type": "keybinding", + "default": "\"up\"", + "description": "Move workspace selection up in navigate mode." + }, + { + "key": "keys.navigate_workspace_down", + "type": "keybinding", + "default": "\"down\"", + "description": "Move workspace selection down in navigate mode." + }, + { + "key": "keys.navigate_pane_left", + "type": "keybinding", + "default": "\"h\"", + "description": "Focus the pane to the left in navigate mode. Left arrow is always an alias." + }, + { + "key": "keys.navigate_pane_down", + "type": "keybinding", + "default": "\"j\"", + "description": "Focus the pane below in navigate mode." + }, + { + "key": "keys.navigate_pane_up", + "type": "keybinding", + "default": "\"k\"", + "description": "Focus the pane above in navigate mode." + }, + { + "key": "keys.navigate_pane_right", + "type": "keybinding", + "default": "\"l\"", + "description": "Focus the pane to the right in navigate mode. Right arrow is always an alias." + }, + { + "key": "keys.detach", + "type": "keybinding", + "default": "\"prefix+q\"", + "description": "Detach from server/client mode, or exit --no-session mode." + }, + { + "key": "keys.reload_config", + "type": "keybinding", + "default": "\"prefix+shift+r\"", + "description": "Reload config.toml in the running app/server." + }, + { + "key": "keys.open_notification_target", + "type": "keybinding", + "default": "\"prefix+o\"", + "description": "Focus the currently visible notification target." + }, + { + "key": "keys.previous_workspace", + "type": "keybinding", + "default": "unset", + "description": "Select the previous workspace. Unset by default." + }, + { + "key": "keys.next_workspace", + "type": "keybinding", + "default": "unset", + "description": "Select the next workspace. Unset by default." + }, + { + "key": "keys.previous_agent", + "type": "keybinding", + "default": "unset", + "description": "Focus the previous agent shown in the agent panel. Unset by default." + }, + { + "key": "keys.next_agent", + "type": "keybinding", + "default": "unset", + "description": "Focus the next agent shown in the agent panel. Unset by default." + }, + { + "key": "keys.focus_agent", + "type": "keybinding", + "default": "unset", + "description": "Focus an agent by index 1-9. Unset by default." + }, + { + "key": "keys.remote_image_paste", + "type": "string", + "default": "\"ctrl+v\"", + "description": "Local-client shortcut that sends a clipboard image to a remote Herdr session." + }, + { + "key": "keys.new_tab", + "type": "keybinding", + "default": "\"prefix+c\"", + "description": "Create a new tab in the active workspace." + }, + { + "key": "keys.rename_tab", + "type": "keybinding", + "default": "\"prefix+shift+t\"", + "description": "Rename the active tab." + }, + { + "key": "keys.previous_tab", + "type": "keybinding", + "default": "\"prefix+p\"", + "description": "Select the previous tab." + }, + { + "key": "keys.next_tab", + "type": "keybinding", + "default": "\"prefix+n\"", + "description": "Select the next tab." + }, + { + "key": "keys.switch_tab", + "type": "keybinding", + "default": "\"prefix+1..9\"", + "description": "Switch to tab 1-9." + }, + { + "key": "keys.switch_workspace", + "type": "keybinding", + "default": "unset", + "description": "Switch to workspace 1-9 from prefix mode. Unset by default." + }, + { + "key": "keys.close_tab", + "type": "keybinding", + "default": "\"prefix+shift+x\"", + "description": "Close the active tab." + }, + { + "key": "keys.rename_pane", + "type": "keybinding", + "default": "\"prefix+shift+p\"", + "description": "Rename the focused pane." + }, + { + "key": "keys.edit_scrollback", + "type": "keybinding", + "default": "\"prefix+e\"", + "description": "Open the focused pane scrollback in $EDITOR." + }, + { + "key": "keys.copy_mode", + "type": "keybinding", + "default": "\"prefix+[\"", + "description": "Enter keyboard copy mode for the focused pane." + }, + { + "key": "keys.focus_pane_left", + "type": "keybinding", + "default": "\"prefix+h\"", + "description": "Focus the pane to the left." + }, + { + "key": "keys.focus_pane_down", + "type": "keybinding", + "default": "\"prefix+j\"", + "description": "Focus the pane below." + }, + { + "key": "keys.focus_pane_up", + "type": "keybinding", + "default": "\"prefix+k\"", + "description": "Focus the pane above." + }, + { + "key": "keys.focus_pane_right", + "type": "keybinding", + "default": "\"prefix+l\"", + "description": "Focus the pane to the right." + }, + { + "key": "keys.swap_pane_left", + "type": "keybinding", + "default": "\"prefix+shift+h\"", + "description": "Swap the focused pane with the pane to the left." + }, + { + "key": "keys.swap_pane_down", + "type": "keybinding", + "default": "\"prefix+shift+j\"", + "description": "Swap the focused pane with the pane below." + }, + { + "key": "keys.swap_pane_up", + "type": "keybinding", + "default": "\"prefix+shift+k\"", + "description": "Swap the focused pane with the pane above." + }, + { + "key": "keys.swap_pane_right", + "type": "keybinding", + "default": "\"prefix+shift+l\"", + "description": "Swap the focused pane with the pane to the right." + }, + { + "key": "keys.cycle_pane_next", + "type": "keybinding", + "default": "\"prefix+tab\"", + "description": "Cycle to the next pane." + }, + { + "key": "keys.cycle_pane_previous", + "type": "keybinding", + "default": "\"prefix+shift+tab\"", + "description": "Cycle to the previous pane." + }, + { + "key": "keys.last_pane", + "type": "keybinding", + "default": "unset", + "description": "Focus the last focused pane across workspaces and tabs. Unset by default." + }, + { + "key": "keys.split_vertical", + "type": "keybinding", + "default": "\"prefix+v\"", + "description": "Split pane vertically (side by side)." + }, + { + "key": "keys.split_horizontal", + "type": "keybinding", + "default": "\"prefix+minus\"", + "description": "Split pane horizontally (stacked)." + }, + { + "key": "keys.close_pane", + "type": "keybinding", + "default": "\"prefix+x\"", + "description": "Close the focused pane." + }, + { + "key": "keys.zoom", + "type": "keybinding", + "default": "\"prefix+z\"", + "description": "Toggle zoom for the focused pane. The legacy key name `fullscreen` is accepted as an alias." + }, + { + "key": "keys.resize_mode", + "type": "keybinding", + "default": "\"prefix+r\"", + "description": "Enter resize mode." + }, + { + "key": "keys.toggle_sidebar", + "type": "keybinding", + "default": "\"prefix+b\"", + "description": "Toggle sidebar collapse." + }, + { + "key": "keys.indexed.tabs", + "type": "string", + "default": "unset", + "description": "Modifier combo for tab shortcuts 1-9. Unset by default." + }, + { + "key": "keys.indexed.workspaces", + "type": "string", + "default": "unset", + "description": "Modifier combo for workspace shortcuts 1-9. Unset by default." + }, + { + "key": "keys.indexed.agents", + "type": "string", + "default": "unset", + "description": "Modifier combo for agent shortcuts 1-9. Unset by default." + } + ] + }, + { + "id": "ui", + "title": "UI and sidebar", + "keys": [ + { + "key": "ui.sidebar_width", + "type": "integer", + "default": "26", + "description": "Default expanded sidebar width in columns. Auto-scales based on workspace names." + }, + { + "key": "ui.sidebar_min_width", + "type": "integer", + "default": "18", + "description": "Minimum sidebar width (columns) when expanded." + }, + { + "key": "ui.sidebar_max_width", + "type": "integer", + "default": "36", + "description": "Maximum sidebar width (columns) when expanded." + }, + { + "key": "ui.sidebar_start_collapsed", + "type": "boolean", + "default": "false", + "description": "Start Herdr with the sidebar collapsed. Changes take effect on the next launch." + }, + { + "key": "ui.sidebar_collapsed_mode", + "type": "enum", + "default": "compact", + "description": "Collapsed sidebar presentation.", + "values": [ + "compact", + "hidden" + ] + }, + { + "key": "ui.mobile_width_threshold", + "type": "integer", + "default": "64", + "description": "Terminal width at or below which Herdr uses the mobile single-column layout." + }, + { + "key": "ui.mouse_capture", + "type": "boolean", + "default": "true", + "description": "Capture mouse input for Herdr's mouse UI." + }, + { + "key": "ui.copy_on_select", + "type": "boolean", + "default": "true", + "description": "Automatically copy text selected by mouse drag or double-click. When disabled, Ctrl+C or a host-forwarded Cmd+C copies and clears the retained selection." + }, + { + "key": "ui.host_cursor", + "type": "enum", + "default": "auto", + "description": "Host cursor policy.", + "values": [ + "auto", + "native", + "drawn" + ] + }, + { + "key": "ui.right_click_passthrough_modifier", + "type": "string", + "default": "\"\"", + "description": "Modifier that lets right-click gestures pass through to pane apps. Empty disables it. Accepts ctrl, alt, cmd, super, meta, hyper, or a + separated combination; shift is rejected because many terminals reserve Shift+mouse." + }, + { + "key": "ui.redraw_on_focus_gained", + "type": "boolean", + "default": "true", + "description": "Force a full host-terminal redraw when the outer terminal regains focus." + }, + { + "key": "ui.mouse_scroll_lines", + "type": "integer", + "default": "3", + "description": "Lines to scroll per mouse wheel notch." + }, + { + "key": "ui.confirm_close", + "type": "boolean", + "default": "true", + "description": "Ask for confirmation before closing a workspace." + }, + { + "key": "ui.prompt_new_tab_name", + "type": "boolean", + "default": "true", + "description": "Ask for a tab name before creating a new tab." + }, + { + "key": "ui.prompt_new_workspace_name", + "type": "boolean", + "default": "false", + "description": "Ask for a workspace name before interactive TUI creation." + }, + { + "key": "ui.pane_borders", + "type": "boolean", + "default": "true", + "description": "Draw borders around split panes." + }, + { + "key": "ui.pane_gaps", + "type": "boolean", + "default": "true", + "description": "Keep split panes visually separated instead of sharing divider borders." + }, + { + "key": "ui.show_agent_labels_on_pane_borders", + "type": "boolean", + "default": "false", + "description": "Show agent labels in split pane borders when no manual pane label is set." + }, + { + "key": "ui.hide_tab_bar_when_single_tab", + "type": "boolean", + "default": "false", + "description": "Hide the tab row when the workspace has one tab." + }, + { + "key": "ui.agent_panel_sort", + "type": "enum", + "default": "\"spaces\"", + "description": "Agent sidebar ordering. Saved values are \"spaces\" or \"priority\"; \"workspaces\" is accepted as an alias for \"spaces\".", + "values": [ + "spaces", + "priority" + ] + }, + { + "key": "ui.sidebar.agents.row_gap", + "type": "integer", + "default": "0", + "description": "Blank terminal rows between expanded Agent sidebar entries. Set to 1 to restore the previous spacing." + }, + { + "key": "ui.sidebar.agents.rows", + "type": "list of token rows", + "default": "[[\"state_icon\", \"workspace\", \"tab\"], [\"agent\"]]", + "description": "Default expanded Agent sidebar layout. Entries may be token strings or inline { token, fg, bold, dim } style tables. Supports built-in and $name metadata tokens; at most 16 rows and 16 tokens per row." + }, + { + "key": "ui.sidebar.agents.rows_by_agent", + "type": "table of token rows", + "default": "{}", + "description": "Complete Agent-row overrides keyed by strict canonical agent id. Agents without an override use ui.sidebar.agents.rows." + }, + { + "key": "ui.sidebar.spaces.row_gap", + "type": "integer", + "default": "0", + "description": "Blank terminal rows between expanded Space sidebar entries. Set to 1 to restore the previous spacing. Consecutive indented worktree children remain packed as one group." + }, + { + "key": "ui.sidebar.spaces.rows", + "type": "list of token rows", + "default": "[[\"state_icon\", \"workspace\"], [\"branch\", \"git_status\"]]", + "description": "Expanded Space sidebar layout. Entries may be token strings or inline { token, fg, bold, dim } style tables. Supports built-in and $name metadata tokens; at most 16 rows and 16 tokens per row." + }, + { + "key": "ui.accent", + "type": "color", + "default": "\"cyan\"", + "description": "Accent color for highlights, borders, and navigation UI. Accepts hex (#89b4fa), named colors (cyan, blue), or RGB (rgb(137,180,250))." + } + ] + }, + { + "id": "notifications", + "title": "Notifications", + "keys": [ + { + "key": "ui.toast.delivery", + "type": "enum", + "default": "\"off\"", + "description": "Popup notification delivery. off disables popups, herdr shows in-app toasts, terminal asks the outer terminal for a desktop notification, system asks the OS notification service directly.", + "values": [ + "off", + "herdr", + "terminal", + "system" + ] + }, + { + "key": "ui.toast.delay_seconds", + "type": "integer", + "default": "1", + "description": "Seconds to wait before sending finished or needs-input agent notifications. Herdr notifies only if the pane is still in the same state when the delay expires. 0 is instant; valid values are 0 through 3600." + }, + { + "key": "ui.toast.herdr.position", + "type": "enum", + "default": "\"bottom-right\"", + "description": "In-app toast position, relative to the full Herdr frame.", + "values": [ + "top-left", + "top-right", + "bottom-left", + "bottom-right" + ] + }, + { + "key": "ui.toast.clipboard.enabled", + "type": "boolean", + "default": "true", + "description": "Show the copied-to-clipboard popup after a mouse copy." + }, + { + "key": "ui.toast.clipboard.position", + "type": "enum", + "default": "\"bottom-center\"", + "description": "Copied-to-clipboard popup position.", + "values": [ + "top-left", + "top-center", + "top-right", + "bottom-left", + "bottom-center", + "bottom-right" + ] + } + ] + }, + { + "id": "sound", + "title": "Sound", + "keys": [ + { + "key": "ui.sound.enabled", + "type": "boolean", + "default": "true", + "description": "Play sounds when agents change state in background workspaces." + }, + { + "key": "ui.sound.path", + "type": "path", + "default": "unset", + "description": "Optional mp3 file path used for all notification sounds. Relative paths are resolved from the config file's directory." + }, + { + "key": "ui.sound.done_path", + "type": "path", + "default": "unset", + "description": "Optional mp3 file path for \"done\" notifications. Relative paths are resolved from the config file's directory." + }, + { + "key": "ui.sound.request_path", + "type": "path", + "default": "unset", + "description": "Optional mp3 file path for \"request\" notifications. Relative paths are resolved from the config file's directory." + }, + { + "key": "ui.sound.agents.pi", + "type": "enum", + "default": "\"default\"", + "description": "Sound override for detected Pi agents.", + "values": [ + "default", + "on", + "off" + ] + }, + { + "key": "ui.sound.agents.claude", + "type": "enum", + "default": "\"default\"", + "description": "Sound override for detected Claude Code agents.", + "values": [ + "default", + "on", + "off" + ] + }, + { + "key": "ui.sound.agents.codex", + "type": "enum", + "default": "\"default\"", + "description": "Sound override for detected Codex agents.", + "values": [ + "default", + "on", + "off" + ] + }, + { + "key": "ui.sound.agents.gemini", + "type": "enum", + "default": "\"default\"", + "description": "Sound override for detected Gemini CLI agents.", + "values": [ + "default", + "on", + "off" + ] + }, + { + "key": "ui.sound.agents.cursor", + "type": "enum", + "default": "\"default\"", + "description": "Sound override for detected Cursor Agent CLI agents.", + "values": [ + "default", + "on", + "off" + ] + }, + { + "key": "ui.sound.agents.devin", + "type": "enum", + "default": "\"default\"", + "description": "Sound override for detected Devin agents.", + "values": [ + "default", + "on", + "off" + ] + }, + { + "key": "ui.sound.agents.agy", + "type": "enum", + "default": "\"default\"", + "description": "Sound override for detected Agy agents.", + "values": [ + "default", + "on", + "off" + ] + }, + { + "key": "ui.sound.agents.cline", + "type": "enum", + "default": "\"default\"", + "description": "Sound override for detected Cline agents.", + "values": [ + "default", + "on", + "off" + ] + }, + { + "key": "ui.sound.agents.open_code", + "type": "enum", + "default": "\"default\"", + "description": "Sound override for detected OpenCode agents.", + "values": [ + "default", + "on", + "off" + ] + }, + { + "key": "ui.sound.agents.github_copilot", + "type": "enum", + "default": "\"default\"", + "description": "Sound override for detected GitHub Copilot CLI agents.", + "values": [ + "default", + "on", + "off" + ] + }, + { + "key": "ui.sound.agents.kimi", + "type": "enum", + "default": "\"default\"", + "description": "Sound override for detected Kimi Code CLI agents.", + "values": [ + "default", + "on", + "off" + ] + }, + { + "key": "ui.sound.agents.kiro", + "type": "enum", + "default": "\"default\"", + "description": "Sound override for detected Kiro agents.", + "values": [ + "default", + "on", + "off" + ] + }, + { + "key": "ui.sound.agents.droid", + "type": "enum", + "default": "\"off\"", + "description": "Sound override for detected Droid agents.", + "values": [ + "default", + "on", + "off" + ] + }, + { + "key": "ui.sound.agents.amp", + "type": "enum", + "default": "\"default\"", + "description": "Sound override for detected Amp agents.", + "values": [ + "default", + "on", + "off" + ] + }, + { + "key": "ui.sound.agents.grok", + "type": "enum", + "default": "\"default\"", + "description": "Sound override for detected Grok CLI agents.", + "values": [ + "default", + "on", + "off" + ] + }, + { + "key": "ui.sound.agents.hermes", + "type": "enum", + "default": "\"default\"", + "description": "Sound override for detected Hermes Agent agents.", + "values": [ + "default", + "on", + "off" + ] + }, + { + "key": "ui.sound.agents.kilo", + "type": "enum", + "default": "\"default\"", + "description": "Sound override for detected Kilo Code CLI agents.", + "values": [ + "default", + "on", + "off" + ] + }, + { + "key": "ui.sound.agents.qodercli", + "type": "enum", + "default": "\"default\"", + "description": "Sound override for detected Qoder CLI agents.", + "values": [ + "default", + "on", + "off" + ] + }, + { + "key": "ui.sound.agents.maki", + "type": "enum", + "default": "\"default\"", + "description": "Sound override for detected Maki agents.", + "values": [ + "default", + "on", + "off" + ] + } + ] + }, + { + "id": "session", + "title": "Session", + "keys": [ + { + "key": "session.resume_agents_on_restore", + "type": "boolean", + "default": "true", + "description": "Resume supported AI-agent panes into their native conversation sessions when restoring a Herdr session." + } + ] + }, + { + "id": "worktrees", + "title": "Worktrees", + "keys": [ + { + "key": "worktrees.directory", + "type": "string", + "default": "\"~/.herdr/worktrees\"", + "description": "Root directory under which Herdr creates <repo>/<branch-slug> checkouts." + } + ] + }, + { + "id": "remote", + "title": "Remote", + "keys": [ + { + "key": "remote.manage_ssh_config", + "type": "boolean", + "default": "true", + "description": "Add keepalive fallbacks and private connection reuse for `herdr --remote`. Set false to run plain ssh unchanged." + } + ] + }, + { + "id": "advanced", + "title": "Advanced", + "keys": [ + { + "key": "advanced.scrollback_limit_bytes", + "type": "integer", + "default": "10000000", + "description": "Maximum scrollback buffer size in bytes retained per pane terminal. The legacy key name `scrollback_lines` is accepted as an alias." + } + ] + }, + { + "id": "experimental", + "title": "Experimental", + "keys": [ + { + "key": "experimental.allow_nested", + "type": "boolean", + "default": "false", + "description": "Allow launching herdr inside an existing herdr pane." + }, + { + "key": "experimental.kitty_graphics", + "type": "boolean", + "default": "false", + "description": "Experimental local Kitty graphics rendering for attached clients." + }, + { + "key": "experimental.pane_history", + "type": "boolean", + "default": "false", + "description": "Persist pane screen history to session-history.json." + }, + { + "key": "experimental.reveal_hidden_cursor_for_cjk_ime", + "type": "boolean", + "default": "false", + "description": "Expose the focused pane's cursor anchor to the outer terminal even when the pane requested `?25l`, so macOS native input methods keep tracking the candidate window when TUIs paint their own cursor (Claude Code, pi, codex, etc.). Default: false. When the pane reports no cursor position, falls back to the pane's top-left so a stable IME anchor is always available. Trade-off when enabled: an extra hardware cursor will be visible in the outer terminal for apps that hide the cursor without painting a replacement (vim normal mode, etc.). See #149." + }, + { + "key": "experimental.cjk_ime_agents", + "type": "list of strings", + "default": "[]", + "description": "Restrict `reveal_hidden_cursor_for_cjk_ime` to focused panes whose detected agent matches one of these names (case-insensitive). Empty list means apply to any focused pane. Unknown agent names are ignored; if the list contains no valid names, the reveal does not apply. Accepted names: pi, claude, codex, gemini, cursor, devin, cline, opencode, copilot, kimi, kiro, droid, amp, grok, hermes, kilo, qodercli, qoder, maki." + }, + { + "key": "experimental.cjk_ime_cursor_shape", + "type": "enum", + "default": "\"steady_block\"", + "description": "Cursor shape rendered for the IME anchor when `reveal_hidden_cursor_for_cjk_ime` is enabled.", + "values": [ + "block", + "steady_block", + "underline", + "steady_underline", + "bar", + "steady_bar" + ] + }, + { + "key": "experimental.switch_ascii_input_source_in_prefix", + "type": "boolean", + "default": "false", + "description": "While prefix mode is active, temporarily switch the host input source to an ASCII-capable mode so prefix commands are read as ASCII even when an IME is active, then restore the previous input source when prefix mode exits. On macOS this selects the ASCII-capable keyboard layout; on Windows it switches the IME to English (ASCII) input. Windows support is currently limited to the Korean IME; with an IME for any other language, the input source is left unchanged. macOS and Windows only; a no-op elsewhere and a best-effort no-op if the switch fails." + } + ] + } + ] +} diff --git a/docs/versions/README.md b/docs/versions/README.md index 023d23f7..dbfd01f6 100644 --- a/docs/versions/README.md +++ b/docs/versions/README.md @@ -8,6 +8,6 @@ Do not edit snapshot files manually. They must match the release tag recorded in node website/scripts/docs-versions.mjs check ``` -Release CI creates a new snapshot from the tagged `docs/next` tree after the GitHub Release succeeds. `website/scripts/prepare-docs.mjs` renders these snapshots at `/docs/<version>/` while keeping `/docs/` on the latest stable documentation and `/docs/preview/` on unreleased work. +Release CI creates a new snapshot from the tagged `docs/next` tree after the GitHub Release succeeds. `website/scripts/prepare-docs.mjs` renders these snapshots at `/docs/<version>/` and uses the current snapshot for `/docs/` after the legacy stable-doc migration completes. `/docs/preview/` comes only from the active preview release snapshot in `docs/preview/`, never directly from `docs/next/`. The historical backfill starts at v0.5.11, the first release that included the Astro/Starlight documentation site. diff --git a/docs/versions/manifest.json b/docs/versions/manifest.json index d18be00f..a40c6566 100644 --- a/docs/versions/manifest.json +++ b/docs/versions/manifest.json @@ -1,5 +1,6 @@ { "schema_version": 1, + "stable_source": "legacy", "current": "0.7.5", "versions": [ { diff --git a/justfile b/justfile index a3726db0..e5f9e1b2 100644 --- a/justfile +++ b/justfile @@ -79,6 +79,7 @@ release-docs-check: python3 scripts/agent_detection_manifest_check.py --require-website python3 scripts/config_reference_check.py node website/scripts/docs-versions.mjs check + node website/scripts/docs-preview.mjs check @test -f docs/next/README.md @test -f docs/next/README.zh-CN.md @if ! diff -u CHANGELOG.md docs/next/CHANGELOG.md; then \ @@ -109,8 +110,8 @@ release-docs-check: fi; \ done python3 scripts/docs_translation_parity.py --docs-root docs/next/website/src/content/docs - python3 scripts/docs_translation_parity.py --docs-root website/src/content/docs just website-build + cd website && bun run build:draft # Prepare the release commit without tagging or pushing (usage: just release-prepare 0.1.1) release-prepare version: diff --git a/scripts/docs_translation_parity.py b/scripts/docs_translation_parity.py index 6b15441b..a7f117c3 100644 --- a/scripts/docs_translation_parity.py +++ b/scripts/docs_translation_parity.py @@ -114,7 +114,7 @@ def parse_args(argv: list[str]) -> argparse.Namespace: ) parser.add_argument( "--docs-root", - default="website/src/content/docs", + default="docs/next/website/src/content/docs", type=Path, help="Docs content root containing English .mdx files and locale subdirectories.", ) diff --git a/scripts/test_preview.py b/scripts/test_preview.py index 7270e45c..c280f572 100644 --- a/scripts/test_preview.py +++ b/scripts/test_preview.py @@ -181,8 +181,11 @@ file: ../../../public/assets/logo.svg self.assertIn("file: ../../../../public/assets/logo.svg", output) self.assertIn("from '../../../components/ConfigReference.astro'", output) self.assertIn("from '../../../../components/LocaleWidget.astro'", output) - self.assertIn("Next docs describe unreleased work", output) - self.assertIn("edit/master/docs/next/website/src/content/docs/", output) + self.assertIn("Preview build `2026-07-29-44b3adb12552`", output) + self.assertIn( + "blob/44b3adb125524ea9a55739eee3776f922f2115ad/docs/next/website/src/content/docs/", + output, + ) def test_version_docs_rewrite_links_and_source_paths(self): source = """--- diff --git a/website/.gitignore b/website/.gitignore index ddd50bda..70deacfd 100644 --- a/website/.gitignore +++ b/website/.gitignore @@ -2,8 +2,8 @@ node_modules/ public/ dist/ .astro/ -src/content/docs/preview/ -src/content/docs/_versions/ +src/content/docs/ +src/data/config-reference.json src/data/config-reference-preview.json src/data/config-reference-versions.json src/data/docs-versions.json diff --git a/website/README.md b/website/README.md index fd8d8ea2..a511a861 100644 --- a/website/README.md +++ b/website/README.md @@ -1,21 +1,34 @@ # herdr website -The homepage is `index.html`. The documentation source is in `src/content/docs/` and is rendered by Astro Starlight. +The homepage is `index.html`. Astro Starlight renders the documentation. ```bash bun install -bun run dev -bun run build +bun run dev # render the unpublished docs/next draft locally +bun run build # render only published stable and preview snapshots +bun run build:draft # validate the unpublished draft ``` The build output is `dist/`. Configure Cloudflare Pages to use `website` as the project root and publish `dist`. -Stable docs live in `src/content/docs/`. Unreleased docs live in `../docs/next/website/src/content/docs/` and are generated at `/docs/preview/`. Immutable release snapshots live in `../docs/versions/` and are generated at `/docs/<version>/`. +Documentation has three lifecycle states: -Do not promote docs manually before a release. After the GitHub Release succeeds, release CI runs: +- `../docs/next/website/` is the committed, author-edited draft. Production builds never read it. +- `../docs/preview/website/` is the latest preview release snapshot, rendered at `/docs/preview/`. +- `../docs/versions/<version>/website/` contains immutable stable release snapshots, rendered at `/docs/<version>/`. + +The current stable site temporarily remains tracked under `src/content/docs/` because it contains post-v0.7.5 documentation corrections. The next stable release switches `docs/versions/manifest.json` to snapshot-backed stable docs and removes that legacy copy. From then on, `src/content/docs/` is entirely generated and ignored. + +Preview CI snapshots the selected commit and updates `preview.json` in one commit: + +```bash +node website/scripts/docs-preview.mjs snapshot <commit> +node website/scripts/docs-preview.mjs check +``` + +Stable release CI snapshots the exact tag after the GitHub Release succeeds: ```bash node website/scripts/docs-versions.mjs publish <tag> +node website/scripts/docs-versions.mjs check ``` - -This snapshots the tagged next docs and promotes the same tagged content to stable before the website deploy. Use `node website/scripts/docs-versions.mjs check` to validate committed snapshots against their release tags. diff --git a/website/astro.config.mjs b/website/astro.config.mjs index 929c142b..61e79cfc 100644 --- a/website/astro.config.mjs +++ b/website/astro.config.mjs @@ -199,7 +199,7 @@ export default defineConfig({ translations: { ja: 'ヘルプ', 'zh-CN': '帮助' }, items: [ { label: 'Troubleshooting', translations: { ja: 'トラブルシューティング', 'zh-CN': '故障排除' }, slug: 'docs/troubleshooting' }, - { label: 'Next docs', translations: { ja: '次期版ドキュメント', 'zh-CN': '下一版文档' }, slug: 'docs/preview' }, + { label: 'Preview docs', translations: { ja: 'プレビュー版ドキュメント', 'zh-CN': '预览版文档' }, slug: 'docs/preview' }, ], }, ], diff --git a/website/index.html b/website/index.html index c07654fd..4230c84a 100644 --- a/website/index.html +++ b/website/index.html @@ -182,7 +182,7 @@ <div class="windows-install" aria-label="Windows beta preview install"> <div class="windows-install-heading"> <span>Windows beta · preview only</span> - <a href="/docs/preview/windows-beta/">Next docs</a> + <a href="/docs/preview/windows-beta/">Preview docs</a> </div> <div class="install-card install-card-secondary" aria-label="Windows beta install command"> <span class="prompt">PS></span> diff --git a/website/package.json b/website/package.json index 5fdb4ba8..01f1430a 100644 --- a/website/package.json +++ b/website/package.json @@ -4,9 +4,10 @@ "type": "module", "scripts": { "prepare-docs": "node scripts/prepare-docs.mjs", - "dev": "node scripts/prepare-docs.mjs && astro dev", + "dev": "node scripts/prepare-docs.mjs --draft && astro dev", "test": "bun test", "build": "bun run test && node scripts/prepare-docs.mjs && astro build && node scripts/check-built-docs.mjs", + "build:draft": "node scripts/prepare-docs.mjs --draft && astro build && node scripts/check-built-docs.mjs", "preview": "astro preview" }, "dependencies": { diff --git a/website/scripts/check-built-docs.mjs b/website/scripts/check-built-docs.mjs index 6f5dfa0b..8841bf74 100644 --- a/website/scripts/check-built-docs.mjs +++ b/website/scripts/check-built-docs.mjs @@ -40,6 +40,10 @@ if (stable.includes('name="robots" content="noindex')) { throw new Error('stable docs must remain indexable'); } assertIncludes(preview, 'data-pagefind-filter="version[content]" content="preview"'); +assertIncludes(preview, `Preview build <code dir="auto">${versions.preview.build_id}</code>`); +if (versions.preview.commit !== 'master') { + assertIncludes(preview, versions.preview.commit.slice(0, 12)); +} assertIncludes(preview, 'name="robots" content="noindex, nofollow"'); assertIncludes(archived, `data-pagefind-filter="version[content]" content="${versions.current}"`); assertIncludes(archived, 'name="robots" content="noindex, nofollow"'); diff --git a/website/scripts/docs-preview.integration.test.ts b/website/scripts/docs-preview.integration.test.ts new file mode 100644 index 00000000..f850689c --- /dev/null +++ b/website/scripts/docs-preview.integration.test.ts @@ -0,0 +1,90 @@ +import { afterEach, describe, expect, test } from 'bun:test'; +import { execFileSync } from 'node:child_process'; +import { mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises'; +import { join, resolve } from 'node:path'; +import { tmpdir } from 'node:os'; + +const script = resolve(import.meta.dir, 'docs-preview.mjs'); +const temporaryDirectories: string[] = []; + +afterEach(async () => { + await Promise.all( + temporaryDirectories.splice(0).map((path) => rm(path, { recursive: true, force: true })), + ); +}); + +describe('preview documentation snapshots', () => { + test('snapshots and validates the selected commit exactly', async () => { + const root = await fixture(); + const commit = git(root, ['rev-parse', 'HEAD']).trim(); + await write(root, 'docs/preview/website/stale.mdx', 'stale\n'); + await writeManifest(root, commit); + + runScript(root, ['snapshot', commit]); + runScript(root, ['check']); + + expect(await read(root, 'docs/preview/website/src/content/docs/index.mdx')).toBe( + 'selected preview\n', + ); + await expect(read(root, 'docs/preview/website/stale.mdx')).rejects.toThrow(); + }); + + test('rejects snapshot drift', async () => { + const root = await fixture(); + const commit = git(root, ['rev-parse', 'HEAD']).trim(); + await writeManifest(root, commit); + runScript(root, ['snapshot', commit]); + await write(root, 'docs/preview/website/src/content/docs/index.mdx', 'changed\n'); + + expect(() => runScript(root, ['check'])).toThrow(); + }); +}); + +async function fixture() { + const root = await mkdtemp(join(tmpdir(), 'herdr-preview-docs-')); + temporaryDirectories.push(root); + await write(root, 'docs/next/website/src/content/docs/index.mdx', 'selected preview\n'); + await write(root, 'docs/next/website/src/data/config-reference.json', '{"preview":true}\n'); + git(root, ['init', '-q']); + git(root, ['config', 'user.email', 'test@example.com']); + git(root, ['config', 'user.name', 'Test']); + git(root, ['add', '.']); + git(root, ['commit', '-qm', 'preview fixture']); + return root; +} + +async function writeManifest(root: string, commit: string) { + await write( + root, + 'website/preview.json', + `${JSON.stringify({ + schema_version: 1, + channel: 'preview', + build_id: 'test-build', + commit, + builds: { 'test-build': { commit, tag: 'preview-test-build' } }, + })}\n`, + ); +} + +async function write(root: string, path: string, content: string) { + const destination = resolve(root, path); + await mkdir(resolve(destination, '..'), { recursive: true }); + await writeFile(destination, content, 'utf8'); +} + +async function read(root: string, path: string) { + return readFile(resolve(root, path), 'utf8'); +} + +function git(root: string, args: string[]) { + return execFileSync('git', args, { cwd: root, encoding: 'utf8', stdio: 'pipe' }); +} + +function runScript(root: string, args: string[]) { + execFileSync('node', [script, ...args], { + cwd: root, + env: { ...process.env, HERDR_DOCS_REPO_ROOT: root }, + stdio: 'pipe', + }); +} diff --git a/website/scripts/docs-preview.mjs b/website/scripts/docs-preview.mjs new file mode 100644 index 00000000..c0de1fcd --- /dev/null +++ b/website/scripts/docs-preview.mjs @@ -0,0 +1,75 @@ +import { readFile } from 'node:fs/promises'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath, pathToFileURL } from 'node:url'; +import { + compareGitTree, + createGit, + extractGitTree, + gitPathExists, + resolveCommit, +} from './docs-snapshot.mjs'; + +const websiteDir = dirname(fileURLToPath(import.meta.url)); +const repoRoot = process.env.HERDR_DOCS_REPO_ROOT + ? resolve(process.env.HERDR_DOCS_REPO_ROOT) + : resolve(websiteDir, '../..'); +const git = createGit(repoRoot); +const sourceRoot = 'docs/next/website'; +const snapshotRoot = resolve(repoRoot, 'docs/preview/website'); +const manifestPath = resolve(repoRoot, 'website/preview.json'); +const fullShaPattern = /^[0-9a-f]{40}$/; + +export async function snapshotPreview(ref) { + const commit = resolveCommit(git, ref); + if (!gitPathExists(git, commit, `${sourceRoot}/src/content/docs`)) { + throw new Error(`${commit} does not contain staged website documentation`); + } + if (!gitPathExists(git, commit, `${sourceRoot}/src/data/config-reference.json`)) { + throw new Error(`${commit} does not contain the staged config reference`); + } + await extractGitTree(git, commit, sourceRoot, snapshotRoot); + process.stdout.write(`snapshotted preview documentation from ${commit}\n`); +} + +export async function checkPreview() { + const manifest = JSON.parse(await readFile(manifestPath, 'utf8')); + if (manifest.schema_version !== 1 || manifest.channel !== 'preview') { + throw new Error(`${manifestPath} has an unsupported schema`); + } + if (typeof manifest.commit !== 'string' || !fullShaPattern.test(manifest.commit)) { + throw new Error(`${manifestPath} must contain a full preview commit SHA`); + } + const build = manifest.builds?.[manifest.build_id]; + if (!build || build.commit !== manifest.commit) { + throw new Error(`${manifestPath} active build does not match its preview commit`); + } + const expectedTag = `preview-${manifest.build_id}`; + if (build.tag !== expectedTag) { + throw new Error(`${manifestPath} active build tag does not match ${expectedTag}`); + } + if (!gitPathExists(git, manifest.commit, `${sourceRoot}/src/content/docs`)) { + throw new Error(`${manifest.commit} does not contain staged website documentation`); + } + if (!gitPathExists(git, manifest.commit, `${sourceRoot}/src/data/config-reference.json`)) { + throw new Error(`${manifest.commit} does not contain the staged config reference`); + } + await compareGitTree(git, manifest.commit, sourceRoot, snapshotRoot); + process.stdout.write(`validated preview documentation snapshot ${manifest.commit}\n`); +} + +async function main() { + const [command, value] = process.argv.slice(2); + if (command === 'snapshot' && value) { + await snapshotPreview(value); + return; + } + if (command === 'check' && !value) { + await checkPreview(); + return; + } + throw new Error('usage: node website/scripts/docs-preview.mjs check | snapshot <commit>'); +} + +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { + await main(); +} diff --git a/website/scripts/docs-snapshot.mjs b/website/scripts/docs-snapshot.mjs new file mode 100644 index 00000000..6a1d1bc6 --- /dev/null +++ b/website/scripts/docs-snapshot.mjs @@ -0,0 +1,124 @@ +import { execFileSync } from 'node:child_process'; +import { mkdir, readFile, readdir, rm, writeFile } from 'node:fs/promises'; +import { dirname, relative, resolve } from 'node:path'; + +export function createGit(repoRoot) { + return function git(args, options = {}) { + return execFileSync('git', args, { + cwd: repoRoot, + encoding: options.binary ? undefined : 'utf8', + maxBuffer: 64 * 1024 * 1024, + stdio: ['ignore', 'pipe', 'pipe'], + }); + }; +} + +export function resolveCommit(git, ref) { + return git(['rev-parse', `${ref}^{commit}`]).trim(); +} + +export function gitPathExists(git, ref, path) { + try { + git(['cat-file', '-e', `${ref}:${path}`]); + return true; + } catch { + return false; + } +} + +export function gitTreesEqual(git, ref, left, right) { + try { + git(['diff', '--quiet', `${ref}:${left}`, `${ref}:${right}`]); + return true; + } catch { + return false; + } +} + +export function listGitEntries(git, ref, root) { + const output = git([ + 'ls-tree', + '-r', + '-z', + '--format=%(objectmode) %(objecttype) %(objectname)%x09%(path)', + ref, + '--', + root, + ]); + return output + .split('\0') + .filter(Boolean) + .map((line) => { + const separator = line.indexOf('\t'); + const [mode, type, object] = line.slice(0, separator).split(' '); + return { mode, type, object, path: line.slice(separator + 1) }; + }); +} + +export async function extractGitTree(git, ref, sourceRoot, destinationRoot) { + const entries = listGitEntries(git, ref, sourceRoot); + if (entries.length === 0) throw new Error(`${ref}:${sourceRoot} contains no files`); + + await rm(destinationRoot, { recursive: true, force: true }); + for (const entry of entries) { + assertOrdinaryFile(entry, ref); + const relativePath = relative(sourceRoot, entry.path); + assertRelativePath(relativePath, entry.path); + const destination = resolve(destinationRoot, relativePath); + await mkdir(dirname(destination), { recursive: true }); + await writeFile(destination, git(['cat-file', 'blob', entry.object], { binary: true })); + } +} + +export async function compareGitTree(git, ref, sourceRoot, snapshotRoot) { + const expectedEntries = listGitEntries(git, ref, sourceRoot); + if (expectedEntries.length === 0) throw new Error(`${ref}:${sourceRoot} contains no files`); + + const expected = new Map(); + for (const entry of expectedEntries) { + assertOrdinaryFile(entry, ref); + const relativePath = relative(sourceRoot, entry.path); + assertRelativePath(relativePath, entry.path); + expected.set(relativePath, entry); + } + const actualPaths = await listSnapshotPaths(snapshotRoot); + const expectedPaths = [...expected.keys()].sort(); + if (JSON.stringify(actualPaths) !== JSON.stringify(expectedPaths)) { + throw new Error(`${snapshotRoot} file list differs from ${ref}:${sourceRoot}`); + } + + for (const relativePath of expectedPaths) { + const entry = expected.get(relativePath); + const snapshotPath = resolve(snapshotRoot, relativePath); + const expectedContent = git(['cat-file', 'blob', entry.object], { binary: true }); + if (!(await readFile(snapshotPath)).equals(expectedContent)) { + throw new Error(`${relativePath} differs from ${ref}:${entry.path}`); + } + } +} + +async function listSnapshotPaths(root) { + const paths = []; + async function walk(directory) { + for (const entry of await readdir(directory, { withFileTypes: true })) { + const path = resolve(directory, entry.name); + if (entry.isDirectory()) await walk(path); + else if (entry.isFile()) paths.push(relative(root, path)); + else throw new Error(`${path} is not an ordinary documentation file`); + } + } + await walk(root); + return paths.sort(); +} + +function assertOrdinaryFile(entry, ref) { + if (entry.type !== 'blob' || entry.mode !== '100644') { + throw new Error(`${ref}:${entry.path} is not an ordinary documentation file`); + } +} + +function assertRelativePath(relativePath, originalPath) { + if (!relativePath || relativePath === '..' || relativePath.startsWith(`..${process.platform === 'win32' ? '\\' : '/'}`)) { + throw new Error(`unsafe snapshot path ${originalPath}`); + } +} diff --git a/website/scripts/docs-versions.integration.test.ts b/website/scripts/docs-versions.integration.test.ts index c3f97bfe..5e2d36a5 100644 --- a/website/scripts/docs-versions.integration.test.ts +++ b/website/scripts/docs-versions.integration.test.ts @@ -5,6 +5,7 @@ import { join, resolve } from 'node:path'; import { tmpdir } from 'node:os'; const script = resolve(import.meta.dir, 'docs-versions.mjs'); +const prepareScript = resolve(import.meta.dir, 'prepare-docs.mjs'); const temporaryDirectories: string[] = []; afterEach(async () => { @@ -12,7 +13,7 @@ afterEach(async () => { }); describe('documentation release publishing', () => { - test('snapshots tagged next docs and promotes the same content', async () => { + test('snapshots tagged next docs and switches stable to generated snapshots', async () => { const root = await mkdtemp(join(tmpdir(), 'herdr-docs-')); temporaryDirectories.push(root); await write(root, 'website/src/content/docs/index.mdx', 'stable docs\n'); @@ -30,26 +31,63 @@ describe('documentation release publishing', () => { git(root, ['config', 'user.name', 'Test']); git(root, ['add', '.']); git(root, ['commit', '-qm', 'release fixture']); + git(root, ['tag', 'v0.9.0']); git(root, ['tag', 'v1.0.0']); runScript(root, ['publish', 'v1.0.0']); - expect(await read(root, 'website/src/content/docs/index.mdx')).toBe('next docs\n'); - expect(await read(root, 'website/src/data/config-reference.json')).toBe('{"next":true}\n'); + await expect(read(root, 'website/src/content/docs/index.mdx')).rejects.toThrow(); + await expect(read(root, 'website/src/data/config-reference.json')).rejects.toThrow(); expect(await read(root, 'README.md')).toBe('next readme\n'); expect(await read(root, 'README.zh-CN.md')).toBe('next readme zh-cn\n'); expect(await read(root, 'docs/versions/1.0.0/website/src/content/docs/index.mdx')).toBe('next docs\n'); const manifest = JSON.parse(await read(root, 'docs/versions/manifest.json')); expect(manifest.current).toBe('1.0.0'); + expect(manifest.stable_source).toBe('snapshot'); expect(manifest.versions[0]).toMatchObject({ version: '1.0.0', tag: 'v1.0.0', + commit: expect.stringMatching(/^[0-9a-f]{40}$/), source: 'docs/next/website/src/content/docs', }); + const previewCommit = git(root, ['rev-parse', 'HEAD']).trim(); + await write(root, 'docs/preview/website/src/content/docs/index.mdx', 'preview docs\n'); + await write( + root, + 'docs/preview/website/src/data/config-reference.json', + '{"preview":true}\n', + ); + await write( + root, + 'website/preview.json', + `${JSON.stringify({ build_id: 'preview-test', commit: previewCommit })}\n`, + ); + runPrepare(root); + expect(await read(root, 'website/src/content/docs/index.mdx')).toBe('next docs\n'); + expect(await read(root, 'website/src/content/docs/preview/index.mdx')).toContain( + 'Preview build `preview-test`', + ); + expect(await read(root, 'website/src/data/config-reference.json')).toBe('{"next":true}\n'); + await write(root, 'website/latest.json', '{"version":"1.0.0"}\n'); runScript(root, ['check']); + + await write(root, 'README.md', 'post-release correction\n'); + runScript(root, ['publish', 'v1.0.0']); + expect(await read(root, 'README.md')).toBe('post-release correction\n'); + + runScript(root, ['publish', 'v0.9.0']); + const archivedManifest = JSON.parse(await read(root, 'docs/versions/manifest.json')); + expect(archivedManifest.current).toBe('1.0.0'); + expect(archivedManifest.versions.map(({ version }) => version)).toEqual(['1.0.0', '0.9.0']); + expect(await read(root, 'README.md')).toBe('post-release correction\n'); + runScript(root, ['check']); + + delete archivedManifest.versions[0].commit; + await write(root, 'docs/versions/manifest.json', `${JSON.stringify(archivedManifest)}\n`); + expect(() => runScript(root, ['check'])).toThrow(); }); }); @@ -64,7 +102,7 @@ async function read(root: string, path: string) { } function git(root: string, args: string[]) { - execFileSync('git', args, { cwd: root, stdio: 'pipe' }); + return execFileSync('git', args, { cwd: root, encoding: 'utf8', stdio: 'pipe' }); } function runScript(root: string, args: string[]) { @@ -74,3 +112,11 @@ function runScript(root: string, args: string[]) { stdio: 'pipe', }); } + +function runPrepare(root: string) { + execFileSync('node', [prepareScript, '--docs-only'], { + cwd: root, + env: { ...process.env, HERDR_DOCS_REPO_ROOT: root }, + stdio: 'pipe', + }); +} diff --git a/website/scripts/docs-versions.mjs b/website/scripts/docs-versions.mjs index 7b6b0c24..30cb099d 100644 --- a/website/scripts/docs-versions.mjs +++ b/website/scripts/docs-versions.mjs @@ -1,7 +1,14 @@ -import { execFileSync } from 'node:child_process'; -import { mkdir, readFile, readdir, rm, writeFile } from 'node:fs/promises'; -import { dirname, join, relative, resolve } from 'node:path'; +import { mkdir, readFile, rm, writeFile } from 'node:fs/promises'; +import { dirname, resolve } from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; +import { + compareGitTree, + createGit, + extractGitTree, + gitPathExists, + gitTreesEqual, + resolveCommit, +} from './docs-snapshot.mjs'; const websiteDir = dirname(fileURLToPath(import.meta.url)); const repoRoot = process.env.HERDR_DOCS_REPO_ROOT @@ -11,6 +18,7 @@ const versionsDir = resolve(repoRoot, 'docs/versions'); const manifestPath = resolve(versionsDir, 'manifest.json'); const stableDocsDir = resolve(repoRoot, 'website/src/content/docs'); const stableReferencePath = resolve(repoRoot, 'website/src/data/config-reference.json'); +const git = createGit(repoRoot); const VERSION_PATTERN = /^v?(\d+\.\d+\.\d+)$/; @@ -21,94 +29,58 @@ export function normalizeVersion(value) { } export function sortVersionsNewestFirst(versions) { - return [...versions].sort((left, right) => { - const a = left.version.split('.').map(Number); - const b = right.version.split('.').map(Number); - for (let index = 0; index < 3; index += 1) { - if (a[index] !== b[index]) return b[index] - a[index]; - } - return 0; - }); + return [...versions].sort((left, right) => compareVersions(right.version, left.version)); } -function git(args, options = {}) { - return execFileSync('git', args, { - cwd: repoRoot, - encoding: options.binary ? undefined : 'utf8', - maxBuffer: 64 * 1024 * 1024, - stdio: ['ignore', 'pipe', 'pipe'], - }); -} - -function gitTreesEqual(ref, left, right) { - try { - git(['diff', '--quiet', `${ref}:${left}`, `${ref}:${right}`]); - return true; - } catch { - return false; - } -} - -function gitPathExists(ref, path) { - try { - git(['cat-file', '-e', `${ref}:${path}`]); - return true; - } catch { - return false; - } -} - -function listGitFiles(ref, root) { - const output = git(['ls-tree', '-r', '--name-only', ref, '--', root]); - return output.split('\n').filter(Boolean); -} - -async function extractGitTree(ref, sourceRoot, destinationRoot) { - const files = listGitFiles(ref, sourceRoot); - if (files.length === 0) throw new Error(`${ref}:${sourceRoot} contains no files`); - - await rm(destinationRoot, { recursive: true, force: true }); - for (const file of files) { - const destination = resolve(destinationRoot, relative(sourceRoot, file)); - await mkdir(dirname(destination), { recursive: true }); - await writeFile(destination, git(['show', `${ref}:${file}`], { binary: true })); +function compareVersions(left, right) { + const a = left.split('.').map(Number); + const b = right.split('.').map(Number); + for (let index = 0; index < 3; index += 1) { + if (a[index] !== b[index]) return a[index] - b[index]; } + return 0; } async function readManifest() { try { return JSON.parse(await readFile(manifestPath, 'utf8')); } catch (error) { - if (error.code === 'ENOENT') return { schema_version: 1, current: null, versions: [] }; + if (error.code === 'ENOENT') { + return { schema_version: 1, stable_source: 'legacy', current: null, versions: [] }; + } throw error; } } async function writeManifest(manifest) { - manifest.versions = sortVersionsNewestFirst(manifest.versions).map(({ version, tag, source }) => ({ - version, - tag, - source, - })); + manifest.versions = sortVersionsNewestFirst(manifest.versions).map( + ({ version, tag, commit, source }) => ({ + version, + tag, + ...(commit ? { commit } : {}), + source, + }), + ); await mkdir(dirname(manifestPath), { recursive: true }); await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, 'utf8'); } function releaseMetadata(tag) { const version = normalizeVersion(tag); - return { version, tag: `v${version}` }; + const normalizedTag = `v${version}`; + return { version, tag: normalizedTag, commit: resolveCommit(git, normalizedTag) }; } async function snapshotTag(tag, sourceRoot) { const metadata = { ...releaseMetadata(tag), source: sourceRoot }; const destination = resolve(versionsDir, metadata.version); await rm(destination, { recursive: true, force: true }); - await extractGitTree(tag, sourceRoot, resolve(destination, 'website/src/content/docs')); + await extractGitTree(git, tag, sourceRoot, resolve(destination, 'website/src/content/docs')); const referenceSource = sourceRoot.includes('docs/next/') ? 'docs/next/website/src/data/config-reference.json' : 'website/src/data/config-reference.json'; - if (gitPathExists(tag, referenceSource)) { + if (gitPathExists(git, tag, referenceSource)) { const referenceDestination = resolve(destination, 'website/src/data/config-reference.json'); await mkdir(dirname(referenceDestination), { recursive: true }); await writeFile(referenceDestination, git(['show', `${tag}:${referenceSource}`], { binary: true })); @@ -124,12 +96,16 @@ export async function backfillVersions() { for (const tag of tags) { const version = normalizeVersion(tag); - if (entries.has(version) || !gitPathExists(tag, 'website/src/content/docs')) continue; + if (entries.has(version)) continue; const nextRoot = 'docs/next/website/src/content/docs'; + const legacyRoot = 'website/src/content/docs'; + const hasNext = gitPathExists(git, tag, nextRoot); + const hasLegacy = gitPathExists(git, tag, legacyRoot); + if (!hasNext && !hasLegacy) continue; const sourceRoot = - gitPathExists(tag, nextRoot) && !gitTreesEqual(tag, 'website/src/content/docs', nextRoot) + hasNext && (!hasLegacy || !gitTreesEqual(git, tag, legacyRoot, nextRoot)) ? nextRoot - : 'website/src/content/docs'; + : legacyRoot; const metadata = await snapshotTag(tag, sourceRoot); entries.set(metadata.version, metadata); process.stdout.write(`snapshotted ${tag}\n`); @@ -138,6 +114,7 @@ export async function backfillVersions() { const current = JSON.parse(await readFile(resolve(repoRoot, 'website/latest.json'), 'utf8')).version; await writeManifest({ schema_version: 1, + stable_source: manifest.stable_source ?? 'legacy', current: normalizeVersion(current), versions: [...entries.values()], }); @@ -145,7 +122,11 @@ export async function backfillVersions() { export async function checkVersions() { const manifest = await readManifest(); - if (manifest.schema_version !== 1 || typeof manifest.current !== 'string') { + if ( + manifest.schema_version !== 1 || + typeof manifest.current !== 'string' || + !['legacy', 'snapshot'].includes(manifest.stable_source ?? 'legacy') + ) { throw new Error(`${manifestPath} has an unsupported schema`); } @@ -166,24 +147,29 @@ export async function checkVersions() { if (normalizeVersion(entry.tag) !== entry.version) { throw new Error(`docs version ${entry.version} has mismatched tag ${entry.tag}`); } + const requiresCommit = + entry.source === 'docs/next/website/src/content/docs' || + ((manifest.stable_source ?? 'legacy') === 'snapshot' && entry.version === manifest.current); + if (requiresCommit && !entry.commit) { + throw new Error(`docs version ${entry.version} is missing commit provenance`); + } + if (entry.commit) { + if (!/^[0-9a-f]{40}$/.test(entry.commit)) { + throw new Error(`docs version ${entry.version} has invalid commit ${entry.commit}`); + } + const taggedCommit = resolveCommit(git, entry.tag); + if (taggedCommit !== entry.commit) { + throw new Error( + `docs version ${entry.version} tag ${entry.tag} moved from ${entry.commit} to ${taggedCommit}`, + ); + } + } if (!['website/src/content/docs', 'docs/next/website/src/content/docs'].includes(entry.source)) { throw new Error(`docs version ${entry.version} has unsupported source ${entry.source}`); } const sourceRoot = entry.source; const snapshotRoot = resolve(versionsDir, entry.version, 'website/src/content/docs'); - const expected = listGitFiles(entry.tag, sourceRoot).map((path) => relative(sourceRoot, path)).sort(); - const actual = (await listFiles(snapshotRoot)).sort(); - if (JSON.stringify(actual) !== JSON.stringify(expected)) { - throw new Error(`documentation file list for ${entry.version} differs from ${entry.tag}:${sourceRoot}`); - } - for (const relativePath of expected) { - const actualContent = await readFile(resolve(snapshotRoot, relativePath)); - const taggedPath = `${sourceRoot}/${relativePath.split('\\').join('/')}`; - const taggedContent = git(['show', `${entry.tag}:${taggedPath}`], { binary: true }); - if (!actualContent.equals(taggedContent)) { - throw new Error(`${entry.version}/${relativePath} differs from ${entry.tag}:${sourceRoot}`); - } - } + await compareGitTree(git, entry.tag, sourceRoot, snapshotRoot); const referenceSource = sourceRoot.includes('docs/next/') ? 'docs/next/website/src/data/config-reference.json' @@ -193,7 +179,7 @@ export async function checkVersions() { entry.version, 'website/src/data/config-reference.json', ); - if (gitPathExists(entry.tag, referenceSource)) { + if (gitPathExists(git, entry.tag, referenceSource)) { const actualReference = await readFile(referenceSnapshot); const taggedReference = git(['show', `${entry.tag}:${referenceSource}`], { binary: true }); if (!actualReference.equals(taggedReference)) { @@ -217,32 +203,64 @@ export async function checkVersions() { export async function publishVersion(tag) { const release = releaseMetadata(tag); - if (!gitPathExists(tag, 'docs/next/website/src/content/docs')) { + if (!gitPathExists(git, tag, 'docs/next/website/src/content/docs')) { throw new Error(`${tag} does not contain staged website documentation`); } const manifest = await readManifest(); const existing = manifest.versions.find((entry) => entry.version === release.version); - if (existing && existing.tag !== release.tag) { - throw new Error(`version ${release.version} is already associated with ${existing.tag}`); + if (manifest.current === release.version) { + if ((manifest.stable_source ?? 'legacy') === 'legacy') { + process.stdout.write(`documentation ${release.tag} is already published with the legacy stable source\n`); + return; + } + if (!existing?.commit || existing.commit !== release.commit) { + throw new Error(`published documentation ${release.version} has mismatched commit provenance`); + } + await compareGitTree(git, release.commit, existing.source, resolve(versionsDir, release.version, 'website/src/content/docs')); + process.stdout.write(`documentation snapshot ${release.tag} is already published\n`); + return; + } + if (manifest.current && compareVersions(release.version, manifest.current) < 0) { + if (existing) { + if (existing.tag !== release.tag) { + throw new Error(`version ${release.version} is already associated with ${existing.tag}`); + } + if (existing.commit && existing.commit !== release.commit) { + throw new Error(`version ${release.version} has mismatched commit provenance`); + } + await compareGitTree( + git, + release.commit, + existing.source, + resolve(versionsDir, release.version, 'website/src/content/docs'), + ); + process.stdout.write(`documentation snapshot ${release.tag} is already archived\n`); + return; + } + const archived = await snapshotTag(tag, 'docs/next/website/src/content/docs'); + const archivedEntries = new Map(manifest.versions.map((entry) => [entry.version, entry])); + archivedEntries.set(archived.version, archived); + await writeManifest({ + ...manifest, + versions: [...archivedEntries.values()], + }); + process.stdout.write( + `archived documentation snapshot ${release.tag} without changing current ${manifest.current}\n`, + ); + return; + } + if (existing) { + throw new Error(`version ${release.version} already has a documentation snapshot`); } const metadata = await snapshotTag(tag, 'docs/next/website/src/content/docs'); - const snapshotRoot = resolve(versionsDir, metadata.version, 'website'); - await replaceDirectory(resolve(snapshotRoot, 'src/content/docs'), stableDocsDir); - - const snapshotReference = resolve(snapshotRoot, 'src/data/config-reference.json'); - try { - await mkdir(dirname(stableReferencePath), { recursive: true }); - await writeFile(stableReferencePath, await readFile(snapshotReference)); - } catch (error) { - if (error.code !== 'ENOENT') throw error; - await rm(stableReferencePath, { force: true }); - } + await rm(stableDocsDir, { recursive: true, force: true }); + await rm(stableReferencePath, { force: true }); for (const readme of ['README.md', 'README.zh-CN.md']) { const nextReadme = `docs/next/${readme}`; - if (gitPathExists(tag, nextReadme)) { + if (gitPathExists(git, tag, nextReadme)) { await writeFile(resolve(repoRoot, readme), git(['show', `${tag}:${nextReadme}`], { binary: true })); } } @@ -251,43 +269,13 @@ export async function publishVersion(tag) { entries.set(metadata.version, metadata); await writeManifest({ schema_version: 1, + stable_source: 'snapshot', current: metadata.version, versions: [...entries.values()], }); process.stdout.write(`published documentation snapshot ${metadata.tag}\n`); } -async function replaceDirectory(source, destination) { - await rm(destination, { recursive: true, force: true }); - await copyDirectory(source, destination); -} - -async function listFiles(root) { - const files = []; - async function walk(directory) { - for (const entry of await readdir(directory, { withFileTypes: true })) { - const path = join(directory, entry.name); - if (entry.isDirectory()) await walk(path); - else if (entry.isFile()) files.push(relative(root, path)); - } - } - await walk(root); - return files; -} - -async function copyDirectory(source, destination) { - await mkdir(destination, { recursive: true }); - for (const entry of await readdir(source, { withFileTypes: true })) { - const sourcePath = join(source, entry.name); - const destinationPath = join(destination, entry.name); - if (entry.isDirectory()) { - await copyDirectory(sourcePath, destinationPath); - } else if (entry.isFile()) { - await writeFile(destinationPath, await readFile(sourcePath)); - } - } -} - async function main() { const [command, value] = process.argv.slice(2); if (command === 'backfill' && !value) { @@ -302,7 +290,14 @@ async function main() { await checkVersions(); return; } - throw new Error('usage: node website/scripts/docs-versions.mjs backfill | check | publish <tag>'); + if (command === 'current' && !value) { + const manifest = await readManifest(); + process.stdout.write(`${manifest.current ?? ''}\n`); + return; + } + throw new Error( + 'usage: node website/scripts/docs-versions.mjs backfill | check | current | publish <tag>', + ); } if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { diff --git a/website/scripts/prepare-docs.mjs b/website/scripts/prepare-docs.mjs index 6b5c4b5f..a96e2c41 100644 --- a/website/scripts/prepare-docs.mjs +++ b/website/scripts/prepare-docs.mjs @@ -4,18 +4,20 @@ import { fileURLToPath } from 'node:url'; import process from 'node:process'; const websiteDir = dirname(fileURLToPath(import.meta.url)); -const repoRoot = resolve(websiteDir, '../..'); +const repoRoot = process.env.HERDR_DOCS_REPO_ROOT + ? resolve(process.env.HERDR_DOCS_REPO_ROOT) + : resolve(websiteDir, '../..'); const publicDir = resolve(repoRoot, 'website/public'); const stableDocsDir = resolve(repoRoot, 'website/src/content/docs'); -const previewDocsSourceDir = resolve(repoRoot, 'docs/next/website/src/content/docs'); const previewDocsDir = resolve(stableDocsDir, 'preview'); const generatedVersionsDocsDir = resolve(stableDocsDir, '_versions'); const versionsDir = resolve(repoRoot, 'docs/versions'); const versionsManifestPath = resolve(versionsDir, 'manifest.json'); +const previewManifestPath = resolve(repoRoot, 'website/preview.json'); const generatedVersionsDataPath = resolve(repoRoot, 'website/src/data/docs-versions.json'); -const previewConfigReferenceSource = resolve( +const stableConfigReferenceDestination = resolve( repoRoot, - 'docs/next/website/src/data/config-reference.json', + 'website/src/data/config-reference.json', ); const previewConfigReferenceDestination = resolve( repoRoot, @@ -29,7 +31,12 @@ const generatedVersionReferencesPath = resolve( if (process.argv[2] === '--rewrite-preview-doc-fixture') { const chunks = []; for await (const chunk of process.stdin) chunks.push(chunk); - process.stdout.write(rewritePreviewDocContent(Buffer.concat(chunks).toString('utf8'))); + process.stdout.write( + rewritePreviewDocContent(Buffer.concat(chunks).toString('utf8'), '', { + buildId: '2026-07-29-44b3adb12552', + commit: '44b3adb125524ea9a55739eee3776f922f2115ad', + }), + ); } else if (process.argv[2] === '--rewrite-version-doc-fixture') { const chunks = []; for await (const chunk of process.stdin) chunks.push(chunk); @@ -42,8 +49,13 @@ if (process.argv[2] === '--rewrite-preview-doc-fixture') { }), ); } else { - await preparePublicAssets(); - await prepareDocs(); + const supported = new Set(['--docs-only', '--draft']); + const unsupported = process.argv.slice(2).filter((argument) => !supported.has(argument)); + if (unsupported.length > 0) { + throw new Error('usage: node website/scripts/prepare-docs.mjs [--docs-only] [--draft]'); + } + if (!process.argv.includes('--docs-only')) await preparePublicAssets(); + await prepareDocs({ draft: process.argv.includes('--draft') }); } async function preparePublicAssets() { @@ -75,22 +87,69 @@ async function preparePublicAssets() { } } -async function prepareDocs() { - await rm(previewDocsDir, { recursive: true, force: true }); - await rm(generatedVersionsDocsDir, { recursive: true, force: true }); +async function prepareDocs({ draft }) { + const manifest = JSON.parse(await readFile(versionsManifestPath, 'utf8')); + if ( + manifest.schema_version !== 1 || + typeof manifest.current !== 'string' || + !['legacy', 'snapshot'].includes(manifest.stable_source ?? 'legacy') + ) { + throw new Error(`${versionsManifestPath} has an unsupported schema`); + } + const currentEntry = manifest.versions.find((entry) => entry.version === manifest.current); + if (!currentEntry) throw new Error(`current docs version ${manifest.current} has no snapshot`); + + const previewManifest = JSON.parse(await readFile(previewManifestPath, 'utf8')); + if (!/^[0-9a-f]{40}$/.test(previewManifest.commit ?? '')) { + throw new Error(`${previewManifestPath} must contain a full preview commit SHA`); + } + const previewWebsiteRoot = resolve(repoRoot, draft ? 'docs/next/website' : 'docs/preview/website'); + const previewDocsSourceDir = resolve(previewWebsiteRoot, 'src/content/docs'); + const previewConfigReferenceSource = resolve( + previewWebsiteRoot, + 'src/data/config-reference.json', + ); + + if ((manifest.stable_source ?? 'legacy') === 'snapshot') { + await rm(stableDocsDir, { recursive: true, force: true }); + const currentSnapshotRoot = resolve(versionsDir, manifest.current, 'website'); + await copyPreparedDocs( + resolve(currentSnapshotRoot, 'src/content/docs'), + stableDocsDir, + (content, relativePath) => + rewriteStableDocContent(content, { + tag: currentEntry.tag, + sourceRoot: currentEntry.source, + relativePath, + }), + ); + const currentReference = resolve(currentSnapshotRoot, 'src/data/config-reference.json'); + try { + await cp(currentReference, stableConfigReferenceDestination); + } catch (error) { + if (error.code !== 'ENOENT') throw error; + await rm(stableConfigReferenceDestination, { force: true }); + } + } else { + await rm(previewDocsDir, { recursive: true, force: true }); + await rm(generatedVersionsDocsDir, { recursive: true, force: true }); + } await copyPreparedDocs(previewDocsSourceDir, previewDocsDir, (content, relativePath) => - rewritePreviewDocContent(content, relativePath), + rewritePreviewDocContent(content, relativePath, { + buildId: draft ? 'draft' : previewManifest.build_id, + commit: draft ? 'master' : previewManifest.commit, + }), ); await cp(previewConfigReferenceSource, previewConfigReferenceDestination); - const manifest = JSON.parse(await readFile(versionsManifestPath, 'utf8')); - if (manifest.schema_version !== 1 || typeof manifest.current !== 'string') { - throw new Error(`${versionsManifestPath} has an unsupported schema`); - } - const scopes = { - stable: await collectDocsScope(stableDocsDir, new Set(['preview', '_versions'])), + stable: await collectDocsScope( + (manifest.stable_source ?? 'legacy') === 'snapshot' + ? resolve(versionsDir, manifest.current, 'website/src/content/docs') + : stableDocsDir, + new Set(['preview', '_versions']), + ), preview: await collectDocsScope(previewDocsSourceDir), }; const configReferences = {}; @@ -123,7 +182,14 @@ async function prepareDocs() { await writeFile( generatedVersionsDataPath, - `${JSON.stringify({ ...manifest, scopes }, null, 2)}\n`, + `${JSON.stringify({ + ...manifest, + preview: { + build_id: draft ? 'draft' : previewManifest.build_id, + commit: draft ? 'master' : previewManifest.commit, + }, + scopes, + }, null, 2)}\n`, 'utf8', ); await writeFile( @@ -183,29 +249,39 @@ async function collectDocsScope(sourceDir, excludedDirectories = new Set()) { return { locales }; } -export function rewritePreviewDocContent(content, relativePath = '') { - const rewritten = rewriteRelativeDocPaths( +export function rewritePreviewDocContent( + content, + relativePath = '', + { buildId, commit } = { + buildId: 'preview', + commit: 'master', + }, +) { + const taggedContent = rewriteRepositoryLinks( content.replaceAll('/docs/', '/docs/preview/'), - 1, + commit, ); - const withEditLink = setGeneratedEditUrl( + const rewritten = rewriteRelativeDocPaths(taggedContent, 1); + const withSourceLink = setGeneratedEditUrl( rewritten, - `https://github.com/herdrdev/herdr/edit/master/docs/next/website/src/content/docs/${relativePath}`, + `https://github.com/herdrdev/herdr/blob/${commit}/docs/next/website/src/content/docs/${relativePath}`, + ); + return insertPreviewNotice(withSourceLink, relativePath, { buildId, commit }); +} + +export function rewriteStableDocContent(content, { tag, sourceRoot, relativePath }) { + const taggedContent = rewriteRepositoryLinks(content, tag); + return setGeneratedEditUrl( + taggedContent, + `https://github.com/herdrdev/herdr/blob/${tag}/${sourceRoot}/${relativePath}`, ); - return insertPreviewNotice(withEditLink, relativePath); } export function rewriteVersionDocContent(content, { version, tag, sourceRoot, relativePath }) { - const taggedContent = content - .replaceAll('/docs/', `/docs/${version}/`) - .replaceAll( - 'https://github.com/herdrdev/herdr/blob/master/', - `https://github.com/herdrdev/herdr/blob/${tag}/`, - ) - .replaceAll( - 'https://raw.githubusercontent.com/herdrdev/herdr/master/', - `https://raw.githubusercontent.com/herdrdev/herdr/${tag}/`, - ); + const taggedContent = rewriteRepositoryLinks( + content.replaceAll('/docs/', `/docs/${version}/`), + tag, + ); const rewritten = rewriteRelativeDocPaths(taggedContent, 2); return setGeneratedEditUrl( rewritten, @@ -213,6 +289,18 @@ export function rewriteVersionDocContent(content, { version, tag, sourceRoot, re ); } +function rewriteRepositoryLinks(content, ref) { + return content + .replaceAll( + 'https://github.com/herdrdev/herdr/blob/master/', + `https://github.com/herdrdev/herdr/blob/${ref}/`, + ) + .replaceAll( + 'https://raw.githubusercontent.com/herdrdev/herdr/master/', + `https://raw.githubusercontent.com/herdrdev/herdr/${ref}/`, + ); +} + function rewriteRelativeDocPaths(content, extraDepth) { const parents = '../'.repeat(extraDepth); return content @@ -225,15 +313,16 @@ function setGeneratedEditUrl(content, editUrl) { return content.replace(/^---\n/, `---\neditUrl: ${editUrl}\n`); } -function insertPreviewNotice(content, relativePath) { +function insertPreviewNotice(content, relativePath, { buildId, commit }) { + const source = commit === 'master' ? '`master` draft' : `[${commit.slice(0, 12)}](https://github.com/herdrdev/herdr/commit/${commit})`; const notice = [ - '> Next docs describe unreleased work from `master`. Stable docs remain at [/docs/](/docs/).', + `> Preview build \`${buildId}\`, published from ${source}. Stable docs remain at [/docs/](/docs/).`, '', '', ].join('\n'); const indexPrefix = relativePath === 'index.mdx' - ? content.replace('title: Herdr documentation', 'title: Herdr next documentation') + ? content.replace('title: Herdr documentation', 'title: Herdr preview documentation') : content; const frontmatter = indexPrefix.match(/^---\n[\s\S]*?\n---\n/); if (!frontmatter) { diff --git a/website/src/components/DocsVersionSelect.astro b/website/src/components/DocsVersionSelect.astro index cc6ea67f..5ccd856c 100644 --- a/website/src/components/DocsVersionSelect.astro +++ b/website/src/components/DocsVersionSelect.astro @@ -5,9 +5,9 @@ import { docsRoute, docsTargetHref } from '../docs-path'; const route = docsRoute(Astro.url.pathname); const locale = route.locale ?? 'root'; const labels = { - root: { accessible: 'Documentation version', latest: 'Latest', next: 'Unreleased' }, - ja: { accessible: 'ドキュメントのバージョン', latest: '最新', next: '未リリース' }, - 'zh-cn': { accessible: '文档版本', latest: '最新', next: '未发布' }, + root: { accessible: 'Documentation version', latest: 'Latest', preview: 'Preview' }, + ja: { accessible: 'ドキュメントのバージョン', latest: '最新', preview: 'プレビュー' }, + 'zh-cn': { accessible: '文档版本', latest: '最新', preview: '预览版' }, }[locale]; const scopes = versions.scopes; --- @@ -26,7 +26,7 @@ const scopes = versions.scopes; value={docsTargetHref(route, 'preview', scopes)} selected={route.target === 'preview'} > - {labels.next} + {labels.preview} </option> {versions.versions .filter((entry) => entry.version !== versions.current || route.target === versions.current)