Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .cargo/mutants.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@
# - submit_resource_action is the thin authenticated wrapper around the
# GraphQL transport; its request construction and error handling are unit
# tested through submit_resource_action_with.
# - Windows control transport behavior is covered by native Windows tests and
# cannot be exercised by the Ubuntu mutation job because the module is
# excluded at compile time by cfg(windows).
exclude_globs = ["src/control/transport/windows.rs"]

exclude_re = [
"replace run_tui .* with Ok\\(\\(\\)\\)",
"replace restore_snapshot with \\(\\)",
Expand Down
25 changes: 25 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,28 @@ jobs:

- name: Run local CI gate
run: scripts/ci-local.sh

windows:
name: windows
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v5

- name: Install Rust
run: |
rustup toolchain install stable --profile minimal
rustup default stable
rustup component add clippy rustfmt

- name: Check formatting
run: cargo fmt --check

- name: Check all targets
run: cargo check --all-targets

- name: Run tests
run: cargo test

- name: Run Clippy
run: cargo clippy --all-targets --all-features -- -D warnings
13 changes: 13 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ path = "src/bin/ghzinga.rs"
anyhow = "1"
clap = { version = "4", features = ["derive"] }
crossterm = { version = "0.29", features = ["bracketed-paste"] }
fs2 = "0.4"
ratatui = "0.30"
regex = "1"
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
Expand All @@ -47,6 +48,14 @@ tokio = { version = "1", features = ["io-util", "macros", "net", "process", "rt-
toml = "0.8"
unicode-width = "0.2"

[target.'cfg(windows)'.dependencies]
getrandom = "0.3"
windows-sys = { version = "0.61", features = [
"Win32_Foundation",
"Win32_Security",
"Win32_Security_Authorization",
] }

[dev-dependencies]
assert_cmd = "2"
predicates = "3"
Expand Down
15 changes: 11 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ Install from crates.io:
cargo install ghzinga
```

The standalone CLI and TUI support Linux, macOS, and Windows. Live session
control uses Unix domain sockets on Unix and authenticated, owner-only named
pipes on Windows. The optional Herdr plugin currently supports Linux and macOS.

Install from a local checkout:

```sh
Expand Down Expand Up @@ -156,7 +160,8 @@ permission.
The config file is:

```text
~/.config/ghzinga/config.toml
Linux/macOS: ~/.config/ghzinga/config.toml
Windows: %APPDATA%\ghzinga\config.toml
```

Default config:
Expand Down Expand Up @@ -196,20 +201,22 @@ Supported setting values:
`ghzinga` saves open PR/issue tabs and UI state under:

```text
~/.local/state/ghzinga
Linux/macOS: ~/.local/state/ghzinga
Windows: %LOCALAPPDATA%\ghzinga\state
```

Cached GitHub resource snapshots are stored separately under:

```text
~/.cache/ghzinga
Linux/macOS: ~/.cache/ghzinga
Windows: %LOCALAPPDATA%\ghzinga\cache
```

Environment overrides:

- `GZG_STATE_HOME`: alternate session state directory
- `GZG_CACHE_HOME`: alternate resource cache directory
- `GZG_RUNTIME_HOME`: alternate runtime socket directory for live session control
- `GZG_RUNTIME_HOME`: alternate runtime namespace for live session control
- `GZG_SESSION`: default named session

Session commands:
Expand Down
4 changes: 2 additions & 2 deletions captures/ghzinga-issue-88499/large/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
"title": "openai-responses provider: 404 on previous_response_id when store=false (default)",
"mode": "issue",
"binary": "target/debug/gzg",
"git_commit": "6f6025ba04b4163207e40f9c2db7d587171d0b65",
"app_tree_hash": "77e909b0100ae5aabbd3f1835c7818b33714b140fbdf25300bb7f848c483c8e2",
"git_commit": "4610a3b31158270e104a04acbd239c1146cbdb7d",
"app_tree_hash": "8602f176e5e95474cc1fec42d52947f8bba6b6c4233c59edd5ff0f4059137015",
"config_path": "captures/ghzinga-issue-88499/capture-empty-config.toml",
"requested_columns": 160,
"requested_rows": 50,
Expand Down
4 changes: 2 additions & 2 deletions captures/ghzinga-issue-88499/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
"title": "openai-responses provider: 404 on previous_response_id when store=false (default)",
"mode": "issue",
"binary": "target/debug/gzg",
"git_commit": "6f6025ba04b4163207e40f9c2db7d587171d0b65",
"app_tree_hash": "77e909b0100ae5aabbd3f1835c7818b33714b140fbdf25300bb7f848c483c8e2",
"git_commit": "4610a3b31158270e104a04acbd239c1146cbdb7d",
"app_tree_hash": "8602f176e5e95474cc1fec42d52947f8bba6b6c4233c59edd5ff0f4059137015",
"config_path": "captures/ghzinga-issue-88499/capture-empty-config.toml",
"offline_fixture": "fixtures/issue-88499.json",
"offline_resource_fixtures": [],
Expand Down
4 changes: 2 additions & 2 deletions captures/ghzinga-issue-88499/medium/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
"title": "openai-responses provider: 404 on previous_response_id when store=false (default)",
"mode": "issue",
"binary": "target/debug/gzg",
"git_commit": "6f6025ba04b4163207e40f9c2db7d587171d0b65",
"app_tree_hash": "77e909b0100ae5aabbd3f1835c7818b33714b140fbdf25300bb7f848c483c8e2",
"git_commit": "4610a3b31158270e104a04acbd239c1146cbdb7d",
"app_tree_hash": "8602f176e5e95474cc1fec42d52947f8bba6b6c4233c59edd5ff0f4059137015",
"config_path": "captures/ghzinga-issue-88499/capture-empty-config.toml",
"requested_columns": 120,
"requested_rows": 36,
Expand Down
4 changes: 2 additions & 2 deletions captures/ghzinga-issue-88499/mouse-smoke/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
"fixtures/issue-66943.json"
],
"binary": "target/debug/gzg",
"git_commit": "6f6025ba04b4163207e40f9c2db7d587171d0b65",
"app_tree_hash": "77e909b0100ae5aabbd3f1835c7818b33714b140fbdf25300bb7f848c483c8e2",
"git_commit": "4610a3b31158270e104a04acbd239c1146cbdb7d",
"app_tree_hash": "8602f176e5e95474cc1fec42d52947f8bba6b6c4233c59edd5ff0f4059137015",
"config_path": "captures/ghzinga-issue-88499/mouse-smoke/capture-empty-config.toml",
"command": "cd . && TERM=xterm-256color GZG_CONFIG_PATH=./captures/ghzinga-issue-88499/mouse-smoke/capture-empty-config.toml GZG_STATE_HOME=./captures/ghzinga-issue-88499/mouse-smoke/.capture-state GZG_CACHE_HOME=./captures/ghzinga-issue-88499/mouse-smoke/.capture-cache BROWSER=./captures/ghzinga-issue-88499/mouse-smoke/capture-open-url.sh GZG_COPY_COMMAND=./captures/ghzinga-issue-88499/mouse-smoke/capture-copy-url.sh ./target/debug/gzg https://github.com/openclaw/openclaw/issues/88499 --offline-fixture ./captures/ghzinga-issue-88499/mouse-smoke/navigation-fixture.json --offline-resource-fixture ./fixtures/issue-66943.json --no-restore --refresh-seconds 0",
"actual_tmux_size": "120x36",
Expand Down
4 changes: 2 additions & 2 deletions captures/ghzinga-issue-88499/narrow/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
"title": "openai-responses provider: 404 on previous_response_id when store=false (default)",
"mode": "issue",
"binary": "target/debug/gzg",
"git_commit": "6f6025ba04b4163207e40f9c2db7d587171d0b65",
"app_tree_hash": "77e909b0100ae5aabbd3f1835c7818b33714b140fbdf25300bb7f848c483c8e2",
"git_commit": "4610a3b31158270e104a04acbd239c1146cbdb7d",
"app_tree_hash": "8602f176e5e95474cc1fec42d52947f8bba6b6c4233c59edd5ff0f4059137015",
"config_path": "captures/ghzinga-issue-88499/capture-empty-config.toml",
"requested_columns": 80,
"requested_rows": 24,
Expand Down
4 changes: 2 additions & 2 deletions captures/ghzinga-pr-81834/large/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
"title": "feat(senseaudio): add SenseAudio TTS provider",
"mode": "pr",
"binary": "target/debug/gzg",
"git_commit": "6f6025ba04b4163207e40f9c2db7d587171d0b65",
"app_tree_hash": "77e909b0100ae5aabbd3f1835c7818b33714b140fbdf25300bb7f848c483c8e2",
"git_commit": "4610a3b31158270e104a04acbd239c1146cbdb7d",
"app_tree_hash": "8602f176e5e95474cc1fec42d52947f8bba6b6c4233c59edd5ff0f4059137015",
"config_path": "captures/ghzinga-pr-81834/capture-empty-config.toml",
"requested_columns": 160,
"requested_rows": 50,
Expand Down
4 changes: 2 additions & 2 deletions captures/ghzinga-pr-81834/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
"title": "feat(senseaudio): add SenseAudio TTS provider",
"mode": "pr",
"binary": "target/debug/gzg",
"git_commit": "6f6025ba04b4163207e40f9c2db7d587171d0b65",
"app_tree_hash": "77e909b0100ae5aabbd3f1835c7818b33714b140fbdf25300bb7f848c483c8e2",
"git_commit": "4610a3b31158270e104a04acbd239c1146cbdb7d",
"app_tree_hash": "8602f176e5e95474cc1fec42d52947f8bba6b6c4233c59edd5ff0f4059137015",
"config_path": "captures/ghzinga-pr-81834/capture-empty-config.toml",
"offline_fixture": null,
"offline_resource_fixtures": [],
Expand Down
4 changes: 2 additions & 2 deletions captures/ghzinga-pr-81834/medium/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
"title": "feat(senseaudio): add SenseAudio TTS provider",
"mode": "pr",
"binary": "target/debug/gzg",
"git_commit": "6f6025ba04b4163207e40f9c2db7d587171d0b65",
"app_tree_hash": "77e909b0100ae5aabbd3f1835c7818b33714b140fbdf25300bb7f848c483c8e2",
"git_commit": "4610a3b31158270e104a04acbd239c1146cbdb7d",
"app_tree_hash": "8602f176e5e95474cc1fec42d52947f8bba6b6c4233c59edd5ff0f4059137015",
"config_path": "captures/ghzinga-pr-81834/capture-empty-config.toml",
"requested_columns": 120,
"requested_rows": 36,
Expand Down
4 changes: 2 additions & 2 deletions captures/ghzinga-pr-81834/mouse-smoke/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
"fixtures/issue-66943.json"
],
"binary": "target/debug/gzg",
"git_commit": "6f6025ba04b4163207e40f9c2db7d587171d0b65",
"app_tree_hash": "77e909b0100ae5aabbd3f1835c7818b33714b140fbdf25300bb7f848c483c8e2",
"git_commit": "4610a3b31158270e104a04acbd239c1146cbdb7d",
"app_tree_hash": "8602f176e5e95474cc1fec42d52947f8bba6b6c4233c59edd5ff0f4059137015",
"config_path": "captures/ghzinga-pr-81834/mouse-smoke/capture-empty-config.toml",
"command": "cd . && TERM=xterm-256color GZG_CONFIG_PATH=./captures/ghzinga-pr-81834/mouse-smoke/capture-empty-config.toml GZG_STATE_HOME=./captures/ghzinga-pr-81834/mouse-smoke/.capture-state GZG_CACHE_HOME=./captures/ghzinga-pr-81834/mouse-smoke/.capture-cache BROWSER=./captures/ghzinga-pr-81834/mouse-smoke/capture-open-url.sh GZG_COPY_COMMAND=./captures/ghzinga-pr-81834/mouse-smoke/capture-copy-url.sh ./target/debug/gzg 'openclaw/openclaw#81834' --offline-fixture ./captures/ghzinga-pr-81834/mouse-smoke/navigation-fixture.json --offline-resource-fixture ./fixtures/issue-66943.json --no-restore --refresh-seconds 0",
"actual_tmux_size": "120x36",
Expand Down
4 changes: 2 additions & 2 deletions captures/ghzinga-pr-81834/narrow/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
"title": "feat(senseaudio): add SenseAudio TTS provider",
"mode": "pr",
"binary": "target/debug/gzg",
"git_commit": "6f6025ba04b4163207e40f9c2db7d587171d0b65",
"app_tree_hash": "77e909b0100ae5aabbd3f1835c7818b33714b140fbdf25300bb7f848c483c8e2",
"git_commit": "4610a3b31158270e104a04acbd239c1146cbdb7d",
"app_tree_hash": "8602f176e5e95474cc1fec42d52947f8bba6b6c4233c59edd5ff0f4059137015",
"config_path": "captures/ghzinga-pr-81834/capture-empty-config.toml",
"requested_columns": 80,
"requested_rows": 24,
Expand Down
5 changes: 3 additions & 2 deletions docs/2026-06-01-ghzinga-settings-config-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ live.
Use a TOML config file:

```text
~/.config/ghzinga/config.toml
Linux/macOS: ~/.config/ghzinga/config.toml
Windows: %APPDATA%\ghzinga\config.toml
```

Respect `XDG_CONFIG_HOME` when set:
Respect `XDG_CONFIG_HOME` when set on Unix:

```text
$XDG_CONFIG_HOME/ghzinga/config.toml
Expand Down
47 changes: 35 additions & 12 deletions docs/2026-06-05-ghzinga-session-restore-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ for GitHub resources.

## Storage Layout

Use XDG state and cache paths:
On Unix, use XDG state and cache paths:

```text
$XDG_STATE_HOME/ghzinga/sessions/<session-id>/session.json
Expand All @@ -45,6 +45,15 @@ Fallbacks:
~/.cache/ghzinga/...
```

On Windows, use native per-user directories:

```text
%LOCALAPPDATA%\ghzinga\state\sessions\<session-id>\session.json
%LOCALAPPDATA%\ghzinga\state\session-index.json
%TEMP%\ghzinga-<user>\<session-id>.pipe
%LOCALAPPDATA%\ghzinga\cache\resources\<owner>\<repo>\<number>.json
```

Test and scripting overrides:

```text
Expand Down Expand Up @@ -350,7 +359,7 @@ Control command behavior:

## Runtime Control Channel

Each running persistent TUI owns a local Unix socket:
On Unix, each running persistent TUI owns a local socket:

```text
$XDG_RUNTIME_DIR/ghzinga/<session-id>.sock
Expand All @@ -362,18 +371,31 @@ Fallback when `XDG_RUNTIME_DIR` is absent:
/tmp/ghzinga-$UID/<session-id>.sock
```

On Windows, the per-user runtime directory contains an endpoint file:

```text
%TEMP%\ghzinga-<user>\<session-id>.pipe
```

The endpoint file points to a cryptographically random, owner-only local named
pipe and carries the random authentication token used by control commands.

Rules:

- Socket paths are runtime-only and are not the session source of truth.
- On TUI startup, create the socket after the session id is resolved and remove
stale sockets for the same session when the owning process is gone.
- The socket should accept newline-delimited JSON commands so it can evolve
without breaking older clients.
- Control endpoints are runtime-only and are not the session source of truth.
- On TUI startup, create the platform endpoint after the session id is resolved
and remove stale endpoint metadata for the same session.
- Hold an exclusive per-session runtime lock for the server lifetime so endpoint
publication and cleanup cannot race with another process.
- Windows named pipes must reject remote clients, use an owner-only DACL, and
authenticate commands using an unguessable per-run token.
- The control transport accepts bounded newline-delimited JSON commands so it
can evolve without breaking older clients.
- Commands must include a schema version and command id.
- Replies must include `ok`, the command id, and either a short result or an
error.
- Replies must include `ok`, the matching command id, and either a short result
or an error.
- Unsupported command versions should fail cleanly with a user-facing error.
- A command sent to a stale socket should fail fast, remove the stale runtime
- A command sent to a stale endpoint should fail fast, remove the stale runtime
marker if safe, and fall back to saved-state mutation when appropriate.
- Runtime control is local only. Do not expose a network listener.
- File and socket permissions should be owner-only where the platform permits
Expand All @@ -398,7 +420,8 @@ Initial reply shapes:
Cache normalized GitHub resources separately from session files:

```text
$XDG_CACHE_HOME/ghzinga/resources/osolmaz/ghzinga/28.json
Unix: $XDG_CACHE_HOME/ghzinga/resources/osolmaz/ghzinga/28.json
Windows: %LOCALAPPDATA%\ghzinga\cache\resources\osolmaz\ghzinga\28.json
```

Cache metadata should include:
Expand Down Expand Up @@ -484,7 +507,7 @@ Do not auto-delete sessions silently in the first version.
width mode, fixed width, and scrollbar visibility.
- Reuse existing app actions for opening/focusing tabs and applying settings so
runtime commands behave the same as keyboard/mouse UI commands.
- Add tests for running-session command delivery, stale socket fallback,
- Add tests for running-session command delivery, stale endpoint fallback,
ambiguous session resolution, saved-state mutation, and invalid setting
errors.

Expand Down
3 changes: 3 additions & 0 deletions slophammer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ rust:
min_tokens: 100
unsafe:
policy: forbid
allow:
- path: "src/control/transport/windows.rs"
reason: narrowly scoped Windows security-descriptor FFI required for owner-only named-pipe ACLs
mutation:
targets:
- src/app
Expand Down
Loading