mirror of
https://github.com/l0ng-ai/tty7.git
synced 2026-11-06 08:15:35 +00:00
* feat(panel): add Search and GitHub tabs to the right panel The right panel grows from three tabs to five. Five word labels do not fit the panel's 280px resting width, so the tab row now draws a glyph per tab and names it in a tooltip. Both new panes are placeholders here; the content search and the GitHub issues/PR browser land on top of this. * feat(panel): find in files in the right panel's Search tab The Search tab replaces its placeholder with a content search over the active tab's project -- the same roots the Files tab shows -- on the host that project lives on. Hits arrive as you type (debounced, with a generation counter so a stale answer never lands), grouped by file with a count, each line excerpted with its matches highlighted. Clicking a hit opens the built-in editor at that line and column; Enter searches again. Match-case, whole-word and regex toggles sit at the end of the field, and the tab focuses its field whenever it is brought forward. Host::search_content is new on the Host trait, implemented once in host::content_search (ignore walk + regex) and run by LocalHost directly and by tty7-server over a new SearchContent control request. The walk honours .gitignore with or without a repository, skips dot-entries, binary files and files over 1 MB, and reports a capped search as truncated. The request is gated on a new `content-search` hello feature, so a server that predates it is never sent it; the panel says the server needs updating instead of showing no results. Conformance cases cover local and the stdio server alike. * feat(panel): browse GitHub issues and pull requests in the right panel The GitHub tab follows the focused pane's repository: its root is resolved the way the Source Control tab does, its remotes are read through the Host (the tree may be on another machine), and the github.com remote is bound, upstream over origin in a fork, with a menu to pick another. The list switches between issues and pull requests, open and closed, 50 rows a page with Load more; rows carry a state glyph distinct by shape, labels (click one to filter by it) and relative times. A row opens the detail in place: title, state, author, labels, description and comments as Markdown, and for a pull request its branches, size and changed files. A file opens in the diff overlay through a new supplied-patch DiffSource, so GitHub's patch renders exactly like a local one without a git probe. Read-only, and sign-in reuses the GitHub CLI: GH_TOKEN, GITHUB_TOKEN, then `gh auth token`, found on PATH or at the Homebrew locations a Finder launch cannot see. Signed out, public repositories still work; 401, 403, 404 and rate limits are told apart and explained. Requests go out from this machine over the installer's ureq stack and proxy settings, on threads of their own, cached per repository with background revalidation. Remote images in issue text become links instead of loading, and non-web link targets are disarmed. The Info tab gains a GitHub row that opens the branch on the remote it tracks, or the repository for a branch never pushed. * docs: list ShowRightPanelGitHub with the other panel actions * feat(panel): one-line GitHub rows, a pill for the current tab - GitHub list rows are one line: state glyph, #number, title. Labels and the age of the last update appear on hover, from state rather than a group_hover display switch, which gpui cannot paint. - The current right panel tab sits on the sidebar's selected fill; ink alone could not tell five same-weight glyphs apart. - The GitHub glyph is a 1.8px outline like the other tab icons, not the filled mark. - The detail byline names both times (opened / updated) so it no longer reads as disagreeing with the list's update age. * feat(github): show screenshots pasted into issues Images GitHub hosts itself (github.com/user-attachments, a repo's /assets, *.githubusercontent.com) now render in issue and PR text, each in a paragraph of its own so the text view draws it at its size rather than at line height. Images from any other host stay links, so opening an issue still tells no third party that you read it. gpui held a null HTTP client, so no remote image could load; the app now installs the update check's reqwest client (same user agent and proxy) at launch. * fix(github): load private-repo screenshots, give inline code a neutral fill - Pasted attachments (github.com/user-attachments/assets/<uuid>) want a browser session on a private repository, which an API token is not. The detail and comment requests now ask for the full media type, and each attachment is swapped for the signed private-user-images URL the rendered body_html carries for the same uuid. - Inline code in rendered Markdown (the GitHub tab and the editor's preview) sits on a faint neutral fill instead of the theme accent, which is also the selection colour. Needs gpui-component 6af19d91 for TextViewStyle::inline_code_background. * style(panel): tidy the GitHub and Search tabs' top rows - GitHub drops its heading row on macOS. It existed only to hold the refresh tile, and no other tab has one; refresh now sits with the repository's other actions, in the repo row and a detail's header. - Search's Aa / ab / .* toggles are muted while off instead of body ink. - Search's idle note puts the folder on its own line, spelled ~/…, so the narrow column no longer breaks the path at a slash. * feat(panel): order the right panel's tabs Info, Files, Search, Changes, GitHub Info stays first as the default and the pane's overview; after it come two pairs, the project's files (Files, Search) and its version control from local to remote (Changes, GitHub), where Changes and GitHub were split by the file tabs before. The palette, the Keybindings list and the docs follow the same order. * style(panel): drop the change count from the Changes tab Beside one glyph of five, the number read as a badge on that tab alone, and the Changes tab already leads with the same count under its own heading. right_panel_tabs no longer needs the row's width, which it only measured to decide whether the count fit. * style(icons): fit the GitHub glyph to the other tab icons' size The Lucide mark filled its whole 24px box, edge to edge, where tty7's own icons keep about 3.5px clear, so at 15px it drew a size larger than the four tabs beside it. Scale it to 0.9 about the centre, and raise the stroke to 2.0 so it still renders at the others' 1.8. * style(icons): a simpler GitHub glyph Drop the Lucide mark's tail and redraw the head and legs on tty7's own grid: the same ~15px live area and 1.8 stroke as the other tab icons, no scale transform. The legs keep it reading as the Octocat; a head alone read as any cat. * test(github): find gh on PATH in the blank-variable token test The test placed gh only at /opt/homebrew/bin/gh, which gh_candidates never offers on Windows, so the Windows CI job panicked at unwrap. Put gh on a PATH directory spelled with the platform's exe name instead. * fix(github): close image and link bypasses in the issue Markdown sanitiser Checked against markdown-rs (the parser TextView uses), several inputs got past the line-based rewrite: - is_github_hosted cut the host only at `/`, so `https://evil.io?.githubusercontent.com/x.png` (and `#`, `\`, `/`) counted as GitHub-hosted and was fetched from evil.io. The host now ends at the first of `/?#\` and may hold only DNS characters. - `<img src>` values were written into `` unescaped, so a `)` in the value closed the image and opened a second one from any host. Written destinations are now percent-encoded. - `<image>` (which the HTML parser reads as `<img>`) passed as an ordinary tag and loaded its src. - A kept link target was copied without scanning; when the parser ended the link elsewhere (open title, unbalanced paren) a `` inside it came alive. Markup characters in it are now encoded. - `file:///...` and similar character references passed is_safe_target and decoded to a `file:` link. References are decoded before judging. The rewrite still cannot see every construct the way the parser does (code spans inside tag attributes, fences the parser rejects, multi-line link definitions), so the detail view now also checks the parsed tree: a block containing a non-GitHub image, an unsafe link or definition, or raw `<img>` is drawn as its plain source instead. * fix(github): hide gh's console, bound Retry-After, and reject URL authorities with ?#\ - run gh through proc::output_within with hide_console, so a Windows GUI launch does not flash a console window and stdout is drained while gh runs. - saturating_add a hostile Retry-After instead of overflowing i64. - parse_github_url no longer accepts `https://evil.io#@github.com/o/r`. * fix(search): no panic on an unbounded time budget, and read files through the size cap ContentLimits arrive off the wire on a server; Instant + u64::MAX ms panicked. A file that grew between the size check and the read was read whole; it is now read through a take() at the cap. * fix(panel): keep Load more on an empty filtered page, and drop another host's hits - /issues pages filtered to one kind can come back empty while later pages hold matches; the GitHub list said "No issues" and hid Load more. It now reads on through up to five such pages and keeps Load more offered. - While a new search runs, the previous hits stay on screen; if they came from another host, a click opened their path on the active host. They are now kept only when the host is the same.
249 lines
12 KiB
TOML
249 lines
12 KiB
TOML
[package]
|
||
name = "tty7-core"
|
||
version.workspace = true
|
||
edition.workspace = true
|
||
description = "tty7's framework-free core: wire protocol, session daemon, PTY, SSH engine, and the domain model both the GUI and the headless server build on"
|
||
repository = "https://github.com/l0ng-ai/tty7"
|
||
license = "Apache-2.0"
|
||
publish = false
|
||
|
||
# The whole point of this crate is that it does *not* depend on gpui. Everything
|
||
# here has to compile and run on a headless Linux box (that is what
|
||
# `tty7-server` is), so nothing windowing-, rendering- or GUI-shaped belongs in
|
||
# these dependencies.
|
||
[dependencies]
|
||
anyhow.workspace = true
|
||
log.workspace = true
|
||
serde = { workspace = true }
|
||
serde_json.workspace = true
|
||
|
||
# SSH connection-manager data layer (`core::ssh_profile` / `core::keychain`) and
|
||
# workspace identity (`core::session`). `uuid` mints stable ids (v4) and
|
||
# serde-serializes them as strings.
|
||
#
|
||
# These live here rather than in the GUI because `Config` embeds
|
||
# `Vec<SshProfile>` and `config.json` has to parse identically on the server.
|
||
#
|
||
# Deliberately *absent*: `keyring`. Nothing in this crate reads or writes a
|
||
# secret — the daemon gets them pre-resolved on the wire (`NativeSshSpec`) — and
|
||
# a headless `tty7-server` has no OS keychain to read from, so the vault's
|
||
# storage half lives in the GUI crate instead (`tty7::core::keychain`). Keeping
|
||
# it out here is what stops a static server binary from linking
|
||
# `zbus`/`secret-service` and 30-odd crates behind them for code it can never
|
||
# call. What stays is the *naming* half: `CredentialRef` and the account scheme,
|
||
# which `config.json` parsing needs.
|
||
uuid = { version = "1", features = ["v4", "serde"] }
|
||
|
||
# Two unrelated hashes, both server-side: `daemon::install::checksums` verifies a
|
||
# downloaded `tty7-server` asset against the release's sha256 manifest, and
|
||
# `core::keychain::key_account_from_contents` derives the sha512-hex account key
|
||
# a private key's passphrase is stored under (PRD §7.2) — the account *name* is
|
||
# part of the persisted config contract, so it belongs next to `CredentialRef`
|
||
# even though only the GUI computes one today.
|
||
sha2 = "0.11"
|
||
|
||
# The gitignore matcher chain (`core::gitignore`) the file tree dims entries
|
||
# with — the same crate ripgrep uses. Lives here rather than in the GUI because
|
||
# the remote server has to answer "is this path ignored?" with the identical
|
||
# implementation.
|
||
ignore = "0.4"
|
||
|
||
# The matcher behind `Host::search_content` (the right panel's Search tab):
|
||
# a literal, whole-word or regular-expression query compiled once and run over
|
||
# each file the `ignore` walk above hands it. Lives here for the same reason
|
||
# `ignore` does — a remote `tty7-server` answers the search with the identical
|
||
# implementation. Already in the tree via the GUI and `ignore`, so this pins no
|
||
# new code.
|
||
regex = "1"
|
||
|
||
# Format-preserving TOML editing for `core::agent_hooks`: Kimi Code takes its
|
||
# hooks as `[[hooks]]` entries in the same `config.toml` that holds the user's
|
||
# providers, models and comments, so installing must edit that file in place
|
||
# without reformatting it — which rules out the plain `toml` crate's
|
||
# parse-and-reserialize round trip. Already in the tree transitively, so this
|
||
# pins no new code.
|
||
toml_edit = "0.25"
|
||
|
||
# Lane assignment for the commit graph (`core::git::log`) keeps a couple of
|
||
# parents and a handful of edges per row; a SmallVec keeps those off the heap
|
||
# for the shapes that make up almost all of a real history. Already in the tree
|
||
# via the GUI, so this pins no new code.
|
||
smallvec.workspace = true
|
||
|
||
# Cross-platform PTY for the daemon: a Unix pty on Unix, ConPTY on Windows,
|
||
# behind one blocking `Read`/`Write`/`resize` API. This is what lets
|
||
# `daemon::pane` share a single code path across platforms instead of
|
||
# hand-rolling fd/ioctl/signal code.
|
||
portable-pty = "0.8"
|
||
|
||
# Native (pure-Rust) SSH client for the daemon's russh session engine
|
||
# (`daemon::ssh`) — see the root manifest's note for why we own this stack
|
||
# instead of shelling out to `ssh`.
|
||
# HTTPS client for the remote-server installer (`daemon::install::download`).
|
||
# The GUI's own update check rides `reqwest_client`, which wraps Zed's reqwest
|
||
# fork behind `gpui::http_client` — unavailable here, since this crate must not
|
||
# depend on gpui. `ureq` is blocking (matching the installer, which runs on a
|
||
# daemon std thread), rustls-based (no OpenSSL to find at build time), and
|
||
# reuses the `rustls`/`http` versions already in the tree. `gzip` is off: the
|
||
# assets are already-compressed binaries.
|
||
#
|
||
# Optional, and off by default, so the static musl `tty7-server` never links a
|
||
# TLS stack for a code path it cannot take — it *is* the binary being
|
||
# downloaded. The GUI package turns the feature on (see the root `Cargo.toml`).
|
||
ureq = { version = "3", default-features = false, features = [
|
||
"rustls",
|
||
"win-system-proxy",
|
||
"socks-proxy",
|
||
], optional = true }
|
||
|
||
russh = "0.62"
|
||
russh-sftp = "2"
|
||
|
||
# tokio powers only the russh session engine — a single runtime `daemon::ssh`
|
||
# owns. The daemon's PTY/reader/writer threads remain std threads and never
|
||
# touch it; they cross into async through bounded/unbounded channels.
|
||
tokio = { version = "1", features = [
|
||
"rt-multi-thread",
|
||
"net",
|
||
"io-util",
|
||
"sync",
|
||
"time",
|
||
"macros",
|
||
"process",
|
||
"fs",
|
||
] }
|
||
|
||
# Filesystem watching for `host::local` (`Host::watch`): one non-recursive
|
||
# watcher per expanded directory, coalesced into 100ms batches. Same crate and
|
||
# version the GUI already used for the file tree, so the server watches a remote
|
||
# tree exactly the way the client watched a local one.
|
||
notify = "8"
|
||
|
||
# `smol::channel` carries watch batches out of the coalescing thread. Only the
|
||
# channel is used — the executor stays in the GUI — but taking it from `smol`
|
||
# rather than `async-channel` directly keeps the `Receiver` type identical to the
|
||
# one gpui code already awaits.
|
||
smol.workspace = true
|
||
|
||
# SIMD byte search for the OSC tokenizer's Ground/Ignore fast paths — the
|
||
# sniffers sit on the full-throughput output stream (100+ MB/s at full drain),
|
||
# where a per-byte state machine costs a measurable slice of the reader loop.
|
||
memchr = "2"
|
||
|
||
# Base64 for the byte fields that cross the control dialect's JSON wire
|
||
# (`host::Output`'s stdout/stderr). JSON has no byte type — `serde_json` renders
|
||
# a `Vec<u8>` as an array of decimal numbers, inflating a 1 MB `git diff` to
|
||
# roughly 4 MB. Base64 costs 1.33× instead. Already in the tree via russh.
|
||
base64 = "0.22"
|
||
|
||
# zlib inflate for the kitty graphics protocol's `o=z` (compressed) pixel
|
||
# payloads (`core::kitty_graphics`). Pure-Rust, no new native code, and already
|
||
# in the tree transitively — the same crate `terminal-browser` uses for the
|
||
# deflate side, so the two ends agree on the wire format.
|
||
miniz_oxide = "0.8"
|
||
# Reading coding agents' past sessions (`core::agent_history`), on this
|
||
# machine or a remote one's server: Copilot names a session in YAML, Cursor
|
||
# keys a chat by the md5 of its directory and hex-encodes its metadata, and
|
||
# OpenCode and Cursor keep history in SQLite — bundled, so no system library
|
||
# is needed on any host the server lands on.
|
||
serde_yaml = "0.9"
|
||
md5 = "0.8"
|
||
hex = "0.4"
|
||
rusqlite = { version = "0.40", features = ["bundled"] }
|
||
|
||
[target.'cfg(unix)'.dependencies]
|
||
libc = "0.2"
|
||
# GSSAPI/Kerberos SSH auth — see the `gssapi` feature below for why it is
|
||
# optional rather than an unconditional Unix dependency.
|
||
libgssapi = { version = "0.11", optional = true }
|
||
|
||
# CFStringTokenizer-adjacent CoreFoundation FFI: `daemon::pane` reads a pane's
|
||
# foreground process name through it on macOS.
|
||
[target.'cfg(target_os = "macos")'.dependencies]
|
||
core-foundation = "0.10"
|
||
# Read the system proxy out of SCDynamicStore for tty7's own downloads
|
||
# (`daemon::install::proxy`). It pins core-foundation 0.9, so that module goes
|
||
# through this crate's `core_foundation` re-export rather than the 0.10 above —
|
||
# `get_proxies` hands back 0.9 types and the two versions do not interoperate.
|
||
system-configuration = { version = "0.6", optional = true }
|
||
system-configuration-sys = { version = "0.6", optional = true }
|
||
|
||
# The Windows GUI⇄daemon transport is loopback TCP, which (unlike a Unix socket)
|
||
# any local process can connect to — so the daemon authenticates each connection
|
||
# against a random token it writes into the user-private port file. `getrandom`
|
||
# is the OS CSPRNG that mints that token.
|
||
[target.'cfg(windows)'.dependencies]
|
||
getrandom = "0.3"
|
||
|
||
# Toolhelp process enumeration + `TerminateProcess`, used by `daemon::winproc` to
|
||
# title a pane by its foreground command and to tear down a shell's descendant
|
||
# tree on hangup (ConPTY's `kill` only reaches the shell itself). `Registry`
|
||
# additionally lets `daemon::windows_env` re-read the two environment hives at
|
||
# pane-spawn time so a long-lived daemon stops handing out its startup `PATH`.
|
||
# `IpHelper` is `GetExtendedTcpTable`, which is how `daemon::procinfo` answers
|
||
# the Info tab's Ports section here — the platform has no `lsof` to shell out to.
|
||
windows-sys = { version = "0.59", features = [
|
||
"Win32_Foundation",
|
||
"Win32_NetworkManagement_IpHelper",
|
||
"Win32_Security",
|
||
"Win32_System_Console",
|
||
"Win32_System_Diagnostics_ToolHelp",
|
||
"Win32_System_Registry",
|
||
"Win32_System_Threading",
|
||
"Win32_UI_WindowsAndMessaging",
|
||
] }
|
||
# Read the system proxy out of the registry for tty7's own downloads
|
||
# (`daemon::install::proxy`).
|
||
winreg = { version = "0.52", optional = true }
|
||
|
||
[dev-dependencies]
|
||
# Sandboxes for the `host::conformance` suite: a fresh empty directory per case,
|
||
# removed on drop.
|
||
tempfile = "3"
|
||
|
||
# `test-util` unlocks tokio's paused clock (`start_paused`) so the ssh prompt
|
||
# broker's timeout/retry tests run instantly instead of in real time.
|
||
tokio = { version = "1", features = ["test-util", "macros", "rt"] }
|
||
|
||
# `gssapi` — GSSAPI/Kerberos `gssapi-with-mic` SSH auth (`daemon::ssh::auth`).
|
||
#
|
||
# Off by default, and the `tty7` GUI turns it on, so the GUI's behavior is
|
||
# unchanged. It has to be optional because `libgssapi` binds the *system* MIT /
|
||
# Heimdal krb5 through bindgen: a machine without krb5 headers cannot build it
|
||
# at all, and a static musl `tty7-server` — the binary remote workspaces push
|
||
# onto arbitrary hosts (decision D10) — cannot link it under any
|
||
# circumstances. Without the feature, `SshAuthMode::Gssapi` reports that the
|
||
# method is unavailable in this build and the other auth families are untouched.
|
||
#
|
||
# **Test coverage.** Everything in `daemon::ssh::auth` that can be tested without
|
||
# krb5 — the service-host list — is gated on `unix` alone, so a plain
|
||
# `cargo test -p tty7-core` runs it. Only the libgssapi FFI half needs
|
||
# `--features gssapi`, and it has no unit tests (it is all foreign calls). Do not
|
||
# gate a testable helper on this feature: `cargo test --workspace` unifies it on
|
||
# from the GUI package, so a feature-gated test looks green there and silently
|
||
# vanishes the moment anyone narrows to `-p tty7-core`.
|
||
[features]
|
||
default = []
|
||
gssapi = ["dep:libgssapi"]
|
||
# Lets this build download a `tty7-server` release asset over HTTPS and push it
|
||
# onto a remote machine. Off by default so `tty7-server` — which is
|
||
# the thing being downloaded, and never the thing doing the downloading — links
|
||
# no HTTP client. Without it, `daemon::install` still installs from bytes it is
|
||
# handed and still launches/probes a remote daemon; only the fetch fails, with a
|
||
# message saying so.
|
||
# The platform proxy readers ride along: they exist only to configure the
|
||
# downloader, and each is a no-op on the targets it is not written for.
|
||
remote-install = [
|
||
"dep:ureq",
|
||
"dep:winreg",
|
||
"dep:system-configuration",
|
||
"dep:system-configuration-sys",
|
||
]
|
||
# The GitHub tab's REST client (`core::github::http`). It rides the same HTTPS
|
||
# stack and system-proxy resolution as the installer, so it is that feature
|
||
# plus a name of its own: the server, which never reads GitHub, builds neither.
|
||
github = ["remote-install"]
|
||
|
||
[lints]
|
||
workspace = true
|