M0: compilable skeleton — Kigi 0.1.0 fork surgery

Hard fork of xai-org/grok-build (Apache-2.0) re-targeted as Kigi, an
unofficial Kimi Code CLI community build.

Rename & identity
- 72 xai-*/xai-grok-* crates -> kigi-* (explicit: xai-grok-pager-bin ->
  kigi-bin [binary `kigi`], xai-grok-pager -> kigi-tui; rest mechanical);
  ptyctl, ptyctl-cli, third_party/ unchanged; proto package
  xai.grok.tools.v1 -> kigi.tools.v1
- Config home ~/.kigi (KIGI_SHARE_DIR override), env prefix GROK_* ->
  KIGI_*, `kigi --version` carries the unofficial-community-build notice
- clap identity, help text, startup banner, prompt templates rebranded
  (templates re-encrypted)

Deletions (PRD removal list #5/#6/#7/#9/#10)
- voice input (xai-grok-voice) and all TUI wiring
- telemetry: Mixpanel client, external OTel stream, Sentry, OTLP layers,
  trace/GCS/S3 upload queues (kigi-file-utils halved), workspace upload
  module & dc_log, heap-profile uploader, auth-diagnostics uploader,
  session-analytics halves of feedback; local zero-egress observability
  preserved in new kigi-log crate (unified log, --debug firehose,
  subsystem file logs, opt-in instrumentation)
- announcements (crate, remote-settings fields, TUI surfaces)
- plugin marketplace (crate, sources/browse/CTA/extensions-modal tab);
  direct plugin install/uninstall/update via kigi-agent git_install kept
- relay/gateway/assets endpoints and features (agent relay, headless
  relay transport, gateway bridge, LeaderEnvUrls); leader IPC socket now
  ~/.kigi/leader.sock + KIGI_LEADER_SOCKET, no ws-url derivation
- functional types rehomed instead of deleted: PermissionMode ->
  kigi-config-types, McpInitStrategy -> kigi-mcp, PrCreationSource ->
  session signals, TerminalDiagnostics -> kigi-pager-render, agent_id ->
  shell util

Endpoints
- kigi-env rewritten: single production KigiEndpoints {coding_api_base_url
  https://api.kimi.com/coding/v1 (KIGI_CODE_BASE_URL), oauth_host
  https://auth.kimi.com (KIGI_OAUTH_HOST), update_base_url (GitHub
  Releases API), upgrade_page_url}; GrokBuildEnvironment enum deleted

Toolchain & workspace hygiene
- Rust 1.97.0 pinned; edition 2024; full cargo update; git2 hoisted to
  workspace at 0.21 (Option->Result API migration), quick-xml 0.41
- Root Cargo.toml hand-maintained (PRD §8.1): version 0.1.0 inherited by
  all members, members sorted, unused deps pruned
- cargo-deny advisories gate (deny.toml with documented transitive
  exceptions); CI workflow (check/clippy/fmt/deny/test, macOS+Linux)
- cross-crate test seams re-gated behind `test-support` cargo feature;
  insta snapshot baselines renamed to the kigi_tui prefix
- clippy --workspace --all-targets: zero warnings; fmt clean

Fixes surfaced by the port
- updater probe/installer divergence (bin/kigi vs bin/grok symlink set)
- idle model-metadata refresh dead under KIGI_CODE_BASE_URL override
  (new is_effective_coding_endpoint_url, loopback+override aware)
- macOS symlinked-TMPDIR fixture canonicalization (foreign_sessions,
  fast-worktree); RSS measurement tests serialized via serial_test

Docs & legal (Apache §4)
- NOTICE added (upstream attribution + change statement); THIRD-PARTY
  notices sustained; kigi-tools ported-code notices extended; README,
  CONTRIBUTING, SECURITY, AGENTS.md rewritten

Out of scope for M0 (tracked): Kimi auth/inference (M1), search/fetch,
command parity, config import (M2), Computer Hub excision & final
brand-token sweep (M2), distribution & self-update rewrite (M3).
This commit is contained in:
2026-07-17 05:31:01 -04:00
commit d6c20fc13f
2612 changed files with 1353757 additions and 0 deletions
@@ -0,0 +1,212 @@
//! Runtime XTVERSION probe (`CSI > 0 q` → `DCS > | text ST`), run when
//! env-based brand detection yields Unknown (SSH, plain xterm) or a
//! headfully-validated allowlisted brand (see [`gate_allows_probe`]).
//!
//! Fire-and-forget, parser-integrated model (as in helix and similar TUIs):
//! the query is written once at startup with no timed read; the reply is
//! recognized and swallowed by the event loop's `XtversionFilter` whenever
//! it arrives.
//!
//! Safety invariants:
//! - Query write must happen after `enable_raw_mode()` and before the
//! `EventStream` filter is constructed.
//! - Accepted residuals: SSH *from* JediTerm still probes (its env marker
//! doesn't cross SSH) and leaks the query there; a reply whose first
//! event arrives only after the filter's 5s arm window types as
//! Alt+Shift+P + literal text; on a silent terminal with a fully idle
//! session the `OnceLock` stays unset (`record_no_reply` only runs from
//! the filter, which only runs on input) — `detected()` is None either
//! way, so both consumers are unaffected.
use std::sync::OnceLock;
use std::sync::atomic::{AtomicBool, Ordering};
/// Startup probe outcome.
#[derive(Debug)]
enum ProbeResult {
Skipped,
NoReply,
Identified(String),
}
/// Unset while the query is in flight (or never sent).
static XTVERSION: OnceLock<ProbeResult> = OnceLock::new();
/// True once the query bytes were written to the terminal.
static QUERY_SENT: AtomicBool = AtomicBool::new(false);
/// XTVERSION query alone — no DA1 sentinel: nothing waits on reply
/// ordering here, and a stale unsolicited DA1 reply could mis-answer a
/// future crossterm DA1-waiting probe.
#[cfg(unix)]
const QUERY: &[u8] = b"\x1b[>0q";
/// Returns the terminal's self-reported name/version, if the terminal
/// answered (e.g. `"kitty 0.35.2"`, `"foot(1.22.0)"`).
pub fn detected() -> Option<&'static str> {
match XTVERSION.get() {
Some(ProbeResult::Identified(v)) => Some(v),
_ => None,
}
}
/// True when the query was sent and no reply has been recorded yet — the
/// event loop arms its response filter on this.
pub fn reply_pending() -> bool {
QUERY_SENT.load(Ordering::Relaxed) && XTVERSION.get().is_none()
}
/// Record the DCS payload recognized by the event-loop filter.
pub fn record_reply(payload: &str) {
let result = match sanitize_payload(payload) {
Some(v) => ProbeResult::Identified(v),
None => ProbeResult::NoReply,
};
tracing::info!(?result, "XTVERSION probe");
let _ = XTVERSION.set(result);
}
/// Record that the filter disarmed without seeing a reply. Only invoked
/// from the filter on input, so a fully idle session can leave the
/// `OnceLock` unset (benign — see module doc).
pub fn record_no_reply() {
if XTVERSION.set(ProbeResult::NoReply).is_ok() {
tracing::info!("XTVERSION probe: no reply");
}
}
/// Send the XTVERSION query once at startup (fire-and-forget); no-ops when
/// the gate rejects the brand/multiplexer or stdin is not a TTY.
pub fn probe_at_startup() {
use std::io::IsTerminal;
if XTVERSION.get().is_some() || QUERY_SENT.load(Ordering::Relaxed) {
return;
}
let ctx = super::terminal_context();
if !gate_allows_probe(ctx) || !std::io::stdin().is_terminal() {
let _ = XTVERSION.set(ProbeResult::Skipped);
return;
}
send_query();
}
/// Crush-style brand allowlist: Unknown plus brands headfully validated as
/// clean XTVERSION responders (version fidelity is the payoff there).
/// CSI-intercepting multiplexers skip — the innermost layer answers as
/// itself, which the `multiplexer` field already records. Transparent muxes
/// (e.g. cmux) need no special case.
fn gate_allows_probe(ctx: &super::TerminalContext) -> bool {
use super::TerminalName::*;
matches!(
ctx.brand,
Unknown | Kitty | WezTerm | Ghostty | Iterm2 | Rio
) && !ctx.multiplexer.intercepts_csi_queries()
}
#[cfg(unix)]
fn send_query() {
if super::probe::write_query(QUERY) {
QUERY_SENT.store(true, Ordering::Relaxed);
} else {
// Brand-Unknown TTY whose query can't reach the terminal is a
// feedback-triage signal worth tracing.
tracing::debug!("XTVERSION probe skipped: query write failed or output is not a TTY");
let _ = XTVERSION.set(ProbeResult::Skipped);
}
}
#[cfg(not(unix))]
fn send_query() {
// ConPTY does not implement XTVERSION.
let _ = XTVERSION.set(ProbeResult::Skipped);
}
/// Strip controls and trim; `None` for an empty payload.
fn sanitize_payload(payload: &str) -> Option<String> {
let cleaned: String = payload.chars().filter(|c| !c.is_control()).collect();
let cleaned = cleaned.trim().to_owned();
if cleaned.is_empty() {
None
} else {
Some(cleaned)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sanitize_plain_payload() {
assert_eq!(
sanitize_payload("kitty 0.35.2").as_deref(),
Some("kitty 0.35.2")
);
assert_eq!(
sanitize_payload("XTerm(388)").as_deref(),
Some("XTerm(388)")
);
}
#[test]
fn sanitize_strips_controls_and_whitespace() {
assert_eq!(
sanitize_payload(" We\x01zTerm 2.0 ").as_deref(),
Some("WezTerm 2.0")
);
}
#[test]
fn sanitize_empty_is_none() {
assert_eq!(sanitize_payload(""), None);
assert_eq!(sanitize_payload(" \x07 "), None);
}
#[test]
fn gate_allows_unknown_and_allowlisted_brands() {
use crate::terminal::{MultiplexerKind, TerminalContext, TerminalName};
let ctx = |brand, multiplexer| TerminalContext {
brand,
multiplexer,
..Default::default()
};
for brand in [
TerminalName::Unknown,
TerminalName::Kitty,
TerminalName::WezTerm,
TerminalName::Ghostty,
TerminalName::Iterm2,
TerminalName::Rio,
] {
assert!(
gate_allows_probe(&ctx(brand, MultiplexerKind::Undetected)),
"{brand:?} should be probed"
);
// Transparent mux (cmux) does not intercept CSI; probe still runs.
assert!(
gate_allows_probe(&ctx(brand, MultiplexerKind::Cmux)),
"{brand:?} under cmux should still be probed"
);
// CSI-intercepting multiplexers override the brand allowlist.
assert!(
!gate_allows_probe(&ctx(brand, MultiplexerKind::Tmux)),
"{brand:?} under tmux should be skipped"
);
}
// JediTerm renders the query as garbage and must never be probed.
assert!(!gate_allows_probe(&ctx(
TerminalName::JetBrains,
MultiplexerKind::Undetected
)));
}
// Sets the process-global OnceLock — safe under nextest's
// process-per-test isolation.
#[test]
fn diagnostics_snapshot_includes_recorded_reply() {
record_reply("PtyHarnessTerm 9.9");
let t = crate::terminal::terminal_context().diagnostics_snapshot();
assert_eq!(t.xtversion, "PtyHarnessTerm 9.9");
}
}