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
+246
View File
@@ -0,0 +1,246 @@
use std::borrow::Cow;
use serde_yaml::Value;
use crate::theme::{MermaidTheme, MermaidThemePreset, MermaidThemeVariables};
#[derive(Debug, Clone, Default, PartialEq)]
pub struct ParsedMermaidSource<'a> {
pub body: Cow<'a, str>,
pub frontmatter: Option<MermaidFrontmatter>,
pub config: RenderConfig,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct MermaidFrontmatter {
pub title: Option<String>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct RenderConfig {
pub theme: Option<MermaidThemePreset>,
pub theme_variables: MermaidThemeVariables,
/// Parsed for Mermaid frontmatter compatibility, but not currently rendered.
pub layout: Option<String>,
/// Parsed for Mermaid frontmatter compatibility, but not currently rendered.
pub look: Option<String>,
/// Parsed for Mermaid frontmatter compatibility, but not currently rendered.
pub security_level: Option<String>,
pub font_family: Option<String>,
pub font_size: Option<String>,
pub flowchart: FlowchartConfig,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct FlowchartConfig {
pub curve: Option<String>,
/// Parsed for Mermaid frontmatter compatibility, but not currently rendered.
pub html_labels: Option<bool>,
pub node_spacing: Option<u32>,
pub rank_spacing: Option<u32>,
pub padding: Option<u32>,
/// Parsed for Mermaid frontmatter compatibility, but not currently rendered.
pub diagram_padding: Option<u32>,
pub wrapping_width: Option<u32>,
/// Parsed for Mermaid frontmatter compatibility, but not currently rendered.
pub use_max_width: Option<bool>,
/// Parsed for Mermaid frontmatter compatibility, but not currently rendered.
pub default_renderer: Option<String>,
}
impl RenderConfig {
pub fn to_mermaid_theme(&self) -> Option<MermaidTheme> {
if self.theme.is_none() && self.theme_variables.is_empty() {
return None;
}
let mut theme = self.theme.unwrap_or(MermaidThemePreset::Default).to_theme();
self.theme_variables.apply_to(&mut theme);
Some(theme)
}
pub fn font_size_px(&self) -> Option<f64> {
self.font_size.as_deref().and_then(parse_font_size)
}
}
pub fn parse_mermaid_frontmatter(source: &str) -> ParsedMermaidSource<'_> {
let Some((yaml_start, yaml_end, body_start)) = frontmatter_bounds(source) else {
return ParsedMermaidSource {
body: Cow::Borrowed(source),
frontmatter: None,
config: RenderConfig::default(),
};
};
let body = Cow::Owned(source[body_start..].to_string());
let yaml = &source[yaml_start..yaml_end];
let Some(value) = parse_yaml_value(yaml) else {
return ParsedMermaidSource {
body,
frontmatter: Some(MermaidFrontmatter::default()),
config: RenderConfig::default(),
};
};
let frontmatter = parse_frontmatter_metadata(&value);
let config = parse_render_config(&value);
ParsedMermaidSource {
body,
frontmatter: Some(frontmatter),
config,
}
}
fn parse_yaml_value(yaml: &str) -> Option<Value> {
if yaml.trim().is_empty() {
return Some(Value::Null);
}
serde_yaml::from_str::<Value>(yaml).ok()
}
fn parse_frontmatter_metadata(value: &Value) -> MermaidFrontmatter {
MermaidFrontmatter {
title: mapping_value(value, "title").and_then(value_to_string),
}
}
fn parse_render_config(value: &Value) -> RenderConfig {
let Some(config) = mapping_value(value, "config") else {
return RenderConfig::default();
};
RenderConfig {
theme: mapping_value(config, "theme")
.and_then(value_to_string)
.and_then(|theme| MermaidThemePreset::parse(&theme)),
theme_variables: parse_theme_variables(mapping_value(config, "themeVariables")),
layout: mapping_value(config, "layout").and_then(value_to_string),
look: mapping_value(config, "look").and_then(value_to_string),
security_level: mapping_value(config, "securityLevel").and_then(value_to_string),
font_family: mapping_value(config, "fontFamily").and_then(value_to_string),
font_size: mapping_value(config, "fontSize").and_then(value_to_string),
flowchart: parse_flowchart_config(mapping_value(config, "flowchart")),
}
}
fn parse_theme_variables(value: Option<&Value>) -> MermaidThemeVariables {
let mut variables = MermaidThemeVariables::default();
let Some(Value::Mapping(mapping)) = value else {
return variables;
};
for (key, value) in mapping {
let Some(key) = key.as_str() else {
continue;
};
let Some(value) = value_to_string(value) else {
continue;
};
variables.apply_mermaid_alias(key, value);
}
variables
}
fn parse_flowchart_config(value: Option<&Value>) -> FlowchartConfig {
let Some(flowchart) = value else {
return FlowchartConfig::default();
};
FlowchartConfig {
curve: mapping_value(flowchart, "curve").and_then(value_to_string),
html_labels: mapping_value(flowchart, "htmlLabels").and_then(value_to_bool),
node_spacing: mapping_value(flowchart, "nodeSpacing").and_then(value_to_u32),
rank_spacing: mapping_value(flowchart, "rankSpacing").and_then(value_to_u32),
padding: mapping_value(flowchart, "padding").and_then(value_to_u32),
diagram_padding: mapping_value(flowchart, "diagramPadding").and_then(value_to_u32),
wrapping_width: mapping_value(flowchart, "wrappingWidth").and_then(value_to_u32),
use_max_width: mapping_value(flowchart, "useMaxWidth").and_then(value_to_bool),
default_renderer: mapping_value(flowchart, "defaultRenderer").and_then(value_to_string),
}
}
fn mapping_value<'a>(value: &'a Value, key: &str) -> Option<&'a Value> {
let Value::Mapping(mapping) = value else {
return None;
};
mapping.get(&Value::String(key.to_string()))
}
fn value_to_string(value: &Value) -> Option<String> {
match value {
Value::String(value) => Some(value.clone()),
Value::Number(value) => Some(value.to_string()),
Value::Bool(value) => Some(value.to_string()),
_ => None,
}
}
fn value_to_bool(value: &Value) -> Option<bool> {
match value {
Value::Bool(value) => Some(*value),
Value::String(value) => match value.as_str() {
"true" => Some(true),
"false" => Some(false),
_ => None,
},
_ => None,
}
}
fn value_to_u32(value: &Value) -> Option<u32> {
match value {
Value::Number(value) => value.as_u64().and_then(|value| u32::try_from(value).ok()),
Value::String(value) => value.parse().ok(),
_ => None,
}
}
fn parse_font_size(value: &str) -> Option<f64> {
let trimmed = value.trim();
let numeric = trimmed.strip_suffix("px").unwrap_or(trimmed).trim();
numeric
.parse::<f64>()
.ok()
.filter(|size| size.is_finite() && *size > 0.0)
}
fn frontmatter_bounds(source: &str) -> Option<(usize, usize, usize)> {
let mut cursor = 0;
while cursor < source.len() {
let end = next_line_end(source, cursor);
let line = source[cursor..end].trim();
if line.is_empty() {
cursor = end;
continue;
}
if line != "---" {
return None;
}
let yaml_start = end;
let mut scan = end;
while scan < source.len() {
let scan_end = next_line_end(source, scan);
if source[scan..scan_end].trim() == "---" {
return Some((yaml_start, scan, scan_end));
}
scan = scan_end;
}
return None;
}
None
}
fn next_line_end(source: &str, start: usize) -> usize {
source[start..]
.find('\n')
.map(|position| start + position + 1)
.unwrap_or(source.len())
}