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:
ZacharyZhang-NY committed 2026-07-17 05:31:01 -04:00
commit d6c20fc13f
2612 files changed
+1353757

No files matched your search

+193
View File
@@ -0,0 +1,193 @@
//! Vim-style key notation parser.
//!
//! Converts strings like `"hello<CR>"`, `"<C-c>"`, `"<Esc>:wq<CR>"` into
//! raw terminal byte sequences using the `terminput` crate.
use std::io;
use anyhow::{Result, bail};
use terminput::{Encoding, Event, KeyCode, KeyEvent, KeyModifiers};
/// Parse a vim-notation key string into raw terminal bytes.
///
/// # Examples
/// - `"hello"` -> literal bytes for h, e, l, l, o
/// - `"<CR>"` or `"<Enter>"` -> `\r`
/// - `"<C-c>"` -> Ctrl+C (0x03)
/// - `"<Esc>:wq<CR>"` -> ESC, :, w, q, CR
/// - `"<Up><Up><CR>"` -> up arrow, up arrow, CR
pub fn parse_keys(input: &str) -> Result<Vec<u8>> {
let events = parse_to_events(input)?;
let mut bytes = Vec::new();
let mut buf = [0u8; 64];
for event in events {
let ev = Event::Key(event);
match ev.encode(&mut buf, Encoding::Xterm) {
Ok(n) => bytes.extend_from_slice(&buf[..n]),
Err(e) if e.kind() == io::ErrorKind::Unsupported => {
// Some keys may not be encodable; skip them.
}
Err(e) => return Err(e.into()),
}
}
Ok(bytes)
}
/// Parse vim notation into a sequence of `KeyEvent`s.
fn parse_to_events(input: &str) -> Result<Vec<KeyEvent>> {
let mut events = Vec::new();
let mut chars = input.chars().peekable();
while let Some(&ch) = chars.peek() {
if ch == '<' {
// Try to parse a special key notation.
let start_pos: String = chars.clone().collect();
if let Some(end) = start_pos.find('>') {
let notation = &start_pos[1..end]; // between < and >
// Consume chars including the >.
for _ in 0..=end {
chars.next();
}
let event = parse_special(notation)?;
events.push(event);
} else {
// No closing '>', treat '<' as literal.
chars.next();
events.push(key(KeyCode::Char('<'), KeyModifiers::NONE));
}
} else {
chars.next();
events.push(key(KeyCode::Char(ch), KeyModifiers::NONE));
}
}
Ok(events)
}
/// Helper to build a KeyEvent with modifiers.
fn key(code: KeyCode, modifiers: KeyModifiers) -> KeyEvent {
let mut ev = KeyEvent::new(code);
ev.modifiers = modifiers;
ev
}
/// Parse the content between `<` and `>` as a special key.
fn parse_special(notation: &str) -> Result<KeyEvent> {
let lower = notation.to_lowercase();
// Parse modifiers: C-, M-/A-, S- (in any order).
let mut modifiers = KeyModifiers::NONE;
let mut remaining = lower.as_str();
loop {
if let Some(rest) = remaining.strip_prefix("c-") {
modifiers |= KeyModifiers::CTRL;
remaining = rest;
} else if let Some(rest) = remaining
.strip_prefix("m-")
.or(remaining.strip_prefix("a-"))
{
modifiers |= KeyModifiers::ALT;
remaining = rest;
} else if let Some(rest) = remaining.strip_prefix("s-") {
modifiers |= KeyModifiers::SHIFT;
remaining = rest;
} else {
break;
}
}
let code = match remaining {
"cr" | "enter" | "return" => KeyCode::Enter,
"esc" | "escape" => KeyCode::Esc,
"bs" | "backspace" => KeyCode::Backspace,
"tab" => KeyCode::Tab,
"space" | "spc" => KeyCode::Char(' '),
"up" => KeyCode::Up,
"down" => KeyCode::Down,
"left" => KeyCode::Left,
"right" => KeyCode::Right,
"home" => KeyCode::Home,
"end" => KeyCode::End,
"pageup" | "pgup" => KeyCode::PageUp,
"pagedown" | "pgdn" => KeyCode::PageDown,
"insert" | "ins" => KeyCode::Insert,
"delete" | "del" => KeyCode::Delete,
"f1" => KeyCode::F(1),
"f2" => KeyCode::F(2),
"f3" => KeyCode::F(3),
"f4" => KeyCode::F(4),
"f5" => KeyCode::F(5),
"f6" => KeyCode::F(6),
"f7" => KeyCode::F(7),
"f8" => KeyCode::F(8),
"f9" => KeyCode::F(9),
"f10" => KeyCode::F(10),
"f11" => KeyCode::F(11),
"f12" => KeyCode::F(12),
"lt" => KeyCode::Char('<'),
"gt" => KeyCode::Char('>'),
"bar" => KeyCode::Char('|'),
"bslash" => KeyCode::Char('\\'),
s if s.len() == 1 => {
let c = s.chars().next().unwrap();
// For Shift+letter with no other modifiers, uppercase it (vim behavior).
if modifiers == KeyModifiers::SHIFT && c.is_ascii_alphabetic() {
modifiers = KeyModifiers::NONE;
KeyCode::Char(c.to_ascii_uppercase())
} else {
KeyCode::Char(c)
}
}
_ => bail!("unknown key notation: <{notation}>"),
};
Ok(key(code, modifiers))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_literal_text() {
let bytes = parse_keys("hello").unwrap();
assert_eq!(bytes, b"hello");
}
#[test]
fn test_enter() {
let bytes = parse_keys("<CR>").unwrap();
assert_eq!(bytes, b"\r");
}
#[test]
fn test_escape() {
let bytes = parse_keys("<Esc>").unwrap();
assert_eq!(bytes, b"\x1b");
}
#[test]
fn test_ctrl_c() {
let bytes = parse_keys("<C-c>").unwrap();
assert_eq!(bytes, b"\x03");
}
#[test]
fn test_mixed() {
let bytes = parse_keys("hello<CR>").unwrap();
assert_eq!(&bytes[..5], b"hello");
assert_eq!(bytes[5], b'\r');
}
#[test]
fn test_case_insensitive() {
let a = parse_keys("<cr>").unwrap();
let b = parse_keys("<CR>").unwrap();
let c = parse_keys("<Cr>").unwrap();
assert_eq!(a, b);
assert_eq!(b, c);
}
}