Author SHA1 Message Date
ZacharyZhang-NYandClaude Fable 5 f9c9b3c364 Bring the landing site into the repo as site/
Vite 8 vanilla page for omarchycn.zacharyzhang.com: kami palette on a
12-col Swiss grid, self-hosted Fusion Pixel zh_hans (OFL shipped),
GSAP ScrollSmoother/ScrollTrigger with reduced-motion opt-out, reicon
icons, ISO download plus one-click convert command copy, real
installer screenshot. shots.mjs is the rendered-state harness; deploy
is npx wrangler deploy from site/ (custom domain + workers.dev).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019hLK3wsDuKVAC37GgDqg6H
2026-08-27 20:07:43 -04:00
ZacharyZhang-NYandClaude Fable 5 f72a48f747 One-click convert between vanilla Omarchy and OmarchyCN
omarchy-cn-convert turns a package-based install into OmarchyCN in one
curl-able run: registry key + [omarchycn] repo, --ask=4 swap onto the
cn-built packages (Chinese menu/bindings and OMARCHY CN branding ride
the settings tree), [omarchy] mirror line, zh_CN locale, fcitx5-rime
seeding, CN Arch mirrors, and a plymouth rebrand, recording pre-state
in /var/lib/omarchycn/convert-state. omarchy-cn-revert restores that
state symmetrically and survives user-removed packages, self-deletion
mid-swap, and cosmetic plymouth failures after the state is cleared.
Checkout installs are refused toward the overlay.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019hLK3wsDuKVAC37GgDqg6H
2026-08-27 18:20:51 -04:00
ZacharyZhang-NYandClaude Fable 5 c51df9977b Publish cn-built packages to the omarchycn registry before each release
packages/publish-cn-packages.sh uploads the omarchy-dev and
omarchy-settings-dev builds (token via curl config on stdin), and the
release checklist runs it as step 8 ahead of the public Release so the
one-click convert never installs stale packages.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019hLK3wsDuKVAC37GgDqg6H
2026-08-27 18:20:51 -04:00
ZacharyZhang-NYandClaude Fable 5 1535e7871f Bump cn/release to 7 after publishing 4.0.0.alpha-cn.6
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019hLK3wsDuKVAC37GgDqg6H
2026-08-27 18:20:51 -04:00
ZacharyZhang-NYandClaude Fable 5 c4dcd2b63a Document and test the [omarchy] mirror wiring
Static suite assertions pin the stable conf's server order and keep
edge/rc upstream-only; the guard test proves exact-line semantics.
docs/pacman-repo.md gains the mirror section with the honest
operational contract: staleness is invisible to pacman, fallback
fires only on retrieval failure, and omarchy-refresh-pacman's -Syyuu
downgrade wipes the cn layer while upstream ships older omarchy-dev.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019hLK3wsDuKVAC37GgDqg6H
2026-08-27 16:29:34 -04:00
ZacharyZhang-NYandClaude Fable 5 b183be2832 Put the OmarchyCN [omarchy] mirror first on the stable channel
The Gitea mirror db is registry-signed, so installs trust the registry
key right after the pacman.conf restore, and a cn migration does the
same on existing systems with an idempotent exact-line guard. The
insertion lives in cn/lib/mirror.sh so 'omarchycn doctor mirror --fix'
can restore the line after omarchy-refresh-pacman resets an overlay
system's conf. Only stable is mirrored: the Gitea repository name must
equal the pacman section name, and edge/rc stay on upstream directly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019hLK3wsDuKVAC37GgDqg6H
2026-08-27 16:20:48 -04:00
ZacharyZhang-NYandClaude Fable 5 fc9afae2b6 Cover the ollama provider paths in the AI mock regression suite
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019hLK3wsDuKVAC37GgDqg6H
2026-08-27 16:02:06 -04:00
ZacharyZhang-NYandClaude Fable 5 111b857cda Add local Ollama as an AI Hub provider for claude-code
Ollama v0.14+ speaks the Anthropic Messages API natively, so the
registry drives it with a fixed 'ollama' token and runtime-listed
models: static_token skips secret storage everywhere keys resolve,
models_dynamic defers model validation to the live /api/tags list,
and the setup wizard installs the right GPU variant and starts the
service before listing local models. Doctor stops treating the
endpoint port as part of the hostname.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019hLK3wsDuKVAC37GgDqg6H
2026-08-27 15:55:15 -04:00
ZacharyZhang-NYandClaude Fable 5 4b280a1767 Add DSH to the agent roster and Install > AI
Official npm @deepseek-ai/dsh (bin: dsh) rides the mise path like Dim.
dsh has no TUI, so the interactive launch is the documented 'dsh web'
and one-shot prompts use 'dsh --profile headless'. Auth is dsh-managed,
so it stays out of the AI Hub registry.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019hLK3wsDuKVAC37GgDqg6H
2026-08-27 15:42:46 -04:00
ZacharyZhang-NYandClaude Fable 5 602a82621a Run the package repo mirror sync from CI every six hours
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019hLK3wsDuKVAC37GgDqg6H
2026-08-27 15:34:44 -04:00
ZacharyZhang-NYandClaude Fable 5 87b4cc4da1 Mirror the upstream stable package repo into the Gitea Arch registry
packages/sync-omarchy-repo.sh diffs the upstream omarchy.db against the
registry's own db by filename, streams missing packages through (409 =
already present), and deletes entries upstream no longer lists. Only a
404 counts as first sync; any other mirror-db failure aborts rather
than faking an empty mirror.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019hLK3wsDuKVAC37GgDqg6H
2026-08-27 15:34:01 -04:00
ZacharyZhang-NYandClaude Fable 5 70895d4f1c Bump cn/release to 6 after publishing 4.0.0.alpha-cn.5
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-25 22:36:59 -04:00
44 changed files with 2662 additions and 26 deletions
+24
View File
@@ -0,0 +1,24 @@
name: pkg-repo-sync
on:
schedule:
- cron: "17 */6 * * *"
workflow_dispatch:
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: quattro
- name: Mirror upstream [omarchy] stable into the Gitea Arch registry
env:
PKG_SYNC_TOKEN: ${{ secrets.PKG_SYNC_TOKEN }}
run: |
set -euo pipefail
if ! command -v zstd > /dev/null; then
apt-get update -qq && apt-get install -y -qq zstd
fi
bash packages/sync-omarchy-repo.sh
+6 -3
View File
@@ -15,16 +15,19 @@ matching guide before starting:
This fork carries the OmarchyCN China-integration layer on top of upstream `basecamp/omarchy`:
- `bin/omarchycn` routes to `omarchy cn <command>`; all cn commands are `bin/omarchy-cn-*` and follow the upstream bin conventions (metadata, helpers, `$OMARCHY_PATH`sole exception: the overlay installer bootstraps by resolving its own checkout)
- `bin/omarchycn` routes to `omarchy cn <command>`; all cn commands are `bin/omarchy-cn-*` and follow the upstream bin conventions (metadata, helpers, `$OMARCHY_PATH`bootstrap exceptions: the overlay installer resolves its own checkout, and `omarchy-cn-convert` / `omarchy-cn-revert` are curl-able standalone scripts that default `OMARCHY_PATH` because they run before/while the cn tree exists)
- Convert/revert: `omarchy-cn-convert` turns a vanilla package-based Omarchy into OmarchyCN (registry key + [omarchycn] repo, `--ask=4` package swap to the cn-built omarchy-dev/omarchy-settings-dev published by `packages/publish-cn-packages.sh`, [omarchy] mirror line, zh_CN locale, fcitx5 seeding, plymouth rebrand) recording pre-state in `/var/lib/omarchycn/convert-state`; `omarchy-cn-revert` restores that state; checkout installs are refused toward the overlay
- `cn/` holds the data layer: `mirrors.json`, `dev-mirrors.json`, `apps.json`, `registry/` (AI providers/harnesses/compatibility), `fcitx5/`, `fontconfig/`, `keys/`, `lib/` (sourced helpers), `release` (cn release number)
- cn migrations live in `cn/migrations/*.sh`, run by `omarchy-cn-update` with per-file completion markers under `~/.local/state/omarchycn/`
- Packaging: `packages/omarchy-pkgs-cn.patch` must be applied to the sibling `omarchy-pkgs` checkout so `omarchy-dev` ships `cn/`; keyring in `packages/omarchycn-keyring/`
- Upstream repo mirror: `packages/sync-omarchy-repo.sh` + `.gitea/workflows/pkg-repo-sync.yml` mirror the upstream `[omarchy]` stable channel into the Gitea Arch registry every 6h; `pacman-stable.conf` lists the mirror first (upstream fallback), clients must trust the registry key (install import + cn migration), `omarchycn doctor mirror --fix` re-heals the line; see `docs/pacman-repo.md`
- ISO: `packages/omarchy-iso-cn.patch` must be applied to the sibling `omarchy-iso` checkout — Chinese installer (cage+foot graphical console with English VT fallback) and live-env packages
- Release process: `docs/release-checklist.md`; signing: `docs/release-signing.md`; pacman repo: `docs/pacman-repo.md`
- Site: `site/` is the omarchycn.zacharyzhang.com landing page (Vite 8 vanilla + GSAP + self-hosted Fusion Pixel + reicon, kami palette on a 12-col grid); deploy with `npx wrangler deploy` from `site/`, rendered-state checks via `node shots.mjs` against a preview or the live URL
- Chinese-first defaults: OmarchyCN ships Simplified Chinese as the default UX. User-visible strings in `default/omarchy/omarchy-menu.jsonc` (all labels), `default/hypr/bindings/*.lua` (bind descriptions, including the generated workspace/group/panel loops), `bin/omarchy-menu-keybindings` (its merge list and priority patterns must match the shipped Chinese descriptions), `bin/omarchy-update-confirm`, and `install/user/first-run/*` notifications are Chinese with brand names kept in English; menu search stays reachable in English through leaf ids. When syncing upstream, translate new strings in these files and resolve string conflicts toward our Chinese text.
- Default set changes vs upstream: `install/omarchy-base.packages` adds fcitx5-rime/chinese-addons/configtool and drops aether/libreoffice-fresh/obs-studio; the Basecamp/Discord/HEY/Google/WhatsApp/X launchers, their webapp keybindings, the whatsapp-slim extension, and the HEY mailto handler are removed (preinstall add/remove lists, launcher.hides, chromium flags, and mimeapps stay in sync); `install/user/cn-chinese.sh` seeds IME/font config on zh_CN systems
- AI: `cn/registry/` drives providers (DeepSeek/Kimi/Z.AI/MiniMax) with zero-code adapters; `omarchy-default-agent` also accepts kimi (official installer script, not mise) and deepcode (mise npm); the Default Agent menu carries them plus an AI Hub combo entry
- Upstream sync: `.gitea/workflows/upstream-sync.yml` opens a PR per upstream change; keep upstream file edits minimal and inventoried (currently: one `GROUP_DESCRIPTIONS[cn]` line in `bin/omarchy`, the localized menu/bindings/update/first-run surfaces above, the western-app removals above, a rewritten `README.md` (known recurring sync conflict, resolve toward ours), `AGENTS.md` additions, and two `.gitignore` lines)
- AI: `cn/registry/` drives providers (DeepSeek/Kimi/Z.AI/MiniMax/Ollama-local) with zero-code adapters; Ollama uses a fixed `static_token` and runtime-listed models (`models_dynamic`); `omarchy-default-agent` also accepts kimi (official installer script, not mise), deepcode, dim, and dsh (mise npm); the Default Agent menu carries them plus an AI Hub combo entry
- Upstream sync: `.gitea/workflows/upstream-sync.yml` opens a PR per upstream change; keep upstream file edits minimal and inventoried (currently: one `GROUP_DESCRIPTIONS[cn]` line in `bin/omarchy`, the localized menu/bindings/update/first-run surfaces above, the western-app removals above, the cn agent roster in `bin/omarchy-default-agent` / `bin/omarchy-agent` (kimi/deepcode/dim/dsh), the `[omarchy]` mirror line in `default/pacman/pacman-stable.conf`, the registry-key import in `install/post-install/pacman.sh`, a rewritten `README.md` (known recurring sync conflict, resolve toward ours), `AGENTS.md` additions, and two `.gitignore` lines)
- cn tests: `test/shell.d/omarchycn-test.sh`, `test/shell.d/omarchycn-ai-test.sh`; localized UI expectations live in the upstream suites (menu, keybindings-menu, hyprland-default-config, binding-conflicts, clock, screenrecording, update-disk-space)
# Documentation Layout
+19 -2
View File
@@ -18,9 +18,10 @@
- **中国镜像管理**Arch / npm / pip / Cargo / Go 等软件源测速、自动选择与故障切换
- **中文环境开箱即用**zh_CN locale、思源黑体 / 宋体字体栈、高分屏分数缩放预设
- **中文输入法**Fcitx5 + Rime 预配置,Wayland / GTK / Qt / Electron 全栈兼容,快捷键冲突自动处理
- **AI Hub**Kimi、DeepSeek、Z.AI/GLM 一等 Provider 支持,与 Claude Code、Codex、OpenCode、Kimi Code、Deep Code 等 Harness 的统一配置向导、凭据安全存储与连接诊断
- **AI Hub**Kimi、DeepSeek、Z.AI/GLM、MiniMax 与本地 Ollama 的一等 Provider 支持,与 Claude Code、Codex、OpenCode、Kimi Code、Deep Code 等 Harness 的统一配置向导、凭据安全存储与连接诊断;另有 Dim、DSH 等自管登录的 Agent CLI 一键安装
- **国内应用中心**:微信、QQ、飞书、钉钉、腾讯会议、WPS 等应用的可信安装入口
- **Overlay 安装器**:在现有 Omarchy 上叠加 OmarchyCN,全程可逆、可卸载
- **一键转换**:原版 Omarchy(包安装)一条命令转成 OmarchyCN,logo/菜单/键位/输入法/软件源全套切换,`omarchy cn revert` 一键还原
- **Overlay 安装器**:在现有 Omarchy checkout 上叠加 OmarchyCN,全程可逆、可卸载
- **统一诊断**`omarchycn doctor` 覆盖网络、镜像、输入法、显示与 AI 配置
## 下载与安装
@@ -31,6 +32,22 @@
sha256sum -c SHA256SUMS.txt
```
## 已有原版 Omarchy?一键转换
包安装的原版 Omarchy(quattro)可直接转换,无需重装系统:
```bash
curl -fsSL https://git.zacharyzhang.com/ZacharyZhang-NY/omarchycn/raw/branch/quattro/bin/omarchy-cn-convert | bash
```
转换内容:换装 OmarchyCN 构建的系统包(中文菜单/键位/更新界面、OMARCHY CN 品牌、AI 全家桶)、启用 [omarchy] 自建镜像与 [omarchycn] 仓库、zh_CN 语言与 Fcitx5+Rime 输入法、国内 Arch 镜像自动测速。重新登录后生效。
随时一键还原(按转换时记录的状态逐项回退):
```bash
omarchy cn revert
```
## 上游文档
Omarchy 完整英文手册在 [`manual/`](manual/) 目录,亦见 [learn.omacom.io](https://learn.omacom.io/2/the-omarchy-manual)。
+8
View File
@@ -115,6 +115,14 @@ dim)
command=(dim)
fi
;;
dsh)
# dsh has no TUI: `dsh web` is the interactive form, headless the one-shot
if [[ -n ${prompt:-} ]]; then
command=(dsh --profile headless "$prompt")
else
command=(dsh web)
fi
;;
*)
echo "Unsupported default agent: $agent" >&2
exit 1
+3 -2
View File
@@ -36,7 +36,7 @@ if omarchy-cmd-missing "$command"; then
$install
fi
key=$(omarchy-cn-ai-secret get "$provider")
key=$(cn_ai_resolve_key "$provider")
case "$harness" in
claude-code)
@@ -45,7 +45,8 @@ claude-code)
settings="$HOME/.claude/settings.json"
mkdir -p "${settings%/*}"
base=$(cn_ai_endpoint "$provider" anthropic)
fast=$(cn_ai_fast_model "$provider")
# Dynamic-model providers have no fast alias: every tier maps to the model
fast=$(cn_ai_fast_model "$provider" 2>/dev/null || printf '%s' "$model")
current="{}"
if [[ -s $settings ]]; then
current=$(cat "$settings")
+3 -1
View File
@@ -38,6 +38,8 @@ fi
if [[ $proto == "native" ]]; then
echo "INFO ai: $harness 自管鉴权,跳过 OmarchyCN Key 检查"
elif cn_ai_static_token "$provider" > /dev/null 2>&1; then
echo "PASS ai: $provider 使用内置 token,无需存储 Key"
elif omarchy-cn-ai-secret get "$provider" > /dev/null 2>&1; then
echo "PASS ai: $provider API Key stored"
else
@@ -47,7 +49,7 @@ fi
if [[ $proto != "native" ]]; then
base=$(cn_ai_endpoint "$provider" "$proto")
host=$(sed -E 's|https?://([^/]+).*|\1|' <<<"$base")
host=$(sed -E 's|https?://([^/:]+).*|\1|' <<<"$base")
if getent hosts "$host" > /dev/null 2>&1; then
echo "PASS ai: endpoint DNS resolves ($host)"
else
+1 -1
View File
@@ -32,7 +32,7 @@ fi
case "$config_method" in
env)
key=$(omarchy-cn-ai-secret get "$provider")
key=$(cn_ai_resolve_key "$provider")
if [[ $harness == "codex" ]]; then
cn_ai_render_codex_config "$provider" "$model" "$key"
exec "$command"
+4 -2
View File
@@ -33,7 +33,9 @@ if [[ $level == "unsupported" ]]; then
fi
allowlist=$(cn_ai_combo_models "$harness" "$provider")
if [[ -n $allowlist ]]; then
if cn_ai_models_dynamic "$provider"; then
: # runtime-listed models; omarchycn ai test validates against the live server
elif [[ -n $allowlist ]]; then
# Combo-restricted model set (e.g. codex uses its own catalog slugs)
if ! grep -qxF "$model" <<<"$allowlist"; then
echo "Model $model not supported for $harness x $provider" >&2
@@ -68,6 +70,6 @@ secret_ref = "omarchycn://ai/$provider"
EOF
echo "Profile $name: $harness x $provider x $model (compat: $level)"
if ! omarchy-cn-ai-secret get "$provider" > /dev/null 2>&1; then
if ! cn_ai_resolve_key "$provider" > /dev/null 2>&1; then
echo "提示: 尚未存储 $provider 的 API Key,运行: omarchycn ai secret set $provider"
fi
+26 -2
View File
@@ -24,7 +24,31 @@ provider=$(printf '%s\n' "${providers[@]}" | gum choose --header "选择 Provide
harness_proto=$(cn_ai_harness_field "$harness" protocol)
allowlist=$(cn_ai_combo_models "$harness" "$provider")
if [[ -n $allowlist ]]; then
if cn_ai_models_dynamic "$provider"; then
base=$(cn_ai_endpoint "$provider" "$harness_proto")
if [[ $provider == "ollama" ]]; then
if omarchy-cmd-missing ollama; then
gum confirm "Ollama 未安装,现在安装本地服务?" || exit 1
if omarchy-cmd-present nvidia-smi; then
ollama_pkg=ollama-cuda
elif omarchy-cmd-present rocminfo; then
ollama_pkg=ollama-rocm
else
ollama_pkg=ollama
fi
omarchy-pkg-add "$ollama_pkg"
fi
if ! curl -sf --connect-timeout 3 "$base/api/tags" > /dev/null; then
sudo systemctl enable --now ollama
fi
fi
models=$(curl -sf --connect-timeout 3 "$base/api/tags" | jq -r '.models[].name' || true)
if [[ -z $models ]]; then
echo "无法列出本地模型(服务未运行或尚未拉取):先 ollama pull <模型>(如 qwen3-coder)再重试" >&2
exit 1
fi
model=$(gum choose --header "选择本地模型" <<<"$models")
elif [[ -n $allowlist ]]; then
model=$(gum choose --header "选择模型" <<<"$allowlist")
else
# Hide models bound to a different wire protocol than the harness speaks
@@ -48,7 +72,7 @@ if [[ $proto == "native" ]]; then
exit 0
fi
if ! omarchy-cn-ai-secret get "$provider" > /dev/null 2>&1; then
if ! cn_ai_resolve_key "$provider" > /dev/null 2>&1; then
key=$(gum input --password --header "$provider API Key(获取: $(jq -r --arg p "$provider" '.providers[$p].key_url' "$CN_AI_PROVIDERS")")
printf '%s' "$key" | omarchy-cn-ai-secret set "$provider"
fi
+1 -1
View File
@@ -31,7 +31,7 @@ fi
base="${OMARCHYCN_AI_TEST_BASE_URL:-$(cn_ai_endpoint "$provider" "${proto%-responses}")}"
echo "INFO ai-test: $profile ($harness x $provider x $model) -> $base"
if ! key=$(omarchy-cn-ai-secret get "$provider" 2>/dev/null); then
if ! key=$(cn_ai_resolve_key "$provider" 2>/dev/null); then
echo "FAIL ai-test: 未存储 $provider 的 API Key (omarchycn ai secret set $provider)"
exit 1
fi
+107
View File
@@ -0,0 +1,107 @@
#!/bin/bash
# omarchy:summary=Convert a vanilla package-based Omarchy install into OmarchyCN
# omarchy:examples=omarchy cn convert
# Curl-able bootstrap (like the overlay installer): everything before the
# package swap must run without the cn tree, so no $OMARCHY_PATH until then.
set -euo pipefail
GITEA=${GITEA:-https://git.zacharyzhang.com}
OWNER=${OWNER:-ZacharyZhang-NY}
REGISTRY="$GITEA/api/packages/$OWNER/arch"
REG_KEY_FPR=74DCF57ACD812B24D959F146BD386048867B33B4
STATE_DIR=/var/lib/omarchycn
STATE=$STATE_DIR/convert-state
# Bootstrap exception: curl|bash runs outside a session, so default the path
export OMARCHY_PATH=${OMARCHY_PATH:-/usr/share/omarchy}
main() {
command -v pacman > /dev/null || { echo "需要 Arch Linux(未找到 pacman" >&2; exit 1; }
if (( EUID == 0 )); then
echo "请以普通用户运行(脚本内部按需 sudo" >&2
exit 1
fi
local prev_omarchy prev_settings
prev_omarchy=$(pacman -Q omarchy 2> /dev/null || pacman -Q omarchy-dev 2> /dev/null || true)
prev_settings=$(pacman -Q omarchy-settings 2> /dev/null || pacman -Q omarchy-settings-dev 2> /dev/null || true)
if [[ -z $prev_omarchy || -z $prev_settings ]]; then
if [[ -d $HOME/.local/share/omarchy/.git ]]; then
echo "检测到 checkout 安装(非包安装):请改用 overlaybin/omarchycn-install-overlay" >&2
else
echo "未检测到 Omarchy 包安装(需要 omarchy 与 omarchy-settings" >&2
fi
exit 1
fi
[[ -f $STATE ]] && echo "已转换过($STATE 在案):本次仅同步更新,不覆盖回退状态"
echo "==> 信任 OmarchyCN registry 公钥并配置 [omarchycn] 仓库"
local tmpkey
tmpkey=$(mktemp)
curl -fsSL "$REGISTRY/repository.key" -o "$tmpkey"
sudo pacman-key --add "$tmpkey"
sudo pacman-key --lsign-key "$REG_KEY_FPR"
rm -f "$tmpkey"
if ! grep -q '^\[omarchycn\]' /etc/pacman.conf; then
printf '\n[omarchycn]\nSigLevel = Required DatabaseOptional\nServer = %s/omarchycn/x86_64\n' \
"$REGISTRY" | sudo tee -a /etc/pacman.conf > /dev/null
fi
if [[ ! -f $STATE ]]; then
echo "==> 记录转换前状态(供 omarchy cn revert 还原)"
sudo mkdir -p "$STATE_DIR"
if [[ -f /etc/locale.conf ]]; then
sudo cp /etc/locale.conf "$STATE_DIR/locale.conf.pre"
fi
sudo cp /etc/pacman.d/mirrorlist "$STATE_DIR/mirrorlist.pre"
local p new_pkgs=""
for p in fcitx5-rime fcitx5-chinese-addons fcitx5-configtool omarchycn-keyring; do
pacman -Q "$p" > /dev/null 2>&1 || new_pkgs+="$p "
done
# Values are quoted because revert sources this file; publish atomically
{
echo "prev_omarchy=\"${prev_omarchy%% *}\""
echo "prev_settings=\"${prev_settings%% *}\""
echo "new_pkgs=\"${new_pkgs% }\""
echo "converted_at=\"$(date +%s)\""
} | sudo tee "$STATE.new" > /dev/null
sudo mv "$STATE.new" "$STATE"
fi
echo "==> 系统更新并换装 OmarchyCN 软件包(替换 ${prev_omarchy%% *}/${prev_settings%% *}"
sudo pacman -Syu --noconfirm
sudo pacman -S --noconfirm omarchycn/omarchycn-keyring
# --ask=4 auto-confirms removing the conflicting upstream omarchy packages
sudo pacman -S --noconfirm --ask=4 omarchycn/omarchy-dev omarchycn/omarchy-settings-dev
sudo pacman -S --needed --noconfirm fcitx5-rime fcitx5-chinese-addons fcitx5-configtool
echo "==> [omarchy] 仓库启用自建镜像(Cloudflare 上游兜底)"
source /usr/share/omarchy/cn/lib/mirror.sh
cn_mirror_omarchy_repo_fix
echo "==> 系统语言 zh_CN.UTF-8"
grep -q '^zh_CN.UTF-8 UTF-8' /etc/locale.gen ||
echo 'zh_CN.UTF-8 UTF-8' | sudo tee -a /etc/locale.gen > /dev/null
sudo locale-gen > /dev/null
if grep -q '^LANG=' /etc/locale.conf 2> /dev/null; then
sudo sed -i 's/^LANG=.*/LANG=zh_CN.UTF-8/' /etc/locale.conf
else
echo 'LANG=zh_CN.UTF-8' | sudo tee -a /etc/locale.conf > /dev/null
fi
echo "==> 中文输入法与字体(用户级)"
OMARCHY_PATH=/usr/share/omarchy bash /usr/share/omarchy/install/user/cn-chinese.sh
systemctl --user restart omarchy-fcitx5.service 2> /dev/null || true
echo "==> Arch 官方仓库切换国内镜像(自动测速,失败则保留现状)"
omarchy-cn-mirror-apply china || echo "国内镜像不可达,保留当前 mirrorlist"
echo "==> OMARCHY CN 开机画面"
omarchy-refresh-plymouth
omarchy-notification-send "OmarchyCN" "转换完成:重新登录后菜单/键位/输入法全中文" 2> /dev/null || true
echo
echo "转换完成(重新登录生效)。回退: omarchy cn revert"
}
main "$@"
+26 -1
View File
@@ -69,6 +69,27 @@ check_mirror() {
fi
}
# refresh-pacman restores the checkout's template, which on overlay installs
# has no OmarchyCN mirror line; this check makes that loss visible and fixable
check_omarchy_repo() {
local conf="${OMARCHYCN_PACMAN_CONF:-/etc/pacman.conf}"
if ! grep -q "^Server = https://pkgs.omarchy.org/stable/" "$conf"; then
echo "INFO omarchy-repo: 非 stable 通道,跳过 OmarchyCN 镜像检查"
return 0
fi
if cn_mirror_omarchy_repo_ok; then
echo "PASS omarchy-repo: OmarchyCN 镜像行在位"
return 0
fi
if [[ $fix == "true" ]] && cn_mirror_omarchy_repo_fix && cn_mirror_omarchy_repo_ok; then
echo "PASS omarchy-repo: 已重新插入 OmarchyCN 镜像行"
return 0
fi
echo "FAIL omarchy-repo: [omarchy] 缺 OmarchyCN 镜像行(omarchycn doctor mirror --fix 可修复)"
failures=$((failures + 1))
}
check_dev_mirror() {
omarchy-cn-dev-mirror-doctor || failures=$((failures + 1))
}
@@ -79,12 +100,16 @@ check_ime() {
case "$module" in
network) check_network ;;
mirror) check_mirror ;;
mirror)
check_mirror
check_omarchy_repo
;;
dev-mirror) check_dev_mirror ;;
ime) check_ime ;;
all)
check_network
check_mirror
check_omarchy_repo
check_dev_mirror
check_ime
;;
+89
View File
@@ -0,0 +1,89 @@
#!/bin/bash
# omarchy:summary=Revert an OmarchyCN conversion back to vanilla Omarchy
# omarchy:examples=omarchy cn revert
# Curl-able and self-deleting-safe: main() is fully parsed before the package
# swap removes this file from the cn tree it may be running from.
set -euo pipefail
STATE_DIR=/var/lib/omarchycn
STATE=$STATE_DIR/convert-state
MIRROR_LINE="Server = https://git.zacharyzhang.com/api/packages/ZacharyZhang-NY/arch/omarchy/x86_64"
REG_KEY_FPR=74DCF57ACD812B24D959F146BD386048867B33B4
# Bootstrap exception: curl|bash runs outside a session, so default the path
export OMARCHY_PATH=${OMARCHY_PATH:-/usr/share/omarchy}
main() {
if [[ ! -f $STATE ]]; then
echo "未发现转换状态($STATE):本机不是由 omarchy cn convert 转换而来" >&2
exit 1
fi
if (( EUID == 0 )); then
echo "请以普通用户运行(脚本内部按需 sudo" >&2
exit 1
fi
local prev_omarchy="" prev_settings="" new_pkgs=""
# State is trusted root-owned key=value lines written by convert
source "$STATE"
[[ -n $prev_omarchy && -n $prev_settings ]] || { echo "转换状态损坏: $STATE" >&2; exit 1; }
echo "==> 清理转换时播种的用户配置(与 cn 层原件一致才删除)"
local seeded
for seeded in \
"$HOME/.config/fcitx5/profile:/usr/share/omarchy/cn/fcitx5/profile" \
"$HOME/.config/fontconfig/conf.d/64-omarchycn-cjk.conf:/usr/share/omarchy/cn/fontconfig/64-omarchycn-cjk.conf"; do
if cmp -s "${seeded%%:*}" "${seeded#*:}" 2> /dev/null; then
rm -f "${seeded%%:*}"
fi
done
echo "==> 还原 mirrorlist 与系统语言"
sudo cp "$STATE_DIR/mirrorlist.pre" /etc/pacman.d/mirrorlist
if [[ -f $STATE_DIR/locale.conf.pre ]]; then
sudo cp "$STATE_DIR/locale.conf.pre" /etc/locale.conf
fi
echo "==> pacman.conf 移除 [omarchycn] 与 [omarchy] 镜像行"
sudo sed -i "\|^$MIRROR_LINE\$|d" /etc/pacman.conf
# Delete the section header and body, but keep any section that follows
sudo sed -i '/^\[omarchycn\]$/,/^\[/{ /^\[omarchycn\]$/d; /^\[/!d; }' /etc/pacman.conf
echo "==> 换回上游软件包($prev_omarchy/$prev_settings"
sudo pacman -Sy
# --ask=4 auto-confirms removing the conflicting cn packages
sudo pacman -S --noconfirm --ask=4 "omarchy/$prev_omarchy" "omarchy/$prev_settings"
if [[ -n $new_pkgs ]]; then
# Only what is still installed: a user may have removed some already,
# and a -R failure here would strand the swap-completed system
local p still=""
for p in $new_pkgs; do
# grep without -q consumes the whole list: -q's early exit would
# SIGPIPE pacman and read as "not installed" under pipefail
pacman -Qq | grep -xF "$p" > /dev/null && still+="$p "
done
if [[ -n $still ]]; then
echo "==> 移除转换时新装的包: ${still% }"
# shellcheck disable=SC2086
sudo pacman -R --noconfirm ${still% }
fi
fi
echo "==> 移除 registry 公钥信任"
sudo pacman-key --delete "$REG_KEY_FPR" > /dev/null 2>&1 || true
# The system is vanilla again: clear state before the cosmetic tail so a
# plymouth failure cannot strand a reverted machine in "converted" state
systemctl --user restart omarchy-fcitx5.service 2> /dev/null || true
sudo rm -rf "$STATE_DIR"
echo "==> 还原上游开机画面"
if ! omarchy-refresh-plymouth; then
echo "开机画面还原失败:手动执行 omarchy-refresh-plymouth 即可" >&2
exit 1
fi
echo
echo "已回退为原版 Omarchy(重新登录生效)。再次转换: omarchy cn convert"
}
main "$@"
+3 -2
View File
@@ -1,7 +1,7 @@
#!/bin/bash
# omarchy:summary=Set and launch the default coding agent
# omarchy:args=[pi|omp|opencode|ori|claude|codex|grok|agy|copilot|crush|kimi|deepcode|dim]
# omarchy:args=[pi|omp|opencode|ori|claude|codex|grok|agy|copilot|crush|kimi|deepcode|dim|dsh]
# omarchy:examples=omarchy default agent | omarchy default agent codex | omarchy default agent kimi
installing=false
@@ -37,8 +37,9 @@ copilot | github-copilot) agent="copilot"; name="GitHub Copilot" ;;
kimi | kimi-code) agent="kimi"; name="Kimi Code" ;;
deepcode | deep-code) agent="deepcode"; name="Deep Code"; agent_package="npm:@vegamo/deepcode-cli" ;;
dim | dimcode | dim-agent) agent="dim"; name="Dim Agent"; agent_package="npm:dimcode" ;;
dsh | deepseek-harness) agent="dsh"; name="DSH"; agent_package="npm:@deepseek-ai/dsh" ;;
*)
echo "Usage: omarchy-default-agent <pi|omp|opencode|ori|claude|codex|grok|agy|copilot|crush|kimi|deepcode|dim>"
echo "Usage: omarchy-default-agent <pi|omp|opencode|ori|claude|codex|grok|agy|copilot|crush|kimi|deepcode|dim|dsh>"
exit 1
;;
esac
+17 -1
View File
@@ -31,6 +31,21 @@ cn_ai_harness_field() {
jq -re --arg h "$1" --arg f "$2" '.harnesses[$h][$f]' "$CN_AI_HARNESSES"
}
# Providers with a fixed token (local servers) need no stored secret.
# `// empty` keeps stdout clean when absent (bare -re would print "null").
cn_ai_static_token() {
jq -re --arg p "$1" '.providers[$p].static_token // empty' "$CN_AI_PROVIDERS"
}
cn_ai_resolve_key() {
cn_ai_static_token "$1" 2>/dev/null || omarchy-cn-ai-secret get "$1"
}
# Dynamic-model providers list models at runtime, not in the registry
cn_ai_models_dynamic() {
jq -e --arg p "$1" '.providers[$p].models_dynamic == true' "$CN_AI_PROVIDERS" > /dev/null
}
cn_ai_combo_level() {
jq -r --arg h "$1" --arg p "$2" '.combos[$h][$p].level // "unsupported"' "$CN_AI_COMPAT"
}
@@ -68,7 +83,8 @@ cn_ai_render_env() {
case "$harness" in
claude-code)
base=$(cn_ai_endpoint "$provider" anthropic)
fast=$(cn_ai_fast_model "$provider")
# Dynamic-model providers have no fast alias: every tier maps to the model
fast=$(cn_ai_fast_model "$provider" 2>/dev/null || printf '%s' "$model")
printf 'export ANTHROPIC_BASE_URL=%q\n' "$base"
# Docs vary between AUTH_TOKEN (deepseek/zai) and API_KEY (kimi): set both
printf 'export ANTHROPIC_AUTH_TOKEN=%q\n' "$key"
+13
View File
@@ -4,6 +4,19 @@
CN_MIRRORS_JSON="$OMARCHY_PATH/cn/mirrors.json"
CN_MIRRORLIST="/etc/pacman.d/mirrorlist"
CN_PROFILE_FILE="$HOME/.config/omarchycn/mirror-profile"
CN_OMARCHY_MIRROR="https://git.zacharyzhang.com/api/packages/ZacharyZhang-NY/arch/omarchy/x86_64"
# [omarchy] mirror line management (stable channel only); shared by the
# rollout migration and doctor --fix so refresh-pacman resets stay healable
cn_mirror_omarchy_repo_ok() {
grep -qxF "Server = $CN_OMARCHY_MIRROR" "${OMARCHYCN_PACMAN_CONF:-/etc/pacman.conf}"
}
cn_mirror_omarchy_repo_fix() {
local conf="${OMARCHYCN_PACMAN_CONF:-/etc/pacman.conf}"
cn_mirror_omarchy_repo_ok && return 0
sudo sed -i "s|^Server = https://pkgs.omarchy.org/stable/|Server = $CN_OMARCHY_MIRROR\nServer = https://pkgs.omarchy.org/stable/|" "$conf"
}
cn_mirror_ids() {
jq -r '.mirrors[].id' "$CN_MIRRORS_JSON"
+8
View File
@@ -0,0 +1,8 @@
echo "Trust the OmarchyCN registry key and put the [omarchy] mirror first"
sudo pacman-key --add "$OMARCHY_PATH/cn/keys/omarchycn-registry.asc"
sudo pacman-key --lsign-key 74DCF57ACD812B24D959F146BD386048867B33B4
# Only the stable channel is mirrored; edge/rc systems keep upstream directly
source "$OMARCHY_PATH/cn/lib/mirror.sh"
cn_mirror_omarchy_repo_fix
+5
View File
@@ -21,6 +21,11 @@
"level": "stable",
"doc": "https://platform.minimax.io/docs/token-plan/claude-code",
"adapter": true
},
"ollama": {
"level": "stable",
"doc": "https://docs.ollama.com/api/anthropic-compatibility",
"adapter": true
}
},
"codex": {
+13 -1
View File
@@ -1,5 +1,5 @@
{
"_verified": "2026-08-25, from official docs (see OmarchyCN PRD §27; minimax: platform.minimax.io/docs/token-plan/claude-code)",
"_verified": "2026-08-27, from official docs (see OmarchyCN PRD §27; minimax: platform.minimax.io/docs/token-plan/claude-code; ollama: docs.ollama.com/api/anthropic-compatibility, fixed token \"ollama\", models are whatever is pulled locally)",
"providers": {
"deepseek": {
"display_name": "DeepSeek",
@@ -124,6 +124,18 @@
}
]
},
"ollama": {
"display_name": "Ollama 本地",
"auth": [
"none"
],
"static_token": "ollama",
"endpoints": {
"anthropic": "http://localhost:11434"
},
"models": [],
"models_dynamic": true
},
"minimax": {
"display_name": "MiniMax",
"auth": [
+1 -1
View File
@@ -1 +1 @@
5
7
+3
View File
@@ -142,6 +142,7 @@
"setup.default.agent.crush": {"icon":"󰋑","label":"Crush","checked":"[[ \"$(omarchy-default-agent)\" == \"crush\" ]]","action":"omarchy-default-agent crush"},
"setup.default.agent.deepcode": {"icon":"󱚤","label":"Deep Code","checked":"[[ \"$(omarchy-default-agent)\" == \"deepcode\" ]]","action":"omarchy-default-agent deepcode"},
"setup.default.agent.dim": {"icon":"󱚤","label":"Dim Agent","checked":"[[ \"$(omarchy-default-agent)\" == \"dim\" ]]","action":"omarchy-default-agent dim"},
"setup.default.agent.dsh": {"icon":"󱚤","label":"DSH","checked":"[[ \"$(omarchy-default-agent)\" == \"dsh\" ]]","action":"omarchy-default-agent dsh"},
"setup.default.agent.grok": {"icon":"","iconFont":"omarchy","label":"Grok","checked":"[[ \"$(omarchy-default-agent)\" == \"grok\" ]]","action":"omarchy-default-agent grok"},
"setup.default.agent.kimi": {"icon":"󱚤","label":"Kimi Code","checked":"[[ \"$(omarchy-default-agent)\" == \"kimi\" ]]","action":"omarchy-default-agent kimi"},
"setup.default.agent.omp": {"icon":"","iconFont":"omarchy","label":"omp","checked":"[[ \"$(omarchy-default-agent)\" == \"omp\" ]]","action":"omarchy-default-agent omp"},
@@ -244,6 +245,7 @@
"install.ai.deep-code": {"icon":"󱚤","label":"Deep Code","disabled":"omarchy-cmd-present deepcode","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-mise-install npm:@vegamo/deepcode-cli deepcode && deepcode'"},
"install.ai.dictation": {"icon":"","label":"语音听写","disabled":"omarchy-pkg-present voxtype-bin","action":"omarchy-launch-floating-terminal-with-presentation omarchy-voxtype-install"},
"install.ai.dim": {"icon":"󱚤","label":"Dim Agent","disabled":"omarchy-cmd-present dim","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-mise-install npm:dimcode dim && dim'"},
"install.ai.dsh": {"icon":"󱚤","label":"DSH","disabled":"omarchy-cmd-present dsh","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-mise-install npm:@deepseek-ai/dsh dsh && dsh web'"},
"install.ai.grok-bot": {"icon":"","iconFont":"omarchy","label":"Grok Bot","disabled":"omarchy-pkg-present grok-bot","action":"omarchy-install-and-launch 'Grok Bot' grok-bot grok-bot"},
"install.ai.kimi-code": {"icon":"󱚤","label":"Kimi Code","disabled":"omarchy-cmd-present kimi","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-cn-kimi-install && kimi'"},
"install.ai.lm-studio": {"icon":"","iconFont":"omarchy","label":"LM Studio","disabled":"omarchy-pkg-present lmstudio-bin","action":"omarchy-install-app 'LM Studio' lmstudio-bin"},
@@ -416,5 +418,6 @@
"omarchycn.diagnostics.mirror-fix": {"icon":"󰇧","label":"镜像修复","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-cn-doctor mirror --fix'"},
"omarchycn.diagnostics.ime": {"icon":"󰌌","label":"输入法状态","action":"omarchy-launch-floating-terminal-with-presentation omarchy-cn-ime-status"},
"omarchycn.update": {"icon":"","label":"更新","when":"[[ -f ~/.local/state/omarchycn/overlay-manifest ]]","action":"omarchy-launch-floating-terminal-with-presentation omarchy-cn-update"},
"omarchycn.revert": {"icon":"󰕍","label":"回退为原版 Omarchy","when":"[[ -f /var/lib/omarchycn/convert-state ]]","action":"omarchy-launch-floating-terminal-with-presentation omarchy-cn-revert"},
"omarchycn.about": {"icon":"","label":"关于","action":"omarchy-launch-floating-terminal-with-presentation omarchy-cn-status"},
}
+2
View File
@@ -27,4 +27,6 @@ Include = /etc/pacman.d/mirrorlist
[omarchy]
SigLevel = Optional TrustAll
# OmarchyCN mirror first (registry-signed db, key via cn/keys), upstream fallback
Server = https://git.zacharyzhang.com/api/packages/ZacharyZhang-NY/arch/omarchy/x86_64
Server = https://pkgs.omarchy.org/stable/$arch
+10
View File
@@ -3,6 +3,16 @@
OmarchyCN 的 pacman 仓库托管在 Gitea Arch package registry,每个上传的包由
registry 密钥自动签名,数据库同源生成。
# [omarchy] 上游仓库镜像
上游 `pkgs.omarchy.org`Cloudflare 托管)的 stable 通道被完整镜像到同一 Gitea registry 的 `omarchy` 仓库。`packages/sync-omarchy-repo.sh` 按文件名对上游与镜像 db 做差集,缺失包转传、上游移除的按 name/version/arch 精确删除;`.gitea/workflows/pkg-repo-sync.yml` 每 6 小时调度一次(幂等,可手动 dispatch)。正常情况下镜像滞后不超过一个周期;调度失败或 runner 掉线时滞后会持续到下一次成功运行。注意 pacman 无法感知"陈旧":只要镜像可达,客户端就使用镜像的 db,看到的是滞后版本;只有镜像无法提供所请求的 db/包文件(宕机、404)时才回退到上游 Server。
客户端接线(`default/pacman/pacman-stable.conf`):`[omarchy]` 段镜像行在前、上游为兜底。镜像 db 由 registry 密钥签名,因此系统必须信任该公钥——安装期由 `install/post-install/pacman.sh` 导入,既有系统由 cn 迁移 `1787861071.sh` 处理。仅 stable 被镜像:Gitea 仓库名必须等于 pacman 段名(客户端固定请求 `omarchy.db`),edge/rc 通道保持上游直连。
`omarchy-refresh-pacman` 会用检出内模板覆写 `/etc/pacman.conf`:ISO/包安装系统的模板自带镜像行;overlay 系统的模板来自上游,覆写后镜像行丢失,用 `omarchycn doctor mirror --fix` 重新插入。
已知事实:上游仓库本身含 `omarchy`/`omarchy-dev` 包,当前版本(4.0.0.r1832)低于 cn 层自构建版本(r1955+)。常规 `pacman -Syu` 不会降级覆盖;但 `omarchy-refresh-pacman` 收尾运行的是 `pacman -Syyuu``-uu` 明确允许降级——在任何 cn 系统上执行都会把 `omarchy-dev` 降回上游版本、抹掉 cn 层(该风险先于本镜像存在,镜像只是如实转载上游内容)。若上游版本号超过 cn 构建,普通 `-Syu` 也会覆盖 cn 层——每次发布前需核对上游版本。
## 接入
```bash
+5 -3
View File
@@ -9,9 +9,11 @@
5. `sha256sum``SHA256SUMS.txt`;签名 SUMS 与 ISO(发布子钥)
6. SBOM 两份:syftlive airootfs+ 离线仓库 .PKGINFO 采集
7. `release.json`(版本、双向提交、包版本表、迁移列表、min_compatible、产物清单)→ 签名 release.json
8. 建 tag 与 Release,上传全部产物,Release Notes 写明上游基线与已知问题
9. 匿名回读已发布 ISO 并 sha256 复核 == 本地构建值
10. `cn/release` 数字 +1,提交
8. 把本次构建的 `omarchy-dev` / `omarchy-settings-dev` 发布到 [omarchycn] registry
`packages/publish-cn-packages.sh`;先于公开 Release,一键转换才不会装到旧包)
9. 建 tag 与 Release,上传全部产物,Release Notes 写明上游基线与已知问题
10. 匿名回读已发布 ISO 并 sha256 复核 == 本地构建值
11. `cn/release` 数字 +1,提交
## 版本规则
+4
View File
@@ -3,6 +3,10 @@
cp -f "$OMARCHY_PATH/default/pacman/pacman-${OMARCHY_MIRROR:-stable}.conf" /etc/pacman.conf
cp -f "$OMARCHY_PATH/default/pacman/mirrorlist-${OMARCHY_MIRROR:-stable}" /etc/pacman.d/mirrorlist
# The [omarchy] mirror db is signed by the OmarchyCN registry key
pacman-key --add "$OMARCHY_PATH/cn/keys/omarchycn-registry.asc"
pacman-key --lsign-key 74DCF57ACD812B24D959F146BD386048867B33B4
# omarchy-settings skips this override until cups-browsed is actually present
# to avoid pacman creating cups-browsed.conf.pacnew during ISO package install.
if [[ -f $OMARCHY_PATH/etc-overrides/cups-cups-browsed.conf && -d /etc/cups ]]; then
+21 -1
View File
@@ -24,7 +24,27 @@ omarchycn setup
向导依次配置:语言、时区、显示缩放、中文 locale、中文字体、Fcitx5+Rime 输入法、输入法切换键、pacman 镜像、开发工具镜像、国内应用、AI Hub、隐私说明。每一步都可跳过,中断后重跑会从未完成的步骤继续。
## 现有 Omarchy 叠加安装(Overlay
## 原版 Omarchy 一键转换(包安装
quattro 的原版 Omarchy 是包安装形态(`pacman -Q omarchy` 有输出),可以整机转换成 OmarchyCN,无需重装:
```bash
curl -fsSL https://git.zacharyzhang.com/ZacharyZhang-NY/omarchycn/raw/branch/quattro/bin/omarchy-cn-convert | bash
```
转换做的事:信任 OmarchyCN registry 公钥并接入 [omarchycn] 仓库;把 `omarchy`/`omarchy-settings` 换成 OmarchyCN 构建的 `omarchy-dev`/`omarchy-settings-dev`(中文菜单、中文键位描述、OMARCHY CN 品牌、AI 全家桶随包生效);给 `[omarchy]` 仓库加自建镜像(Cloudflare 上游兜底);系统语言切到 zh_CN.UTF-8 并安装配置 Fcitx5+Rime;Arch 官方仓库自动测速切换国内镜像;刷新 OMARCHY CN 开机画面。重新登录后生效。
转换前的包名、语言与 mirrorlist 会记录在 `/var/lib/omarchycn/convert-state`,随时一键还原:
```bash
omarchy cn revert
```
还原会换回上游软件包、恢复语言与 mirrorlist、移除 [omarchycn] 仓库与镜像行、卸载转换时新装的输入法包,并恢复上游开机画面。菜单 OmarchyCN 区段也有「回退为原版 Omarchy」入口。
## 现有 Omarchy 叠加安装(Overlaycheckout 形态)
老的 checkout 安装(`~/.local/share/omarchy` 是 git 检出)用 Overlay 叠加,不走一键转换:
```bash
git clone https://git.zacharyzhang.com/ZacharyZhang-NY/omarchycn.git
+26
View File
@@ -0,0 +1,26 @@
#!/bin/bash
# Publishes cn-built packages into the Gitea [omarchycn] Arch registry.
# usage: publish-cn-packages.sh <pkg.tar.zst>... (maintainer/release step)
set -euo pipefail
GITEA=${GITEA:-https://git.zacharyzhang.com}
OWNER=${OWNER:-ZacharyZhang-NY}
: "${PKG_SYNC_TOKEN:?PKG_SYNC_TOKEN (Gitea package-write token) is required}"
(($# > 0)) || { echo "usage: publish-cn-packages.sh <pkg.tar.zst>..." >&2; exit 1; }
for pkg in "$@"; do
[[ -f $pkg ]] || { echo "no such file: $pkg" >&2; exit 1; }
# Token travels via curl config on stdin, never in the process argument list
code=$(curl -sS --retry 2 -o /tmp/publish.out -w '%{http_code}' -X PUT -K - \
--upload-file "$pkg" "$GITEA/api/packages/$OWNER/arch/omarchycn" \
<<< "header = \"Authorization: token $PKG_SYNC_TOKEN\"")
case "$code" in
201) echo "published: ${pkg##*/}" ;;
409) echo "already present: ${pkg##*/}" ;;
*)
echo "upload of ${pkg##*/} failed (HTTP $code): $(head -c 300 /tmp/publish.out)" >&2
exit 1
;;
esac
done
+101
View File
@@ -0,0 +1,101 @@
#!/bin/bash
# Mirrors the upstream [omarchy] stable repo into the OmarchyCN Gitea Arch registry.
# Maintainer/CI script (pkg-repo-sync.yml); needs a package-write token, network.
set -euo pipefail
export LC_ALL=C
GITEA=${GITEA:-https://git.zacharyzhang.com}
OWNER=${OWNER:-ZacharyZhang-NY}
REPO=${REPO:-omarchy}
UPSTREAM=${UPSTREAM:-https://pkgs.omarchy.org/stable/x86_64}
: "${PKG_SYNC_TOKEN:?PKG_SYNC_TOKEN (Gitea package-write token) is required}"
registry="$GITEA/api/packages/$OWNER/arch"
mirror="$registry/$REPO/x86_64"
workdir=$(mktemp -d)
trap 'rm -rf "$workdir"' EXIT
# One "filename name version arch" line per package in a repo db (zstd or gzip tar)
db_entries() {
local db="$1" dir="$2" desc
mkdir -p "$dir"
tar --zstd -xf "$db" -C "$dir" 2>/dev/null || tar -xzf "$db" -C "$dir"
for desc in "$dir"/*/desc; do
[[ -f $desc ]] || continue
awk '
/^%FILENAME%$/ { getline; f = $0 }
/^%NAME%$/ { getline; n = $0 }
/^%VERSION%$/ { getline; v = $0 }
/^%ARCH%$/ { getline; a = $0 }
END {
if (f == "" || n == "" || v == "" || a == "") {
print "malformed desc: " FILENAME > "/dev/stderr"
exit 1
}
print f, n, v, a
}
' "$desc"
done
}
curl -sSf --retry 3 -o "$workdir/upstream.db" "$UPSTREAM/omarchy.db"
db_entries "$workdir/upstream.db" "$workdir/upstream" | sort > "$workdir/upstream.list"
# Only 404 means first sync; any other failure must not fake an empty mirror,
# which would skip deletions and report a bogus success
code=$(curl -sS --retry 3 -o "$workdir/mirror.db" -w '%{http_code}' "$mirror/$REPO.db" || true)
[[ -z $code ]] && code=000
case "$code" in
200)
db_entries "$workdir/mirror.db" "$workdir/mirror" | sort > "$workdir/mirror.list"
;;
404)
: > "$workdir/mirror.list"
;;
*)
echo "mirror db fetch failed (HTTP $code): $mirror/$REPO.db" >&2
exit 1
;;
esac
comm -23 "$workdir/upstream.list" "$workdir/mirror.list" > "$workdir/to-add.list"
comm -13 "$workdir/upstream.list" "$workdir/mirror.list" > "$workdir/to-remove.list"
echo "upstream: $(wc -l < "$workdir/upstream.list") packages," \
"mirror: $(wc -l < "$workdir/mirror.list")," \
"to add: $(wc -l < "$workdir/to-add.list")," \
"to remove: $(wc -l < "$workdir/to-remove.list")"
added=0
while read -r filename name version arch; do
echo "add: $filename"
curl -sSf --retry 3 -o "$workdir/pkg" "$UPSTREAM/$filename"
code=$(curl -sS --retry 2 -o "$workdir/put.out" -w '%{http_code}' -X PUT \
-H "Authorization: token $PKG_SYNC_TOKEN" \
--upload-file "$workdir/pkg" "$registry/$REPO")
case "$code" in
201) added=$((added + 1)) ;;
409) echo " already present (409)" ;;
*)
echo "upload of $filename failed (HTTP $code): $(head -c 300 "$workdir/put.out")" >&2
exit 1
;;
esac
rm -f "$workdir/pkg"
done < "$workdir/to-add.list"
removed=0
while read -r filename name version arch; do
echo "remove: $filename"
code=$(curl -sS --retry 2 -o "$workdir/del.out" -w '%{http_code}' -X DELETE \
-H "Authorization: token $PKG_SYNC_TOKEN" \
"$registry/$REPO/$name/$version/$arch")
if [[ $code != 2?? && $code != 404 ]]; then
echo "removal of $filename failed (HTTP $code): $(head -c 300 "$workdir/del.out")" >&2
exit 1
fi
removed=$((removed + 1))
done < "$workdir/to-remove.list"
echo "Sync complete: $added added, $removed removed"
+27
View File
@@ -0,0 +1,27 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
shots/
TASKS.md
.wrangler/
+117
View File
@@ -0,0 +1,117 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>OmarchyCN · 为中国重铸的 Omarchy</title>
<meta name="description" content="OmarchyCN:华丽、现代、观点鲜明的 Arch + Hyprland 桌面,中国版。下载 ISO 全新安装,或一条命令把原版 Omarchy 转换为 OmarchyCN。" />
<link rel="icon" href="/favicon.svg" />
<link rel="preload" href="/fonts/fusion-pixel-12px-proportional-zh_hans.otf.woff2" as="font" type="font/woff2" crossorigin />
<link rel="preload" href="/fonts/fusion-pixel-12px-monospaced-zh_hans.otf.woff2" as="font" type="font/woff2" crossorigin />
<link rel="stylesheet" href="/src/style.css" />
</head>
<body>
<div id="smooth-wrapper">
<div id="smooth-content">
<div class="wrap">
<header class="band topbar">
<p class="brand" style="grid-column: 1 / 7">OMARCHY CN</p>
<nav class="topnav" style="grid-column: 7 / 13" aria-label="站点">
<a href="https://git.zacharyzhang.com/ZacharyZhang-NY/omarchycn/src/branch/quattro/manual/zh-cn/01-install.md">手册</a>
<a href="https://git.zacharyzhang.com/ZacharyZhang-NY/omarchycn/releases">发布</a>
<a href="https://git.zacharyzhang.com/ZacharyZhang-NY/omarchycn">源码</a>
</nav>
</header>
<main>
<section class="band hero" aria-labelledby="hero-title">
<p class="eyebrow" style="grid-column: 1 / 13">OMARCHY CN · 4.0.0.alpha-cn.6</p>
<h1 id="hero-title" style="grid-column: 1 / 12">
<span class="line">华丽、现代、观点鲜明</span><span class="line">的 Linux。中国版。</span>
</h1>
<p class="lede" style="grid-column: 1 / 9">
基于 Arch + Hyprland 的 Omarchy 下游发行版:中文体验、国内软件源与 AI 工具链,开箱即用。
</p>
<div class="actions" style="grid-column: 1 / 13">
<a
class="btn"
href="https://git.zacharyzhang.com/ZacharyZhang-NY/omarchycn/releases/download/4.0.0.alpha-cn.6/omarchy-2026.08.27-x86_64-local.iso"
>
<span class="i" data-icon="Download" aria-hidden="true"></span>下载 ISO<span class="btn-meta">5.9 GB</span>
</a>
<div class="convert">
<p class="cmd-label" id="cmd-label">已有原版 Omarchy?一键转换:</p>
<div class="cmd" aria-labelledby="cmd-label">
<code id="cmd-text">curl -fsSL https://git.zacharyzhang.com/ZacharyZhang-NY/omarchycn/raw/branch/quattro/bin/omarchy-cn-convert | bash</code>
<button class="copy" id="copy-btn" type="button" aria-label="复制转换命令">
<span class="i" data-icon="Copy" aria-hidden="true"></span><span class="copy-text">复制</span>
</button>
</div>
</div>
</div>
<p class="meta mono" style="grid-column: 1 / 13">
上游基线 basecamp/omarchy quattro · sha256 4c1cc4a2…60bcca · 转换可随时 omarchy cn revert 还原
</p>
</section>
<section class="band shotband" aria-label="安装器截图">
<figure class="shot" style="grid-column: 1 / 13">
<img src="/shot.png" width="1280" height="800" alt="OmarchyCN 安装器欢迎屏:像素风 OMARCHY CN 字标,按回车开始安装" loading="lazy" />
<figcaption class="mono">UEFI 安装器 · 全程中文 · 无人值守可选</figcaption>
</figure>
</section>
<section class="band feats" aria-label="特性">
<article class="feat" style="grid-column: 1 / 4">
<p class="num mono">01</p>
<h2>开箱中文</h2>
<p>安装器、菜单、键位说明、Fcitx5 + Rime 输入法与 CJK 字体,全程 zh_CN。</p>
</article>
<article class="feat" style="grid-column: 4 / 7">
<p class="num mono">02</p>
<h2>国内软件源</h2>
<p>上游 [omarchy] 仓库自建镜像每 6 小时同步,Cloudflare 原源兜底。</p>
</article>
<article class="feat" style="grid-column: 7 / 10">
<p class="num mono">03</p>
<h2>AI 全家桶</h2>
<p>DeepSeek、Kimi、GLM、MiniMax 与本地 OllamaClaude Code 到 DSH 一键就位。</p>
</article>
<article class="feat" style="grid-column: 10 / 13">
<p class="num mono">04</p>
<h2>随时回退</h2>
<p>转换前状态全程记录,omarchy cn revert 一条命令还原为原版。</p>
</article>
</section>
<section class="band verify" aria-labelledby="verify-title">
<h2 id="verify-title" style="grid-column: 1 / 4">校验</h2>
<dl class="mono" style="grid-column: 4 / 13">
<div><dt>sha256</dt><dd>4c1cc4a2726a28c6f2e548234be13381556bc2e9da9b7426d02c821bbd60bcca</dd></div>
<div><dt>签名子钥</dt><dd>PGP 211B9B82E17CFB67</dd></div>
<div>
<dt>清单</dt>
<dd><a href="https://git.zacharyzhang.com/ZacharyZhang-NY/omarchycn/releases/download/4.0.0.alpha-cn.6/SHA256SUMS.txt">SHA256SUMS.txt</a> · <a href="https://git.zacharyzhang.com/ZacharyZhang-NY/omarchycn/releases/download/4.0.0.alpha-cn.6/SHA256SUMS.txt.asc">SHA256SUMS.txt.asc</a></dd>
</div>
</dl>
</section>
</main>
<footer class="band foot">
<p style="grid-column: 1 / 8">
OmarchyCN 是独立社区发行版,与 Basecamp、37signals 及 Omarchy 官方无隶属关系。代码沿用上游 MIT 许可证。
</p>
</footer>
<div class="guides" aria-hidden="true">
<div class="cols"></div>
<div class="rows"></div>
</div>
</div>
</div>
</div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
+1185
View File
File diff suppressed because it is too large Load Diff
+19
View File
@@ -0,0 +1,19 @@
{
"name": "omarchycn-site",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"devDependencies": {
"puppeteer-core": "^25.9.0",
"vite": "^8.2.2"
},
"dependencies": {
"gsap": "^3.15.0",
"reicon": "^1.2.1"
}
}
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><rect width="32" height="32" rx="6" fill="#1B365D"/><path fill-rule="evenodd" d="M8 8h16v16H8Zm4 4v8h8v-8Z" fill="#f5f4ed"/></svg>

After

Width:  |  Height:  |  Size: 191 B

+96
View File
@@ -0,0 +1,96 @@
Fusion Pixel Font
https://github.com/TakWolf/fusion-pixel-font
Copyright (c) 2022, TakWolf (https://takwolf.com).
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://openfontlicense.org
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

+70
View File
@@ -0,0 +1,70 @@
// Rendered-state verification: full-page shots at key widths + interaction states
import puppeteer from "puppeteer-core";
import { mkdirSync } from "node:fs";
const CHROME = process.env.CHROME;
const BASE = process.env.BASE ?? "http://localhost:4173";
if (!CHROME) throw new Error("CHROME env (chrome.exe path) is required");
mkdirSync("shots", { recursive: true });
const browser = await puppeteer.launch({ executablePath: CHROME, headless: "new" });
const page = await browser.newPage();
// Layout passes: reduced motion = static truth of the full page
await page.emulateMediaFeatures([{ name: "prefers-reduced-motion", value: "reduce" }]);
for (const w of [1440, 1280, 960, 375]) {
await page.setViewport({ width: w, height: 900 });
await page.goto(BASE, { waitUntil: "networkidle0" });
await page.evaluate(() => document.fonts.ready);
await new Promise((r) => setTimeout(r, 1400));
await page.screenshot({ path: `shots/w${w}.png`, fullPage: true });
console.log(`shot w${w}`);
}
// Motion pass: real scroll, reveals must land every section at opacity 1
await page.emulateMediaFeatures([{ name: "prefers-reduced-motion", value: "no-preference" }]);
await page.setViewport({ width: 1440, height: 900 });
await page.goto(BASE, { waitUntil: "networkidle0" });
await new Promise((r) => setTimeout(r, 1200));
await page.evaluate(async () => {
for (let y = 0; y <= document.body.scrollHeight; y += 300) {
window.scrollTo(0, y);
await new Promise((r) => setTimeout(r, 60));
}
});
await new Promise((r) => setTimeout(r, 1200));
const opacities = await page.evaluate(() =>
[".shot", ".feat", ".verify h2", ".foot p"].map(
(s) => `${s}=${getComputedStyle(document.querySelector(s)).opacity}`,
),
);
console.log("post-scroll opacities:", opacities.join(" "));
await page.screenshot({ path: "shots/motion-bottom.png" });
await page.setViewport({ width: 1440, height: 900 });
await page.goto(BASE, { waitUntil: "networkidle0" });
await new Promise((r) => setTimeout(r, 1400));
await page.keyboard.press("g");
await new Promise((r) => setTimeout(r, 300));
await page.screenshot({ path: "shots/grid-overlay.png", fullPage: false });
console.log("shot grid-overlay");
await page.keyboard.press("g");
const ctx = browser.defaultBrowserContext();
await ctx.overridePermissions(BASE, ["clipboard-read", "clipboard-write", "clipboard-sanitized-write"]).catch(() => {});
await page.hover(".cmd");
await new Promise((r) => setTimeout(r, 200));
await page.screenshot({ path: "shots/cmd-hover.png" });
await page.click("#copy-btn");
await new Promise((r) => setTimeout(r, 400));
const state = await page.evaluate(() => ({
label: document.querySelector(".copy-text").textContent,
clip: navigator.clipboard.readText ? undefined : "n/a",
}));
const clip = await page.evaluate(() => navigator.clipboard.readText().catch(() => "READ-FAIL"));
console.log("copy label:", state.label, "| clipboard:", clip.slice(0, 60) + "…");
await page.screenshot({ path: "shots/cmd-copied.png" });
await browser.close();
console.log("SHOTS-DONE");
+95
View File
@@ -0,0 +1,95 @@
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import { ScrollSmoother } from "gsap/ScrollSmoother";
import { Download, Copy, Check } from "reicon";
const ICONS = { Download, Copy, Check };
const ICON_OPTS = { size: 18, strokeWidth: 1.5 };
for (const el of document.querySelectorAll(".i[data-icon]")) {
const icon = ICONS[el.dataset.icon];
if (!icon) throw new Error(`unknown icon: ${el.dataset.icon}`);
el.replaceChildren(icon(ICON_OPTS));
}
/* Copy the convert command */
const copyBtn = document.getElementById("copy-btn");
const copyText = copyBtn.querySelector(".copy-text");
const copyIcon = copyBtn.querySelector(".i");
copyText.setAttribute("aria-live", "polite");
let copyTimer;
let copySeq = 0;
copyBtn.addEventListener("click", async () => {
const cmd = document.getElementById("cmd-text").textContent.trim();
const seq = ++copySeq;
let ok = true;
try {
await navigator.clipboard.writeText(cmd);
} catch (err) {
console.error("clipboard write failed", err);
ok = false;
}
// Only the latest click may touch the feedback state
if (seq !== copySeq) return;
clearTimeout(copyTimer);
copyBtn.classList.remove("done", "fail");
copyBtn.classList.add(ok ? "done" : "fail");
copyIcon.replaceChildren(ok ? Check(ICON_OPTS) : Copy(ICON_OPTS));
copyText.textContent = ok ? "已复制" : "复制失败";
copyTimer = setTimeout(() => {
copyBtn.classList.remove("done", "fail");
copyIcon.replaceChildren(Copy(ICON_OPTS));
copyText.textContent = "复制";
}, 2000);
});
/* Grid overlay: G key */
document.addEventListener("keydown", (e) => {
if (e.key !== "g" && e.key !== "G") return;
if (e.metaKey || e.ctrlKey || e.altKey) return;
if (/^(input|textarea|select)$/i.test(e.target.tagName)) return;
document.body.classList.toggle("grid-on");
});
/* Motion: smooth scrolling + reveals, skipped under reduced motion */
if (!window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
gsap.registerPlugin(ScrollTrigger, ScrollSmoother);
ScrollSmoother.create({
wrapper: "#smooth-wrapper",
content: "#smooth-content",
smooth: 1,
});
const enter = [".hero .eyebrow", ".hero h1 .line", ".hero .lede", ".hero .actions", ".hero .meta"];
gsap.set(enter, { y: 24, autoAlpha: 0 });
document.fonts.ready.then(() => {
gsap.to(enter, {
y: 0,
autoAlpha: 1,
duration: 0.7,
ease: "power2.out",
stagger: 0.09,
});
});
// Document-tail elements never reach the 85% line, so they fire on entry
const reveals = [
[".shot", "top 85%"],
[".feat", "top 85%"],
[".verify > *", "top 92%"],
[".foot > *", "top bottom"],
];
for (const [sel, start] of reveals) {
for (const el of gsap.utils.toArray(sel)) {
gsap.from(el, {
y: 24,
autoAlpha: 0,
duration: 0.6,
ease: "power2.out",
scrollTrigger: { trigger: el, start },
});
}
}
}
+419
View File
@@ -0,0 +1,419 @@
/* OmarchyCN landing — kami palette on a Müller-Brockmann grid, Fusion Pixel type */
@font-face {
font-family: "Fusion Pixel";
src: url("/fonts/fusion-pixel-12px-proportional-zh_hans.otf.woff2") format("woff2");
font-weight: 400;
font-display: swap;
}
@font-face {
font-family: "Fusion Pixel Mono";
src: url("/fonts/fusion-pixel-12px-monospaced-zh_hans.otf.woff2") format("woff2");
font-weight: 400;
font-display: swap;
}
:root {
--cols: 12;
--bl: 8px;
--lh: 24px;
--gutter: 24px;
--margin: 72px;
--pad: 24px;
--maxw: 1296px;
--paper: #f5f4ed;
--ivory: #faf9f5;
--sand: #e8e6dc;
--ink: #141413;
--ink-2: #3d3d3a;
--stone: #6b6a64;
--brand: #1b365d;
--brand-light: #2d5a8a;
--tint: #eef2f7;
--line: #d8d5c8;
--shot-bg: #141318;
--err: #8a2d2d;
--pixel: "Fusion Pixel", "Noto Sans SC", system-ui, sans-serif;
--pixel-mono: "Fusion Pixel Mono", ui-monospace, monospace;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
}
body {
background: var(--paper);
color: var(--ink);
font-family: var(--pixel);
font-size: 12px;
line-height: var(--lh);
-webkit-font-smoothing: none;
text-rendering: geometricPrecision;
}
.mono {
font-family: var(--pixel-mono);
}
a {
color: var(--ink);
text-decoration-thickness: 1px;
text-underline-offset: 4px;
transition: color 150ms ease-out;
}
a:hover {
color: var(--brand);
}
:is(a, button):focus-visible {
outline: 2px solid var(--brand);
outline-offset: 2px;
}
/* Grid frame */
.wrap {
position: relative;
max-width: var(--maxw);
margin: 0 auto;
padding: var(--pad) var(--margin) 96px;
}
.band {
display: grid;
grid-template-columns: repeat(var(--cols), 1fr);
column-gap: var(--gutter);
row-gap: var(--lh);
align-items: start;
}
/* Topbar */
.topbar {
align-items: center;
}
.brand {
font-size: 12px;
letter-spacing: 2px;
}
.topnav {
display: flex;
justify-content: flex-end;
gap: 24px;
}
.topnav a {
color: var(--stone);
text-decoration: none;
}
.topnav a:hover {
color: var(--brand);
text-decoration: underline;
}
/* Hero */
.hero {
margin-top: 144px;
}
.eyebrow {
font-family: var(--pixel-mono);
color: var(--brand);
letter-spacing: 1px;
}
h1 {
font-size: 60px;
line-height: 72px;
font-weight: 400;
word-break: keep-all;
}
h1 .line {
display: block;
}
.lede {
font-size: 24px;
line-height: 40px;
color: var(--ink-2);
margin-top: 8px;
}
.actions {
margin-top: 24px;
display: grid;
grid-template-columns: repeat(var(--cols), 1fr);
column-gap: var(--gutter);
row-gap: var(--lh);
align-items: end;
}
.btn {
grid-column: 1 / 4;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
height: 48px;
padding: 0 24px;
background: var(--brand);
color: var(--ivory);
border-radius: 8px;
text-decoration: none;
transition: background-color 150ms ease-out;
}
.btn:hover {
background: var(--brand-light);
color: var(--ivory);
}
.btn .i {
color: var(--ivory);
}
.btn-meta {
color: var(--sand);
}
.convert {
grid-column: 4 / 11;
}
.cmd-label {
color: var(--stone);
margin-bottom: 8px;
}
.cmd {
position: relative;
display: flex;
align-items: flex-start;
gap: 16px;
background: var(--ivory);
border: 1px solid var(--line);
border-radius: 8px;
padding: 12px 16px;
}
.cmd code {
font-family: var(--pixel-mono);
font-size: 12px;
line-height: 24px;
white-space: pre-wrap;
word-break: break-all;
flex: 1;
}
.copy {
position: relative;
display: flex;
align-items: center;
gap: 4px;
border: 0;
background: none;
font: inherit;
font-family: var(--pixel-mono);
color: var(--stone);
cursor: pointer;
padding: 0 4px;
height: 24px;
border-radius: 4px;
transition: color 150ms ease-out, background-color 150ms ease-out;
}
.copy::after {
content: "";
position: absolute;
inset: -8px;
}
.cmd:hover .copy,
.copy:hover,
.copy:focus-visible {
color: var(--brand);
}
.copy:hover {
background: var(--tint);
}
.copy.done {
color: var(--brand);
}
.copy.fail {
color: var(--err);
}
.meta {
color: var(--stone);
margin-top: 8px;
}
/* Screenshot */
.shotband {
margin-top: 96px;
}
.shot {
background: var(--shot-bg);
border: 1px solid var(--line);
border-radius: 8px;
padding: 24px;
}
.shot img {
display: block;
width: 100%;
height: auto;
outline: 1px solid rgba(255, 255, 255, 0.1);
outline-offset: -1px;
}
.shot figcaption {
color: var(--sand);
margin-top: 16px;
}
/* Features */
.feats {
margin-top: 144px;
}
.num {
color: var(--brand);
}
.feat h2 {
font-size: 24px;
line-height: 32px;
font-weight: 400;
margin: 16px 0 8px;
}
.feat p:last-child {
color: var(--stone);
}
/* Verify */
.verify {
margin-top: 144px;
padding-top: 24px;
border-top: 1px solid var(--line);
}
.verify h2 {
font-size: 24px;
line-height: 32px;
font-weight: 400;
}
.verify dl > div {
display: grid;
grid-template-columns: 96px 1fr;
gap: 24px;
margin-bottom: 8px;
}
.verify dt {
color: var(--stone);
}
.verify dd {
word-break: break-all;
}
/* Footer */
.foot {
margin-top: 144px;
padding-top: 24px;
border-top: 1px solid var(--line);
color: var(--stone);
}
.foot a {
color: var(--stone);
}
/* Icons */
.i {
display: inline-flex;
width: 24px;
height: 24px;
align-items: center;
justify-content: center;
color: currentColor;
}
/* Grid overlay (toggle: G or footer button) */
.guides {
position: absolute;
inset: 0;
pointer-events: none;
opacity: 0;
}
body.grid-on .guides {
opacity: 1;
}
.guides .cols {
position: absolute;
top: 0;
bottom: 0;
left: var(--margin);
right: var(--margin);
display: grid;
grid-template-columns: repeat(var(--cols), 1fr);
column-gap: var(--gutter);
}
.guides .cols::before {
content: "";
grid-column: 1 / -1;
background: repeating-linear-gradient(
to right,
rgba(27, 54, 93, 0.1) 0,
rgba(27, 54, 93, 0.1) calc((100% - 11 * var(--gutter)) / 12),
transparent 0,
transparent calc((100% - 11 * var(--gutter)) / 12 + var(--gutter))
);
}
.guides .rows {
position: absolute;
left: var(--margin);
right: var(--margin);
top: var(--pad);
bottom: 0;
background-image: repeating-linear-gradient(
to bottom,
rgba(27, 54, 93, 0.22) 0 1px,
transparent 1px var(--lh)
);
}
/* Responsive */
@media (max-width: 960px) {
:root {
--margin: 24px;
}
h1 {
font-size: 36px;
line-height: 48px;
}
h1 .line {
display: inline;
}
.lede {
font-size: 12px;
line-height: 24px;
}
.hero {
margin-top: 96px;
}
.band > * {
grid-column: 1 / -1 !important;
}
.feat {
grid-column: span 6 !important;
}
.btn {
grid-column: 1 / 7;
}
.convert {
grid-column: 1 / -1;
}
.topbar .brand {
grid-column: 1 / 5 !important;
}
.topbar .topnav {
grid-column: 5 / 13 !important;
}
}
@media (max-width: 640px) {
.feat {
grid-column: 1 / -1 !important;
}
.btn {
grid-column: 1 / -1;
}
}
@media (prefers-reduced-motion: reduce) {
html {
scroll-behavior: auto;
}
}
+14
View File
@@ -0,0 +1,14 @@
{
"name": "omarchycn-site",
"compatibility_date": "2026-08-27",
"assets": {
"directory": "./dist"
},
"workers_dev": true,
"routes": [
{
"pattern": "omarchycn.zacharyzhang.com",
"custom_domain": true
}
]
}
+1 -1
View File
@@ -248,7 +248,7 @@ assertDeepEqual(
defaultItems
.filter(item => item.parent === 'setup.default.agent')
.map(item => item.label),
['Antigravity', 'Claude', 'Codex', 'Copilot', 'Crush', 'Deep Code', 'Dim Agent', 'Grok', 'Kimi Code', 'omp', 'OpenCode', 'Ori', 'Pi', '国产模型组合 (AI Hub)'],
['Antigravity', 'Claude', 'Codex', 'Copilot', 'Crush', 'Deep Code', 'Dim Agent', 'DSH', 'Grok', 'Kimi Code', 'omp', 'OpenCode', 'Ori', 'Pi', '国产模型组合 (AI Hub)'],
'menu sorts coding agents alphabetically'
)
const expectedDefaults = {
+28 -1
View File
@@ -38,6 +38,11 @@ for p in deepseek kimi zai minimax; do
jq -e --arg p "$p" '.providers[$p].endpoints.anthropic // .providers[$p].endpoints.openai' \
"$ROOT/cn/registry/ai-providers.json" > /dev/null || fail "provider $p has an endpoint"
done
jq -e '.providers.ollama | (.static_token == "ollama") and (.models_dynamic == true)
and (.endpoints.anthropic == "http://localhost:11434")' \
"$ROOT/cn/registry/ai-providers.json" > /dev/null || fail "ollama provider contract"
jq -e '.combos["claude-code"].ollama.adapter' \
"$ROOT/cn/registry/ai-compatibility.json" > /dev/null || fail "claude-code x ollama combo present"
pass "AI registries parse with P0 providers"
# Secret file backend roundtrip with 0600
@@ -59,8 +64,17 @@ fi
if omarchy-cn-ai-profile-create bad codex deepseek "deepseek-v4-pro[1m]" > /dev/null 2>&1; then
fail "codex rejects provider-namespace slug"
fi
omarchy-cn-ai-profile-create loc claude-code ollama qwen3-coder > /dev/null ||
fail "dynamic-model provider accepts a runtime-listed model"
pass "profile validation enforces combos, protocols, and allowlists"
# Static-token provider needs no stored secret; absent fields stay silent
source "$ROOT/cn/lib/ai.sh"
[[ $(cn_ai_resolve_key ollama) == "ollama" ]] || fail "ollama resolves its static token"
static_out=$(cn_ai_static_token deepseek || true)
[[ -z $static_out ]] || fail "absent static_token must print nothing" "$static_out"
pass "static token resolution: fixed ollama key, silent absent field"
# claude-code env render for all three providers, exact endpoints
source "$ROOT/cn/lib/ai.sh"
declare -A bases=(
@@ -77,6 +91,10 @@ for p in deepseek kimi zai minimax; do
done
out=$(cn_ai_render_env claude-code minimax "MiniMax-M3[1m]" sk-x)
grep -qF "ANTHROPIC_DEFAULT_HAIKU_MODEL=MiniMax-M3" <<<"$out" || fail "minimax haiku slot falls back to default-coding"
out=$(cn_ai_render_env claude-code ollama qwen3-coder "$(cn_ai_resolve_key ollama)")
grep -qF "ANTHROPIC_BASE_URL=http://localhost:11434" <<<"$out" || fail "ollama base url" "$out"
grep -q "ANTHROPIC_AUTH_TOKEN=ollama" <<<"$out" || fail "ollama static token rendered"
grep -qF "ANTHROPIC_DEFAULT_HAIKU_MODEL=qwen3-coder" <<<"$out" || fail "ollama haiku slot falls back to the chosen model"
out=$(cn_ai_render_env opencode deepseek test-model sk-x)
grep -q "DEEPSEEK_API_KEY=sk-x" <<<"$out" || fail "opencode deepseek env"
if cn_ai_render_env opencode zai m k > /dev/null 2>&1; then
@@ -107,7 +125,7 @@ class H(http.server.BaseHTTPRequestHandler):
def do_POST(self):
body = self.rfile.read(int(self.headers.get("Content-Length", 0)))
key = self.headers.get("x-api-key") or (self.headers.get("Authorization") or "").replace("Bearer ", "")
if key != "sk-secret-1":
if key not in ("sk-secret-1", "ollama"):
code = 401
elif self.path == "/v1/messages" and b"messages" in body and self.headers.get("anthropic-version"):
code = 200
@@ -131,6 +149,7 @@ done
OMARCHYCN_AI_TEST_BASE_URL="http://127.0.0.1:$port" omarchy-cn-ai-test good | grep -q "PASS" || fail "anthropic mock 200"
OMARCHYCN_AI_TEST_BASE_URL="http://127.0.0.1:$port" omarchy-cn-ai-test cdx | grep -q "PASS" || fail "codex responses mock 200"
OMARCHYCN_AI_TEST_BASE_URL="http://127.0.0.1:$port" omarchy-cn-ai-test loc | grep -q "PASS" || fail "ollama static-token mock 200"
echo "wrong-key" | omarchy-cn-ai-secret set deepseek > /dev/null
if OMARCHYCN_AI_TEST_BASE_URL="http://127.0.0.1:$port" omarchy-cn-ai-test good > /dev/null 2>&1; then
fail "anthropic mock 401 fails the test"
@@ -165,3 +184,11 @@ PATH="$restricted" omarchy-cn-ai-default good > /dev/null
[[ $(stat -c %a "$HOME/.claude/settings.json") == 600 ]] || fail "claude settings 0600"
[[ -f $TEST_HOME/installer-ran ]] || fail "missing harness triggered installer"
pass "ai-default maps the profile onto the upstream agent entry"
# ai-default with the static-token local provider writes the official contract
PATH="$restricted" omarchy-cn-ai-default loc > /dev/null
[[ $(jq -r '.env.ANTHROPIC_BASE_URL' "$HOME/.claude/settings.json") == "http://localhost:11434" ]] || fail "ollama ai-default base url"
[[ $(jq -r '.env.ANTHROPIC_AUTH_TOKEN' "$HOME/.claude/settings.json") == "ollama" ]] || fail "ollama ai-default static token"
[[ $(jq -r '.env.ANTHROPIC_DEFAULT_HAIKU_MODEL' "$HOME/.claude/settings.json") == "qwen3-coder" ]] || fail "ollama ai-default haiku fallback"
[[ $(jq -r '.env.KEEP' "$HOME/.claude/settings.json") == 1 ]] || fail "ollama ai-default preserves user env"
pass "ai-default renders the ollama static-token contract"
+41
View File
@@ -80,6 +80,47 @@ if missing or not weak_appends:
PYEOF
pass "fontconfig priority file carries SC/JP/KR rules and weak fallback"
# [omarchy] mirror wiring: stable is mirror-first with upstream fallback
mapfile -t stable_servers < <(sed -n '/^\[omarchy\]/,$p' "$ROOT/default/pacman/pacman-stable.conf" | grep '^Server = ')
[[ ${stable_servers[0]:-} == "Server = https://git.zacharyzhang.com/api/packages/ZacharyZhang-NY/arch/omarchy/x86_64" ]] ||
fail "stable conf lists the OmarchyCN mirror first" "${stable_servers[0]:-none}"
[[ ${stable_servers[1]:-} == 'Server = https://pkgs.omarchy.org/stable/$arch' ]] ||
fail "stable conf keeps the upstream fallback second" "${stable_servers[1]:-none}"
for ch in edge rc; do
if grep -q git.zacharyzhang.com "$ROOT/default/pacman/pacman-$ch.conf"; then
fail "$ch conf must stay upstream-only (mirror carries stable only)"
fi
done
bash -n "$ROOT/cn/migrations/1787861071.sh" || fail "mirror migration parses"
grep -q "pacman-key --add" "$ROOT/install/post-install/pacman.sh" || fail "install imports the registry key"
pass "[omarchy] mirror wiring: stable mirror-first, edge/rc untouched, key import present"
# Mirror-line guard: comments and substrings must not count as present
tmpconf=$(mktemp)
printf 'Server = https://pkgs.omarchy.org/stable/$arch\n' > "$tmpconf"
(
source "$ROOT/cn/lib/mirror.sh"
OMARCHYCN_PACMAN_CONF=$tmpconf cn_mirror_omarchy_repo_ok && exit 1
printf '# Server = %s\n' "$CN_OMARCHY_MIRROR" >> "$tmpconf"
OMARCHYCN_PACMAN_CONF=$tmpconf cn_mirror_omarchy_repo_ok && exit 1
printf 'Server = %s\n' "$CN_OMARCHY_MIRROR" >> "$tmpconf"
OMARCHYCN_PACMAN_CONF=$tmpconf cn_mirror_omarchy_repo_ok
) || fail "omarchy repo guard exact-line semantics"
rm -f "$tmpconf"
pass "omarchy mirror guard ignores comments and matches only the exact line"
# Convert/revert bootstrap pair: parse, self-delete guard, no constant drift
for s in omarchy-cn-convert omarchy-cn-revert; do
bash -n "$ROOT/bin/$s" || fail "$s parses"
grep -q '^main "\$@"$' "$ROOT/bin/$s" || fail "$s wraps work in main() against self-deletion"
done
grep -q -- '--ask=4' "$ROOT/bin/omarchy-cn-convert" || fail "convert auto-confirms the conflict swap"
lib_mirror=$(grep -m1 '^CN_OMARCHY_MIRROR=' "$ROOT/cn/lib/mirror.sh" | cut -d'"' -f2)
revert_mirror=$(grep -m1 '^MIRROR_LINE=' "$ROOT/bin/omarchy-cn-revert" | cut -d'"' -f2)
[[ "Server = $lib_mirror" == "$revert_mirror" ]] ||
fail "revert MIRROR_LINE drifted from cn/lib/mirror.sh" "$revert_mirror"
pass "convert/revert pair: parse, main() guard, ask=4, mirror constant in sync"
# Menu: OmarchyCN entries parse and reference only existing commands
node -e '
const fs = require("fs");