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:
@@ -0,0 +1,298 @@
|
||||
//! Terminal-native palette for minimal mode.
|
||||
//!
|
||||
//! Any RGB theme is designed for one background polarity, so composited on
|
||||
//! the terminal's own canvas it can land dark-on-dark or light-on-light
|
||||
//! (e.g. macOS in Light Mode + a dark terminal profile). Polarity detection
|
||||
//! is not reliable either: OS appearance and OSC 11 both disagree with the
|
||||
//! actual canvas in edge cases and can change mid-session. Terminal
|
||||
//! profiles, however, tune their **default** fg/bg to be legible against
|
||||
//! their own background — this is how `git` and `ls` stay readable on any
|
||||
//! terminal — so a palette built from `Reset` (body) + sparse named ANSI-16
|
||||
//! accents is polarity-safe without detection.
|
||||
//!
|
||||
//! ## Grays / secondary text
|
||||
//!
|
||||
//! Do **not** paint body or status text as `DarkGray` (ANSI bright black).
|
||||
//! Many dark profiles deliberately set that slot very dark for subtle
|
||||
//! chrome, which washes out tool stdout and the prompt info bar. Instead:
|
||||
//!
|
||||
//! - **Primary content** (`text_primary`, `gray_bright`, …) → `Color::Reset`
|
||||
//! (terminal default foreground).
|
||||
//! - **Secondary chrome** (`gray`, `gray_dim`, `text_secondary`) → also
|
||||
//! `Color::Reset`; [`Theme::muted`] / [`Theme::dim`] apply `Modifier::DIM`
|
||||
//! so de-emphasis tracks the terminal's own fg (polarity-safe), unlike
|
||||
//! hard-coding bright black.
|
||||
|
||||
use ratatui::style::{Color, Modifier};
|
||||
|
||||
use super::Theme;
|
||||
|
||||
impl Theme {
|
||||
/// The fixed terminal-native palette used by minimal mode: every field
|
||||
/// is `Color::Reset` or a named ANSI-16 color (see the module docs).
|
||||
pub const fn terminal_default() -> Self {
|
||||
// Secondary roles store Reset; Theme::muted / Theme::dim apply SGR dim.
|
||||
const MUTED: Color = Color::Reset;
|
||||
|
||||
Self {
|
||||
bg_base: Color::Reset,
|
||||
bg_light: Color::Reset,
|
||||
bg_dark: Color::Reset,
|
||||
bg_highlight: Color::Reset,
|
||||
bg_hover: Color::Reset,
|
||||
bg_terminal: Color::Reset,
|
||||
|
||||
accent_user: Color::Reset,
|
||||
accent_assistant: Color::Magenta,
|
||||
accent_thinking: MUTED,
|
||||
accent_tool: MUTED,
|
||||
accent_system: Color::Blue,
|
||||
accent_error: Color::Red,
|
||||
accent_success: Color::Green,
|
||||
accent_running: Color::Magenta,
|
||||
accent_skill: Color::Blue,
|
||||
|
||||
text_primary: Color::Reset,
|
||||
text_secondary: MUTED,
|
||||
|
||||
gray_dim: MUTED,
|
||||
gray: MUTED,
|
||||
gray_bright: Color::Reset,
|
||||
|
||||
command: Color::Yellow,
|
||||
path: Color::Cyan,
|
||||
running: Color::Cyan,
|
||||
warning: Color::Yellow,
|
||||
|
||||
fuzzy_accent: Color::Cyan,
|
||||
|
||||
accent_plan: Color::Yellow,
|
||||
accent_verify: Color::Magenta,
|
||||
accent_feedback: Color::Cyan,
|
||||
accent_remember: Color::Green,
|
||||
|
||||
selection_border: MUTED,
|
||||
hover_border: MUTED,
|
||||
prompt_border: MUTED,
|
||||
prompt_border_active: Color::Reset,
|
||||
|
||||
accent_model: Color::Cyan,
|
||||
|
||||
scrollbar_bg: Color::Reset,
|
||||
scrollbar_fg: MUTED,
|
||||
|
||||
diff_delete_bg: Color::Reset,
|
||||
diff_delete_fg: Color::Red,
|
||||
diff_insert_bg: Color::Reset,
|
||||
diff_insert_fg: Color::Green,
|
||||
diff_equal_fg: MUTED,
|
||||
diff_gutter_fg: MUTED,
|
||||
|
||||
bg_visual: Color::Reset,
|
||||
|
||||
paste_bg: Color::Reset,
|
||||
paste_fg: MUTED,
|
||||
paste_dim: MUTED,
|
||||
|
||||
md_heading_h1: Color::Reset,
|
||||
md_heading_h1_mod: Modifier::BOLD.union(Modifier::UNDERLINED),
|
||||
md_heading_h2: Color::Reset,
|
||||
md_heading_h2_mod: Modifier::BOLD,
|
||||
md_heading_h3: Color::Reset,
|
||||
md_heading_h3_mod: Modifier::BOLD,
|
||||
md_heading_h4: Color::Reset,
|
||||
md_heading_h4_mod: Modifier::BOLD,
|
||||
md_heading_h5: Color::Reset,
|
||||
md_heading_h5_mod: Modifier::BOLD,
|
||||
md_heading_h6: MUTED,
|
||||
md_heading_h6_mod: Modifier::BOLD,
|
||||
md_code: Color::Cyan,
|
||||
md_task_checked: Color::Green,
|
||||
md_task_unchecked: MUTED,
|
||||
md_muted: MUTED,
|
||||
md_code_bg: Color::Reset,
|
||||
md_text: Color::Reset,
|
||||
link_fg: Color::Blue,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn all_colors(theme: &Theme) -> Vec<(&'static str, Color)> {
|
||||
vec![
|
||||
("bg_base", theme.bg_base),
|
||||
("bg_light", theme.bg_light),
|
||||
("bg_dark", theme.bg_dark),
|
||||
("bg_highlight", theme.bg_highlight),
|
||||
("bg_hover", theme.bg_hover),
|
||||
("bg_terminal", theme.bg_terminal),
|
||||
("accent_user", theme.accent_user),
|
||||
("accent_assistant", theme.accent_assistant),
|
||||
("accent_thinking", theme.accent_thinking),
|
||||
("accent_tool", theme.accent_tool),
|
||||
("accent_system", theme.accent_system),
|
||||
("accent_error", theme.accent_error),
|
||||
("accent_success", theme.accent_success),
|
||||
("accent_running", theme.accent_running),
|
||||
("accent_skill", theme.accent_skill),
|
||||
("text_primary", theme.text_primary),
|
||||
("text_secondary", theme.text_secondary),
|
||||
("gray_dim", theme.gray_dim),
|
||||
("gray", theme.gray),
|
||||
("gray_bright", theme.gray_bright),
|
||||
("command", theme.command),
|
||||
("path", theme.path),
|
||||
("running", theme.running),
|
||||
("warning", theme.warning),
|
||||
("fuzzy_accent", theme.fuzzy_accent),
|
||||
("accent_plan", theme.accent_plan),
|
||||
("accent_verify", theme.accent_verify),
|
||||
("accent_feedback", theme.accent_feedback),
|
||||
("accent_remember", theme.accent_remember),
|
||||
("selection_border", theme.selection_border),
|
||||
("hover_border", theme.hover_border),
|
||||
("prompt_border", theme.prompt_border),
|
||||
("prompt_border_active", theme.prompt_border_active),
|
||||
("accent_model", theme.accent_model),
|
||||
("scrollbar_bg", theme.scrollbar_bg),
|
||||
("scrollbar_fg", theme.scrollbar_fg),
|
||||
("diff_delete_bg", theme.diff_delete_bg),
|
||||
("diff_delete_fg", theme.diff_delete_fg),
|
||||
("diff_insert_bg", theme.diff_insert_bg),
|
||||
("diff_insert_fg", theme.diff_insert_fg),
|
||||
("diff_equal_fg", theme.diff_equal_fg),
|
||||
("diff_gutter_fg", theme.diff_gutter_fg),
|
||||
("bg_visual", theme.bg_visual),
|
||||
("paste_bg", theme.paste_bg),
|
||||
("paste_fg", theme.paste_fg),
|
||||
("paste_dim", theme.paste_dim),
|
||||
("md_heading_h1", theme.md_heading_h1),
|
||||
("md_heading_h2", theme.md_heading_h2),
|
||||
("md_heading_h3", theme.md_heading_h3),
|
||||
("md_heading_h4", theme.md_heading_h4),
|
||||
("md_heading_h5", theme.md_heading_h5),
|
||||
("md_heading_h6", theme.md_heading_h6),
|
||||
("md_code", theme.md_code),
|
||||
("md_task_checked", theme.md_task_checked),
|
||||
("md_task_unchecked", theme.md_task_unchecked),
|
||||
("md_muted", theme.md_muted),
|
||||
("md_code_bg", theme.md_code_bg),
|
||||
("md_text", theme.md_text),
|
||||
("link_fg", theme.link_fg),
|
||||
]
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn terminal_default_uses_only_reset_and_named_ansi() {
|
||||
let theme = Theme::terminal_default();
|
||||
for (name, color) in all_colors(&theme) {
|
||||
assert!(
|
||||
!matches!(color, Color::Rgb(..) | Color::Indexed(_)),
|
||||
"{name} must be Reset or a named ANSI color, got {color:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn terminal_default_backgrounds_are_transparent() {
|
||||
let theme = Theme::terminal_default();
|
||||
for (name, color) in [
|
||||
("bg_base", theme.bg_base),
|
||||
("bg_light", theme.bg_light),
|
||||
("bg_dark", theme.bg_dark),
|
||||
("bg_terminal", theme.bg_terminal),
|
||||
("md_code_bg", theme.md_code_bg),
|
||||
("diff_delete_bg", theme.diff_delete_bg),
|
||||
("diff_insert_bg", theme.diff_insert_bg),
|
||||
("paste_bg", theme.paste_bg),
|
||||
] {
|
||||
assert_eq!(color, Color::Reset, "{name} must defer to the canvas");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn terminal_default_leaves_cursor_color_alone() {
|
||||
let theme = Theme::terminal_default();
|
||||
assert_eq!(theme.accent_user, Color::Reset);
|
||||
assert_eq!(
|
||||
crate::render::color::resolve_to_rgb(theme.accent_user),
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn terminal_default_survives_quantization() {
|
||||
use crate::theme::color_support::ColorLevel;
|
||||
let theme = Theme::terminal_default();
|
||||
for level in [
|
||||
ColorLevel::Basic,
|
||||
ColorLevel::Ansi256,
|
||||
ColorLevel::TrueColor,
|
||||
] {
|
||||
let quantized = theme.quantized(level);
|
||||
for ((name, before), (_, after)) in
|
||||
all_colors(&theme).into_iter().zip(all_colors(&quantized))
|
||||
{
|
||||
assert_eq!(before, after, "{name} must survive {level:?}");
|
||||
}
|
||||
}
|
||||
let stripped = theme.quantized(ColorLevel::None);
|
||||
for (name, color) in all_colors(&stripped) {
|
||||
assert_eq!(color, Color::Reset, "{name} must strip under NO_COLOR");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn terminal_default_primary_is_reset_not_dark_gray() {
|
||||
let theme = Theme::terminal_default();
|
||||
assert_eq!(theme.text_primary, Color::Reset);
|
||||
assert_eq!(theme.gray, Color::Reset);
|
||||
assert_eq!(theme.gray_dim, Color::Reset);
|
||||
// Must not hard-code bright black for body/secondary roles.
|
||||
assert_ne!(theme.text_primary, Color::DarkGray);
|
||||
assert_ne!(theme.gray, Color::DarkGray);
|
||||
assert_ne!(theme.gray_dim, Color::DarkGray);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn terminal_default_muted_and_dim_use_sgr_dim_not_bright_black() {
|
||||
use ratatui::style::Modifier;
|
||||
let theme = Theme::terminal_default();
|
||||
let muted = theme.muted();
|
||||
let dim = theme.dim();
|
||||
assert!(
|
||||
muted.add_modifier.contains(Modifier::DIM),
|
||||
"muted should DIM the terminal default fg: {muted:?}"
|
||||
);
|
||||
assert!(
|
||||
dim.add_modifier.contains(Modifier::DIM),
|
||||
"dim should DIM the terminal default fg: {dim:?}"
|
||||
);
|
||||
// No explicit DarkGray paint — dim tracks the host palette.
|
||||
assert!(
|
||||
muted.fg.is_none() || muted.fg == Some(Color::Reset),
|
||||
"muted must not set a hard gray: {muted:?}"
|
||||
);
|
||||
assert!(
|
||||
dim.fg.is_none() || dim.fg == Some(Color::Reset),
|
||||
"dim must not set a hard gray: {dim:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rgb_theme_muted_keeps_explicit_gray_without_forced_dim() {
|
||||
use ratatui::style::Modifier;
|
||||
// GrokNight paints real RGB grays; muted/dim must not invent DIM.
|
||||
let theme = Theme::groknight();
|
||||
assert!(!matches!(theme.gray, Color::Reset));
|
||||
let muted = theme.muted();
|
||||
assert_eq!(muted.fg, Some(theme.gray));
|
||||
assert!(
|
||||
!muted.add_modifier.contains(Modifier::DIM),
|
||||
"RGB muted should not force SGR dim: {muted:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user