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,318 @@
//! Logo component — renders the braille art logo.
//!
//! Hidden entirely on legacy Windows consoles: the U+2800 braille block is
//! not covered by the ConHost raster fonts and would render as tofu.
use ratatui::buffer::Buffer;
use ratatui::layout::{Alignment, Rect};
use ratatui::style::{Color, Style};
use ratatui::text::{Line, Span};
use ratatui::widgets::{Paragraph, Widget};
use crate::render::color::blend_color;
use crate::theme::Theme;
const LOGO: &str = include_str!("../../../assets/logo/logo07.txt");
const LOGO_SMALL: &str = include_str!("../../../assets/logo/logo05.txt");
/// Height at or above which the small logo is shown (below it, no logo).
const SMALL_LOGO_MIN_HEIGHT: u16 = 22;
/// Height at or above which the full logo is shown.
const FULL_LOGO_MIN_HEIGHT: u16 = 26;
fn pick_logo(window_height: u16) -> Option<&'static str> {
pick_logo_for(window_height, logo_hidden())
}
/// Pure tier selection so tests can drive the legacy-console flag directly.
fn pick_logo_for(window_height: u16, hidden: bool) -> Option<&'static str> {
if hidden || window_height < SMALL_LOGO_MIN_HEIGHT {
None
} else if window_height < FULL_LOGO_MIN_HEIGHT {
Some(LOGO_SMALL)
} else {
Some(LOGO)
}
}
/// The braille art has no ASCII stand-in; see the module doc.
fn logo_hidden() -> bool {
crate::glyphs::is_legacy_windows_console()
}
fn non_empty_lines(logo: &str) -> impl Iterator<Item = &str> {
logo.lines().filter(|l| !l.is_empty())
}
fn count_lines(logo: &str) -> u16 {
non_empty_lines(logo).count() as u16
}
fn visual_width(logo: &str) -> u16 {
non_empty_lines(logo)
.map(unicode_width::UnicodeWidthStr::width)
.max()
.unwrap_or(24) as u16
}
/// Animation phase in seconds since the first render. Wall-clock based so the
/// shimmer speed is independent of the frame rate.
fn anim_phase_secs() -> f32 {
use std::sync::OnceLock;
use std::time::Instant;
static START: OnceLock<Instant> = OnceLock::new();
START.get_or_init(Instant::now).elapsed().as_secs_f32()
}
/// Shimmer redraw cadence in frames per second. The sweep is slow, so a few fps
/// looks smooth while sparing the long-lived welcome screen from full-rate
/// repaints.
const SHIMMER_FPS: f32 = 12.0;
/// Quantized shimmer frame for the current wall-clock phase. The welcome screen
/// redraws only when this advances, throttling the animation to ~`SHIMMER_FPS`
/// rather than the full event-loop tick rate. Pinned to 0 when the logo is
/// hidden.
pub fn shimmer_frame() -> u64 {
if logo_hidden() {
return 0;
}
(anim_phase_secs() * SHIMMER_FPS) as u64
}
/// Per-glyph shine opacity in `[0, 1]` at normalized diagonal position `diag`
/// (0 = bottom-left .. 1 = top-right) and animation time `secs`. A raised-cosine
/// band sweeps bottom-left → top-right and parks off-screen between sweeps; a
/// gentle global pulse breathes underneath it. 0 keeps the resting gray, 1 is
/// full bright.
fn shine_opacity(diag: f32, secs: f32) -> f32 {
const BAND: f32 = 0.38; // half-width of the shine band — wider = more gradual falloff
const CYCLE: f32 = 4.0; // seconds per sweep + rest
const SWEEP_FRAC: f32 = 0.32; // portion of the cycle spent sweeping (~1.3s glint, rest idles)
const SHINE: f32 = 0.33; // peak shine strength
const PULSE: f32 = 0.06; // global breathing amount
const PULSE_SECS: f32 = 5.0; // breathing period
let p = (secs % CYCLE) / CYCLE;
let q = (p / SWEEP_FRAC).min(1.0); // parks the band off-screen during the rest
let band_pos = -BAND + q * (1.0 + 2.0 * BAND);
let pulse = PULSE * (0.5 - 0.5 * (std::f32::consts::TAU * secs / PULSE_SECS).cos());
let d = (diag - band_pos).abs();
let shine = if d < BAND {
0.5 * (1.0 + (std::f32::consts::PI * d / BAND).cos())
} else {
0.0
};
(pulse + SHINE * shine).clamp(0.0, 1.0)
}
fn render_into(area: Rect, buf: &mut Buffer, theme: &Theme, logo: &str) {
let lines: Vec<&str> = non_empty_lines(logo).collect();
let rows = lines.len().max(1) as f32;
let cols = lines
.iter()
.map(|l| l.chars().count())
.max()
.unwrap_or(1)
.max(1) as f32;
let secs = anim_phase_secs();
// Blend each glyph from the resting gray toward the bright text color by its
// shine opacity, so a sheen sweeps across the braille art. Adjacent glyphs
// that land on the same blended color share one Span to hold down the
// per-frame allocation.
let base = theme.gray;
let hilite = theme.text_primary;
let logo_lines: Vec<Line> = lines
.iter()
.enumerate()
.map(|(row, line)| {
let mut spans: Vec<Span> = Vec::new();
let mut run = String::new();
let mut run_color: Option<Color> = None;
for (col, ch) in line.chars().enumerate() {
// Sweep along the bottom-left → top-right diagonal: the
// coordinate grows as col increases and row decreases.
let diag = (col as f32 + (rows - 1.0 - row as f32)) / (cols + rows);
let color = blend_color(base, hilite, shine_opacity(diag, secs)).unwrap_or(base);
if run_color != Some(color) {
if let Some(prev) = run_color {
spans.push(Span::styled(
std::mem::take(&mut run),
Style::default().fg(prev),
));
}
run_color = Some(color);
}
run.push(ch);
}
if let Some(prev) = run_color {
spans.push(Span::styled(run, Style::default().fg(prev)));
}
Line::from(spans).alignment(Alignment::Center)
})
.collect();
Paragraph::new(logo_lines).render(area, buf);
}
pub fn logo_line_count(window_height: u16) -> u16 {
pick_logo(window_height).map_or(0, count_lines)
}
pub fn logo_visual_width(window_height: u16) -> u16 {
pick_logo(window_height).map_or(24, visual_width)
}
pub fn render_logo(area: Rect, buf: &mut Buffer, theme: &Theme, window_height: u16) {
if let Some(logo) = pick_logo(window_height) {
render_into(area, buf, theme, logo);
}
}
/// The hero box always shows the full logo: it is laid out beside the menu, so
/// it fits whenever the box does. These report and render that logo directly,
/// independent of the height-based [`pick_logo`] tiers used by the stacked
/// layout. When [`logo_hidden`], they report 0 and render nothing.
pub fn full_logo_line_count() -> u16 {
full_logo_line_count_for(logo_hidden())
}
fn full_logo_line_count_for(hidden: bool) -> u16 {
if hidden { 0 } else { count_lines(LOGO) }
}
pub fn full_logo_visual_width() -> u16 {
full_logo_visual_width_for(logo_hidden())
}
fn full_logo_visual_width_for(hidden: bool) -> u16 {
if hidden { 0 } else { visual_width(LOGO) }
}
pub fn render_full_logo(area: Rect, buf: &mut Buffer, theme: &Theme) {
if !logo_hidden() {
render_into(area, buf, theme, LOGO);
}
}
/// Line count of the small logo used in minimal's committed welcome card
/// (0 on a legacy Windows console, where the braille art is suppressed).
pub fn compact_logo_line_count() -> u16 {
if logo_hidden() {
0
} else {
count_lines(LOGO_SMALL)
}
}
/// Render the small braille logo (centered) into `area` for minimal's welcome
/// card. No-op when the logo is hidden.
pub fn render_compact_logo(area: Rect, buf: &mut Buffer, theme: &Theme) {
if !logo_hidden() {
render_into(area, buf, theme, LOGO_SMALL);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn logo_sizes_by_height() {
assert!(pick_logo_for(SMALL_LOGO_MIN_HEIGHT - 1, false).is_none());
assert_eq!(
pick_logo_for(SMALL_LOGO_MIN_HEIGHT, false),
Some(LOGO_SMALL)
);
assert_eq!(
pick_logo_for(FULL_LOGO_MIN_HEIGHT - 1, false),
Some(LOGO_SMALL)
);
assert_eq!(pick_logo_for(FULL_LOGO_MIN_HEIGHT, false), Some(LOGO));
}
// The braille art has no legacy-safe stand-in, so every height tier must
// collapse to no logo when the legacy-console flag is set.
#[test]
fn logo_hidden_on_legacy_console_at_every_height() {
for h in [0, SMALL_LOGO_MIN_HEIGHT, FULL_LOGO_MIN_HEIGHT, u16::MAX] {
assert!(pick_logo_for(h, true).is_none(), "height {h}");
}
}
#[test]
fn hero_box_always_uses_full_logo() {
// The box renders the full logo regardless of height (it's laid out
// beside the menu), and it's the large variant — never the small one.
assert_eq!(full_logo_line_count_for(false), count_lines(LOGO));
assert_eq!(full_logo_visual_width_for(false), visual_width(LOGO));
assert!(full_logo_line_count_for(false) > count_lines(LOGO_SMALL));
assert!(full_logo_visual_width_for(false) > visual_width(LOGO_SMALL));
}
#[test]
fn full_logo_helpers_collapse_when_hidden() {
assert_eq!(full_logo_line_count_for(true), 0);
assert_eq!(full_logo_visual_width_for(true), 0);
}
#[test]
fn compact_logo_line_count_matches_small_logo_when_visible() {
// The minimal welcome card budgets exactly the small logo's rows. When
// the logo isn't hidden, the count equals the small art's line count and
// is strictly shorter than the full logo.
if !logo_hidden() {
assert_eq!(compact_logo_line_count(), count_lines(LOGO_SMALL));
assert!(compact_logo_line_count() < count_lines(LOGO));
assert!(compact_logo_line_count() > 0);
} else {
assert_eq!(compact_logo_line_count(), 0);
}
}
#[test]
fn shine_opacity_stays_in_unit_range() {
let mut secs = 0.0;
while secs < 10.0 {
for i in 0..=20 {
let diag = i as f32 / 20.0;
let op = shine_opacity(diag, secs);
assert!(
(0.0..=1.0).contains(&op),
"opacity {op} out of range at diag {diag}, secs {secs}"
);
}
secs += 0.13;
}
}
#[test]
fn shine_band_sweeps_across() {
// The brightest point along the diagonal advances left → right as the
// sweep progresses through its active phase.
let brightest = |secs: f32| -> f32 {
(0..=100)
.map(|i| i as f32 / 100.0)
.max_by(|a, b| {
shine_opacity(*a, secs)
.partial_cmp(&shine_opacity(*b, secs))
.unwrap()
})
.unwrap()
};
let early = brightest(0.1);
let mid = brightest(0.4);
let late = brightest(0.7);
assert!(early < mid, "early {early} should precede mid {mid}");
assert!(mid < late, "mid {mid} should precede late {late}");
}
#[test]
fn shine_rests_dim_between_sweeps() {
// During the rest phase the band is parked off-screen, so an interior
// glyph falls back to at most the gentle pulse — never full bright.
let op = shine_opacity(0.5, 6.0); // secs % 4.0 = 2.0 → past SWEEP_FRAC, in the rest phase
assert!(op < 0.2, "resting opacity {op} should stay dim");
}
}