From 9fb82291b2ae342aac9181bcd937066dac194b28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Mon, 10 Aug 2026 21:13:19 -0400 Subject: [PATCH] chore(migration): repoint every published link off GitHub MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The canonical repo is git.zacharyzhang.com now, so every URL a user can actually reach had to move with it. Self-updater: `PRODUCTION_ENDPOINTS.update_base_url` targets Gitea's `/api/v1/repos/.../releases`, which serves the same wire format GitHub's releases API did — `Release`/asset deserialisation is unchanged. The alpha channel requests `?per_page=30&limit=30` because GitHub reads the page size from `per_page` and Gitea from `limit`, and each ignores the other's, so one URL stays correct on either. Installers: same API base, and the documented one-liners become `https://kigicli.dev/install.{sh,ps1}`. That domain's Worker proxies both files from Gitea raw `main`, so the published install command survives any future forge move without being reprinted everywhere. install.ps1 also drops the GitHub-specific `Accept: application/vnd.github+json`. Everything else user-facing: `/feedback` and the shell's feedback fallback point at the Gitea issue tracker, `/docs` at the Gitea-rendered README, and web_fetch's User-Agent at kigicli.dev. SECURITY.md now routes reports to the issue tracker with a warning to withhold exploitable detail, because Gitea has no equivalent of GitHub's private vulnerability reporting. Comments naming `.github/workflows/release.yml` name `.gitea/` now. Tests assert against these constants rather than literal URLs, so they follow automatically; the one URL-shape assertion (production endpoints are https) still holds. Co-Authored-By: Claude Opus 5 (1M context) --- README.md | 8 +++---- SECURITY.md | 8 ++++--- crates/codegen/kigi-env/src/lib.rs | 7 +++--- .../kigi-shell/src/agent/feedback_client.rs | 5 ++-- .../implementations/kigi/web_fetch/config.rs | 2 +- .../kigi-tui/src/slash/commands/docs.rs | 5 ++-- .../kigi-tui/src/slash/commands/feedback.rs | 9 ++++---- crates/codegen/kigi-update/src/auto_update.rs | 23 ++++++++++--------- crates/codegen/kigi-update/src/version.rs | 23 +++++++++++-------- .../codegen/kigi-update/tests/common/mod.rs | 2 +- install.ps1 | 17 +++++++------- install.sh | 15 ++++++------ 12 files changed, 68 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index 3321595..4a94382 100644 --- a/README.md +++ b/README.md @@ -36,12 +36,12 @@ key? OpenAI, Anthropic, Google, DeepSeek, Groq, Moonshot and ```sh # macOS / Linux -curl -fsSL https://raw.githubusercontent.com/ZacharyZhang-NY/Kigi-CLI/main/install.sh | bash +curl -fsSL https://kigicli.dev/install.sh | bash ``` ```powershell # Windows PowerShell -irm https://raw.githubusercontent.com/ZacharyZhang-NY/Kigi-CLI/main/install.ps1 | iex +irm https://kigicli.dev/install.ps1 | iex ``` ```sh @@ -176,8 +176,8 @@ keyring entry, and its own `KIGI_*` env vars, and never touches what the official `kimi` CLI installed. `kigi import-kimi` copies your old config over once, read-only. -**Zero telemetry.** It talks to the APIs you configured, GitHub Releases, and -your own MCP servers. Nothing else. +**Zero telemetry.** It talks to the APIs you configured, the Kigi release +server, and your own MCP servers. Nothing else. ## License diff --git a/SECURITY.md b/SECURITY.md index 45f2801..978e13f 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,8 +1,10 @@ # Security Policy -Kigi is a community build; report vulnerabilities via -[GitHub private vulnerability reporting](https://github.com/ZacharyZhang-NY/Kigi-CLI/security/advisories/new) -on this repository. Please do not open public issues for security reports. +Kigi is a community build; report vulnerabilities on the +[Kigi issue tracker](https://git.zacharyzhang.com/ZacharyZhang-NY/Kigi-CLI/issues). +Issues there are public, so please leave out any detail that would make a +report directly exploitable — open with the affected area and impact, and a +maintainer will follow up for the rest. Dependency advisories are gated in CI with `cargo deny check advisories`; documented exceptions live in `deny.toml`. diff --git a/crates/codegen/kigi-env/src/lib.rs b/crates/codegen/kigi-env/src/lib.rs index ee7b092..8a9ed24 100644 --- a/crates/codegen/kigi-env/src/lib.rs +++ b/crates/codegen/kigi-env/src/lib.rs @@ -11,7 +11,8 @@ pub struct KigiEndpoints { pub coding_api_base_url: &'static str, /// OAuth device-flow host for Kimi Code subscription login. pub oauth_host: &'static str, - /// GitHub Releases API endpoint the self-updater polls. + /// Releases API endpoint the self-updater polls (GitHub-Releases-shaped; + /// Gitea's `/api/v1` serves the same wire format). pub update_base_url: &'static str, /// Human-facing page for subscription upgrade guidance. pub upgrade_page_url: &'static str, @@ -20,7 +21,7 @@ pub struct KigiEndpoints { pub const PRODUCTION_ENDPOINTS: KigiEndpoints = KigiEndpoints { coding_api_base_url: "https://api.kimi.com/coding/v1", oauth_host: "https://auth.kimi.com", - update_base_url: "https://api.github.com/repos/ZacharyZhang-NY/Kigi-CLI/releases", + update_base_url: "https://git.zacharyzhang.com/api/v1/repos/ZacharyZhang-NY/Kigi-CLI/releases", upgrade_page_url: "https://www.kimi.com/code/", }; @@ -29,7 +30,7 @@ pub const CODE_BASE_URL_ENV: &str = "KIGI_CODE_BASE_URL"; /// Env var overriding [`oauth_host`] (PRD F1). pub const OAUTH_HOST_ENV: &str = "KIGI_OAUTH_HOST"; /// Env var overriding [`update_base_url`] (PRD F8). Points the self-updater -/// at an alternate GitHub-Releases-shaped API (mirrors, tests). +/// at an alternate GitHub/Gitea-Releases-shaped API (mirrors, tests). pub const UPDATE_BASE_URL_ENV: &str = "KIGI_UPDATE_BASE_URL"; fn resolve(var: &str, compiled: &'static str) -> String { diff --git a/crates/codegen/kigi-shell/src/agent/feedback_client.rs b/crates/codegen/kigi-shell/src/agent/feedback_client.rs index 522a7c2..8791dbb 100644 --- a/crates/codegen/kigi-shell/src/agent/feedback_client.rs +++ b/crates/codegen/kigi-shell/src/agent/feedback_client.rs @@ -3,7 +3,7 @@ //! Port of kimi-cli's `/feedback` slash command (kimi-cli //! `src/kimi_cli/ui/shell/slash.py`, `feedback()`): subscription (OAuth) //! sessions POST the user's feedback text to `{coding_api_base_url}/feedback` -//! with a Bearer token; everyone else is pointed at the GitHub issue tracker. +//! with a Bearer token; everyone else is pointed at the project issue tracker. //! The request body carries exactly the fields kimi-cli sends: //! `session_id`, `content`, `version`, `os`, `model`. @@ -12,7 +12,8 @@ use std::sync::Arc; use serde::Serialize; /// Where non-subscription users (no OAuth session) submit feedback instead. -pub const FEEDBACK_ISSUES_URL: &str = "https://github.com/ZacharyZhang-NY/Kigi-CLI/issues"; +pub const FEEDBACK_ISSUES_URL: &str = + "https://git.zacharyzhang.com/ZacharyZhang-NY/Kigi-CLI/issues"; /// HTTP error from the feedback endpoint with a preserved status code, so /// callers can distinguish auth failures (401) without string matching. diff --git a/crates/codegen/kigi-tools/src/implementations/kigi/web_fetch/config.rs b/crates/codegen/kigi-tools/src/implementations/kigi/web_fetch/config.rs index b620737..9467291 100644 --- a/crates/codegen/kigi-tools/src/implementations/kigi/web_fetch/config.rs +++ b/crates/codegen/kigi-tools/src/implementations/kigi/web_fetch/config.rs @@ -10,7 +10,7 @@ use crate::register_resource; pub const MAX_URL_LENGTH: usize = 2_000; pub const MAX_REDIRECTS: usize = 10; pub const USER_AGENT_STRING: &str = - "Mozilla/5.0 (compatible; kigi-agent/1.0; +https://github.com/ZacharyZhang-NY/Kigi-CLI)"; + "Mozilla/5.0 (compatible; kigi-agent/1.0; +https://kigicli.dev)"; /// Runtime-configurable parameters for the `web_fetch` tool. /// diff --git a/crates/codegen/kigi-tui/src/slash/commands/docs.rs b/crates/codegen/kigi-tui/src/slash/commands/docs.rs index bb6dfd5..df0724f 100644 --- a/crates/codegen/kigi-tui/src/slash/commands/docs.rs +++ b/crates/codegen/kigi-tui/src/slash/commands/docs.rs @@ -9,8 +9,9 @@ use crate::docs::{all_titles, find_doc}; use crate::slash::command::{AppCtx, ArgItem, CommandExecCtx, CommandResult, SlashCommand}; /// Online docs landing page (hardcoded like other TUI deep-links; the -/// project README is the docs entry point for the GitHub-hosted docs). -pub const BUILD_DOCS_URL: &str = "https://github.com/ZacharyZhang-NY/Kigi-CLI#readme"; +/// project README is the docs entry point for the repo-hosted docs). +pub const BUILD_DOCS_URL: &str = + "https://git.zacharyzhang.com/ZacharyZhang-NY/Kigi-CLI/src/branch/main/README.md"; /// Open How-to Guides or online Build docs. pub struct DocsCommand; diff --git a/crates/codegen/kigi-tui/src/slash/commands/feedback.rs b/crates/codegen/kigi-tui/src/slash/commands/feedback.rs index 939bd0d..5fddb83 100644 --- a/crates/codegen/kigi-tui/src/slash/commands/feedback.rs +++ b/crates/codegen/kigi-tui/src/slash/commands/feedback.rs @@ -1,12 +1,13 @@ -//! `/feedback` -- open the Kigi GitHub issues page. +//! `/feedback` -- open the Kigi issues page. use crate::app::actions::Action; use crate::slash::command::{CommandExecCtx, CommandResult, SlashCommand}; /// Where feedback goes: the project's own issue tracker. Kigi is a community -/// build, so its feedback belongs on its GitHub repo — mirroring the official +/// build, so its feedback belongs on its own repo — mirroring the official /// kimi-cli, whose `/feedback` opens its repo's issues page. -pub const FEEDBACK_ISSUES_URL: &str = "https://github.com/ZacharyZhang-NY/Kigi-CLI/issues"; +pub const FEEDBACK_ISSUES_URL: &str = + "https://git.zacharyzhang.com/ZacharyZhang-NY/Kigi-CLI/issues"; /// Open the Kigi issue tracker in the browser. pub struct FeedbackCommand; @@ -17,7 +18,7 @@ impl SlashCommand for FeedbackCommand { } fn description(&self) -> &str { - "Report feedback on the Kigi GitHub issues page" + "Report feedback on the Kigi issues page" } fn usage(&self) -> &str { diff --git a/crates/codegen/kigi-update/src/auto_update.rs b/crates/codegen/kigi-update/src/auto_update.rs index 7b0b343..0dc9ece 100644 --- a/crates/codegen/kigi-update/src/auto_update.rs +++ b/crates/codegen/kigi-update/src/auto_update.rs @@ -29,17 +29,18 @@ const MSG_AUTO_UPDATE_BACKGROUND: &str = "Auto-update running in background."; const MSG_RUN_UPDATE_MANUAL: &str = "Run `kigi update` to get the latest version."; /// Manual-install one-liner for this platform's bootstrap installer -/// (install.sh / install.ps1 hosted at the repo root, PRD F8). +/// (install.sh / install.ps1, PRD F8). Served from kigicli.dev rather than the +/// repo host directly, so the published command survives a forge migration. fn manual_install_cmd() -> &'static str { if cfg!(windows) { - "irm https://raw.githubusercontent.com/ZacharyZhang-NY/Kigi-CLI/main/install.ps1 | iex" + "irm https://kigicli.dev/install.ps1 | iex" } else { - "curl -fsSL https://raw.githubusercontent.com/ZacharyZhang-NY/Kigi-CLI/main/install.sh | sh" + "curl -fsSL https://kigicli.dev/install.sh | sh" } } /// Build a reinstall hint for a known installer type. Every installer is -/// "internal" (GitHub Releases) today; the parameter survives so the hint +/// "internal" (the repo's Releases) today; the parameter survives so the hint /// stays correct if another backend ever returns. fn reinstall_hint(_installer: &str) -> String { format!("Please reinstall via:\n {}", manual_install_cmd()) @@ -694,7 +695,7 @@ pub(crate) fn detect_platform() -> Result<(&'static str, &'static str)> { /// Rust target triple for this build — the key that maps a platform to its /// release-asset name. Must stay in lockstep with the five targets built by -/// `.github/workflows/release.yml` and the tables in install.sh/install.ps1. +/// `.gitea/workflows/release.yml` and the tables in install.sh/install.ps1. pub(crate) fn target_triple() -> Result<&'static str> { let triple = if cfg!(all(target_os = "macos", target_arch = "aarch64")) { "aarch64-apple-darwin" @@ -2604,19 +2605,19 @@ mod tests { // reinstall_hint / manual_install_cmd #[test] - fn test_reinstall_hint_points_at_repo_install_script() { + fn test_reinstall_hint_points_at_published_install_script() { let hint = reinstall_hint("internal"); if cfg!(windows) { assert!(hint.contains("irm"), "should suggest irm install: {hint}"); assert!( - hint.contains("ZacharyZhang-NY/Kigi-CLI/main/install.ps1"), - "should reference the repo's install.ps1: {hint}" + hint.contains("kigicli.dev/install.ps1"), + "should reference the published install.ps1: {hint}" ); } else { assert!(hint.contains("curl"), "should suggest curl install: {hint}"); assert!( - hint.contains("ZacharyZhang-NY/Kigi-CLI/main/install.sh"), - "should reference the repo's install.sh: {hint}" + hint.contains("kigicli.dev/install.sh"), + "should reference the published install.sh: {hint}" ); } // Unknown installers fall back to the same hint. @@ -2652,7 +2653,7 @@ mod tests { #[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))] #[test] fn test_release_asset_name_matches_release_workflow_naming() { - // Must stay in lockstep with .github/workflows/release.yml, which + // Must stay in lockstep with .gitea/workflows/release.yml, which // publishes kigi--.{tar.gz|zip}. let triple = target_triple().unwrap(); assert!( diff --git a/crates/codegen/kigi-update/src/version.rs b/crates/codegen/kigi-update/src/version.rs index c7a1030..90618e11 100644 --- a/crates/codegen/kigi-update/src/version.rs +++ b/crates/codegen/kigi-update/src/version.rs @@ -8,9 +8,9 @@ use kigi_shell::util::kigi_home::kigi_home; const TTL_SECONDS_BEFORE_AUTO_UPDATE: Duration = Duration::from_secs(60 * 30); -/// Release channel: this repo's GitHub Releases (PRD F8). The API base is -/// resolved through [`kigi_env::update_base_url`] — production default -/// `https://api.github.com/repos/ZacharyZhang-NY/Kigi-CLI/releases`, +/// Release channel: this repo's Releases (PRD F8). The API base is resolved +/// through [`kigi_env::update_base_url`] — production default +/// `https://git.zacharyzhang.com/api/v1/repos/ZacharyZhang-NY/Kigi-CLI/releases`, /// overridable via `KIGI_UPDATE_BASE_URL` for mirrors and tests. pub(crate) fn update_base_url() -> String { kigi_env::update_base_url() @@ -167,16 +167,19 @@ async fn fetch_github_json(url: &str) -> Result< /// Latest release for `channel` from a GitHub-Releases-shaped API at `base`. /// -/// - `stable` / `enterprise`: `GET {base}/latest` — GitHub's "latest" already -/// excludes drafts and pre-releases. -/// - `alpha`: `GET {base}?per_page=30` (newest first) and take the semver-max -/// non-draft entry. The list includes both pre-releases and stable -/// releases, so this preserves the max(alpha, stable) channel semantics — -/// alpha users are never stuck behind a newer stable. +/// - `stable` / `enterprise`: `GET {base}/latest` — "latest" already excludes +/// drafts and pre-releases on both GitHub and Gitea. +/// - `alpha`: `GET {base}?per_page=30&limit=30` (newest first) and take the +/// semver-max non-draft entry. The list includes both pre-releases and +/// stable releases, so this preserves the max(alpha, stable) channel +/// semantics — alpha users are never stuck behind a newer stable. GitHub +/// reads the page size from `per_page` and Gitea from `limit`; each ignores +/// the other's parameter, so sending both keeps one URL valid on either. pub async fn fetch_latest_release_from_base(channel: &str, base: &str) -> Result { let base = base.trim_end_matches('/'); if channel == "alpha" { - let releases: Vec = fetch_github_json(&format!("{base}?per_page=30")).await?; + let releases: Vec = + fetch_github_json(&format!("{base}?per_page=30&limit=30")).await?; return releases .into_iter() .filter(|r| !r.draft) diff --git a/crates/codegen/kigi-update/tests/common/mod.rs b/crates/codegen/kigi-update/tests/common/mod.rs index 76c10ce..2e3d067 100644 --- a/crates/codegen/kigi-update/tests/common/mod.rs +++ b/crates/codegen/kigi-update/tests/common/mod.rs @@ -114,7 +114,7 @@ pub fn host_platform() -> String { } /// Host Rust target triple, matching `auto_update::target_triple()` and the -/// release-asset naming in `.github/workflows/release.yml`. +/// release-asset naming in `.gitea/workflows/release.yml`. pub fn host_triple() -> &'static str { if cfg!(all(target_os = "macos", target_arch = "aarch64")) { "aarch64-apple-darwin" diff --git a/install.ps1 b/install.ps1 index b9227b4..d4b9654 100644 --- a/install.ps1 +++ b/install.ps1 @@ -1,17 +1,17 @@ # Kigi installer (Windows x86_64) — PRD F8. # -# Downloads the x86_64-pc-windows-msvc artifact from this repo's GitHub -# Releases, verifies its SHA-256 against the release's SHA256SUMS manifest, -# and installs the binary as %USERPROFILE%\.kigi\bin\kigi.exe. +# Downloads the x86_64-pc-windows-msvc artifact from this repo's Releases, +# verifies its SHA-256 against the release's SHA256SUMS manifest, and +# installs the binary as %USERPROFILE%\.kigi\bin\kigi.exe. # # Usage: -# irm https://raw.githubusercontent.com/ZacharyZhang-NY/Kigi-CLI/main/install.ps1 | iex +# irm https://kigicli.dev/install.ps1 | iex # powershell -ExecutionPolicy Bypass -File install.ps1 -Version v0.1.0 # # Environment: # KIGI_SHARE_DIR install root (default: %USERPROFILE%\.kigi) -# KIGI_UPDATE_BASE_URL GitHub-Releases-shaped API base (default: -# https://api.github.com/repos/ZacharyZhang-NY/Kigi-CLI/releases) +# KIGI_UPDATE_BASE_URL releases API base, GitHub/Gitea-shaped (default: +# https://git.zacharyzhang.com/api/v1/repos/ZacharyZhang-NY/Kigi-CLI/releases) [CmdletBinding()] param( @@ -27,7 +27,8 @@ function Fail([string]$Message) { } $Repo = "ZacharyZhang-NY/Kigi-CLI" -$ApiBase = if ($env:KIGI_UPDATE_BASE_URL) { $env:KIGI_UPDATE_BASE_URL } else { "https://api.github.com/repos/$Repo/releases" } +$Forge = "https://git.zacharyzhang.com" +$ApiBase = if ($env:KIGI_UPDATE_BASE_URL) { $env:KIGI_UPDATE_BASE_URL } else { "$Forge/api/v1/repos/$Repo/releases" } $KigiHome = if ($env:KIGI_SHARE_DIR) { $env:KIGI_SHARE_DIR } else { Join-Path $env:USERPROFILE ".kigi" } $Triple = "x86_64-pc-windows-msvc" @@ -49,7 +50,7 @@ if ($Version -and $Version -notmatch '^\d+\.\d+\.\d+([-.][0-9A-Za-z.-]+)?$') { # TLS 1.2 for older PowerShell 5.1 defaults. [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 -$Headers = @{ "User-Agent" = "kigi-install"; "Accept" = "application/vnd.github+json" } +$Headers = @{ "User-Agent" = "kigi-install"; "Accept" = "application/json" } # ── Resolve the release ────────────────────────────────────────────────────── $ReleaseUrl = if ($Version) { "$ApiBase/tags/v$Version" } else { "$ApiBase/latest" } diff --git a/install.sh b/install.sh index 3b80a90..10409a7 100644 --- a/install.sh +++ b/install.sh @@ -2,26 +2,27 @@ # # Kigi installer (macOS / Linux) — PRD F8. # -# Downloads the matching platform artifact from this repo's GitHub Releases, -# verifies its SHA-256 against the release's SHA256SUMS manifest, and installs -# the binary as ~/.kigi/bin/kigi (the same managed layout the self-updater +# Downloads the matching platform artifact from this repo's Releases, verifies +# its SHA-256 against the release's SHA256SUMS manifest, and installs the +# binary as ~/.kigi/bin/kigi (the same managed layout the self-updater # maintains: versioned binary in ~/.kigi/downloads/, atomic symlink in bin/). # # Usage: -# curl -fsSL https://raw.githubusercontent.com/ZacharyZhang-NY/Kigi-CLI/main/install.sh | sh +# curl -fsSL https://kigicli.dev/install.sh | sh # sh install.sh --version v0.1.0 # pin a specific release # # Environment: # KIGI_SHARE_DIR install root (default: ~/.kigi) -# KIGI_UPDATE_BASE_URL GitHub-Releases-shaped API base (default: -# https://api.github.com/repos/ZacharyZhang-NY/Kigi-CLI/releases) +# KIGI_UPDATE_BASE_URL releases API base, GitHub/Gitea-shaped (default: +# https://git.zacharyzhang.com/api/v1/repos/ZacharyZhang-NY/Kigi-CLI/releases) # # Fails fast on any error; never leaves a partial binary as the active kigi. set -eu REPO="ZacharyZhang-NY/Kigi-CLI" -API_BASE="${KIGI_UPDATE_BASE_URL:-https://api.github.com/repos/${REPO}/releases}" +FORGE="https://git.zacharyzhang.com" +API_BASE="${KIGI_UPDATE_BASE_URL:-${FORGE}/api/v1/repos/${REPO}/releases}" KIGI_HOME="${KIGI_SHARE_DIR:-$HOME/.kigi}" err() {