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,414 @@
//! Dropdown renderer for shell command completion suggestions.
//!
//! Mirrors `slash_dropdown.rs` layout: aligned label column, description,
//! selection highlight, mouse hover, and scrollbar when items exceed
//! `MAX_VISIBLE_ROWS`.
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::style::{Modifier, Style};
use ratatui::text::{Line, Span};
use unicode_width::UnicodeWidthStr;
use crate::render::SafeBuf;
use crate::render::line_utils::truncate_str;
use crate::render::scrollbar::render_scrollbar_styled;
use crate::theme::Theme;
use crate::views::suggestion_controller::{CompletionDropdownState, CompletionItemParsed};
/// Maximum visible rows in the completion dropdown.
pub const MAX_VISIBLE_ROWS: u16 = 6;
/// Hard cap on label column width.
const LABEL_CAP: usize = 40;
/// Gap between label and description columns.
const LABEL_DESC_GAP: usize = 2;
/// Prefix width (`" "` or `" "`).
const PREFIX_W: usize = 2;
/// Height needed for the dropdown (separator + items), or 0 when hidden.
pub fn dropdown_height(state: &CompletionDropdownState) -> u16 {
if !state.open || state.items.is_empty() {
return 0;
}
let item_rows = (state.items.len() as u16).min(MAX_VISIBLE_ROWS);
1 + item_rows // separator + items
}
/// Compute the scroll offset so the selected row stays centred.
pub fn scroll_offset(state: &CompletionDropdownState) -> usize {
let total = state.items.len();
let visible = MAX_VISIBLE_ROWS as usize;
let selected = state.selected.min(total.saturating_sub(1));
if total <= visible || selected < visible / 2 {
0
} else if selected + visible / 2 >= total {
total.saturating_sub(visible)
} else {
selected.saturating_sub(visible / 2)
}
}
fn compute_label_column_w(items: &[CompletionItemParsed], content_w: usize) -> usize {
let budget = (content_w * 3 / 5).min(LABEL_CAP);
let max_w = items
.iter()
.map(|r| r.display.width())
.filter(|&w| w <= LABEL_CAP)
.max()
.unwrap_or(0);
max_w.min(budget)
}
/// Render completion dropdown items into `area` (no borders — caller draws chrome).
pub fn render_dropdown(
buf: &mut Buffer,
area: Rect,
state: &CompletionDropdownState,
theme: &Theme,
) {
if area.height == 0 || area.width < 4 || !state.open {
return;
}
let items = &state.items;
let selected = state.selected.min(items.len().saturating_sub(1));
let hovered = state.hovered;
let content_w = area.width as usize;
let visible_rows = area.height as usize;
let needs_scrollbar = items.len() > visible_rows;
let row_w = if needs_scrollbar {
content_w.saturating_sub(2)
} else {
content_w
};
let label_col_w = compute_label_column_w(items, row_w.saturating_sub(PREFIX_W));
let scroll = scroll_offset(state);
for vis_row in 0..visible_rows {
let item_idx = scroll + vis_row;
if item_idx >= items.len() {
break;
}
let item = &items[item_idx];
let y = area.y + vis_row as u16;
let is_selected = item_idx == selected;
let is_hovered = hovered == Some(item_idx) && !is_selected;
let row_bg = match crate::views::modal_window::embedded_row_style(theme, is_selected) {
Some(e) => e.bg,
None if is_selected => theme.bg_visual,
None if is_hovered => theme.bg_hover,
None => theme.bg_light,
};
let line = build_item_line(item, is_selected, label_col_w, row_w, row_bg, theme);
// Skip rows that fall outside the buffer (resize race).
if y < buf.area.y || y >= buf.area.bottom() || area.x >= buf.area.right() {
continue;
}
let clamped_w = row_w.min(buf.area.right().saturating_sub(area.x) as usize) as u16;
let row_rect = Rect {
x: area.x,
y,
width: clamped_w,
height: 1,
};
buf.set_style(row_rect, Style::default().bg(row_bg));
buf.set_line_safe(area.x, y, &line, row_w as u16);
}
if needs_scrollbar {
let sb_x = area.x + area.width.saturating_sub(1);
let sb_y = area.y.max(buf.area.y);
let sb_bottom = (area.y.saturating_add(area.height)).min(buf.area.bottom());
if sb_x < buf.area.right() && sb_bottom > sb_y {
let sb_area = Rect {
x: sb_x,
y: sb_y,
width: 1,
height: sb_bottom - sb_y,
};
let track = Style::default().bg(theme.bg_dark);
let thumb = Style::default().fg(theme.gray_dim).bg(theme.bg_dark);
render_scrollbar_styled(
buf,
Some(sb_area),
items.len() as u16,
sb_area.height,
scroll as u16,
track,
thumb,
);
}
}
}
fn build_item_line(
item: &CompletionItemParsed,
is_selected: bool,
label_col_w: usize,
total_w: usize,
row_bg: ratatui::style::Color,
theme: &Theme,
) -> Line<'static> {
let bold = if is_selected {
Modifier::BOLD
} else {
Modifier::empty()
};
let embed = crate::views::modal_window::embedded_row_style(theme, is_selected);
let primary_fg = embed.map_or(theme.text_primary, |e| e.fg(theme.text_primary));
let desc_fg = embed.map_or(theme.gray, |e| e.fg(theme.gray));
let normal = Style::default()
.fg(primary_fg)
.bg(row_bg)
.add_modifier(bold);
let desc_style = Style::default().fg(desc_fg).bg(row_bg);
let bg_style = Style::default().bg(row_bg);
let prefix = if is_selected {
crate::glyphs::prompt_arrow()
} else {
" "
};
let prefix_span = Span::styled(
prefix.to_string(),
if is_selected { normal } else { bg_style },
);
let label = truncate_str(&item.display, label_col_w);
let label_w = label.width();
let padding = label_col_w.saturating_sub(label_w);
let label_span = Span::styled(label, normal);
let desc_indent = PREFIX_W + label_col_w + LABEL_DESC_GAP;
let desc_w = total_w.saturating_sub(desc_indent).max(1);
let desc = truncate_str(&item.description, desc_w);
let mut spans = vec![prefix_span, label_span];
if padding > 0 {
spans.push(Span::styled(" ".repeat(padding), bg_style));
}
if !desc.is_empty() {
spans.push(Span::styled(" ".to_string(), bg_style));
spans.push(Span::styled(desc, desc_style));
}
Line::from(spans).style(bg_style)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::views::suggestion_controller::SuggestionSource;
fn make_item(display: &str, desc: &str, insert: &str) -> CompletionItemParsed {
CompletionItemParsed {
display: display.into(),
description: desc.into(),
insert_text: insert.into(),
source: SuggestionSource::History,
priority: 0,
replace_range: None,
token_text: None,
truncated: false,
}
}
#[test]
fn height_zero_when_closed() {
let state = CompletionDropdownState::default();
assert_eq!(dropdown_height(&state), 0);
}
/// Items area past buffer bottom must not panic on resize races.
#[test]
fn render_dropdown_past_buffer_bottom_does_not_panic() {
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
let theme = Theme::current();
let items: Vec<CompletionItemParsed> = (0..12)
.map(|i| make_item(&format!("item{i}"), "desc", &format!("item{i}")))
.collect();
let state = CompletionDropdownState {
open: true,
items,
selected: 0,
..Default::default()
};
let mut buf = Buffer::empty(Rect::new(0, 0, 80, 10));
let area = Rect::new(2, 8, 76, 8);
render_dropdown(&mut buf, area, &state, &theme);
}
#[test]
fn height_zero_when_empty() {
let state = CompletionDropdownState {
open: true,
..Default::default()
};
assert_eq!(dropdown_height(&state), 0);
}
#[test]
fn height_with_items() {
let state = CompletionDropdownState {
open: true,
items: vec![
make_item("ls", "list", "ls"),
make_item("cd", "change dir", "cd"),
],
..Default::default()
};
assert_eq!(dropdown_height(&state), 3); // 1 separator + 2 items
}
#[test]
fn height_capped_at_max() {
let items: Vec<_> = (0..20)
.map(|i| make_item(&format!("cmd{i}"), "", &format!("cmd{i}")))
.collect();
let state = CompletionDropdownState {
open: true,
items,
..Default::default()
};
assert_eq!(dropdown_height(&state), 1 + MAX_VISIBLE_ROWS);
}
#[test]
fn move_selection_wraps() {
let mut state = CompletionDropdownState {
open: true,
items: vec![
make_item("a", "", "a"),
make_item("b", "", "b"),
make_item("c", "", "c"),
],
selected: 0,
..Default::default()
};
state.move_selection(-1);
assert_eq!(state.selected, 2);
state.move_selection(1);
assert_eq!(state.selected, 0);
state.move_selection(1);
assert_eq!(state.selected, 1);
}
#[test]
fn scroll_selection_clamps_at_edges() {
let mut state = CompletionDropdownState {
open: true,
items: vec![
make_item("a", "", "a"),
make_item("b", "", "b"),
make_item("c", "", "c"),
],
selected: 0,
..Default::default()
};
// Scrolling up at the first item stays put (no wrap).
state.scroll_selection(-1);
assert_eq!(state.selected, 0);
state.scroll_selection(1);
assert_eq!(state.selected, 1);
// Scrolling down at the last item stays put (no wrap).
state.scroll_selection(1);
assert_eq!(state.selected, 2);
state.scroll_selection(1);
assert_eq!(state.selected, 2);
}
#[test]
fn accept_returns_item_and_closes() {
let mut state = CompletionDropdownState {
open: true,
items: vec![make_item("ls -la", "list all", "ls -la /tmp")],
selected: 0,
..Default::default()
};
let item = state.accept().expect("selected item accepted");
assert_eq!(item.insert_text, "ls -la /tmp");
assert!(!state.open);
}
#[test]
fn accept_without_items_returns_none() {
let mut state = CompletionDropdownState::default();
assert!(state.accept().is_none());
}
/// `accept` is independent of the `open` render flag: the
/// single-candidate insta-accept consumes an item that was never shown.
#[test]
fn accept_works_on_closed_dropdown_with_items() {
let mut state = CompletionDropdownState {
open: false,
items: vec![make_item("ls", "", "ls -la")],
..Default::default()
};
let item = state.accept().expect("item accepted while closed");
assert_eq!(item.insert_text, "ls -la");
}
#[test]
fn scroll_offset_no_scroll_needed() {
let state = CompletionDropdownState {
open: true,
items: vec![make_item("a", "", "a")],
selected: 0,
..Default::default()
};
assert_eq!(scroll_offset(&state), 0);
}
#[test]
fn scroll_offset_centres_selected() {
let items: Vec<_> = (0..20)
.map(|i| make_item(&format!("c{i}"), "", &format!("c{i}")))
.collect();
let state = CompletionDropdownState {
open: true,
items,
selected: 10,
..Default::default()
};
let offset = scroll_offset(&state);
let visible = MAX_VISIBLE_ROWS as usize;
assert!(offset <= 10);
assert!(offset + visible > 10);
}
#[test]
fn close_resets_state() {
let mut state = CompletionDropdownState {
open: true,
items: vec![make_item("a", "", "a")],
selected: 0,
hovered: Some(0),
generation: 5,
request_text: "a".into(),
request_cursor: 1,
};
state.close();
assert!(!state.open);
assert_eq!(state.selected, 0);
assert!(state.hovered.is_none());
assert_eq!(state.generation, 5); // generation preserved
assert!(state.items.is_empty());
// Anchor left in place (inert without items); the next landing
// overwrites it atomically with the new items.
assert_eq!(state.request_text, "a");
assert_eq!(state.request_cursor, 1);
}
}