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).
221 lines
6.8 KiB
Rust
221 lines
6.8 KiB
Rust
//! `/docs` -- open How-to Guides (in-TUI) or online Build docs.
|
|
//!
|
|
//! Bare `/docs` opens the same DocPicker as command-palette "How-to Guides".
|
|
//! `/docs web` opens https://docs.x.ai/build/overview in the browser.
|
|
//! `/docs <title>` opens a single guide by title (case-insensitive).
|
|
|
|
use crate::app::actions::Action;
|
|
use crate::docs::{all_titles, find_doc};
|
|
use crate::slash::command::{AppCtx, ArgItem, CommandExecCtx, CommandResult, SlashCommand};
|
|
|
|
/// Online Build docs landing page (hardcoded like other TUI deep-links; docs.x.ai can redirect if the path moves).
|
|
pub const BUILD_DOCS_URL: &str = "https://docs.x.ai/build/overview";
|
|
|
|
/// Open How-to Guides or online Build docs.
|
|
pub struct DocsCommand;
|
|
|
|
impl SlashCommand for DocsCommand {
|
|
fn name(&self) -> &str {
|
|
"docs"
|
|
}
|
|
|
|
fn aliases(&self) -> &[&str] {
|
|
&["howto", "guides"]
|
|
}
|
|
|
|
fn description(&self) -> &str {
|
|
"Open How-to Guides or online Build docs"
|
|
}
|
|
|
|
fn usage(&self) -> &str {
|
|
"/docs [web|title]"
|
|
}
|
|
|
|
fn takes_args(&self) -> bool {
|
|
true
|
|
}
|
|
|
|
fn args_required(&self) -> bool {
|
|
false
|
|
}
|
|
|
|
fn arg_placeholder(&self) -> Option<&str> {
|
|
Some("[web|title]")
|
|
}
|
|
|
|
fn suggest_args(&self, _ctx: &AppCtx, _args_query: &str) -> Option<Vec<ArgItem>> {
|
|
let mut items = vec![
|
|
ArgItem {
|
|
display: "how-to".into(),
|
|
match_text: "how-to".into(),
|
|
insert_text: "how-to".into(),
|
|
description: "Browse in-TUI How-to Guides".into(),
|
|
},
|
|
ArgItem {
|
|
display: "web".into(),
|
|
match_text: "web".into(),
|
|
insert_text: "web".into(),
|
|
description: "Open docs.x.ai/build in the browser".into(),
|
|
},
|
|
];
|
|
items.extend(all_titles().map(|title| ArgItem {
|
|
display: title.into(),
|
|
match_text: title.into(),
|
|
insert_text: title.into(),
|
|
description: format!("Open \"{title}\""),
|
|
}));
|
|
Some(items)
|
|
}
|
|
|
|
fn run(&self, _ctx: &mut CommandExecCtx, args: &str) -> CommandResult {
|
|
let trimmed = args.trim();
|
|
if trimmed.is_empty() || is_howto_list_arg(trimmed) {
|
|
return CommandResult::Action(Action::OpenHowtoGuides);
|
|
}
|
|
if is_web_arg(trimmed) {
|
|
return CommandResult::Action(Action::OpenUrl(BUILD_DOCS_URL.into()));
|
|
}
|
|
match find_doc(trimmed) {
|
|
Some(doc) => CommandResult::Action(Action::ShowReleaseNotes {
|
|
title: doc.title.into(),
|
|
content: doc.content.into(),
|
|
}),
|
|
None => CommandResult::Error(format!(
|
|
"Unknown docs target {trimmed:?}. Try /docs, /docs web, or a guide title (e.g. /docs Getting Started)."
|
|
)),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn is_howto_list_arg(arg: &str) -> bool {
|
|
matches!(
|
|
arg.to_ascii_lowercase().as_str(),
|
|
"how-to" | "howto" | "guides" | "guide" | "list" | "tui"
|
|
)
|
|
}
|
|
|
|
fn is_web_arg(arg: &str) -> bool {
|
|
matches!(
|
|
arg.to_ascii_lowercase().as_str(),
|
|
"web" | "online" | "browser" | "site" | "www"
|
|
)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::acp::model_state::ModelState;
|
|
|
|
static DEFAULT_BUNDLE_STATE: crate::app::bundle::BundleState =
|
|
crate::app::bundle::BundleState {
|
|
has_cache: false,
|
|
version: String::new(),
|
|
personas: Vec::new(),
|
|
roles: Vec::new(),
|
|
agents: Vec::new(),
|
|
skills: Vec::new(),
|
|
persona_details: Vec::new(),
|
|
role_details: Vec::new(),
|
|
};
|
|
|
|
fn make_ctx<'a>(models: &'a ModelState) -> CommandExecCtx<'a> {
|
|
CommandExecCtx {
|
|
models,
|
|
session_id: None,
|
|
bundle_state: &DEFAULT_BUNDLE_STATE,
|
|
screen_mode: crate::app::ScreenMode::Inline,
|
|
pager_state: crate::settings::PagerLocalSnapshot {
|
|
multiline_mode: false,
|
|
yolo_mode: false,
|
|
..crate::settings::PagerLocalSnapshot::default()
|
|
},
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn bare_docs_opens_howto_guides() {
|
|
let models = ModelState::default();
|
|
let mut ctx = make_ctx(&models);
|
|
assert!(matches!(
|
|
DocsCommand.run(&mut ctx, ""),
|
|
CommandResult::Action(Action::OpenHowtoGuides)
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn howto_aliases_open_list() {
|
|
let models = ModelState::default();
|
|
let mut ctx = make_ctx(&models);
|
|
for args in ["how-to", "howto", "guides", "list", "tui"] {
|
|
assert!(
|
|
matches!(
|
|
DocsCommand.run(&mut ctx, args),
|
|
CommandResult::Action(Action::OpenHowtoGuides)
|
|
),
|
|
"args={args:?}"
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn web_opens_build_docs_url() {
|
|
let models = ModelState::default();
|
|
let mut ctx = make_ctx(&models);
|
|
for args in ["web", "online", "browser"] {
|
|
match DocsCommand.run(&mut ctx, args) {
|
|
CommandResult::Action(Action::OpenUrl(url)) => {
|
|
assert_eq!(url, BUILD_DOCS_URL, "args={args:?}");
|
|
}
|
|
other => panic!("expected OpenUrl for args={args:?}, got {other:?}"),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn title_opens_guide() {
|
|
let models = ModelState::default();
|
|
let mut ctx = make_ctx(&models);
|
|
match DocsCommand.run(&mut ctx, "Getting Started") {
|
|
CommandResult::Action(Action::ShowReleaseNotes { title, content }) => {
|
|
assert_eq!(title, "Getting Started");
|
|
assert!(!content.is_empty());
|
|
}
|
|
other => panic!("expected ShowReleaseNotes, got {other:?}"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn unknown_target_errors() {
|
|
let models = ModelState::default();
|
|
let mut ctx = make_ctx(&models);
|
|
assert!(matches!(
|
|
DocsCommand.run(&mut ctx, "not-a-real-guide"),
|
|
CommandResult::Error(_)
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn aliases_and_metadata() {
|
|
let cmd = DocsCommand;
|
|
assert_eq!(cmd.name(), "docs");
|
|
assert_eq!(cmd.aliases(), &["howto", "guides"]);
|
|
assert!(cmd.takes_args());
|
|
assert!(!cmd.args_required());
|
|
}
|
|
|
|
#[test]
|
|
fn suggest_args_includes_web_and_titles() {
|
|
let models = ModelState::default();
|
|
let cwd = std::path::Path::new(".");
|
|
let ctx = AppCtx {
|
|
models: &models,
|
|
cwd,
|
|
screen_mode: crate::app::ScreenMode::Fullscreen,
|
|
};
|
|
let items = DocsCommand.suggest_args(&ctx, "").expect("suggestions");
|
|
assert!(items.iter().any(|i| i.insert_text == "web"));
|
|
assert!(items.iter().any(|i| i.insert_text == "how-to"));
|
|
assert!(items.iter().any(|i| i.insert_text == "Getting Started"));
|
|
}
|
|
}
|