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:
commit
d6c20fc13f
2612 files changed
+1353757
No files matched your search
@@ -0,0 +1,173 @@
|
||||
//! Tip renderer.
|
||||
|
||||
use ratatui::{
|
||||
buffer::Buffer,
|
||||
layout::Rect,
|
||||
style::{Color, Modifier, Style},
|
||||
text::{Line, Span},
|
||||
widgets::{Paragraph, Widget, Wrap},
|
||||
};
|
||||
|
||||
use crate::render::SafeBuf;
|
||||
use crate::theme::Theme;
|
||||
|
||||
/// Compute the number of rows a tip needs when rendered at the given `width`.
|
||||
pub fn tip_height(width: u16, tip: &str) -> u16 {
|
||||
if width == 0 {
|
||||
return 0;
|
||||
}
|
||||
let line = tip_line(tip);
|
||||
let line_width = line.width() as u16;
|
||||
if line_width <= width {
|
||||
1
|
||||
} else {
|
||||
// Ceiling division — word wrapping may use slightly more rows than
|
||||
// a naive character split, but this is a close-enough upper bound.
|
||||
(line_width as u32)
|
||||
.div_ceil(width as u32)
|
||||
.min(u16::MAX as u32) as u16
|
||||
}
|
||||
}
|
||||
|
||||
fn tip_line(tip: &str) -> Line<'_> {
|
||||
let theme = Theme::current();
|
||||
Line::from(vec![
|
||||
Span::styled(
|
||||
"Tip: ",
|
||||
Style::default().fg(theme.gray).add_modifier(Modifier::BOLD),
|
||||
),
|
||||
Span::styled(tip, Style::default().fg(theme.gray)),
|
||||
])
|
||||
}
|
||||
|
||||
/// Render a tip into the provided area, word-wrapping if it exceeds the width.
|
||||
pub fn render_tip(area: Rect, buf: &mut Buffer, tip: &str) {
|
||||
if area.height == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let theme = Theme::current();
|
||||
|
||||
Paragraph::new(tip_line(tip))
|
||||
.style(Style::default().bg(theme.bg_base))
|
||||
.wrap(Wrap { trim: false })
|
||||
.render(area, buf);
|
||||
}
|
||||
|
||||
/// Blank every cell of `area` (chars, colors, and modifiers) in `color`.
|
||||
///
|
||||
/// Modifiers MUST be reset here: ratatui's `Cell::set_style` only *merges*
|
||||
/// modifiers (`insert(add)` / `remove(sub)`), so a later paint whose style
|
||||
/// carries no `sub_modifier` inherits whatever BOLD/ITALIC/… an earlier
|
||||
/// same-frame paint left behind (e.g. the welcome tip's bold `Tip: ` prefix
|
||||
/// bleeding into the ephemeral tip as "**Queue**d · Enter to send now").
|
||||
fn clear_rect(buf: &mut Buffer, area: Rect, color: Color) {
|
||||
for row in 0..area.height {
|
||||
for col in 0..area.width {
|
||||
if let Some(cell) = buf.cell_mut((area.x + col, area.y + row)) {
|
||||
cell.set_char(' ');
|
||||
cell.fg = color;
|
||||
cell.bg = color;
|
||||
cell.modifier = Modifier::empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Render a pre-styled tip line into the banner rect. The whole rect is
|
||||
/// cleared first (it can be taller than one row when a wrapped session tip
|
||||
/// reserved it) and the line paints on the first row, truncated at width.
|
||||
pub fn render_ephemeral_tip(area: Rect, buf: &mut Buffer, line: &Line<'static>) {
|
||||
if area.height == 0 || area.width == 0 {
|
||||
return;
|
||||
}
|
||||
let theme = Theme::current();
|
||||
clear_rect(buf, area, theme.bg_base);
|
||||
buf.set_line_safe(area.x, area.y, line, area.width);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn row_text(buf: &Buffer, area: Rect, y: u16) -> String {
|
||||
(0..area.width)
|
||||
.map(|x| buf.cell((area.x + x, y)).expect("cell in area").symbol())
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clears_full_rect_and_truncates_to_width() {
|
||||
let area = Rect::new(0, 0, 8, 2);
|
||||
let mut buf = Buffer::empty(area);
|
||||
// Pre-dirty both rows to simulate stale banner content underneath.
|
||||
buf.set_string(0, 0, "XXXXXXXX", Style::default());
|
||||
buf.set_string(0, 1, "XXXXXXXX", Style::default());
|
||||
|
||||
let line = Line::from("0123456789"); // wider than the rect
|
||||
render_ephemeral_tip(area, &mut buf, &line);
|
||||
|
||||
assert_eq!(row_text(&buf, area, 0), "01234567", "truncated at width");
|
||||
assert_eq!(
|
||||
row_text(&buf, area, 1),
|
||||
" ",
|
||||
"stale rows below the line are cleared"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zero_sized_area_is_a_noop() {
|
||||
let area = Rect::new(0, 0, 8, 1);
|
||||
let mut buf = Buffer::empty(area);
|
||||
buf.set_string(0, 0, "XXXXXXXX", Style::default());
|
||||
render_ephemeral_tip(Rect::new(0, 0, 8, 0), &mut buf, &Line::from("tip"));
|
||||
assert_eq!(row_text(&buf, area, 0), "XXXXXXXX", "untouched");
|
||||
}
|
||||
|
||||
/// Regression: a bold underpaint in the banner rect (e.g. the welcome
|
||||
/// tip's `Tip: ` prefix painted the same frame) must not bleed BOLD into
|
||||
/// the ephemeral tip. `Cell::set_style` merges modifiers, so the clear
|
||||
/// pass has to reset them explicitly — otherwise `Queued · Enter …`
|
||||
/// rendered as bold `Queue` + regular `d` (5 leaked bold cells).
|
||||
#[test]
|
||||
fn clears_leaked_modifiers_from_underpaint() {
|
||||
let area = Rect::new(0, 0, 40, 1);
|
||||
let mut buf = Buffer::empty(area);
|
||||
// Simulate the phantom session-tip underpaint: 5 bold cells ("Tip: ").
|
||||
buf.set_string(
|
||||
0,
|
||||
0,
|
||||
"Tip: never gonna give you up",
|
||||
Style::default().add_modifier(Modifier::BOLD),
|
||||
);
|
||||
|
||||
// The send-now tip shape: dim text with a single bold key chord.
|
||||
let dim = Style::default();
|
||||
let bold = Style::default().add_modifier(Modifier::BOLD);
|
||||
let line = Line::from(vec![
|
||||
Span::styled("Queued · ", dim),
|
||||
Span::styled("Enter", bold),
|
||||
Span::styled(" to send now", dim),
|
||||
]);
|
||||
render_ephemeral_tip(area, &mut buf, &line);
|
||||
|
||||
assert_eq!(
|
||||
row_text(&buf, area, 0).trim_end(),
|
||||
"Queued · Enter to send now"
|
||||
);
|
||||
let bold_cols: Vec<u16> = (0..area.width)
|
||||
.filter(|&x| {
|
||||
buf.cell((x, 0))
|
||||
.expect("cell in area")
|
||||
.modifier
|
||||
.contains(Modifier::BOLD)
|
||||
})
|
||||
.collect();
|
||||
// "Queued · " occupies cols 0..9, "Enter" cols 9..14.
|
||||
assert_eq!(
|
||||
bold_cols,
|
||||
(9..14).collect::<Vec<u16>>(),
|
||||
"only the Enter chord may be bold — no leak from the underpaint"
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user