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,217 @@
|
||||
//! Canonical slash-command wording (`/loop`, `/imagine`, `/imagine-video`, `/goal`),
|
||||
//! shared by every front-end (Grok Build shell/pager and other hosts) so
|
||||
//! expansions cannot drift.
|
||||
|
||||
/// Canonical tool name advertised by the scheduler create tool. Gating code
|
||||
/// (shell `CommandAvailability`, pager `required_tools`, host command lists)
|
||||
/// keys `/loop` availability on this name.
|
||||
pub const SCHEDULER_CREATE_TOOL_NAME: &str = "scheduler_create";
|
||||
|
||||
/// Usage hint shown when `/loop` is invoked with no arguments.
|
||||
pub fn loop_usage_message() -> &'static str {
|
||||
"Usage: /loop [interval] <prompt>\n\
|
||||
Example: /loop 30m check deploy status\n\
|
||||
Example: /loop check deploy status every hour\n\n\
|
||||
Tell me how often it should run (e.g. 30m, 1 hour, every 2 days)."
|
||||
}
|
||||
|
||||
/// Build the model instruction that `/loop` expands into for `args`.
|
||||
///
|
||||
/// The model, not brittle host parsing, turns the request into the
|
||||
/// `scheduler_create` interval, accepting every natural phrasing and erroring
|
||||
/// on bad input rather than silently defaulting. See [`loop_usage_message`].
|
||||
pub fn loop_schedule_instruction(args: &str) -> String {
|
||||
format!(
|
||||
"# /loop -- schedule a recurring prompt\n\n\
|
||||
Parse the input below into an interval and a prompt, then schedule it with scheduler_create.\n\n\
|
||||
## Deriving the interval\n\
|
||||
Read how often to run from the user's request — however they phrase it — and convert it\n\
|
||||
to a compact `<number><unit>` string, where unit is one of `s` (seconds), `m` (minutes),\n\
|
||||
`h` (hours), or `d` (days). The interval may appear at the start or end of the request;\n\
|
||||
extract it and use the remaining text as the prompt.\n\n\
|
||||
The minimum interval is 60 seconds; shorter values are raised to 60s, so tell the user if that applies.\n\n\
|
||||
If the request contains no interval at all, ask the user how often it should run before\n\
|
||||
scheduling. Do NOT invent or assume a default interval.\n\n\
|
||||
## Action\n\
|
||||
1. Call scheduler_create with: interval (the compact string you derived), prompt,\n\
|
||||
recurring: true, fire_immediately: true. If the interval is unparseable, the tool\n\
|
||||
returns an error — fix the interval string rather than guessing.\n\
|
||||
2. Confirm: what's scheduled, the cadence, that it auto-expires after 7 days,\n\
|
||||
and that they can cancel with scheduler_delete (include the job ID).\n\
|
||||
3. Do NOT execute the prompt inline. The scheduler will fire it immediately.\n\n\
|
||||
## Input\n\
|
||||
{args}"
|
||||
)
|
||||
}
|
||||
|
||||
/// Canonical name of the image generation tool; gates `/imagine`.
|
||||
pub const IMAGE_GEN_TOOL_NAME: &str = "image_gen";
|
||||
|
||||
/// Advertised name of the /imagine command.
|
||||
pub const IMAGINE_COMMAND_NAME: &str = "imagine";
|
||||
|
||||
/// Canonical name of the image-to-video tool; gates `/imagine-video`.
|
||||
pub const IMAGE_TO_VIDEO_TOOL_NAME: &str = "image_to_video";
|
||||
|
||||
/// Advertised name of the /imagine-video command.
|
||||
pub const IMAGINE_VIDEO_COMMAND_NAME: &str = "imagine-video";
|
||||
|
||||
/// Usage hint shown when `/imagine` is invoked with no arguments.
|
||||
pub fn imagine_usage_message() -> &'static str {
|
||||
"Usage: /imagine <description>\n\
|
||||
Provide a text description to generate an image."
|
||||
}
|
||||
|
||||
/// Build the model instruction that `/imagine` expands into for `prompt`.
|
||||
pub fn imagine_instruction(prompt: &str) -> String {
|
||||
format!(
|
||||
"Call the image_gen tool immediately, passing the user's prompt below \
|
||||
verbatim — do not rewrite, embellish, or expand it. \
|
||||
After the tool completes, briefly acknowledge and mention \
|
||||
where the image was saved.\n\n\
|
||||
Prompt: {prompt}"
|
||||
)
|
||||
}
|
||||
|
||||
/// Usage hint shown when `/imagine-video` is invoked with no arguments.
|
||||
pub fn imagine_video_usage_message() -> &'static str {
|
||||
"Usage: /imagine-video <description>\n\
|
||||
Provide a text description to generate a video."
|
||||
}
|
||||
|
||||
/// Build the model instruction that `/imagine-video` expands into for `prompt`.
|
||||
pub fn imagine_video_instruction(prompt: &str) -> String {
|
||||
format!(
|
||||
"{IMAGINE_VIDEO_SKILL}\n\n\
|
||||
User prompt: {prompt}"
|
||||
)
|
||||
}
|
||||
|
||||
/// Video workflow guidance injected by `/imagine-video`.
|
||||
const IMAGINE_VIDEO_SKILL: &str = "\
|
||||
# Imagine Video
|
||||
|
||||
Video starts from an image — there is no text-to-video tool. \
|
||||
Default to `image_to_video`; use `reference_to_video` only when the user \
|
||||
explicitly asks for it or a shot genuinely needs multiple reference images.
|
||||
|
||||
## Default: single clip
|
||||
|
||||
Unless the user asks for a long video, multiple scenes, or a multi-shot sequence, \
|
||||
generate **one** video:
|
||||
|
||||
1. Create a source image with `image_gen` that stages the first frame \
|
||||
(composition, subject, lighting).
|
||||
2. Call `image_to_video` with that image and a short prompt describing the motion \
|
||||
or camera move (1–2 sentences, present tense).
|
||||
3. After the tool completes, mention the saved file path so the user can find it.
|
||||
|
||||
## Longer / multi-shot videos
|
||||
|
||||
When the user requests a longer video, multiple scenes, or a narrative sequence:
|
||||
|
||||
1. **Plan the story as shots** — break the idea into distinct shots, one beat each.
|
||||
2. **Favor frequent, short shots** — prefer more 6s clips over fewer long ones; more cuts keep it dynamic.
|
||||
3. **Create each shot's source image** with `image_gen` (or `image_edit` to combine references), keeping characters and settings consistent across shots.
|
||||
4. **Animate each shot with `image_to_video`** — the source image becomes frame 1.
|
||||
5. **Assemble with FFmpeg** using stream copy (`ffmpeg -f concat ... -c copy` — never re-encode). \
|
||||
Keep every shot at the same resolution and frame rate so the concat works. \
|
||||
After assembly, mention the final output path.
|
||||
|
||||
## Shot guidance
|
||||
|
||||
- **Prompt-craft:** one short, vivid moment in present tense with a clear camera movement, in 1–2 sentences.
|
||||
- **Minimal but interesting:** one clear subject, one simple motion or camera move per shot. Avoid complex multi-action animation; make the shot compelling through composition, lighting, and a strong moment.
|
||||
- **Complex source image?** Intricate frames (busy geometry, fine detail, heavy reflections) warp when animated. Keep the subject fixed and move only the camera (slow push-in, orbit, or parallax), or break into simpler shots. For new shots, generate a simpler, animation-friendly base image rather than animating a busy one.
|
||||
- **`image_to_video` animates from frame 1** — stage the first frame with `image_gen`/`image_edit` before animating.
|
||||
- **Aspect ratio:** set it on the source image (`image_gen` `aspect_ratio`); don't re-crop an existing video.
|
||||
- **Duration:** 6s or 10s only (prefer 6s); round to the nearest.
|
||||
- **Real people:** reference-first — drive the video from a verified reference image; never animate a named person without one.
|
||||
- Don't loop the same clip unless asked.";
|
||||
|
||||
pub const UPDATE_GOAL_TOOL_NAME: &str = "update_goal";
|
||||
|
||||
pub const GOAL_COMMAND_NAME: &str = "goal";
|
||||
|
||||
/// Bare subcommand tokens reserved for goal lifecycle control rather than
|
||||
/// being treated as an objective, matching the shell's /goal grammar.
|
||||
pub const GOAL_RESERVED_SUBCOMMANDS: &[&str] = &["status", "pause", "resume", "clear", "edit"];
|
||||
|
||||
pub fn goal_usage_message() -> &'static str {
|
||||
"Usage: /goal <objective>\n\
|
||||
Set an objective to work toward until it is complete."
|
||||
}
|
||||
|
||||
pub fn goal_instruction(objective: &str) -> String {
|
||||
format!(
|
||||
"# /goal -- pursue an objective\n\n\
|
||||
A goal has been set: {objective}\n\n\
|
||||
Work directly on this goal and carry it as far as you can. Deliver \
|
||||
everything the user asked for yourself: no follow-up questions, no \
|
||||
manual steps left for the user. If the conversation continues, keep \
|
||||
pursuing the goal until it is complete.\n\n\
|
||||
TRACKING: break the objective into concrete steps and track them \
|
||||
(use your todo tool if one is available), marking each done as you \
|
||||
finish it.\n\n\
|
||||
VERIFY AS YOU GO: test each change on the real path before moving on. \
|
||||
A completion claim must be backed by evidence produced in this \
|
||||
session, not assumptions.\n\n\
|
||||
Call update_goal(completed: true, message: \"summary\") ONLY when the \
|
||||
goal is fully achieved. Call update_goal(blocked_reason: \"reason\") \
|
||||
only when truly stuck after 3+ consecutive failed attempts at the \
|
||||
same problem. Call update_goal(message: \"status note\") to log \
|
||||
progress along the way. If update_goal returns an error, continue \
|
||||
working the goal and report status in your reply instead.\n\n\
|
||||
Start now."
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn imagine_instruction_carries_prompt_verbatim() {
|
||||
let text = imagine_instruction("a golden sunset");
|
||||
assert!(text.contains("a golden sunset"));
|
||||
assert!(text.contains("image_gen"));
|
||||
assert!(text.contains("verbatim"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn imagine_video_instruction_carries_prompt_and_workflow() {
|
||||
let text = imagine_video_instruction("a cat playing piano");
|
||||
assert!(text.contains("a cat playing piano"));
|
||||
assert!(text.contains("image_to_video"));
|
||||
assert!(text.contains("FFmpeg"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn instruction_carries_args_and_contract_tokens() {
|
||||
let text = loop_schedule_instruction("every 30 minutes do x");
|
||||
assert!(text.contains("every 30 minutes do x"));
|
||||
assert!(text.contains("<number><unit>"));
|
||||
assert!(text.contains("ask the user how often"));
|
||||
assert!(!text.contains("10m"), "no host-side default interval");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goal_instruction_carries_objective_and_contract_tokens() {
|
||||
let text = goal_instruction("ship the widget");
|
||||
assert!(text.contains("ship the widget"));
|
||||
assert!(text.contains("update_goal(completed: true"));
|
||||
assert!(text.contains("blocked_reason"));
|
||||
assert!(text.contains("If update_goal returns an error"));
|
||||
assert!(
|
||||
!text.contains("system-reminder"),
|
||||
"expansions ride as user messages and must not claim reminder authority"
|
||||
);
|
||||
assert!(goal_usage_message().contains("Usage: /goal"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usage_message_has_no_default_claim() {
|
||||
assert!(loop_usage_message().contains("Usage: /loop"));
|
||||
assert!(!loop_usage_message().contains("10m"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user