Compare commits

10 Commits
Author SHA1 Message Date
omar a5dd271588 ci: drop release workflow, switch install URLs to raw/HEAD
Upstream Sync / sync (push) Successful in 13s
Gitea has no /releases/latest/download/<file> shortcut (GitHub-only),
so the URL we wired up last commit served 404. Switch all user-facing
install commands to /raw/HEAD/install.{sh,ps1} -- the HEAD ref resolves
to the default branch (secure-main) without naming it, gives a stable
"always latest" URL, and survives a future branch rename.

With install no longer routed through release assets, the release
workflow becomes dead weight: secure-main is the curated branch (every
merge is manually reviewed), so every commit there is already vetted.
A separate tagged-release step duplicates that gate without adding new
information. Drop .gitea/workflows/release.yml; tag manually if you
ever want an immutable audit checkpoint.

The orphan v1.1.7-secure.1 tag + release is left in place as a
historical snapshot; harmless and can be deleted from the Gitea UI
later if desired.
2026-05-21 03:47:26 +03:00
omar 32663a7b18 ci: replace GitHub workflows with Gitea release workflow
Release / release (push) Successful in 9s
The three .github/workflows files (release.yml, compat-daily.yml,
cache-cleanup-weekly.yml) all target the GitHub API:
  - release.yml uses `gh release create`
  - compat-daily.yml does `git push https://x-access-token@github.com/...`
    to a `badges` branch
  - cache-cleanup-weekly.yml uses `gh cache delete`
On Gitea Actions these silently mis-route to GitHub with a Gitea-issued
GITHUB_TOKEN, which github.com rejects ("Invalid username or token") —
so every scheduled run fails.

Drop them on secure-main (upstream main keeps them for the original
GitHub repo, untouched) and add .gitea/workflows/release.yml that:
  - triggers on tag push v* or manual workflow_dispatch
  - builds release notes from the changelog window since the prior tag
  - creates the release via Gitea API, or refreshes assets if it already
    exists (so re-runs are idempotent)
  - attaches install.sh + install.ps1 so the README's
    /releases/latest/download/install.{sh,ps1} URLs resolve

Tag a release with:
  git tag v1.0.0 && git push origin v1.0.0
2026-05-21 03:34:39 +03:00
omar f6ee715478 fix: strip BOM + reverse cp1251 mojibake; migrate URLs to releases/latest/download
Commit 56bc5a7 was authored from a PowerShell session whose console code
page was cp1251 (Cyrillic), so PowerShell read the UTF-8 byte stream of
each touched file as cp1251 and re-saved it as UTF-8 -- adding a leading
EF BB BF BOM and double-encoding every multi-byte character (em-dash,
arrows, CJK). README headings, Chinese/Japanese link labels, and inline
hyphens all turned into vЂ" / в†' / дё-ж–' style mojibake.

Restore the eight affected files (READMEs, docs/clawgod-handbook/*,
*.html) from main's clean blobs, then re-apply the qomar.pw redirect
substitutions on top. Install URLs now point at
git.qomar.pw/omar/clawgod/releases/latest/download/install.{sh,ps1}
instead of /raw/branch/secure-main/... in both the markdown/HTML docs
and the patcher snippet inside install.sh / install.ps1 (the snippet
that rewrites `claude update`).

Note: this assumes a Gitea release pipeline publishes install.sh +
install.ps1 as assets under each tag. Without that, the new URLs 404.
2026-05-21 03:32:41 +03:00
omar 907613f657 Fix: remove non-ASCII characters and fix PowerShell Here-Strings to resolve encoding and parser errors 2026-05-21 03:20:53 +03:00
omar fd65bf1b17 Update sync schedule to @hourly shorthand 2026-05-21 03:15:35 +03:00
omar 766d0cb479 Restore production sync schedule (6 hours) 2026-05-21 03:12:47 +03:00
omar 4a201858e9 Update sync workflow: add push trigger and 30min schedule
Upstream Sync / sync (push) Successful in 4s
2026-05-21 03:08:28 +03:00
omar 1d4cf88f8b Trigger Gitea Actions indexing 2026-05-21 03:07:31 +03:00
omar 56bc5a79e7 Security: redirect updates to private Gitea fork omar/clawgod 2026-05-21 03:04:23 +03:00
omar a6cf45b883 Add Gitea Action for upstream synchronization 2026-05-21 03:02:54 +03:00
14 changed files with 280 additions and 602 deletions
+26
View File
@@ -0,0 +1,26 @@
name: Upstream Sync
on:
schedule:
- cron: '@hourly' # Every hour (Gitea shorthand)
workflow_dispatch: # Manual trigger
jobs:
sync:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: main
fetch-depth: 0
- name: Sync Upstream
run: |
git config user.email "omar@git.qomar.pw"
git config user.name "Gitea Action"
git remote add upstream https://github.com/0Chencc/clawgod.git
git fetch upstream main
git checkout main
git reset --hard upstream/main
git push origin main --force
@@ -1,54 +0,0 @@
name: Cache Cleanup Weekly
# compat-daily.yml writes a fresh `~/.npm` cache entry on every successful
# run (key includes ${{ github.run_id }}, which is unique per run) and reads
# previous entries via restore-keys. GitHub's automatic 7-day cache expiry
# only kicks in for entries that haven't been *accessed* — the restore-keys
# match counts as access, so daily-tested entries never expire on their
# own. Without housekeeping, cache entries accumulate until we hit the
# 10 GB per-repo limit and GitHub starts evicting LRU, which sometimes
# kicks the entries we actually wanted to keep.
#
# Run weekly: list every cache, delete it. Next compat-daily run repopulates
# `~/.npm` from a fresh npm-registry fetch (one extra ~80 MB download per
# week, trivial cost).
on:
schedule:
- cron: '0 4 * * 0' # Sunday 04:00 UTC
workflow_dispatch:
permissions:
actions: write # required for `gh cache delete`
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
jobs:
purge:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Delete all repo caches
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
shell: bash
run: |
set -euo pipefail
# gh cache list paginates; --limit 200 handles up to 200 entries
# in a single call (we'd never expect that many anyway).
ids=$(gh cache list --repo "$REPO" --limit 200 --json id --jq '.[].id')
if [[ -z "$ids" ]]; then
echo "No caches to delete."
exit 0
fi
count=$(printf '%s\n' "$ids" | grep -c . || true)
echo "Deleting $count cache entries…"
while IFS= read -r id; do
[[ -z "$id" ]] && continue
gh cache delete "$id" --repo "$REPO" \
&& echo " ✓ $id" \
|| echo " ✗ $id (already gone or transient error)"
done <<< "$ids"
echo "Done. Next compat-daily run will repopulate ~/.npm fresh."
-212
View File
@@ -1,212 +0,0 @@
name: Compat Daily
# Runs install.sh end-to-end on every supported platform against the current
# latest Claude Code from npm. Catches the kind of regression we hit when
# Anthropic bumps the embedded Bun runtime ahead of Bun's stable release.
on:
schedule:
- cron: '17 7 * * *' # ~07:17 UTC daily
workflow_dispatch:
push:
branches: [main]
paths:
- install.sh
- .github/workflows/compat-daily.yml
pull_request:
paths:
- install.sh
- .github/workflows/compat-daily.yml
permissions:
contents: write # write needed to push the version-badge JSON to `badges` branch
issues: write
# Opt every JS-based action (e.g. actions/checkout, actions/cache,
# actions/github-script) onto Node 24, ahead of GitHub forcing it on
# 2026-06-02. Silences the deprecation warning and keeps us aligned
# with the Node version we use to run patch.mjs / extract-natives.mjs.
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
jobs:
smoke:
# Single Linux runner — a Linux failure almost always means cross-platform
# breakage upstream. macOS / Windows runners are 10x / 2x more expensive
# than Linux on shared-tier accounting, so we don't burn them on a daily
# signal.
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- name: Set up Node.js 24
uses: actions/setup-node@v4
with:
node-version: 24
- name: Set up Bun (canary)
# Anthropic ships claude-code with bleeding-edge Bun (e.g. 1.3.14
# before it leaves the canary channel). Stable Bun panics with
# "Expected CommonJS module to have a function wrapper" when loading
# cli.original.cjs extracted from a newer-Bun-built native binary.
# setup-bun caches the canary binary by commit hash — same canary
# build is restored from cache, new build re-downloads.
uses: oven-sh/setup-bun@v2
with:
bun-version: canary
- name: Cache ~/.npm (claude-code-<plat> tarball)
# install.sh runs `npm pack @anthropic-ai/claude-code-linux-x64@latest`,
# which lands the ~80 MB tarball in ~/.npm/_cacache. Reusing the cache
# lets npm short-circuit the download when @latest hasn't bumped since
# the last run; it still re-validates registry metadata, so we never
# serve a stale binary on a real upgrade day.
uses: actions/cache@v4
with:
path: ~/.npm
key: npm-claude-${{ runner.os }}-${{ github.run_id }}
restore-keys: |
npm-claude-${{ runner.os }}-
- name: Install ripgrep
shell: bash
run: |
sudo apt-get update -qq && sudo apt-get install -y ripgrep
rg --version | head -1
- name: Add ~/.local/bin to PATH (clawgod launcher install target)
shell: bash
run: echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Print runtime versions
shell: bash
run: |
bun --version
node --version
uname -a
- name: Run install.sh (npm-fallback path)
# No official Claude binary pre-installed → install.sh exercises
# its npm-registry fallback to fetch @anthropic-ai/claude-code-<plat>.
# Capture output so the patch-result line can be asserted on; we
# don't trust `patch.mjs --verify` because some patches have no
# post-state sentinel (they use a regex-stale heuristic that
# false-positives once the source survives a partial replacement).
shell: bash
run: |
set -o pipefail
bash install.sh 2>&1 | tee /tmp/install.log
- name: Verify install artifacts
shell: bash
run: |
ls -la "$HOME/.clawgod/" || true
for f in cli.cjs cli.original.cjs patch.mjs extract-natives.mjs post-process.mjs .source-version; do
test -e "$HOME/.clawgod/$f" || { echo "::error::missing ~/.clawgod/$f"; exit 1; }
done
echo "Source: $(cat "$HOME/.clawgod/.source-version")"
- name: Assert all patches applied (parse install log)
# patch.mjs prints "Result: A applied, S skipped, F failed".
# F must be 0 — otherwise upstream has shifted enough that one of
# our regex patches no longer matches and a feature is silently broken.
shell: bash
run: |
line=$(grep -E 'Result: [0-9]+ applied, [0-9]+ skipped, [0-9]+ failed' /tmp/install.log | tail -1 || true)
echo "Patch summary: ${line:-<not found>}"
[[ -n "$line" ]] || { echo "::error::patch.mjs result line missing from install.sh output"; exit 1; }
failed=$(echo "$line" | sed -E 's/.*skipped, ([0-9]+) failed.*/\1/')
[[ "$failed" -eq 0 ]] || { echo "::error::patch.mjs reported $failed failed patches"; exit 1; }
- name: Smoke test — claude --version
# Exercises the wrapper path:
# launcher → bun cli.cjs → require('./cli.original.cjs')
# The Bun CJS-wrapper panic surfaces here, not at install time.
shell: bash
run: |
set +e
out=$(claude --version 2>&1)
rc=$?
set -e
echo "$out"
if echo "$out" | grep -q "Expected CommonJS module to have a function wrapper"; then
echo "::error::Bun CJS-wrapper panic — local Bun lags the embedded Bun"
exit 1
fi
[[ $rc -eq 0 ]] || { echo "::error::claude --version exited $rc"; exit 1; }
- name: Publish supported-Claude-version badge
# Write the version we just verified end-to-end to a JSON file on the
# `badges` branch (force-pushed; that branch holds nothing else and is
# never read as source). README links to it via shields.io endpoint.
# PRs from forks can't push, so skip them.
if: success() && github.event_name != 'pull_request'
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
version=$(cat "$HOME/.clawgod/.source-version")
[[ -n "$version" ]] || { echo "::error::source-version missing"; exit 1; }
tmp=$(mktemp -d)
cd "$tmp"
cat > claude-version.json <<EOF
{
"schemaVersion": 1,
"label": "Claude tested",
"message": "$version",
"color": "brightgreen"
}
EOF
git init -q -b badges
git config user.email "github-actions[bot]@users.noreply.github.com"
git config user.name "github-actions[bot]"
git add claude-version.json
git commit -q -m "compat-daily: claude $version verified"
git push -fq "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" HEAD:badges
echo "Published badge: claude $version"
- name: Open / update issue on scheduled failure
if: failure() && github.event_name == 'schedule'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const path = require('path');
const stamp = path.join(process.env.HOME, '.clawgod', '.source-version');
const claudeVersion = fs.existsSync(stamp)
? fs.readFileSync(stamp, 'utf8').trim()
: 'unknown';
const title = `compat-daily: broke (claude ${claudeVersion})`;
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
const { data: open } = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
labels: 'compat-broken',
per_page: 100,
});
const dup = open.find(i => i.title === title);
if (dup) {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: dup.number,
body: `Re-failed ${new Date().toISOString().slice(0,10)} — ${runUrl}`,
});
} else {
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title,
labels: ['compat-broken', 'bug'],
body: [
`Daily compatibility run failed on \`ubuntu-latest\`.`,
``,
`- Claude binary: \`${claudeVersion}\``,
`- Run: ${runUrl}`,
``,
`Auto-opened by \`.github/workflows/compat-daily.yml\`.`,
].join('\n'),
});
}
-84
View File
@@ -1,84 +0,0 @@
name: Release
on:
push:
tags:
- 'v*'
permissions:
contents: write
# Project policy: never run JS-based actions on Node 20 — they're already
# deprecated and forced off after 2026-06-02. Pin every action that runs
# JS (actions/checkout etc.) to Node 24 across all our workflows.
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Resolve versions
id: versions
run: |
tag="${GITHUB_REF_NAME}"
echo "tag=${tag}" >> "$GITHUB_OUTPUT"
prev=$(git tag --sort=-version:refname | grep -v "^${tag}$" | head -n1)
echo "prev=${prev}" >> "$GITHUB_OUTPUT"
if [ -n "$prev" ]; then
echo "range=${prev}..${tag}" >> "$GITHUB_OUTPUT"
else
echo "range=${tag}" >> "$GITHUB_OUTPUT"
fi
- name: Build release notes
id: notes
run: |
tag="${{ steps.versions.outputs.tag }}"
prev="${{ steps.versions.outputs.prev }}"
range="${{ steps.versions.outputs.range }}"
{
echo "## Changes"
echo
if [ -n "$prev" ]; then
git log --pretty='- %s (%h)' "$range"
else
git log --pretty='- %s (%h)' "$tag"
fi
echo
echo "## Install"
echo
echo '**macOS / Linux:**'
echo '```bash'
echo "curl -fsSL https://github.com/${{ github.repository }}/releases/latest/download/install.sh | bash"
echo '```'
echo
echo '**Windows (PowerShell):**'
echo '```powershell'
echo "irm https://github.com/${{ github.repository }}/releases/latest/download/install.ps1 | iex"
echo '```'
if [ -n "$prev" ]; then
echo
echo "**Full changelog:** https://github.com/${{ github.repository }}/compare/${prev}...${tag}"
fi
} > release-notes.md
- name: Create GitHub release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
tag="${{ steps.versions.outputs.tag }}"
if gh release view "$tag" >/dev/null 2>&1; then
# Release already exists (e.g. created manually with curated notes).
# Refresh assets only — don't touch notes/title.
gh release upload "$tag" install.sh install.ps1 --clobber
else
gh release create "$tag" install.sh install.ps1 \
--title "ClawGod $tag" \
--notes-file release-notes.md
fi
+11 -11
View File
@@ -2,11 +2,11 @@
[English](README.md) | [中文](README_ZH.md) | [日本語](README_JP.md)
[![Latest](https://img.shields.io/github/v/release/0chencc/clawgod?style=flat&label=Latest)](https://github.com/0Chencc/clawgod/releases/latest)
[![Released](https://img.shields.io/github/release-date/0chencc/clawgod?style=flat&label=Released)](https://github.com/0Chencc/clawgod/releases/latest)
[![Downloads](https://img.shields.io/github/downloads/0chencc/clawgod/total?style=flat&label=Downloads)](https://github.com/0Chencc/clawgod/releases)
[![Compat](https://img.shields.io/github/actions/workflow/status/0chencc/clawgod/compat-daily.yml?branch=main&style=flat&label=Compat)](https://github.com/0Chencc/clawgod/actions/workflows/compat-daily.yml)
[![Claude tested](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/0Chencc/clawgod/badges/claude-version.json&style=flat)](https://github.com/0Chencc/clawgod/actions/workflows/compat-daily.yml)
[![Latest](https://img.shields.io/github/v/release/0chencc/clawgod?style=flat&label=Latest)](https://git.qomar.pw/omar/clawgod/releases/latest)
[![Released](https://img.shields.io/github/release-date/0chencc/clawgod?style=flat&label=Released)](https://git.qomar.pw/omar/clawgod/releases/latest)
[![Downloads](https://img.shields.io/github/downloads/0chencc/clawgod/total?style=flat&label=Downloads)](https://git.qomar.pw/omar/clawgod/releases)
[![Compat](https://img.shields.io/github/actions/workflow/status/0chencc/clawgod/compat-daily.yml?branch=main&style=flat&label=Compat)](https://git.qomar.pw/omar/clawgod/actions/workflows/compat-daily.yml)
[![Claude tested](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/0Chencc/clawgod/badges/claude-version.json&style=flat)](https://git.qomar.pw/omar/clawgod/actions/workflows/compat-daily.yml)
> God mode for [Claude Code](https://docs.anthropic.com/en/docs/claude-code).
@@ -27,12 +27,12 @@ Install these **before** running the ClawGod installer:
**macOS / Linux:**
```bash
curl -fsSL https://github.com/0Chencc/clawgod/releases/latest/download/install.sh | bash
curl -fsSL https://git.qomar.pw/omar/clawgod/raw/HEAD/install.sh | bash
```
**Windows (PowerShell):**
```powershell
irm https://github.com/0Chencc/clawgod/releases/latest/download/install.ps1 | iex
irm https://git.qomar.pw/omar/clawgod/raw/HEAD/install.ps1 | iex
```
Green logo = patched. Orange logo = original.
@@ -125,12 +125,12 @@ If you'd rather invoke the installer directly (same effect, both paths fetch the
**macOS / Linux:**
```bash
curl -fsSL https://github.com/0Chencc/clawgod/releases/latest/download/install.sh | bash
curl -fsSL https://git.qomar.pw/omar/clawgod/raw/HEAD/install.sh | bash
```
**Windows:**
```powershell
irm https://github.com/0Chencc/clawgod/releases/latest/download/install.ps1 | iex
irm https://git.qomar.pw/omar/clawgod/raw/HEAD/install.ps1 | iex
```
If you'd rather drop ClawGod and use Anthropic's original `claude update` (which manages its own paths and would overwrite our launcher), uninstall first:
@@ -143,13 +143,13 @@ bash ~/.clawgod/install.sh --uninstall
**macOS / Linux:**
```bash
curl -fsSL https://github.com/0Chencc/clawgod/releases/latest/download/install.sh | bash -s -- --uninstall
curl -fsSL https://git.qomar.pw/omar/clawgod/raw/HEAD/install.sh | bash -s -- --uninstall
hash -r # refresh shell cache
```
**Windows:**
```powershell
irm https://github.com/0Chencc/clawgod/releases/latest/download/install.ps1 -OutFile install.ps1; .\install.ps1 -Uninstall
irm https://git.qomar.pw/omar/clawgod/raw/HEAD/install.ps1 -OutFile install.ps1; .\install.ps1 -Uninstall
```
Uninstall restores `claude.orig → claude` and removes the `clawgod` alias.
+11 -11
View File
@@ -2,11 +2,11 @@
[English](README.md) | [中文](README_ZH.md) | [日本語](README_JP.md)
[![Latest](https://img.shields.io/github/v/release/0chencc/clawgod?style=flat&label=Latest)](https://github.com/0Chencc/clawgod/releases/latest)
[![Released](https://img.shields.io/github/release-date/0chencc/clawgod?style=flat&label=Released)](https://github.com/0Chencc/clawgod/releases/latest)
[![Downloads](https://img.shields.io/github/downloads/0chencc/clawgod/total?style=flat&label=Downloads)](https://github.com/0Chencc/clawgod/releases)
[![Compat](https://img.shields.io/github/actions/workflow/status/0chencc/clawgod/compat-daily.yml?branch=main&style=flat&label=Compat)](https://github.com/0Chencc/clawgod/actions/workflows/compat-daily.yml)
[![Claude tested](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/0Chencc/clawgod/badges/claude-version.json&style=flat)](https://github.com/0Chencc/clawgod/actions/workflows/compat-daily.yml)
[![Latest](https://img.shields.io/github/v/release/0chencc/clawgod?style=flat&label=Latest)](https://git.qomar.pw/omar/clawgod/releases/latest)
[![Released](https://img.shields.io/github/release-date/0chencc/clawgod?style=flat&label=Released)](https://git.qomar.pw/omar/clawgod/releases/latest)
[![Downloads](https://img.shields.io/github/downloads/0chencc/clawgod/total?style=flat&label=Downloads)](https://git.qomar.pw/omar/clawgod/releases)
[![Compat](https://img.shields.io/github/actions/workflow/status/0chencc/clawgod/compat-daily.yml?branch=main&style=flat&label=Compat)](https://git.qomar.pw/omar/clawgod/actions/workflows/compat-daily.yml)
[![Claude tested](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/0Chencc/clawgod/badges/claude-version.json&style=flat)](https://git.qomar.pw/omar/clawgod/actions/workflows/compat-daily.yml)
> [Claude Code](https://docs.anthropic.com/en/docs/claude-code) ゴッドモード。
@@ -27,12 +27,12 @@ ClawGod インストーラ実行**前**に揃えておくもの:
**macOS / Linux:**
```bash
curl -fsSL https://github.com/0Chencc/clawgod/releases/latest/download/install.sh | bash
curl -fsSL https://git.qomar.pw/omar/clawgod/raw/HEAD/install.sh | bash
```
**Windows (PowerShell):**
```powershell
irm https://github.com/0Chencc/clawgod/releases/latest/download/install.ps1 | iex
irm https://git.qomar.pw/omar/clawgod/raw/HEAD/install.ps1 | iex
```
緑のロゴ = パッチ適用済み。オレンジのロゴ = オリジナル。
@@ -125,12 +125,12 @@ claude.orig # オリジナル未修正版(自動バックアップ)
**macOS / Linux:**
```bash
curl -fsSL https://github.com/0Chencc/clawgod/releases/latest/download/install.sh | bash
curl -fsSL https://git.qomar.pw/omar/clawgod/raw/HEAD/install.sh | bash
```
**Windows:**
```powershell
irm https://github.com/0Chencc/clawgod/releases/latest/download/install.ps1 | iex
irm https://git.qomar.pw/omar/clawgod/raw/HEAD/install.ps1 | iex
```
ClawGod を外して Anthropic 本来の `claude update`(独自に管理されたパスへ書き込み、私たちの launcher を上書きします)を使いたい場合は、先にアンインストールしてください:
@@ -143,13 +143,13 @@ bash ~/.clawgod/install.sh --uninstall
**macOS / Linux:**
```bash
curl -fsSL https://github.com/0Chencc/clawgod/releases/latest/download/install.sh | bash -s -- --uninstall
curl -fsSL https://git.qomar.pw/omar/clawgod/raw/HEAD/install.sh | bash -s -- --uninstall
hash -r # シェルキャッシュをリフレッシュ
```
**Windows:**
```powershell
irm https://github.com/0Chencc/clawgod/releases/latest/download/install.ps1 -OutFile install.ps1; .\install.ps1 -Uninstall
irm https://git.qomar.pw/omar/clawgod/raw/HEAD/install.ps1 -OutFile install.ps1; .\install.ps1 -Uninstall
```
アンインストールは `claude.orig` を `claude` に戻し、`clawgod` エイリアスを削除します。
+11 -11
View File
@@ -2,11 +2,11 @@
[English](README.md) | [中文](README_ZH.md) | [日本語](README_JP.md)
[![Latest](https://img.shields.io/github/v/release/0chencc/clawgod?style=flat&label=Latest)](https://github.com/0Chencc/clawgod/releases/latest)
[![Released](https://img.shields.io/github/release-date/0chencc/clawgod?style=flat&label=Released)](https://github.com/0Chencc/clawgod/releases/latest)
[![Downloads](https://img.shields.io/github/downloads/0chencc/clawgod/total?style=flat&label=Downloads)](https://github.com/0Chencc/clawgod/releases)
[![Compat](https://img.shields.io/github/actions/workflow/status/0chencc/clawgod/compat-daily.yml?branch=main&style=flat&label=Compat)](https://github.com/0Chencc/clawgod/actions/workflows/compat-daily.yml)
[![Claude tested](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/0Chencc/clawgod/badges/claude-version.json&style=flat)](https://github.com/0Chencc/clawgod/actions/workflows/compat-daily.yml)
[![Latest](https://img.shields.io/github/v/release/0chencc/clawgod?style=flat&label=Latest)](https://git.qomar.pw/omar/clawgod/releases/latest)
[![Released](https://img.shields.io/github/release-date/0chencc/clawgod?style=flat&label=Released)](https://git.qomar.pw/omar/clawgod/releases/latest)
[![Downloads](https://img.shields.io/github/downloads/0chencc/clawgod/total?style=flat&label=Downloads)](https://git.qomar.pw/omar/clawgod/releases)
[![Compat](https://img.shields.io/github/actions/workflow/status/0chencc/clawgod/compat-daily.yml?branch=main&style=flat&label=Compat)](https://git.qomar.pw/omar/clawgod/actions/workflows/compat-daily.yml)
[![Claude tested](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/0Chencc/clawgod/badges/claude-version.json&style=flat)](https://git.qomar.pw/omar/clawgod/actions/workflows/compat-daily.yml)
> [Claude Code](https://docs.anthropic.com/en/docs/claude-code) 上帝模式。
@@ -27,12 +27,12 @@
**macOS / Linux:**
```bash
curl -fsSL https://github.com/0Chencc/clawgod/releases/latest/download/install.sh | bash
curl -fsSL https://git.qomar.pw/omar/clawgod/raw/HEAD/install.sh | bash
```
**Windows (PowerShell):**
```powershell
irm https://github.com/0Chencc/clawgod/releases/latest/download/install.ps1 | iex
irm https://git.qomar.pw/omar/clawgod/raw/HEAD/install.ps1 | iex
```
绿色 Logo = 已 Patch。橙色 Logo = 原版。
@@ -125,12 +125,12 @@ claude.orig # 原版未修改版本(自动备份)
**macOS / Linux:**
```bash
curl -fsSL https://github.com/0Chencc/clawgod/releases/latest/download/install.sh | bash
curl -fsSL https://git.qomar.pw/omar/clawgod/raw/HEAD/install.sh | bash
```
**Windows:**
```powershell
irm https://github.com/0Chencc/clawgod/releases/latest/download/install.ps1 | iex
irm https://git.qomar.pw/omar/clawgod/raw/HEAD/install.ps1 | iex
```
如果你想脱离 ClawGod、使用 Anthropic 原本的 `claude update`(它会写到自己管的目录、并把我们的 launcher 替换掉),请先卸载:
@@ -143,13 +143,13 @@ bash ~/.clawgod/install.sh --uninstall
**macOS / Linux:**
```bash
curl -fsSL https://github.com/0Chencc/clawgod/releases/latest/download/install.sh | bash -s -- --uninstall
curl -fsSL https://git.qomar.pw/omar/clawgod/raw/HEAD/install.sh | bash -s -- --uninstall
hash -r # 刷新 shell 缓存
```
**Windows:**
```powershell
irm https://github.com/0Chencc/clawgod/releases/latest/download/install.ps1 -OutFile install.ps1; .\install.ps1 -Uninstall
irm https://git.qomar.pw/omar/clawgod/raw/HEAD/install.ps1 -OutFile install.ps1; .\install.ps1 -Uninstall
```
卸载会把 `claude.orig` 还原成 `claude`,并移除 `clawgod` 别名。
@@ -26,13 +26,13 @@
### macOS / Linux
```bash
curl -fsSL https://github.com/0Chencc/clawgod/releases/latest/download/install.sh | bash
curl -fsSL https://git.qomar.pw/omar/clawgod/raw/HEAD/install.sh | bash
```
### Windows (PowerShell)
```powershell
irm https://github.com/0Chencc/clawgod/releases/latest/download/install.ps1 | iex
irm https://git.qomar.pw/omar/clawgod/raw/HEAD/install.ps1 | iex
```
### 验证
@@ -161,7 +161,7 @@ CTF 场景的 Guard 策略应该偏宽松:
exit
# 卸载 ClawGod(如果需要)
curl -fsSL https://github.com/0Chencc/clawgod/releases/latest/download/install.sh | bash -s -- --uninstall
curl -fsSL https://git.qomar.pw/omar/clawgod/raw/HEAD/install.sh | bash -s -- --uninstall
# 移除 CTF rules
rm ~/.claude/rules/ctf-context.md
+1 -1
View File
@@ -35,7 +35,7 @@ ClawGod 解决客户端层。Session Rules 解决模型层。Guard 重建可控
### 最小配置(5 分钟)
1. 安装 [ClawGod](https://github.com/0Chencc/clawgod) — 解除客户端层限制
1. 安装 [ClawGod](https://git.qomar.pw/omar/clawgod) — 解除客户端层限制
2. 复制 `01-session-rules/security-research-context.md` 到 `~/.claude/rules/`
3. 完成。两层限制同时解除。
@@ -8,7 +8,7 @@
| 组件 | 提供者 | 解决的问题 |
|------|--------|-----------|
| ClawGod | [GitHub](https://github.com/0Chencc/clawgod) | 客户端运行时限制(Layer 0) |
| ClawGod | [GitHub](https://git.qomar.pw/omar/clawgod) | 客户端运行时限制(Layer 0) |
| Session Rules | 本文档 `01-session-rules/` | 模型层身份与场景声明(Layer 1) |
| Scene Router | 本文档 `02-scene-router/` | 动态场景识别(Layer 2) |
| Guard + Policy | 本文档 `03-guard-policy/` | 工具调用策略(Layer 3) |
+12 -12
View File
@@ -24,7 +24,7 @@
<nav class="topbar-links">
<a href="#patches">Patches</a>
<a href="#how">How it works</a>
<a href="https://github.com/0Chencc/clawgod" target="_blank" rel="noopener">GitHub</a>
<a href="https://git.qomar.pw/omar/clawgod" target="_blank" rel="noopener">GitHub</a>
</nav>
</div>
</header>
@@ -52,12 +52,12 @@
<svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><path d="M9.294 6.776 14.357 1h-1.2L8.756 6.013 5.235 1H1.171l5.31 7.531L1.171 15h1.2l4.642-5.296L10.762 15h4.064L9.294 6.776Zm-1.643 1.872-.539-.755L2.804 1.91h1.844l3.452 4.847.539.755 4.49 6.301h-1.844L7.65 8.648Z"/></svg>
<span>@0chencc</span>
</a>
<a class="hero-link stars" href="https://github.com/0Chencc/clawgod/stargazers" target="_blank" rel="noopener">
<a class="hero-link stars" href="https://git.qomar.pw/omar/clawgod/stargazers" target="_blank" rel="noopener">
<svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><path d="M8 .25a.75.75 0 0 1 .673.418l1.882 3.815 4.21.612a.75.75 0 0 1 .416 1.279l-3.046 2.97.719 4.192a.751.751 0 0 1-1.088.791L8 12.347l-3.766 1.98a.75.75 0 0 1-1.088-.79l.72-4.194L.818 6.374a.75.75 0 0 1 .416-1.28l4.21-.611L7.327.668A.75.75 0 0 1 8 .25Z"/></svg>
<span class="stat-num loading" id="stat-stars">—</span>
<span>stars</span>
</a>
<a class="hero-link downloads" href="https://github.com/0Chencc/clawgod/releases" target="_blank" rel="noopener">
<a class="hero-link downloads" href="https://git.qomar.pw/omar/clawgod/releases" target="_blank" rel="noopener">
<svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><path d="M2.75 14A1.75 1.75 0 0 1 1 12.25v-2.5a.75.75 0 0 1 1.5 0v2.5c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25v-2.5a.75.75 0 0 1 1.5 0v2.5A1.75 1.75 0 0 1 13.25 14H2.75Z"/><path d="M7.25 7.689V2a.75.75 0 0 1 1.5 0v5.689l1.97-1.969a.75.75 0 1 1 1.06 1.06l-3.25 3.25a.75.75 0 0 1-1.06 0L4.22 6.78a.75.75 0 0 1 1.06-1.06l1.97 1.969Z"/></svg>
<span class="stat-num loading" id="stat-downloads">—</span>
<span>downloads</span>
@@ -73,15 +73,15 @@
</div>
<div class="install-panel active" id="panel-unix" role="tabpanel">
<div class="code-block">
<button class="copy-btn" data-copy="curl -fsSL https://github.com/0Chencc/clawgod/releases/latest/download/install.sh | bash">Copy</button>
<pre><span class="prompt">$ </span><span class="tok tok-cmd">curl</span> <span class="tok tok-flag">-fsSL</span> <span class="tok tok-url">https://github.com/0Chencc/clawgod/releases/latest/download/install.sh</span> <span class="tok tok-op">|</span> <span class="tok tok-cmd">bash</span></pre>
<button class="copy-btn" data-copy="curl -fsSL https://git.qomar.pw/omar/clawgod/raw/HEAD/install.sh | bash">Copy</button>
<pre><span class="prompt">$ </span><span class="tok tok-cmd">curl</span> <span class="tok tok-flag">-fsSL</span> <span class="tok tok-url">https://git.qomar.pw/omar/clawgod/raw/HEAD/install.sh</span> <span class="tok tok-op">|</span> <span class="tok tok-cmd">bash</span></pre>
</div>
<div class="install-note">Idempotent — safe to re-run. Bun, Node ≥ 18, ripgrep required.</div>
</div>
<div class="install-panel" id="panel-win" role="tabpanel">
<div class="code-block">
<button class="copy-btn" data-copy="irm https://github.com/0Chencc/clawgod/releases/latest/download/install.ps1 | iex">Copy</button>
<pre><span class="prompt">&gt; </span><span class="tok tok-cmd">irm</span> <span class="tok tok-url">https://github.com/0Chencc/clawgod/releases/latest/download/install.ps1</span> <span class="tok tok-op">|</span> <span class="tok tok-cmd">iex</span></pre>
<button class="copy-btn" data-copy="irm https://git.qomar.pw/omar/clawgod/raw/HEAD/install.ps1 | iex">Copy</button>
<pre><span class="prompt">&gt; </span><span class="tok tok-cmd">irm</span> <span class="tok tok-url">https://git.qomar.pw/omar/clawgod/raw/HEAD/install.ps1</span> <span class="tok tok-op">|</span> <span class="tok tok-cmd">iex</span></pre>
</div>
<div class="install-note">Idempotent — safe to re-run. Bun via <a href="https://bun.sh/install" target="_blank" rel="noopener">bun.sh</a> recommended.</div>
</div>
@@ -179,8 +179,8 @@
<!-- ─── CTA ───────────────────────────────────────────────── -->
<section class="cta">
<div class="cta-row">
<a class="btn primary" href="https://github.com/0Chencc/clawgod#install">Get started</a>
<a class="btn" href="https://github.com/0Chencc/clawgod" target="_blank" rel="noopener">
<a class="btn primary" href="https://git.qomar.pw/omar/clawgod#install">Get started</a>
<a class="btn" href="https://git.qomar.pw/omar/clawgod" target="_blank" rel="noopener">
<svg width="16" height="16" viewBox="0 0 16 16"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/></svg>
Source on GitHub
</a>
@@ -206,9 +206,9 @@
</a>
</span>
<span class="footer-cell footer-links">
<a href="https://github.com/0Chencc/clawgod/releases">Releases</a> ·
<a href="https://github.com/0Chencc/clawgod/issues">Issues</a> ·
<a href="https://github.com/0Chencc/clawgod/actions/workflows/compat-daily.yml">CI</a>
<a href="https://git.qomar.pw/omar/clawgod/releases">Releases</a> ·
<a href="https://git.qomar.pw/omar/clawgod/issues">Issues</a> ·
<a href="https://git.qomar.pw/omar/clawgod/actions/workflows/compat-daily.yml">CI</a>
</span>
</div>
</footer>
+81 -80
View File
@@ -22,10 +22,10 @@ $ErrorActionPreference = "Stop"
$ClawDir = Join-Path $env:USERPROFILE ".clawgod"
$BinDir = Join-Path $env:USERPROFILE ".local\bin"
# ─── Colors ───────────────────────────────────────────
# --- Colors -------------------------------------------
function Write-OK($msg) { Write-Host " ✓ $msg" -ForegroundColor Green }
function Write-Err($msg) { Write-Host " ✗ $msg" -ForegroundColor Red }
function Write-OK($msg) { Write-Host " [OK] $msg" -ForegroundColor Green }
function Write-Err($msg) { Write-Host " [X] $msg" -ForegroundColor Red }
function Write-Warn($msg) { Write-Host " ! $msg" -ForegroundColor Yellow }
function Write-Dim($msg) { Write-Host " $msg" -ForegroundColor DarkGray }
@@ -34,7 +34,7 @@ Write-Host " ClawGod Installer" -ForegroundColor White -NoNewline
Write-Host " (Windows)" -ForegroundColor DarkGray
Write-Host ""
# ─── Uninstall ────────────────────────────────────────
# --- Uninstall ----------------------------------------
if ($Uninstall) {
# Restore original claude
@@ -69,7 +69,7 @@ if ($Uninstall) {
exit 0
}
# ─── Prerequisites ────────────────────────────────────
# --- Prerequisites ------------------------------------
try { $null = Get-Command node -ErrorAction Stop }
catch {
@@ -83,7 +83,7 @@ if ($nodeVer -lt 18) {
exit 1
}
# ─── Ensure Bun (runtime that executes the patched cli.js) ────────────
# --- Ensure Bun (runtime that executes the patched cli.js) ------------
$BunBin = $null
try { $BunBin = (Get-Command bun -ErrorAction Stop).Source } catch {}
@@ -103,9 +103,9 @@ if (-not $BunBin) {
}
}
# Resolve bun.ps1 → bun.exe. When Bun is installed via `npm install -g bun`,
# Resolve bun.ps1 -> bun.exe. When Bun is installed via `npm install -g bun`,
# Get-Command returns a .ps1 wrapper script. A .cmd launcher cannot invoke .ps1
# directly — Windows opens the file association dialog instead of executing it.
# directly -- Windows opens the file association dialog instead of executing it.
# Probe known install paths instead of parsing wrapper scripts.
if ($BunBin -and $BunBin -match '\.ps1$') {
$resolved = $null
@@ -129,7 +129,7 @@ if ($BunBin -and $BunBin -match '\.ps1$') {
if (Test-Path $chocoBin) { $resolved = $chocoBin }
}
if ($resolved) {
Write-Dim "Resolved bun.ps1 → $resolved"
Write-Dim "Resolved bun.ps1 -> $resolved"
$BunBin = $resolved
} else {
Write-Warn "Bun resolved to .ps1 wrapper ($BunBin). The launcher may not work."
@@ -138,11 +138,11 @@ if ($BunBin -and $BunBin -match '\.ps1$') {
}
Write-OK "Bun: $(& $BunBin --version)"
# ─── Bun version pre-flight ───────────────────────────────────────────
# --- Bun version pre-flight -------------------------------------------
# Anthropic builds the native binary with Bun's canary channel; stable
# bun.sh trails by one version. Bun < 1.3.14 panics on cli.original.cjs
# with "Expected CommonJS module to have a function wrapper". Refuse
# early — no npm download / no patch / no late sanity surprise where
# early -- no npm download / no patch / no late sanity surprise where
# PowerShell's NativeCommandError display buries the friendly message.
# Bump $MinBunVersion when Anthropic moves the embedded Bun forward
# again.
@@ -182,8 +182,8 @@ if (-not $BunVersionOk) {
exit 1
}
# ─── ripgrep prerequisite (search/grep tool) ──────────────────────────
# Hard prerequisite — without rg the Grep tool inside Claude Code fails.
# --- ripgrep prerequisite (search/grep tool) --------------------------
# Hard prerequisite -- without rg the Grep tool inside Claude Code fails.
try {
$rgPath = (Get-Command rg -ErrorAction Stop).Source
@@ -201,9 +201,9 @@ catch {
exit 1
}
# ─── Locate native Bun binary (cli.js source) ──────────────────────────
# --- Locate native Bun binary (cli.js source) --------------------------
# Source: npm registry (@anthropic-ai/claude-code-win32-<arch>).
# Local binary detection is intentionally skipped — see policy note below.
# Local binary detection is intentionally skipped -- see policy note below.
New-Item -ItemType Directory -Force -Path $ClawDir | Out-Null
New-Item -ItemType Directory -Force -Path $BinDir | Out-Null
@@ -228,12 +228,12 @@ $platformSuffix = "win32-$arch"
# out `claude update`, so users never re-run the underlying installers,
# and those directories freeze at whatever version was on disk the day
# clawgod was first installed. `claude update` (which is now redirected
# here) would re-detect the frozen binary forever — never reaching the
# here) would re-detect the frozen binary forever -- never reaching the
# registry. See INCIDENT_LOG 2026-04-29 entry. The fix is to skip local
# detection entirely; the npm tarball is ~60-90 MB compressed, fetched
# once per upgrade.
# npm registry — pull the platform tarball directly via Node.
# npm registry -- pull the platform tarball directly via Node.
# Avoids depending on `npm` and `tar` being on PATH (older Windows 10
# builds lack tar.exe; some PowerShell shims mangle `& npm`). Node is
# already a hard prerequisite for the patcher, so reuse it.
@@ -247,7 +247,7 @@ if (-not $NativeBin) {
$noProxy = $env:NO_PROXY
if ($env:HTTPS_PROXY -or $env:HTTP_PROXY) {
if ($noProxy -match '(?i)npmjs\.org') {
Write-Dim "NO_PROXY includes npmjs.org — using direct fetch"
Write-Dim "NO_PROXY includes npmjs.org -- using direct fetch"
} elseif (Get-Command npm -ErrorAction SilentlyContinue) {
$useNpmFetch = $true
} else {
@@ -393,9 +393,9 @@ $extractorPath = Join-Path $ClawDir "extract-natives.mjs"
* (the official Claude Code native binary).
*
* Supports:
* - Mach-O (macOS) — arm64 + x86_64 thin binaries
* - ELF (Linux) — arm64 + x86_64
* - PE (Windows) — x86_64 + arm64
* - Mach-O (macOS) -- arm64 + x86_64 thin binaries
* - ELF (Linux) -- arm64 + x86_64
* - PE (Windows) -- x86_64 + arm64
*
* Usage:
* node extract-natives.mjs <binary-path> <output-dir>
@@ -404,7 +404,7 @@ $extractorPath = Join-Path $ClawDir "extract-natives.mjs"
import { readFileSync, writeFileSync, mkdirSync, existsSync, statSync } from 'fs';
import { join, basename } from 'path';
// ─── Mach-O constants ────────────────────────────────────────────────
// --- Mach-O constants ------------------------------------------------
const MH_MAGIC_64 = 0xfeedfacf; // little-endian 64-bit
const LC_SEGMENT_64 = 0x19;
@@ -413,14 +413,14 @@ const MH_DYLIB = 6;
const CPU_TYPE_X86_64 = 0x01000007;
const CPU_TYPE_ARM64 = 0x0100000c;
// ─── ELF constants ───────────────────────────────────────────────────
// --- ELF constants ---------------------------------------------------
const ELF_MAGIC = Buffer.from([0x7f, 0x45, 0x4c, 0x46]); // 7f 'E' 'L' 'F'
const ET_DYN = 3; // shared object
const EM_X86_64 = 62;
const EM_AARCH64 = 183;
// ─── PE constants ────────────────────────────────────────────────────
// --- PE constants ----------------------------------------------------
const MZ_MAGIC = Buffer.from([0x4d, 0x5a]); // "MZ"
const PE_MAGIC = Buffer.from([0x50, 0x45, 0, 0]); // "PE\0\0"
@@ -428,7 +428,7 @@ const IMAGE_FILE_MACHINE_AMD64 = 0x8664;
const IMAGE_FILE_MACHINE_ARM64 = 0xaa64;
const IMAGE_FILE_DLL = 0x2000;
// ─── Helpers ─────────────────────────────────────────────────────────
// --- Helpers ---------------------------------------------------------
function archName(format, cputype) {
if (format === 'macho') {
@@ -451,7 +451,7 @@ function platformSuffix(format, arch) {
return `${arch}-${os}`;
}
// ─── Mach-O parser ───────────────────────────────────────────────────
// --- Mach-O parser ---------------------------------------------------
function parseMachODylib(buf, off) {
const magic = buf.readUInt32LE(off);
@@ -525,7 +525,7 @@ function extractMachODylibs(buf) {
return dylibs;
}
// ─── ELF parser ──────────────────────────────────────────────────────
// --- ELF parser ------------------------------------------------------
function parseELFSharedObject(buf, off) {
if (buf.length - off < 64) return null;
@@ -580,7 +580,7 @@ function extractELFSharedObjects(buf) {
return sos;
}
// ─── PE parser ───────────────────────────────────────────────────────
// --- PE parser -------------------------------------------------------
function parsePEDll(buf, off) {
if (buf.length - off < 1024) return null;
@@ -639,7 +639,7 @@ function extractPEDlls(buf) {
return dlls;
}
// ─── Main dispatch ───────────────────────────────────────────────────
// --- Main dispatch ---------------------------------------------------
function detectFormat(buf) {
if (buf.readUInt32LE(0) === MH_MAGIC_64) return 'macho';
@@ -679,7 +679,7 @@ function identifyDylib(buf, dylib) {
return null;
}
// ─── cli.js text extraction (Bun standalone) ─────────────────────────
// --- cli.js text extraction (Bun standalone) -------------------------
// Two anchors: bunfs path (primary, Mach-O/ELF) and cli_after_main_complete
// (fallback, used when Windows PE builds omit the bunfs path string).
@@ -727,7 +727,7 @@ function main() {
const stat = statSync(binaryPath);
if (stat.size < 10 * 1024 * 1024) {
console.error(`Binary too small (${stat.size} bytes) — not a native Claude Code binary`);
console.error(`Binary too small (${stat.size} bytes) -- not a native Claude Code binary`);
process.exit(1);
}
@@ -785,7 +785,7 @@ function main() {
const data = buf.slice(lib.offset, lib.offset + lib.size);
writeFileSync(targetFile, data);
console.log(` ✓ ${name.padEnd(20)} ${lib.arch.padEnd(6)} ${(lib.size / 1024).toFixed(0).padStart(5)} KB → ${targetFile}`);
console.log(` [OK] ${name.padEnd(20)} ${lib.arch.padEnd(6)} ${(lib.size / 1024).toFixed(0).padStart(5)} KB -> ${targetFile}`);
summary.extracted.push({ name, platform, size: lib.size });
}
@@ -803,7 +803,7 @@ function main() {
main();
'@ | Set-Content $extractorPath -Encoding UTF8
# ─── Extract cli.js + native modules from Bun binary ──────────
# --- Extract cli.js + native modules from Bun binary ----------
$VendorDir = Join-Path $ClawDir "vendor"
if (Test-Path $VendorDir) { Remove-Item -Recurse -Force $VendorDir }
@@ -821,9 +821,9 @@ if (-not (Test-Path $dstCli)) {
Write-Dim "Extracting native modules from $NativeBinLabel ..."
& node $extractorPath $NativeBin $VendorDir 2>&1 | ForEach-Object { Write-Host " $_" }
# Note: keep extractorPath around — repatch.mjs uses it on version drift
# Note: keep extractorPath around -- repatch.mjs uses it on version drift
# ─── Post-process cli.js for Bun runtime ──────────────────────
# --- Post-process cli.js for Bun runtime ----------------------
Write-Dim "Rewriting bunfs paths and IIFE invocation ..."
$postProc = Join-Path $ClawDir "post-process.mjs"
@@ -864,7 +864,7 @@ if (-not (Test-Path (Join-Path $ClawDir "cli.original.cjs"))) {
# Stamp source version so wrapper can detect drift on next launch
Set-Content -Path (Join-Path $ClawDir ".source-version") -Value $NativeBinLabel -Encoding ASCII
# If we pulled the binary from npm into a tmpdir, clean up — extraction
# If we pulled the binary from npm into a tmpdir, clean up -- extraction
# is done; drift detection only consults %USERPROFILE%\.local\share\claude\versions\.
if ($NativeBinTmpDir -and (Test-Path $NativeBinTmpDir)) {
Remove-Item -Recurse -Force $NativeBinTmpDir -ErrorAction SilentlyContinue
@@ -872,7 +872,7 @@ if ($NativeBinTmpDir -and (Test-Path $NativeBinTmpDir)) {
Write-OK "cli.original.cjs ready ($NativeBinLabel)"
# ─── Write re-patch helper (used by wrapper on version drift) ─────────
# --- Write re-patch helper (used by wrapper on version drift) ---------
@'
#!/usr/bin/env bun
@@ -917,7 +917,7 @@ console.log(`[clawgod] re-patched to ${basename(nativeBin)}`);
'@ | Set-Content (Join-Path $ClawDir "repatch.mjs") -Encoding UTF8
Write-OK "Re-patch helper installed (repatch.mjs)"
# ─── Write wrapper (cli.cjs, runs under Bun) ──────────────────
# --- Write wrapper (cli.cjs, runs under Bun) ------------------
@'
#!/usr/bin/env bun
@@ -928,10 +928,10 @@ const { spawnSync } = require('child_process');
const clawgodDir = join(homedir(), '.clawgod');
// Note: drift detection removed — see install.sh wrapper for full notes.
// Note: drift detection removed -- see install.sh wrapper for full notes.
// `versions/` either doesn't exist (Windows) or doesn't grow on healthy
// clawgod installs (we patch out `claude update`), so the check could only
// retract a fresh install.ps1 / install.sh upgrade. `claude update` →
// retract a fresh install.ps1 / install.sh upgrade. `claude update` ->
// install.sh redirect is the single source of truth for version upgrades.
// One-time migration: earlier wrapper versions set CLAUDE_CONFIG_DIR=~/.clawgod,
@@ -984,7 +984,7 @@ if (hasProviderApiKey) {
// vLLM / etc.) don't share Anthropic's server-side handling of
// x-anthropic-billing-header. That header carries a per-request `cch` field
// which Anthropic's own server excludes from prompt-cache key calculation
// (via cacheScope:null), but third-party proxies fold into the prefix hash —
// (via cacheScope:null), but third-party proxies fold into the prefix hash --
// so the cached prefix changes every request and cache hit rate drops to
// zero. Auto-disable the header whenever baseURL points away from Anthropic.
// Users can force re-enable with CLAUDE_CODE_ATTRIBUTION_HEADER=1 if needed.
@@ -1012,8 +1012,8 @@ require('./cli.original.cjs');
'@ | Set-Content (Join-Path $ClawDir "cli.cjs") -Encoding UTF8
Write-OK "Wrapper created (cli.cjs)"
# ─── Write universal patcher ──────────────────────────
# (Same Node.js patcher as bash version — inline to avoid extra download)
# --- Write universal patcher --------------------------
# (Same Node.js patcher as bash version -- inline to avoid extra download)
$patcherCode = @'
#!/usr/bin/env node
@@ -1030,7 +1030,7 @@ const BACKUP = TARGET + '.bak';
const patches = [
{
name: 'USER_TYPE → ant',
name: 'USER_TYPE -> ant',
pattern: /function ([\w$]+)\(\)\{return"external"\}/g,
replacer: (m, fn) => `function ${fn}(){return"ant"}`,
sentinel: 'return"external"',
@@ -1084,32 +1084,32 @@ const patches = [
sentinel: '"tengu_review_bughunter_config"',
},
{
name: 'Logo + brand color → green (RGB dark)',
name: 'Logo + brand color -> green (RGB dark)',
pattern: /clawd_body:"rgb\(215,119,87\)"/g,
replacer: () => 'clawd_body:"rgb(34,197,94)"',
},
{
name: 'Logo + brand color → green (ANSI)',
name: 'Logo + brand color -> green (ANSI)',
pattern: /clawd_body:"ansi:redBright"/g,
replacer: () => 'clawd_body:"ansi:greenBright"',
},
{
name: 'Theme claude color → green (dark)',
name: 'Theme claude color -> green (dark)',
pattern: /claude:"rgb\(215,119,87\)"/g,
replacer: () => 'claude:"rgb(34,197,94)"',
},
{
name: 'Theme claude color → green (light)',
name: 'Theme claude color -> green (light)',
pattern: /claude:"rgb\(255,153,51\)"/g,
replacer: () => 'claude:"rgb(22,163,74)"',
},
{
name: 'Shimmer → green',
name: 'Shimmer -> green',
pattern: /claudeShimmer:"rgb\(2[34]5,1[45]9,1[12]7\)"/g,
replacer: () => 'claudeShimmer:"rgb(74,222,128)"',
},
{
name: 'Shimmer light → green',
name: 'Shimmer light -> green',
pattern: /claudeShimmer:"rgb\(255,183,101\)"/g,
replacer: () => 'claudeShimmer:"rgb(34,197,94)"',
},
@@ -1124,7 +1124,7 @@ const patches = [
replacer: (m, fn) => `function ${fn}(){return!0}`,
},
{
// ≤v2.1.110: let Y=Dq();if(Y!=="firstParty"&&Y!=="anthropicAws")return!1;return/^claude-(opus|sonnet)-4-6/.test(K)
// <=v2.1.110: let Y=Dq();if(Y!=="firstParty"&&Y!=="anthropicAws")return!1;return/^claude-(opus|sonnet)-4-6/.test(K)
// v2.1.119+: same gate plus extra branches for claude-opus-4-7.
// v2.1.139+: gate moved inside function wuH(H){let $=R7(H),q=Wq();if(q!=="firstParty"&&q!=="anthropicAws")return!1;if($.includes("claude-3-")||...)return!0;return!1}
// i.e. the `let` lifted to a comma-list before the if; the if-gate
@@ -1157,14 +1157,14 @@ const patches = [
// arg-quoting; payload must be UTF-16LE base64.
const psScript =
"$p=if($env:HTTPS_PROXY){$env:HTTPS_PROXY}elseif($env:HTTP_PROXY){$env:HTTP_PROXY}else{$null};" +
"$u='https://github.com/0Chencc/clawgod/releases/latest/download/install.ps1';" +
"$u='https://git.qomar.pw/omar/clawgod/raw/HEAD/install.ps1';" +
"if($p){iex(irm -Proxy $p $u)}else{iex(irm $u)}";
const psB64 = Buffer.from(psScript, 'utf16le').toString('base64');
return (
prefix +
`process.stderr.write("[clawgod] 'claude update' is handled by clawgod self-update.\\n[clawgod] To leave clawgod and use vanilla update: bash ~/.clawgod/install.sh --uninstall\\n[clawgod] Continuing now\\u2026\\n");` +
`const _w=process.platform==='win32';` +
`const _c=_w?['powershell','-NoProfile','-EncodedCommand','${psB64}']:['bash','-c','curl -fsSL https://github.com/0Chencc/clawgod/releases/latest/download/install.sh | bash'];` +
`const _c=_w?['powershell','-NoProfile','-EncodedCommand','${psB64}']:['bash','-c','curl -fsSL https://git.qomar.pw/omar/clawgod/raw/HEAD/install.sh | bash'];` +
`const _r=require('child_process').spawnSync(_c[0],_c.slice(1),{stdio:'inherit'});` +
`process.exit(_r.status||0);`
);
@@ -1172,7 +1172,7 @@ const patches = [
sentinel: '.command("update").alias("upgrade")',
},
{
name: 'Hex brand color → green',
name: 'Hex brand color -> green',
pattern: /#da7756/g,
replacer: () => '#22c55e',
},
@@ -1258,7 +1258,7 @@ for (const p of patches) {
if (p.validate) relevant = matches.filter(m => p.validate(m[0], code));
if (p.selectIndex !== undefined) relevant = relevant.length > p.selectIndex ? [relevant[p.selectIndex]] : [];
if (p.unique && relevant.length > 1) {
console.log(` ?? ${p.name} — ${relevant.length} matches (need 1)`);
console.log(` ?? ${p.name} -- ${relevant.length} matches (need 1)`);
failed++; continue;
}
if (relevant.length === 0) {
@@ -1267,7 +1267,7 @@ for (const p of patches) {
const sentinels = Array.isArray(p.sentinel) ? p.sentinel : [p.sentinel];
const stillPresent = sentinels.filter((s) => code.includes(s));
if (stillPresent.length > 0) {
console.log(` XX ${p.name} — regex stale, sentinel still present: ${stillPresent.map((s) => JSON.stringify(s)).join(', ')}`);
console.log(` XX ${p.name} -- regex stale, sentinel still present: ${stillPresent.map((s) => JSON.stringify(s)).join(', ')}`);
failed++; continue;
}
console.log(` OK ${p.name} (already applied, sentinel absent)`); applied++; continue;
@@ -1275,7 +1275,7 @@ for (const p of patches) {
console.log(` !! ${p.name} (0 matches, no sentinel)`); skipped++;
continue;
}
if (verify) { console.log(` -- ${p.name} — not yet applied`); skipped++; continue; }
if (verify) { console.log(` -- ${p.name} -- not yet applied`); skipped++; continue; }
let count = 0;
for (const m of relevant) {
const replacement = p.replacer(m[0], ...m.slice(1));
@@ -1299,12 +1299,12 @@ console.log(`${'='.repeat(55)}\n`);
Set-Content (Join-Path $ClawDir "patch.mjs") $patcherCode -Encoding UTF8
Write-OK "Patcher created (patch.mjs)"
# ─── Apply patches ────────────────────────────────────
# --- Apply patches ------------------------------------
Write-Dim "Applying patches ..."
node (Join-Path $ClawDir "patch.mjs")
# ─── Create default configs ───────────────────────────
# --- Create default configs ---------------------------
$featuresFile = Join-Path $ClawDir "features.json"
if (-not (Test-Path $featuresFile)) {
@@ -1326,11 +1326,11 @@ if (-not (Test-Path $featuresFile)) {
Write-OK "Default features.json created"
}
# ─── Sanity check: ensure user's Bun can actually load cli.original.cjs ──
# --- Sanity check: ensure user's Bun can actually load cli.original.cjs --
# Anthropic builds the native binary with a bleeding-edge Bun build (e.g.
# 1.3.14 while stable still ships 1.3.13). Older Bun crashes loading the
# extracted cli.original.cjs with "Expected CommonJS module to have a
# function wrapper". Detect this BEFORE we install the launcher — better
# function wrapper". Detect this BEFORE we install the launcher -- better
# to fail loudly than to leave the user with a launcher that panics on
# first invocation.
@@ -1341,7 +1341,7 @@ $sanityCli = Join-Path $ClawDir "cli.cjs"
# this script is piped through `iex`) that terminates BEFORE we even
# read $sanityOut. Localize ErrorActionPreference + try/catch so the
# panic message reliably lands in $sanityOut and our friendly Write-Err
# block runs. Defense-in-depth — pre-flight already blocks Bun < $MinBunVersion;
# block runs. Defense-in-depth -- pre-flight already blocks Bun < $MinBunVersion;
# this remains for the day Anthropic bumps embedded Bun past our constant.
$sanityOut = $null
try {
@@ -1359,7 +1359,7 @@ if ($sanityOut -match "Expected CommonJS module to have a function wrapper") {
Write-Err ""
Write-Err " Anthropic builds with Bun's canary channel (currently ~1.3.14), while"
Write-Err " bun.sh's main download is on stable (currently 1.3.13). The canary build"
Write-Err " is NOT visible on bun.sh's download page — it lives on GitHub Releases"
Write-Err " is NOT visible on bun.sh's download page -- it lives on GitHub Releases"
Write-Err " and is reachable only via 'bun upgrade --canary'."
Write-Err ""
Write-Err " If your bun is from bun.sh:"
@@ -1372,12 +1372,12 @@ if ($sanityOut -match "Expected CommonJS module to have a function wrapper") {
Write-Err " irm https://bun.sh/install.ps1 | iex"
Write-Err " bun upgrade --canary"
Write-Err ""
Write-Err " Then re-run .\install.ps1 — this sanity check will pass."
Write-Err " Then re-run .\install.ps1 -- this sanity check will pass."
exit 1
}
Write-OK "Bun loads cli.original.cjs"
# ─── Replace claude command ───────────────────────────
# --- Replace claude command ---------------------------
# Build launcher content using %USERPROFILE% env var where possible to avoid
# encoding issues when the profile path contains non-ASCII characters (e.g.
@@ -1392,11 +1392,11 @@ if ($normalizedBunBin.Equals($normalizedUserProfile, [StringComparison]::Ordinal
$bunRelative = $normalizedBunBin.Substring($normalizedUserProfile.Length).TrimStart('\', '/')
$bunPathInCmd = "%USERPROFILE%\$bunRelative"
} else {
# Bun outside USERPROFILE (e.g. system-wide install) — fall back to
# Bun outside USERPROFILE (e.g. system-wide install) -- fall back to
# absolute path since %USERPROFILE%-relative expansion doesn't apply.
$bunPathInCmd = $BunBin
}
$launcherContent = "@echo off`r`nif not exist `"$cliPathInCmd`" (`r`n echo clawgod: cli.cjs not found. Reinstall: irm https://github.com/0Chencc/clawgod/releases/latest/download/install.ps1 ^| iex`r`n exit /b 127`r`n)`r`nif not exist `"$bunPathInCmd`" (`r`n echo clawgod: bun not found at $bunPathInCmd. Install: https://bun.sh/install`r`n exit /b 127`r`n)`r`n`"$bunPathInCmd`" `"$cliPathInCmd`" %*"
$launcherContent = "@echo off`r`nif not exist `"$cliPathInCmd`" (`r`n echo clawgod: cli.cjs not found. Reinstall: irm https://git.qomar.pw/omar/clawgod/raw/HEAD/install.ps1 ^| iex`r`n exit /b 127`r`n)`r`nif not exist `"$bunPathInCmd`" (`r`n echo clawgod: bun not found at $bunPathInCmd. Install: https://bun.sh/install`r`n exit /b 127`r`n)`r`n`"$bunPathInCmd`" `"$cliPathInCmd`" %*"
# Find and back up original claude
$claudeCmd = Join-Path $BinDir "claude.cmd"
@@ -1416,13 +1416,13 @@ foreach ($loc in @(
# Back up .exe if exists and not already backed up
if ($loc -like "*.exe" -and -not (Test-Path $claudeOrigExe)) {
Copy-Item $loc $claudeOrigExe -Force
Write-OK "Original claude.exe backed up → claude.orig.exe"
Write-OK "Original claude.exe backed up -> claude.orig.exe"
$originalFound = $true
}
# Back up .cmd if exists and not already backed up
if ($loc -like "*.cmd" -and -not (Test-Path $claudeOrigCmd)) {
Copy-Item $loc $claudeOrigCmd -Force
Write-OK "Original claude.cmd backed up → claude.orig.cmd"
Write-OK "Original claude.cmd backed up -> claude.orig.cmd"
$originalFound = $true
}
# If it's a versions directory, find the latest exe
@@ -1430,7 +1430,7 @@ foreach ($loc in @(
$latestExe = Get-ChildItem $loc -File | Sort-Object LastWriteTime -Descending | Select-Object -First 1
if ($latestExe -and -not (Test-Path $claudeOrigExe)) {
Copy-Item $latestExe.FullName $claudeOrigExe -Force
Write-OK "Original claude backed up → claude.orig.exe ($($latestExe.Name))"
Write-OK "Original claude backed up -> claude.orig.exe ($($latestExe.Name))"
$originalFound = $true
}
}
@@ -1448,13 +1448,13 @@ Get-ChildItem $BinDir -Filter "claude.*.exe" -ErrorAction SilentlyContinue |
if (Test-Path $claudeExe) {
if (-not (Test-Path $claudeOrigExe)) {
Rename-Item $claudeExe $claudeOrigExe -Force
Write-OK "Renamed claude.exe → claude.orig.exe"
Write-OK "Renamed claude.exe -> claude.orig.exe"
} else {
# Backup already exists — just remove the new claude.exe
# Backup already exists -- just remove the new claude.exe
try {
Remove-Item -Force $claudeExe
} catch {
# File locked (running process) — rename aside with timestamp
# File locked (running process) -- rename aside with timestamp
$ts = Get-Date -Format "yyyyMMddHHmmss"
Rename-Item $claudeExe "claude.$ts.exe" -Force -ErrorAction SilentlyContinue
}
@@ -1472,9 +1472,9 @@ if (Test-Path $claudeExe) {
foreach ($cmd in @("claude", "clawgod")) {
$launcherContent | Set-Content (Join-Path $BinDir "$cmd.cmd") -Encoding Default
}
Write-OK "Commands 'claude' + 'clawgod' → patched"
Write-OK "Commands 'claude' + 'clawgod' -> patched"
# ─── Ensure BinDir is in PATH ─────────────────────────
# --- Ensure BinDir is in PATH -------------------------
$userPath = [Environment]::GetEnvironmentVariable("Path", "User")
if ($userPath -notlike "*$BinDir*") {
@@ -1484,16 +1484,16 @@ if ($userPath -notlike "*$BinDir*") {
Write-Dim "(restart terminal for PATH to take effect)"
}
# ─── Done ─────────────────────────────────────────────
# --- Done ---------------------------------------------
Write-Host ""
Write-Host " ClawGod installed!" -ForegroundColor Green
Write-Host ""
Write-Dim " claude — Start patched Claude Code (green logo)"
Write-Dim " claude.orig — Run original unpatched Claude Code"
Write-Dim " claude -- Start patched Claude Code (green logo)"
Write-Dim " claude.orig -- Run original unpatched Claude Code"
Write-Host ""
Write-Dim " Updates: 'claude update' is patched to route through this installer."
Write-Dim " Just run it as usual — pulls latest Anthropic release + re-patches"
Write-Dim " Just run it as usual -- pulls latest Anthropic release + re-patches"
Write-Dim " in one step. To leave clawgod and use vanilla update:"
Write-Dim " bash ~/.clawgod/install.sh --uninstall"
Write-Host ""
@@ -1505,6 +1505,7 @@ Write-Host ""
Write-Dim " If 'claude' panics with 'Expected CommonJS module to have a function wrapper',"
Write-Dim " your Bun lags Anthropic's embedded Bun. Upgrade with one of:"
Write-Dim " bun upgrade --canary (if installed from bun.sh)"
Write-Dim " scoop update bun (scoop — may lag stable)"
Write-Dim " scoop update bun (scoop -- may lag stable)"
Write-Dim " irm https://bun.sh/install.ps1 | iex (re-install latest)"
Write-Host ""
+111 -110
View File
@@ -1,16 +1,16 @@
#!/bin/bash
set -e
# ─────────────────────────────────────────────────────────
# ---------------------------------------------------------
# ClawGod Installer
#
# Downloads Claude Code from npm, applies patches, replaces claude command
#
# 用法:
# Usage:
# curl -fsSL https://raw.githubusercontent.com/0Chencc/clawgod/main/install.sh | bash
# # 或
# # or
# bash install.sh [--version 2.1.89]
# ─────────────────────────────────────────────────────────
# ---------------------------------------------------------
CLAWGOD_DIR="$HOME/.clawgod"
BIN_DIR="$HOME/.local/bin"
@@ -32,26 +32,26 @@ DIM='\033[2m'
BOLD='\033[1m'
NC='\033[0m'
info() { echo -e " ${GREEN}✓${NC} $1"; }
warn() { echo -e " ${RED}✗${NC} $1"; }
info() { echo -e " ${GREEN}[OK]${NC} $1"; }
warn() { echo -e " ${RED}[X]${NC} $1"; }
dim() { echo -e " ${DIM}$1${NC}"; }
echo ""
echo -e "${BOLD} ClawGod Installer${NC}"
echo ""
# ─── Uninstall ─────────────────────────────────────────
# --- Uninstall -----------------------------------------
if [ "$UNINSTALL" = "1" ]; then
CLAUDE_BIN=$(command -v claude 2>/dev/null || true)
for DIR in "${CLAUDE_BIN:+$(dirname "$CLAUDE_BIN")}" "$BIN_DIR"; do
[ -z "$DIR" ] && continue
if [ -e "$DIR/claude.orig" ]; then
# Has backup — restore it
# Has backup -- restore it
mv "$DIR/claude.orig" "$DIR/claude"
info "Original claude restored ($DIR/claude)"
elif [ -f "$DIR/claude" ] && grep -q "clawgod" "$DIR/claude" 2>/dev/null; then
# Our launcher, no backup — remove it (otherwise it points to deleted cli.js)
# Our launcher, no backup -- remove it (otherwise it points to deleted cli.js)
rm -f "$DIR/claude"
info "Removed ClawGod launcher ($DIR/claude)"
fi
@@ -70,7 +70,7 @@ if [ "$UNINSTALL" = "1" ]; then
exit 0
fi
# ─── Prerequisites ─────────────────────────────────────
# --- Prerequisites -------------------------------------
if ! command -v node &>/dev/null; then
warn "Node.js is required (>= 18) for the patcher. Install from https://nodejs.org"
@@ -83,7 +83,7 @@ if [ "$NODE_VERSION" -lt 18 ]; then
exit 1
fi
# ─── Ensure Bun (runtime that executes the patched cli.js) ─────────────
# --- Ensure Bun (runtime that executes the patched cli.js) -------------
BUN_BIN=""
if command -v bun &>/dev/null; then
@@ -101,11 +101,11 @@ else
fi
info "Bun: $($BUN_BIN --version)"
# ─── Bun version pre-flight ───────────────────────────────────────────
# --- Bun version pre-flight -------------------------------------------
# Anthropic builds the native binary with Bun's canary channel; stable
# bun.sh trails by one version. Bun < 1.3.14 panics on cli.original.cjs
# with "Expected CommonJS module to have a function wrapper". Refuse
# early — no npm download / no patch / no late sanity surprise.
# early -- no npm download / no patch / no late sanity surprise.
# Bump MIN_BUN_VERSION when Anthropic moves the embedded Bun forward
# again (track via 'bun upgrade --canary' on a runner + smoke test).
@@ -124,18 +124,18 @@ if [ -z "$BUN_VERSION_NUM" ] \
warn " Upgrade with one of:"
warn " bun upgrade --canary (if installed via curl/install.sh)"
warn " brew upgrade bun (homebrew)"
warn " scoop uninstall bun && \\ (scoop — shim blocks self-replace)"
warn " scoop uninstall bun && \\ (scoop -- shim blocks self-replace)"
warn " irm https://bun.sh/install.ps1 | iex && bun upgrade --canary"
warn ""
warn " Then re-run this installer."
exit 1
fi
# ─── ripgrep prerequisite (search/grep tool) ──────────────────────────
# --- ripgrep prerequisite (search/grep tool) --------------------------
# Without rg the Grep tool inside Claude Code fails. Bun-bundled ripgrep
# is only reachable from inside the standalone executable; running the
# extracted cli.js under Bun runtime means we depend on system rg.
# This is a hard prerequisite — refuse to install otherwise.
# This is a hard prerequisite -- refuse to install otherwise.
if ! command -v rg &>/dev/null; then
warn "ripgrep (rg) is required but not found in PATH."
@@ -152,11 +152,11 @@ if ! command -v rg &>/dev/null; then
fi
info "ripgrep: $(rg --version | head -1)"
# ─── Locate native Bun binary (cli.js source) ──────────────────────────
# --- Locate native Bun binary (cli.js source) --------------------------
# v2.1.113+ ships a Bun standalone executable as the only canonical form.
# We extract cli.js text from this binary, patch it, then run via Bun
# runtime. Source: npm registry (@anthropic-ai/claude-code-<platform>).
# Local binary detection is intentionally skipped — see policy note below.
# Local binary detection is intentionally skipped -- see policy note below.
mkdir -p "$CLAWGOD_DIR" "$BIN_DIR"
@@ -172,7 +172,7 @@ NATIVE_BIN_TMPDIR=""
# `claude update`, so users never re-run `npm install -g` / `bun add -g`.
# Both directories freeze at whatever version was on disk the day clawgod
# was first installed, and `claude update` (which is now redirected here)
# would re-detect that frozen binary forever — never reaching the
# would re-detect that frozen binary forever -- never reaching the
# registry. See INCIDENT_LOG 2026-04-29 entry. The fix is to skip local
# detection entirely; the npm tarball is ~60-90 MB compressed, fetched
# once per upgrade, and npm's HTTP cache keeps repeats fast.
@@ -254,9 +254,9 @@ cat > "$CLAWGOD_DIR/extract-natives.mjs" << 'EXTRACTOR_EOF'
* (the official Claude Code native binary).
*
* Supports:
* - Mach-O (macOS) — arm64 + x86_64 thin binaries
* - ELF (Linux) — arm64 + x86_64
* - PE (Windows) — x86_64 + arm64
* - Mach-O (macOS) -- arm64 + x86_64 thin binaries
* - ELF (Linux) -- arm64 + x86_64
* - PE (Windows) -- x86_64 + arm64
*
* Usage:
* node extract-natives.mjs <binary-path> <output-dir>
@@ -265,7 +265,7 @@ cat > "$CLAWGOD_DIR/extract-natives.mjs" << 'EXTRACTOR_EOF'
import { readFileSync, writeFileSync, mkdirSync, existsSync, statSync } from 'fs';
import { join, basename } from 'path';
// ─── Mach-O constants ────────────────────────────────────────────────
// --- Mach-O constants ------------------------------------------------
const MH_MAGIC_64 = 0xfeedfacf; // little-endian 64-bit
const LC_SEGMENT_64 = 0x19;
@@ -274,14 +274,14 @@ const MH_DYLIB = 6;
const CPU_TYPE_X86_64 = 0x01000007;
const CPU_TYPE_ARM64 = 0x0100000c;
// ─── ELF constants ───────────────────────────────────────────────────
// --- ELF constants ---------------------------------------------------
const ELF_MAGIC = Buffer.from([0x7f, 0x45, 0x4c, 0x46]); // 7f 'E' 'L' 'F'
const ET_DYN = 3; // shared object
const EM_X86_64 = 62;
const EM_AARCH64 = 183;
// ─── PE constants ────────────────────────────────────────────────────
// --- PE constants ----------------------------------------------------
const MZ_MAGIC = Buffer.from([0x4d, 0x5a]); // "MZ"
const PE_MAGIC = Buffer.from([0x50, 0x45, 0, 0]); // "PE\0\0"
@@ -289,7 +289,7 @@ const IMAGE_FILE_MACHINE_AMD64 = 0x8664;
const IMAGE_FILE_MACHINE_ARM64 = 0xaa64;
const IMAGE_FILE_DLL = 0x2000;
// ─── Helpers ─────────────────────────────────────────────────────────
// --- Helpers ---------------------------------------------------------
function archName(format, cputype) {
if (format === 'macho') {
@@ -312,7 +312,7 @@ function platformSuffix(format, arch) {
return `${arch}-${os}`;
}
// ─── Mach-O parser ───────────────────────────────────────────────────
// --- Mach-O parser ---------------------------------------------------
function parseMachODylib(buf, off) {
const magic = buf.readUInt32LE(off);
@@ -386,7 +386,7 @@ function extractMachODylibs(buf) {
return dylibs;
}
// ─── ELF parser ──────────────────────────────────────────────────────
// --- ELF parser ------------------------------------------------------
function parseELFSharedObject(buf, off) {
if (buf.length - off < 64) return null;
@@ -441,7 +441,7 @@ function extractELFSharedObjects(buf) {
return sos;
}
// ─── PE parser ───────────────────────────────────────────────────────
// --- PE parser -------------------------------------------------------
function parsePEDll(buf, off) {
if (buf.length - off < 1024) return null;
@@ -500,7 +500,7 @@ function extractPEDlls(buf) {
return dlls;
}
// ─── Main dispatch ───────────────────────────────────────────────────
// --- Main dispatch ---------------------------------------------------
function detectFormat(buf) {
if (buf.readUInt32LE(0) === MH_MAGIC_64) return 'macho';
@@ -540,7 +540,7 @@ function identifyDylib(buf, dylib) {
return null;
}
// ─── cli.js text extraction (Bun standalone) ─────────────────────────
// --- cli.js text extraction (Bun standalone) -------------------------
//
// Two-stage anchor strategy:
// 1. Primary: Bun's bunfs path marker, observed in Mach-O / ELF builds.
@@ -575,7 +575,7 @@ function extractCliJs(buf) {
fnStart = candidate;
}
// Resolve the IIFE close — search forward from fnStart so that we close
// Resolve the IIFE close -- search forward from fnStart so that we close
// the wrapper we actually opened, regardless of which anchor located it.
const tailFromFn = buf.indexOf(CLI_TAIL_MARKER, fnStart);
if (tailFromFn === -1) return null;
@@ -600,7 +600,7 @@ function main() {
const stat = statSync(binaryPath);
if (stat.size < 10 * 1024 * 1024) {
console.error(`Binary too small (${stat.size} bytes) — not a native Claude Code binary`);
console.error(`Binary too small (${stat.size} bytes) -- not a native Claude Code binary`);
process.exit(1);
}
@@ -624,7 +624,7 @@ function main() {
mkdirSync(outputDir, { recursive: true });
const out = join(outputDir, 'cli.original.js');
writeFileSync(out, js);
console.log(` cli.js ${(js.length / 1024 / 1024).toFixed(2)} MB → ${out}`);
console.log(` cli.js ${(js.length / 1024 / 1024).toFixed(2)} MB -> ${out}`);
return;
}
@@ -658,7 +658,7 @@ function main() {
const data = buf.slice(lib.offset, lib.offset + lib.size);
writeFileSync(targetFile, data);
console.log(` ✓ ${name.padEnd(20)} ${lib.arch.padEnd(6)} ${(lib.size / 1024).toFixed(0).padStart(5)} KB → ${targetFile}`);
console.log(` [OK] ${name.padEnd(20)} ${lib.arch.padEnd(6)} ${(lib.size / 1024).toFixed(0).padStart(5)} KB -> ${targetFile}`);
summary.extracted.push({ name, platform, size: lib.size });
}
@@ -676,7 +676,7 @@ function main() {
main();
EXTRACTOR_EOF
# ─── Extract cli.js + native modules from Bun binary ──────────
# --- Extract cli.js + native modules from Bun binary ----------
# Note: extract-natives.mjs and post-process.mjs are kept around (NOT deleted)
# so the wrapper's drift detector can re-run them when the user upgrades
# their native Claude binary.
@@ -695,7 +695,7 @@ fi
dim "Extracting native modules from $(echo "$NATIVE_BIN_LABEL") ..."
node "$CLAWGOD_DIR/extract-natives.mjs" "$NATIVE_BIN" "$VENDOR_DIR" 2>&1 | while IFS= read -r line; do echo " $line"; done || true
# ─── Post-process cli.js for Bun runtime ──────────────────────
# --- Post-process cli.js for Bun runtime ----------------------
# 1. Rewrite /$bunfs/root/X.node paths to point at extracted vendor modules
# 2. Rewrite build-time /home/runner/.../*.ts URLs (used by ripgrep,
# sandbox, computer-use, etc. for asset resolution) to __filename so
@@ -715,14 +715,14 @@ const dst = `${here}/cli.original.cjs`;
let code = readFileSync(src, 'utf8');
// (1) bunfs .node module paths → runtime vendor lookup
// (1) bunfs .node module paths -> runtime vendor lookup
code = code.replace(
/require\(['"](\/\$bunfs\/root\/([\w-]+)\.node)['"]\)/g,
(m, _full, name) =>
`require(require('path').join(__dirname,'vendor',${JSON.stringify(name)},\`\${process.arch==='arm64'?'arm64':'x64'}-\${process.platform==='darwin'?'darwin':process.platform==='linux'?'linux':'win32'}\`,${JSON.stringify(name + '.node')}))`,
);
// (2) build-time fileURLToPath() leaks → use cli.cjs's own __filename
// (2) build-time fileURLToPath() leaks -> use cli.cjs's own __filename
code = code.replace(
/[\w$]+\.fileURLToPath\("file:\/\/\/home\/runner\/work\/claude-cli-internal\/claude-cli-internal\/[^"]*"\)/g,
() => '__filename',
@@ -741,7 +741,7 @@ node "$CLAWGOD_DIR/post-process.mjs" 2>&1 | while IFS= read -r line; do echo "
# Stamp the source version so the wrapper can detect drift on next launch
echo "$NATIVE_BIN_LABEL" > "$CLAWGOD_DIR/.source-version"
# If we pulled the binary from npm into a tmpdir, clean it up now —
# If we pulled the binary from npm into a tmpdir, clean it up now --
# extraction is done, drift detection only consults ~/.local/share/claude/versions/.
if [ -n "$NATIVE_BIN_TMPDIR" ]; then
rm -rf "$NATIVE_BIN_TMPDIR"
@@ -749,7 +749,7 @@ fi
info "cli.original.cjs ready ($NATIVE_BIN_LABEL)"
# ─── Write re-patch helper (used by wrapper on version drift) ─────────
# --- Write re-patch helper (used by wrapper on version drift) ---------
cat > "$CLAWGOD_DIR/repatch.mjs" << 'REPATCH_EOF'
#!/usr/bin/env bun
@@ -798,7 +798,7 @@ REPATCH_EOF
chmod +x "$CLAWGOD_DIR/repatch.mjs"
info "Re-patch helper installed (repatch.mjs)"
# ─── Write wrapper (cli.cjs, runs under Bun) ──────────────────
# --- Write wrapper (cli.cjs, runs under Bun) ------------------
cat > "$CLAWGOD_DIR/cli.cjs" << 'WRAPPER_EOF'
#!/usr/bin/env bun
@@ -819,8 +819,8 @@ const clawgodDir = join(homedir(), '.clawgod');
// on a healthy clawgod install.
// In practice the block was reading a directory that never changes, but
// could *retract* a fresher version that install.sh just pulled from npm
// registry — putting users into a re-patch loop. Upgrades now go through
// the patched `claude update` → install.sh redirect, which always pulls
// registry -- putting users into a re-patch loop. Upgrades now go through
// the patched `claude update` -> install.sh redirect, which always pulls
// the latest from npm.
// One-time migration: earlier wrapper versions set CLAUDE_CONFIG_DIR=~/.clawgod,
@@ -873,7 +873,7 @@ if (hasProviderApiKey) {
// vLLM / etc.) don't share Anthropic's server-side handling of
// x-anthropic-billing-header. That header carries a per-request `cch` field
// which Anthropic's own server excludes from prompt-cache key calculation
// (via cacheScope:null), but third-party proxies fold into the prefix hash —
// (via cacheScope:null), but third-party proxies fold into the prefix hash --
// so the cached prefix changes every request and cache hit rate drops to
// zero. Auto-disable the header whenever baseURL points away from Anthropic.
// Users can force re-enable with CLAUDE_CODE_ATTRIBUTION_HEADER=1 if needed.
@@ -904,12 +904,12 @@ WRAPPER_EOF
chmod +x "$CLAWGOD_DIR/cli.cjs"
info "Wrapper created (cli.cjs)"
# ─── Write universal patcher ───────────────────────────
# --- Write universal patcher ---------------------------
cat > "$CLAWGOD_DIR/patch.mjs" << 'PATCHER_EOF'
#!/usr/bin/env node
/**
* ClawGod Universal Patcher — 正则模式匹配, 跨版本兼容
* ClawGod Universal Patcher -- Regex matching, cross-version compatibility
*/
import { readFileSync, writeFileSync, existsSync, copyFileSync } from 'fs';
import { join, dirname } from 'path';
@@ -919,11 +919,11 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
const TARGET = join(__dirname, 'cli.original.cjs');
const BACKUP = TARGET + '.bak';
// ─── Regex-based patches (version-agnostic) ──────────────
// --- Regex-based patches (version-agnostic) --------------
const patches = [
{
name: 'USER_TYPE → ant',
name: 'USER_TYPE -> ant',
pattern: /function ([\w$]+)\(\)\{return"external"\}/g,
replacer: (m, fn) => `function ${fn}(){return"ant"}`,
sentinel: 'return"external"',
@@ -975,8 +975,8 @@ const patches = [
sentinel: 'name:"ultraplan"',
},
{
// ≤v2.1.110: function X(){return Y("tengu_review_bughunter_config",null)?.enabled===!0}
// v2.1.119+: function X(){return Y("tengu_review_bughunter_config",null)} — getter
// <=v2.1.110: function X(){return Y("tengu_review_bughunter_config",null)?.enabled===!0}
// v2.1.119+: function X(){return Y("tengu_review_bughunter_config",null)} -- getter
// and the gate at function Z(){return X()?.enabled===!0} elsewhere.
// We override the getter to always return {enabled:!0}.
name: 'Ultrareview enable',
@@ -995,7 +995,7 @@ const patches = [
replacer: (m, fn) => `function ${fn}(){return!0}`,
},
{
// ≤v2.1.110: let Y=Dq();if(Y!=="firstParty"&&Y!=="anthropicAws")return!1;return/^claude-(opus|sonnet)-4-6/.test(K)
// <=v2.1.110: let Y=Dq();if(Y!=="firstParty"&&Y!=="anthropicAws")return!1;return/^claude-(opus|sonnet)-4-6/.test(K)
// v2.1.119+: same gate plus extra branches for claude-opus-4-7.
// v2.1.139+: gate moved inside function wuH(H){let $=R7(H),q=Wq();if(q!=="firstParty"&&q!=="anthropicAws")return!1;if($.includes("claude-3-")||...)return!0;return!1}
// i.e. the `let` lifted to a comma-list before the if; the if-gate
@@ -1008,7 +1008,7 @@ const patches = [
},
{
// CLI subcommand registered via commander chain:
// .command("update").alias("upgrade").description("…").action(async()=>{…})
// .command("update").alias("upgrade").description("...").action(async()=>{...})
// The original action's update path is broken under clawgod: detectInstallType()
// returns "unknown" because the launcher hides our cli.cjs from upstream's
// path heuristics, and the unknown-fallback branch on macOS overwrites
@@ -1038,59 +1038,59 @@ const patches = [
// arg-quoting; payload must be UTF-16LE base64.
const psScript =
"$p=if($env:HTTPS_PROXY){$env:HTTPS_PROXY}elseif($env:HTTP_PROXY){$env:HTTP_PROXY}else{$null};" +
"$u='https://github.com/0Chencc/clawgod/releases/latest/download/install.ps1';" +
"$u='https://git.qomar.pw/omar/clawgod/raw/HEAD/install.ps1';" +
"if($p){iex(irm -Proxy $p $u)}else{iex(irm $u)}";
const psB64 = Buffer.from(psScript, 'utf16le').toString('base64');
return (
prefix +
`process.stderr.write("[clawgod] 'claude update' is handled by clawgod self-update.\\n[clawgod] To leave clawgod and use vanilla update: bash ~/.clawgod/install.sh --uninstall\\n[clawgod] Continuing now\\u2026\\n");` +
`const _w=process.platform==='win32';` +
`const _c=_w?['powershell','-NoProfile','-EncodedCommand','${psB64}']:['bash','-c','curl -fsSL https://github.com/0Chencc/clawgod/releases/latest/download/install.sh | bash'];` +
`const _c=_w?['powershell','-NoProfile','-EncodedCommand','${psB64}']:['bash','-c','curl -fsSL https://git.qomar.pw/omar/clawgod/raw/HEAD/install.sh | bash'];` +
`const _r=require('child_process').spawnSync(_c[0],_c.slice(1),{stdio:'inherit'});` +
`process.exit(_r.status||0);`
);
},
sentinel: '.command("update").alias("upgrade")',
},
// ── 绿色主题 (patch 标识) ──
// -- Green Theme (patch Identifier) --
{
name: 'Logo + brand color → green (RGB dark)',
name: 'Logo + brand color -> green (RGB dark)',
pattern: /clawd_body:"rgb\(215,119,87\)"/g,
replacer: () => 'clawd_body:"rgb(34,197,94)"',
},
{
name: 'Logo + brand color → green (ANSI)',
name: 'Logo + brand color -> green (ANSI)',
pattern: /clawd_body:"ansi:redBright"/g,
replacer: () => 'clawd_body:"ansi:greenBright"',
},
{
name: 'Theme claude color → green (dark)',
name: 'Theme claude color -> green (dark)',
pattern: /claude:"rgb\(215,119,87\)"/g,
replacer: () => 'claude:"rgb(34,197,94)"',
},
{
name: 'Theme claude color → green (light)',
name: 'Theme claude color -> green (light)',
pattern: /claude:"rgb\(255,153,51\)"/g,
replacer: () => 'claude:"rgb(22,163,74)"',
},
{
name: 'Shimmer → green',
name: 'Shimmer -> green',
pattern: /claudeShimmer:"rgb\(2[34]5,1[45]9,1[12]7\)"/g,
replacer: () => 'claudeShimmer:"rgb(74,222,128)"',
},
{
name: 'Shimmer light → green',
name: 'Shimmer light -> green',
pattern: /claudeShimmer:"rgb\(255,183,101\)"/g,
replacer: () => 'claudeShimmer:"rgb(34,197,94)"',
},
{
name: 'Hex brand color → green',
name: 'Hex brand color -> green',
pattern: /#da7756/g,
replacer: () => '#22c55e',
},
// ── 限制移除 ──
// -- Remove Restrictions --
{
name: 'Remove CYBER_RISK_INSTRUCTION',
@@ -1119,11 +1119,11 @@ const patches = [
optional: true,
},
// ── 消息过滤 ──
// -- Message Filtering --
{
// v2.1.88-~v2.1.91: fn()!=="ant"){if(q.attachment.type==="hook_additional_context"...
// v2.1.92+ : fn()!=="ant"&&paY.has(q.attachment.type) — paY is an empty Set
// v2.1.92+ : fn()!=="ant"&&paY.has(q.attachment.type) -- paY is an empty Set
// in v2.1.110, so this filter is effectively a no-op; patch anyway
// to guard against paY being populated in future versions.
name: 'Attachment filter bypass',
@@ -1132,7 +1132,7 @@ const patches = [
optional: true, // filter may be removed entirely in future versions
},
{
// Legacy (≤v2.1.91) ternary form: fn()!=="ant"?tRY(_,sRY(K)):K
// Legacy (<=v2.1.91) ternary form: fn()!=="ant"?tRY(_,sRY(K)):K
name: 'Message list filter bypass (legacy ternary)',
pattern: /([\w$]+)\(\)!=="ant"\?([\w$]+)\(([\w$]+),([\w$]+)\(([\w$]+)\)\):([\w$]+)/g,
replacer: (m, fn, tRY, underscore, sRY, K, fallback) => fallback,
@@ -1148,7 +1148,7 @@ const patches = [
},
];
// ─── Main ─────────────────────────────────────────────────
// --- Main -------------------------------------------------
const args = process.argv.slice(2);
const dryRun = args.includes('--dry-run');
@@ -1156,14 +1156,14 @@ const verify = args.includes('--verify');
const revert = args.includes('--revert');
if (revert) {
if (!existsSync(BACKUP)) { console.error('❌ No backup found'); process.exit(1); }
if (!existsSync(BACKUP)) { console.error('[X] No backup found'); process.exit(1); }
copyFileSync(BACKUP, TARGET);
console.log('✅ Reverted from backup');
console.log('[OK] Reverted from backup');
process.exit(0);
}
if (!existsSync(TARGET)) {
console.error('❌ Target not found:', TARGET);
console.error('[X] Target not found:', TARGET);
process.exit(1);
}
@@ -1174,11 +1174,11 @@ const origSize = code.length;
const verMatch = code.match(/Version:\s*([\d.]+)/);
const version = verMatch ? verMatch[1] : 'unknown';
console.log(`\n${'═'.repeat(55)}`);
console.log(`\n${'='.repeat(55)}`);
console.log(` ClawGod (universal)`);
console.log(` Target: cli.original.cjs (v${version})`);
console.log(` Mode: ${dryRun ? 'DRY RUN' : verify ? 'VERIFY' : 'APPLY'}`);
console.log(`${'═'.repeat(55)}\n`);
console.log(`${'='.repeat(55)}\n`);
let applied = 0, skipped = 0, failed = 0;
@@ -1196,17 +1196,17 @@ for (const p of patches) {
relevant = relevant.length > p.selectIndex ? [relevant[p.selectIndex]] : [];
}
// Uniqueness check — skip when 0 so the sentinel / already-applied
// Uniqueness check -- skip when 0 so the sentinel / already-applied
// fallthrough can handle it; only fail on >1 (ambiguous).
if (p.unique && relevant.length > 1) {
console.log(` ⚠️ ${p.name} — ${relevant.length} matches, skipping (need 1)`);
console.log(` ! ${p.name} -- ${relevant.length} matches, skipping (need 1)`);
failed++;
continue;
}
if (relevant.length === 0) {
if (p.optional) {
console.log(` ⏭ ${p.name} (not present in this version)`);
console.log(` >> ${p.name} (not present in this version)`);
skipped++;
continue;
}
@@ -1217,21 +1217,21 @@ for (const p of patches) {
const sentinels = Array.isArray(p.sentinel) ? p.sentinel : [p.sentinel];
const stillPresent = sentinels.filter((s) => code.includes(s));
if (stillPresent.length > 0) {
console.log(` ❌ ${p.name} — regex stale, sentinel still in source: ${stillPresent.map((s) => JSON.stringify(s)).join(', ')}`);
console.log(` [X] ${p.name} -- regex stale, sentinel still in source: ${stillPresent.map((s) => JSON.stringify(s)).join(', ')}`);
failed++;
continue;
}
console.log(` ✅ ${p.name} (already applied, sentinel absent)`);
console.log(` [OK] ${p.name} (already applied, sentinel absent)`);
applied++;
continue;
}
console.log(` ⚠️ ${p.name} (0 matches, no sentinel — cannot verify)`);
console.log(` ! ${p.name} (0 matches, no sentinel -- cannot verify)`);
skipped++;
continue;
}
if (verify) {
console.log(` ⬚ ${p.name} — ${relevant.length} match(es), not yet applied`);
console.log(` -> ${p.name} -- ${relevant.length} match(es), not yet applied`);
skipped++;
continue;
}
@@ -1249,37 +1249,37 @@ for (const p of patches) {
}
if (count > 0) {
console.log(` ✅ ${p.name} (${count} replacement${count > 1 ? 's' : ''})`);
console.log(` [OK] ${p.name} (${count} replacement${count > 1 ? 's' : ''})`);
applied++;
} else {
console.log(` ⏭ ${p.name} (no change needed)`);
console.log(` >> ${p.name} (no change needed)`);
skipped++;
}
}
console.log(`\n${'─'.repeat(55)}`);
console.log(`\n${'-'.repeat(55)}`);
console.log(` Result: ${applied} applied, ${skipped} skipped, ${failed} failed`);
if (!dryRun && !verify && applied > 0) {
if (!existsSync(BACKUP)) {
copyFileSync(TARGET, BACKUP);
console.log(` 📦 Backup: ${BACKUP}`);
console.log(` [Backup] Backup: ${BACKUP}`);
}
writeFileSync(TARGET, code, 'utf8');
const diff = code.length - origSize;
console.log(` 📝 Written: cli.original.cjs (${diff >= 0 ? '+' : ''}${diff} bytes)`);
console.log(` [Written] Written: cli.original.cjs (${diff >= 0 ? '+' : ''}${diff} bytes)`);
}
console.log(`${'═'.repeat(55)}\n`);
console.log(`${'='.repeat(55)}\n`);
PATCHER_EOF
info "Patcher created (patch.mjs)"
# ─── Apply patches ─────────────────────────────────────
# --- Apply patches -------------------------------------
dim "Applying patches ..."
node "$CLAWGOD_DIR/patch.mjs" 2>&1 | while IFS= read -r line; do echo " $line"; done
# ─── Create default configs ───────────────────────────
# --- Create default configs ---------------------------
if [ ! -f "$CLAWGOD_DIR/features.json" ]; then
cat > "$CLAWGOD_DIR/features.json" << 'FEATURES_EOF'
@@ -1299,11 +1299,11 @@ FEATURES_EOF
info "Default features.json created"
fi
# ─── Sanity check: ensure user's Bun can actually load cli.original.cjs ──
# --- Sanity check: ensure user's Bun can actually load cli.original.cjs --
# Anthropic builds the native binary with a bleeding-edge Bun build (e.g.
# 1.3.14 while stable still ships 1.3.13). Older Bun crashes loading the
# extracted cli.original.cjs with "Expected CommonJS module to have a
# function wrapper". Detect this BEFORE we install the launcher — better
# function wrapper". Detect this BEFORE we install the launcher -- better
# to fail loudly than to leave the user with a launcher that panics on
# first invocation.
@@ -1315,7 +1315,7 @@ if echo "$sanity_out" | grep -q "Expected CommonJS module to have a function wra
warn ""
warn " Anthropic builds with Bun's canary channel (currently ~1.3.14), while"
warn " bun.sh's main download is on stable (currently 1.3.13). The canary build"
warn " is NOT visible on bun.sh's download page — it lives on GitHub Releases"
warn " is NOT visible on bun.sh's download page -- it lives on GitHub Releases"
warn " and is reachable only via 'bun upgrade --canary'."
warn ""
warn " If your bun is from bun.sh:"
@@ -1328,12 +1328,12 @@ if echo "$sanity_out" | grep -q "Expected CommonJS module to have a function wra
warn " curl -fsSL https://bun.sh/install | bash"
warn " bun upgrade --canary"
warn ""
warn " Then re-run install.sh — this sanity check will pass."
warn " Then re-run install.sh -- this sanity check will pass."
exit 1
fi
info "Bun loads cli.original.cjs"
# ─── Replace claude command ───────────────────────────
# --- Replace claude command ---------------------------
LAUNCHER_CONTENT="#!/bin/bash
# clawgod launcher
@@ -1341,7 +1341,7 @@ CLAWGOD_CLI=\"$CLAWGOD_DIR/cli.cjs\"
BUN_BIN=\"$BUN_BIN\"
if [ ! -f \"\$CLAWGOD_CLI\" ]; then
echo \"clawgod: installation at $CLAWGOD_DIR is missing (cli.cjs not found)\" >&2
echo \"clawgod: reinstall via curl -fsSL https://github.com/0Chencc/clawgod/releases/latest/download/install.sh | bash\" >&2
echo \"clawgod: reinstall via curl -fsSL https://git.qomar.pw/omar/clawgod/raw/HEAD/install.sh | bash\" >&2
echo \"clawgod: or remove this launcher: rm \\\"\$0\\\"\" >&2
exit 127
fi
@@ -1361,7 +1361,7 @@ exec \"\$BUN_BIN\" \"\$CLAWGOD_CLI\" \"\$@\""
# `set -e` via the assignment's exit status under bash 5+.
CLAUDE_BIN=$(command -v claude 2>/dev/null || true)
if [ -z "$CLAUDE_BIN" ]; then
# No claude in PATH — use default location
# No claude in PATH -- use default location
CLAUDE_BIN="$BIN_DIR/claude"
dim "No existing claude found, installing to $BIN_DIR"
fi
@@ -1370,14 +1370,14 @@ CLAUDE_DIR=$(dirname "$CLAUDE_BIN")
# Back up original claude (only once)
if [ ! -e "$CLAUDE_BIN.orig" ]; then
if [ -L "$CLAUDE_BIN" ]; then
# Symlink (native install) — preserve target
# Symlink (native install) -- preserve target
NATIVE_BIN="$(readlink "$CLAUDE_BIN")"
ln -sf "$NATIVE_BIN" "$CLAUDE_BIN.orig"
info "Original claude backed up → claude.orig (→ $NATIVE_BIN)"
info "Original claude backed up -> claude.orig (-> $NATIVE_BIN)"
elif [ -f "$CLAUDE_BIN" ] && file "$CLAUDE_BIN" 2>/dev/null | grep -q "Mach-O\|ELF\|script"; then
# Binary or script (pnpm/npm global install)
cp "$CLAUDE_BIN" "$CLAUDE_BIN.orig"
info "Original claude backed up → claude.orig"
info "Original claude backed up -> claude.orig"
else
# Try versions dir as fallback
VERSIONS_DIR="$HOME/.local/share/claude/versions"
@@ -1387,15 +1387,15 @@ if [ ! -e "$CLAUDE_BIN.orig" ]; then
done)" || true
if [ -n "$NATIVE_BIN" ]; then
ln -sf "$NATIVE_BIN" "$CLAUDE_BIN.orig"
info "Original claude backed up → claude.orig (→ $NATIVE_BIN)"
info "Original claude backed up -> claude.orig (-> $NATIVE_BIN)"
fi
fi
fi
fi
# Write launcher to the SAME directory where claude was found.
# CRITICAL: `echo > $f` follows symlinks — if $CLAUDE_BIN is a symlink
# (e.g. official ~/.local/bin/claude → ~/.local/share/claude/versions/X)
# CRITICAL: `echo > $f` follows symlinks -- if $CLAUDE_BIN is a symlink
# (e.g. official ~/.local/bin/claude -> ~/.local/share/claude/versions/X)
# we'd write our launcher into the real binary and destroy it. Always
# remove the existing entry first so we write a fresh regular file.
write_launcher() {
@@ -1409,7 +1409,7 @@ write_launcher() {
}
write_launcher "$CLAUDE_BIN"
info "Command 'claude' → patched ($CLAUDE_BIN)"
info "Command 'claude' -> patched ($CLAUDE_BIN)"
# Also install to ~/.local/bin if claude was elsewhere (ensures PATH consistency)
if [ "$CLAUDE_DIR" != "$BIN_DIR" ]; then
@@ -1423,9 +1423,9 @@ fi
# - User wants explicit "patched" intent
# - User restored claude.orig via uninstall but still wants the patched one
write_launcher "$BIN_DIR/clawgod"
info "Command 'clawgod' → patched ($BIN_DIR/clawgod)"
info "Command 'clawgod' -> patched ($BIN_DIR/clawgod)"
# ─── Check PATH ───────────────────────────────────────
# --- Check PATH ---------------------------------------
if ! echo "$PATH" | grep -q "$CLAUDE_DIR" && ! echo "$PATH" | grep -q "$BIN_DIR"; then
# Detect shell config file
@@ -1440,20 +1440,20 @@ if ! echo "$PATH" | grep -q "$CLAUDE_DIR" && ! echo "$PATH" | grep -q "$BIN_DIR"
dim " echo 'export PATH=\"\$HOME/.local/bin:\$PATH\"' >> $SHELL_RC && source $SHELL_RC"
fi
# ─── Flush shell cache ────────────────────────────────
# --- Flush shell cache --------------------------------
hash -r 2>/dev/null
# ─── Done ─────────────────────────────────────────────
# --- Done ---------------------------------------------
echo ""
echo -e " ${BOLD}${GREEN}ClawGod installed!${NC}"
echo ""
dim " claude — Start patched Claude Code (green logo)"
dim " claude.orig — Run original unpatched Claude Code"
dim " claude -- Start patched Claude Code (green logo)"
dim " claude.orig -- Run original unpatched Claude Code"
echo ""
dim " Updates: 'claude update' is patched to route through this installer."
dim " Just run it as usual — pulls latest Anthropic release + re-patches"
dim " Just run it as usual -- pulls latest Anthropic release + re-patches"
dim " in one step. To leave clawgod and use vanilla update:"
dim " bash ~/.clawgod/install.sh --uninstall"
echo ""
@@ -1465,6 +1465,7 @@ echo ""
dim " If 'claude' panics with 'Expected CommonJS module to have a function wrapper',"
dim " your Bun lags Anthropic's embedded Bun. Upgrade with one of:"
dim " bun upgrade --canary (if installed via curl/install.sh)"
dim " scoop update bun (scoop — may lag stable)"
dim " scoop update bun (scoop -- may lag stable)"
dim " brew upgrade bun (homebrew)"
echo ""
+12 -12
View File
@@ -24,7 +24,7 @@
<nav class="topbar-links">
<a href="#patches">Patches</a>
<a href="#how">How it works</a>
<a href="https://github.com/0Chencc/clawgod" target="_blank" rel="noopener">GitHub</a>
<a href="https://git.qomar.pw/omar/clawgod" target="_blank" rel="noopener">GitHub</a>
</nav>
</div>
</header>
@@ -52,12 +52,12 @@
<svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><path d="M9.294 6.776 14.357 1h-1.2L8.756 6.013 5.235 1H1.171l5.31 7.531L1.171 15h1.2l4.642-5.296L10.762 15h4.064L9.294 6.776Zm-1.643 1.872-.539-.755L2.804 1.91h1.844l3.452 4.847.539.755 4.49 6.301h-1.844L7.65 8.648Z"/></svg>
<span>@0chencc</span>
</a>
<a class="hero-link stars" href="https://github.com/0Chencc/clawgod/stargazers" target="_blank" rel="noopener">
<a class="hero-link stars" href="https://git.qomar.pw/omar/clawgod/stargazers" target="_blank" rel="noopener">
<svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><path d="M8 .25a.75.75 0 0 1 .673.418l1.882 3.815 4.21.612a.75.75 0 0 1 .416 1.279l-3.046 2.97.719 4.192a.751.751 0 0 1-1.088.791L8 12.347l-3.766 1.98a.75.75 0 0 1-1.088-.79l.72-4.194L.818 6.374a.75.75 0 0 1 .416-1.28l4.21-.611L7.327.668A.75.75 0 0 1 8 .25Z"/></svg>
<span class="stat-num loading" id="stat-stars">—</span>
<span>stars</span>
</a>
<a class="hero-link downloads" href="https://github.com/0Chencc/clawgod/releases" target="_blank" rel="noopener">
<a class="hero-link downloads" href="https://git.qomar.pw/omar/clawgod/releases" target="_blank" rel="noopener">
<svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><path d="M2.75 14A1.75 1.75 0 0 1 1 12.25v-2.5a.75.75 0 0 1 1.5 0v2.5c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25v-2.5a.75.75 0 0 1 1.5 0v2.5A1.75 1.75 0 0 1 13.25 14H2.75Z"/><path d="M7.25 7.689V2a.75.75 0 0 1 1.5 0v5.689l1.97-1.969a.75.75 0 1 1 1.06 1.06l-3.25 3.25a.75.75 0 0 1-1.06 0L4.22 6.78a.75.75 0 0 1 1.06-1.06l1.97 1.969Z"/></svg>
<span class="stat-num loading" id="stat-downloads">—</span>
<span>downloads</span>
@@ -73,15 +73,15 @@
</div>
<div class="install-panel active" id="panel-unix" role="tabpanel">
<div class="code-block">
<button class="copy-btn" data-copy="curl -fsSL https://github.com/0Chencc/clawgod/releases/latest/download/install.sh | bash">Copy</button>
<pre><span class="prompt">$ </span><span class="tok tok-cmd">curl</span> <span class="tok tok-flag">-fsSL</span> <span class="tok tok-url">https://github.com/0Chencc/clawgod/releases/latest/download/install.sh</span> <span class="tok tok-op">|</span> <span class="tok tok-cmd">bash</span></pre>
<button class="copy-btn" data-copy="curl -fsSL https://git.qomar.pw/omar/clawgod/raw/HEAD/install.sh | bash">Copy</button>
<pre><span class="prompt">$ </span><span class="tok tok-cmd">curl</span> <span class="tok tok-flag">-fsSL</span> <span class="tok tok-url">https://git.qomar.pw/omar/clawgod/raw/HEAD/install.sh</span> <span class="tok tok-op">|</span> <span class="tok tok-cmd">bash</span></pre>
</div>
<div class="install-note">Idempotent — safe to re-run. Bun, Node ≥ 18, ripgrep required.</div>
</div>
<div class="install-panel" id="panel-win" role="tabpanel">
<div class="code-block">
<button class="copy-btn" data-copy="irm https://github.com/0Chencc/clawgod/releases/latest/download/install.ps1 | iex">Copy</button>
<pre><span class="prompt">&gt; </span><span class="tok tok-cmd">irm</span> <span class="tok tok-url">https://github.com/0Chencc/clawgod/releases/latest/download/install.ps1</span> <span class="tok tok-op">|</span> <span class="tok tok-cmd">iex</span></pre>
<button class="copy-btn" data-copy="irm https://git.qomar.pw/omar/clawgod/raw/HEAD/install.ps1 | iex">Copy</button>
<pre><span class="prompt">&gt; </span><span class="tok tok-cmd">irm</span> <span class="tok tok-url">https://git.qomar.pw/omar/clawgod/raw/HEAD/install.ps1</span> <span class="tok tok-op">|</span> <span class="tok tok-cmd">iex</span></pre>
</div>
<div class="install-note">Idempotent — safe to re-run. Bun via <a href="https://bun.sh/install" target="_blank" rel="noopener">bun.sh</a> recommended.</div>
</div>
@@ -179,8 +179,8 @@
<!-- ─── CTA ───────────────────────────────────────────────── -->
<section class="cta">
<div class="cta-row">
<a class="btn primary" href="https://github.com/0Chencc/clawgod#install">Get started</a>
<a class="btn" href="https://github.com/0Chencc/clawgod" target="_blank" rel="noopener">
<a class="btn primary" href="https://git.qomar.pw/omar/clawgod#install">Get started</a>
<a class="btn" href="https://git.qomar.pw/omar/clawgod" target="_blank" rel="noopener">
<svg width="16" height="16" viewBox="0 0 16 16"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/></svg>
Source on GitHub
</a>
@@ -206,9 +206,9 @@
</a>
</span>
<span class="footer-cell footer-links">
<a href="https://github.com/0Chencc/clawgod/releases">Releases</a> ·
<a href="https://github.com/0Chencc/clawgod/issues">Issues</a> ·
<a href="https://github.com/0Chencc/clawgod/actions/workflows/compat-daily.yml">CI</a>
<a href="https://git.qomar.pw/omar/clawgod/releases">Releases</a> ·
<a href="https://git.qomar.pw/omar/clawgod/issues">Issues</a> ·
<a href="https://git.qomar.pw/omar/clawgod/actions/workflows/compat-daily.yml">CI</a>
</span>
</div>
</footer>