homebrew pinning
This commit is contained in:
parent
4921973b9a
commit
480c556d32
45 changed files with 1401 additions and 2219 deletions
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
lib,
|
||||
config,
|
||||
scope,
|
||||
...
|
||||
}:
|
||||
let
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
config,
|
||||
machineName,
|
||||
mainUser,
|
||||
scope,
|
||||
...
|
||||
}:
|
||||
scope "boot" {
|
||||
|
|
@ -18,7 +19,7 @@ scope "boot" {
|
|||
{
|
||||
wallpapers = [ config.rice.bg.src ];
|
||||
interface = {
|
||||
brandingColor = 1;
|
||||
brandingColor = pal.normal.red;
|
||||
branding = "hello ${mainUser}. welcome to ${machineName}";
|
||||
};
|
||||
graphicalTerminal =
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
pkgs,
|
||||
inputs,
|
||||
getFlakePkg,
|
||||
scope,
|
||||
...
|
||||
}:
|
||||
let
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
pkgs,
|
||||
lib,
|
||||
getFlakePkg,
|
||||
config,
|
||||
...
|
||||
}:
|
||||
let
|
||||
|
|
@ -16,18 +17,15 @@ let
|
|||
in
|
||||
{
|
||||
options.programs.wry = {
|
||||
enable = lib.mkEnableOption "wry compositor";
|
||||
package = lib.mkOption {
|
||||
type = lib.types.package;
|
||||
default = wry;
|
||||
};
|
||||
};
|
||||
config = {
|
||||
environment.systemPackages = [ wry ];
|
||||
|
||||
services.greetd = {
|
||||
enable = true;
|
||||
settings.default_session.command = "${lib.getExe (getFlakePkg inputs.tuigreet)} --sessions /etc/greetd/wayland-sessions --remember-session --animation doom";
|
||||
};
|
||||
config = lib.mkIf (config.programs.wry.enable) {
|
||||
environment.systemPackages = [ wry ];
|
||||
|
||||
environment.etc."greetd/wayland-sessions/wry.desktop".text = ''
|
||||
[Desktop Entry]
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
pkgs,
|
||||
config,
|
||||
mkWrappers,
|
||||
scope,
|
||||
...
|
||||
}:
|
||||
let
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
pkgs,
|
||||
scope,
|
||||
...
|
||||
}:
|
||||
with pkgs;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
{ ... }:
|
||||
{ scope, ... }:
|
||||
scope "environment.sessionVariables" {
|
||||
NIXOS_OZONE_WL = "1";
|
||||
GBM_BACKEND = "nvidia-drm";
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
{ ... }:
|
||||
{ scope, ... }:
|
||||
scope "hardware" {
|
||||
enableRedistributableFirmware = true;
|
||||
enableAllFirmware = true;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
{ ... }:
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
services.system76-scheduler.enable = true;
|
||||
|
||||
services.scx = {
|
||||
enable = true;
|
||||
scheduler = "scx_lavd";
|
||||
};
|
||||
boot.kernelPackages = pkgs.linuxPackages_xanmod_latest;
|
||||
boot.kernelParams = [ "preempt=lazy" ];
|
||||
|
||||
}
|
||||
|
|
|
|||
960
graphical/llm/clod.nix
Normal file
960
graphical/llm/clod.nix
Normal file
|
|
@ -0,0 +1,960 @@
|
|||
{
|
||||
lib,
|
||||
mainUser,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
inherit (lib) optionals;
|
||||
inherit (lib.lists) singleton;
|
||||
inherit (lib.meta) getExe getExe';
|
||||
inherit (lib.strings) toJSON;
|
||||
|
||||
# Statusline: model, ctx %, in/out tokens, cache %, cost, timing, churn,
|
||||
# cwd, usage quotas. Reads the harness JSON blob on stdin.
|
||||
statusLine = pkgs.writeScriptBin "claude-code-statusline" /* nu */ ''
|
||||
#!${getExe pkgs.nushell}
|
||||
|
||||
def format-duration [ms: int] {
|
||||
let total_s = $ms // 1000
|
||||
let h = $total_s // 3600
|
||||
let m = ($total_s mod 3600) // 60
|
||||
let s = $total_s mod 60
|
||||
if $h > 0 {
|
||||
$"($h)h($m | fill -a r -w 2 -c '0')m($s | fill -a r -w 2 -c '0')s"
|
||||
} else if $m > 0 {
|
||||
$"($m)m($s | fill -a r -w 2 -c '0')s"
|
||||
} else {
|
||||
$"($s)s"
|
||||
}
|
||||
}
|
||||
|
||||
def color-for-pct [pct: number] {
|
||||
let pct_int = $pct | math floor | into int
|
||||
if $pct_int >= 80 {
|
||||
"\e[31m"
|
||||
} else if $pct_int >= 50 {
|
||||
"\e[33m"
|
||||
} else {
|
||||
"\e[32m"
|
||||
}
|
||||
}
|
||||
|
||||
def format-rate-limits [input: record] {
|
||||
let session_pct = try { $input | get rate_limits.five_hour.used_percentage } catch { null }
|
||||
let week_pct = try { $input | get rate_limits.seven_day.used_percentage } catch { null }
|
||||
|
||||
let session_part = if $session_pct != null {
|
||||
let c = color-for-pct $session_pct
|
||||
let v = $session_pct | math round --precision 0 | into int
|
||||
$"session: ($c)($v)%\e[0m"
|
||||
} else { "" }
|
||||
let week_part = if $week_pct != null {
|
||||
let c = color-for-pct $week_pct
|
||||
let v = $week_pct | math round --precision 0 | into int
|
||||
$"week: ($c)($v)%\e[0m"
|
||||
} else { "" }
|
||||
|
||||
[$session_part $week_part] | where {|x| $x | is-not-empty} | str join " "
|
||||
}
|
||||
|
||||
# --- Main ---
|
||||
let input = (^cat | from json)
|
||||
|
||||
let usage_info = format-rate-limits $input
|
||||
|
||||
let model_name = ($input | get model?.display_name? | default ($input | get model?.id? | default "unknown"))
|
||||
let used_pct = ($input | get context_window?.used_percentage? | default null)
|
||||
let total_cost = ($input | get cost?.total_cost_usd? | default 0)
|
||||
let total_input = ($input | get context_window?.s_in? | default ($input | get context_window?.total_input_tokens? | default 0))
|
||||
let total_output = ($input | get context_window?.s_out? | default ($input | get context_window?.total_output_tokens? | default 0))
|
||||
let duration_ms = ($input | get cost?.total_duration_ms? | default 0)
|
||||
let api_duration_ms = ($input | get cost?.total_api_duration_ms? | default 0)
|
||||
let lines_added = ($input | get cost?.total_lines_added? | default 0)
|
||||
let lines_removed = ($input | get cost?.total_lines_removed? | default 0)
|
||||
let exceeds_200k = ($input | get exceeds_200k_tokens? | default false)
|
||||
|
||||
let cache_read = ($input | get context_window?.cache_read_tokens? | default 0)
|
||||
let cache_create = ($input | get context_window?.cache_creation_tokens? | default 0)
|
||||
|
||||
let total_tokens = $total_input + $total_output
|
||||
|
||||
def format-tokens [n: int] {
|
||||
if $n >= 1_000_000 {
|
||||
$"($n / 1_000_000.0 | math round --precision 1)M"
|
||||
} else if $n >= 1_000 {
|
||||
$"($n / 1_000.0 | math round --precision 1)k"
|
||||
} else {
|
||||
$"($n)"
|
||||
}
|
||||
}
|
||||
|
||||
let in_display = (format-tokens ($total_input | into int))
|
||||
let out_display = (format-tokens ($total_output | into int))
|
||||
let tok_display = $"($in_display)/($out_display)"
|
||||
|
||||
let cache_total = $cache_read + $cache_create
|
||||
let cache_display = if $cache_total > 0 {
|
||||
let cache_pct = ($cache_read * 100 / $cache_total | math round --precision 0 | into int)
|
||||
let cache_color = if $cache_pct >= 70 {
|
||||
"\e[32m"
|
||||
} else if $cache_pct >= 40 {
|
||||
"\e[33m"
|
||||
} else {
|
||||
"\e[31m"
|
||||
}
|
||||
$" cache:($cache_color)($cache_pct)%\e[0m"
|
||||
} else { "" }
|
||||
|
||||
let context_display = if $used_pct != null {
|
||||
let color = color-for-pct $used_pct
|
||||
let pct_str = $used_pct | math round --precision 1
|
||||
$"($color)($pct_str)%\e[0m"
|
||||
} else { "--" }
|
||||
|
||||
let cost_cents = ($total_cost * 100 | math round | into int)
|
||||
let cost_dollars = $cost_cents // 100
|
||||
let cost_frac = ($cost_cents mod 100 | math abs | into string | fill -a r -w 2 -c '0')
|
||||
let cost_display = $"$($cost_dollars).($cost_frac)"
|
||||
let elapsed_display = (format-duration ($duration_ms | into int))
|
||||
let wait_display = (format-duration ($api_duration_ms | into int))
|
||||
let churn_display = $"\e[32m+($lines_added)\e[0m/\e[31m-($lines_removed)\e[0m"
|
||||
let marker_200k = if $exceeds_200k { " | \e[31m!200k\e[0m" } else { "" }
|
||||
def format-cwd [dir: string] {
|
||||
if ($dir | is-empty) { return "" }
|
||||
let root_parts = ($root | split row "/")
|
||||
let base = if ($root_parts | length) <= 5 {
|
||||
$root_display
|
||||
} else {
|
||||
let tail = ($root_parts | last 5 | str join "/")
|
||||
$"…/($tail)"
|
||||
}
|
||||
let subpath = if ($dir | str starts-with $root) {
|
||||
$dir | str replace $root "" | str trim -l -c '/'
|
||||
} else { "" }
|
||||
if ($subpath | is-not-empty) {
|
||||
$"\e[36m($base)\e[0m → \e[34m($subpath)\e[0m"
|
||||
} else {
|
||||
$"\e[36m($base)\e[0m"
|
||||
}
|
||||
}
|
||||
|
||||
let cwd_raw = ($input | get workspace?.current_dir? | default "")
|
||||
let cwd_display = if ($cwd_raw | is-not-empty) {
|
||||
let formatted = (format-cwd $cwd_raw)
|
||||
$" | ($formatted)"
|
||||
} else { "" }
|
||||
let quota_section = if ($usage_info | is-not-empty) {
|
||||
" | (usage) " + $usage_info
|
||||
} else { "" }
|
||||
|
||||
print -n $"($model_name) | Ctx: ($context_display) | ($tok_display)($cache_display) | ($cost_display) | t:($elapsed_display) w:($wait_display) | ($churn_display)($marker_200k)($quota_section)($cwd_display)"
|
||||
'';
|
||||
|
||||
# Extract cli.js from the bun --compile --bytecode executable. Since 2.1.113
|
||||
# the npm package ships a bun SEA ELF instead of plain cli.js, but the source
|
||||
# is still embedded as text alongside the V8 parse cache.
|
||||
lift = pkgs.writeScriptBin "lift-claude-bun" /* py */ ''
|
||||
#!${getExe pkgs.python3}
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Skip over .rodata / .text — those contain `// @bun` string literals (error
|
||||
# messages, help text) that would confuse the scanner. The first real module
|
||||
# sat at ~0xd333ec8 in 2.1.113; staying well below that survives future growth.
|
||||
SCAN_FROM: int = 0x6000000
|
||||
|
||||
HEADERS: list[bytes] = [
|
||||
b"// @bun @bytecode @bun-cjs\n(function(exports, require, module, __filename, __dirname) {",
|
||||
b"// @bun @bun-cjs\n(function(exports, require, module, __filename, __dirname) {",
|
||||
]
|
||||
|
||||
CJS_OPEN: bytes = b"(function(exports, require, module, __filename, __dirname) {"
|
||||
CJS_END: bytes = b"})\n\x00"
|
||||
|
||||
|
||||
def find_main_module(data: bytes) -> tuple[int, int]:
|
||||
# In 2.1.117 bun emits cli.js twice: once as a @bytecode blob with the V8
|
||||
# parse cache interleaved between the source and its `})\n\x00` terminator,
|
||||
# and again as a clean source-only copy that terminates normally. Collect
|
||||
# every header past SCAN_FROM and pick the first one whose terminator lies
|
||||
# before the next header — that's the source-only copy.
|
||||
headers: list[tuple[int, int]] = []
|
||||
for header in HEADERS:
|
||||
p: int = SCAN_FROM
|
||||
while True:
|
||||
p = data.find(header, p)
|
||||
if p < 0:
|
||||
break
|
||||
headers.append((p, len(header)))
|
||||
p += 1
|
||||
|
||||
if not headers:
|
||||
sys.exit("lift: no bun CJS module header found past 0x6000000")
|
||||
|
||||
headers.sort()
|
||||
boundaries: list[int] = [p for p, _ in headers] + [len(data)]
|
||||
|
||||
for idx, (start, _) in enumerate(headers):
|
||||
next_header: int = boundaries[idx + 1]
|
||||
end: int = data.find(CJS_END, start, next_header)
|
||||
if end >= 0:
|
||||
return start, end + 3 # include })\n, exclude trailing NUL
|
||||
|
||||
sys.exit("lift: could not find module terminator (})\\n\\x00)")
|
||||
|
||||
|
||||
def unwrap(mod: bytes) -> bytes:
|
||||
nl = mod.find(b"\n")
|
||||
if nl < 0:
|
||||
sys.exit("lift: module has no header newline")
|
||||
body = mod[nl + 1 :]
|
||||
if not body.startswith(CJS_OPEN):
|
||||
sys.exit("lift: module does not open with expected CJS wrapper")
|
||||
body = body[len(CJS_OPEN) :]
|
||||
if body.endswith(b"})\n"):
|
||||
body = body[:-3]
|
||||
elif body.endswith(b"})"):
|
||||
body = body[:-2]
|
||||
else:
|
||||
sys.exit("lift: module does not end with `})` wrapper close")
|
||||
return body
|
||||
|
||||
|
||||
def main() -> None:
|
||||
if len(sys.argv) != 3:
|
||||
sys.exit("usage: lift-claude-bun <claude-binary> <output.cjs>")
|
||||
|
||||
binary = Path(sys.argv[1])
|
||||
output = Path(sys.argv[2])
|
||||
|
||||
data = binary.read_bytes()
|
||||
start, end = find_main_module(data)
|
||||
body = unwrap(data[start:end])
|
||||
|
||||
if b"Anthropic" not in body[:4096]:
|
||||
sys.exit("lift: extracted body is missing Anthropic banner — layout changed?")
|
||||
|
||||
output.write_bytes(body)
|
||||
sys.stderr.write(
|
||||
f"lifted {len(body):,} bytes from {binary.name} "
|
||||
f"(module @ {start:#x}..{end:#x}) -> {output}\n"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
'';
|
||||
|
||||
# Patch the lifted cli.cjs: AGENTS.md loader, macOS config path, hard-disabled
|
||||
# slash commands, telemetry-gate bypass, Av() force-true, 1h prompt cache TTL,
|
||||
# deno bridge spawn via env(1), feature gate flips, background agent timeout
|
||||
# bump, claude-api skill disable, Deno-native OAuth usage fetch.
|
||||
patch = pkgs.writeScriptBin "patch-claude-code-src" /* py */ ''
|
||||
#!${getExe pkgs.python3}
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import sys
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from typing import Union
|
||||
|
||||
type Replacement = Union[bytes, Callable[[re.Match[bytes]], bytes]]
|
||||
|
||||
W: bytes = rb"[\w$]+"
|
||||
# Qualified name: matches `FN` and also `NS.FN` (e.g. `Lf.join`, `Oc7.spawn`).
|
||||
# Since 2.1.113 bun's bundler emits more member-style calls for path/spawn helpers.
|
||||
Q: bytes = rb"[\w$]+(?:\.[\w$]+)*"
|
||||
data: bytes = Path(sys.argv[1]).read_bytes()
|
||||
|
||||
SEARCH_WINDOW: int = 500
|
||||
|
||||
|
||||
def log(msg: str) -> None:
|
||||
sys.stderr.write(msg + "\n")
|
||||
|
||||
|
||||
def patch(label: str, pattern: bytes, replacement: Replacement) -> None:
|
||||
global data
|
||||
data, n = re.subn(pattern, replacement, data)
|
||||
log(f"{label} ({n})")
|
||||
|
||||
|
||||
def replace(label: str, old: bytes, new: bytes) -> None:
|
||||
global data
|
||||
n: int = data.count(old)
|
||||
if n == 0:
|
||||
log(f"{label}: NOT FOUND")
|
||||
return
|
||||
data = data.replace(old, new)
|
||||
log(f"{label} ({n})")
|
||||
|
||||
|
||||
def flip_gates(gates: list[tuple[bytes, str]]) -> None:
|
||||
"""Flip all gate defaults from false to true in a single regex pass."""
|
||||
global data
|
||||
gate_keys: list[bytes] = [g for g, _ in gates]
|
||||
labels: dict[bytes, str] = dict(gates)
|
||||
alternation: bytes = b"|".join(re.escape(g) for g in gate_keys)
|
||||
pat: bytes = W + rb'\("(' + alternation + rb')",!1\)'
|
||||
flipped: set[bytes] = set()
|
||||
|
||||
def replacer(m: re.Match[bytes]) -> bytes:
|
||||
flipped.add(m.group(1))
|
||||
return m[0].replace(b",!1)", b",!0)")
|
||||
|
||||
data, n = re.subn(pat, replacer, data)
|
||||
log(f"feature gates: {n} flipped across {len(flipped)} gates")
|
||||
for key in gate_keys:
|
||||
status = "ok" if key in flipped else "MISSED"
|
||||
log(f" {labels[key]} [{status}]")
|
||||
|
||||
|
||||
# --- AGENTS.md support ---
|
||||
# The CLAUDE.md loader only reads CLAUDE.md. Patch it to also load AGENTS.md
|
||||
# from the same directories. Pattern: let VAR=ME(DIR,"CLAUDE.md");ARR.push(...await XE(VAR,"Project",ARG,BOOL))
|
||||
|
||||
agents_pat: bytes = (
|
||||
rb"let (" + W + rb")=(" + Q + rb")\((" + W + rb'),"CLAUDE\.md"\);'
|
||||
rb"(" + W + rb")\.push\(\.\.\.await (" + W + rb")\(\1,\"Project\",(" + W + rb"),(" + W + rb")\)\)"
|
||||
)
|
||||
|
||||
|
||||
def agents_repl(m: re.Match[bytes]) -> bytes:
|
||||
var, join_fn, dir_, arr, load_fn, arg, flag = [m.group(i) for i in range(1, 8)]
|
||||
return (
|
||||
b'for(let _f of["CLAUDE.md","AGENTS.md"]){let '
|
||||
+ var + b"=" + join_fn + b"(" + dir_ + b",_f);"
|
||||
+ arr + b".push(...await " + load_fn + b"(" + var + b',"Project",' + arg + b"," + flag + b"))}"
|
||||
)
|
||||
|
||||
|
||||
patch("agents.md loader", agents_pat, agents_repl)
|
||||
|
||||
# --- macOS config path ---
|
||||
|
||||
replace(
|
||||
"macOS config path",
|
||||
b'case"macos":return"/Library/Application Support/ClaudeCode"',
|
||||
b'case"macos":return"/etc/claude-code"',
|
||||
)
|
||||
|
||||
# --- Enable hard-disabled slash commands ---
|
||||
|
||||
slash_commands: list[tuple[bytes, str]] = [
|
||||
(b'name:"btw",description:"Ask a quick side question', "/btw"),
|
||||
(b'name:"bridge-kick",description:"Inject bridge failure states', "/bridge-kick"),
|
||||
(b'name:"files",description:"List all files currently in context"', "/files"),
|
||||
]
|
||||
|
||||
for anchor, label in slash_commands:
|
||||
pos: int = data.find(anchor)
|
||||
if pos < 0:
|
||||
log(f"slash command {label}: NOT FOUND")
|
||||
continue
|
||||
window: bytes = data[pos : pos + SEARCH_WINDOW]
|
||||
patched: bytes = window.replace(b"isEnabled:()=>!1", b"isEnabled:()=>!0", 1)
|
||||
if patched == window:
|
||||
log(f"slash command {label}: isEnabled not found in window")
|
||||
continue
|
||||
data = data[:pos] + patched + data[pos + SEARCH_WINDOW :]
|
||||
log(f"slash command {label}: enabled")
|
||||
|
||||
# --- Bypass telemetry gate in feature flag checker ---
|
||||
# The chain is: h8(featureGate) bails to default if !Qo(); Qo()=Ew6();
|
||||
# Ew6()=!Cq6(); Cq6() returns true when on bedrock/vertex/foundry OR when
|
||||
# user-facing telemetry is disabled (s_1()/equivalent). Drop the trailing
|
||||
# telemetry-disabled check so feature gates still resolve with
|
||||
# DISABLE_TELEMETRY=1 while preserving the bedrock/vertex/foundry detection.
|
||||
# Anchor on the stable env-var literal CLAUDE_CODE_USE_BEDROCK; the obfuscated
|
||||
# function name (Cq6) and the trailing wrapper name (s_1) both rotate.
|
||||
|
||||
patch(
|
||||
"telemetry gate (drop telemetry-disabled check)",
|
||||
(
|
||||
rb"function (" + W + rb")\(\)\{return (" + W + rb")\(process\.env\.CLAUDE_CODE_USE_BEDROCK\)"
|
||||
rb"\|\|\2\(process\.env\.CLAUDE_CODE_USE_VERTEX\)"
|
||||
rb"\|\|\2\(process\.env\.CLAUDE_CODE_USE_FOUNDRY\)"
|
||||
rb"\|\|" + W + rb"\(\)\}"
|
||||
),
|
||||
lambda m: re.sub(rb"\|\|" + W + rb"\(\)\}$", b"||!1}", m[0]),
|
||||
)
|
||||
|
||||
# --- Force Av() async-gate to always resolve true ---
|
||||
# Av(flag) is the ASYNC feature-gate resolver. It short-circuits to its default
|
||||
# in two places when telemetry is off: an inline `if(!va())return!1;` AND the
|
||||
# same check inside Irq() which it delegates to. Since Av() hardcodes !1 as the
|
||||
# default passed to Irq, dropping only the inline guard leaves Irq returning
|
||||
# false anyway.
|
||||
#
|
||||
# Every Av() call-site in 2.1.113 targets a gate we intentionally want enabled:
|
||||
# - tengu_ccr_bridge → Qr8() → initReplBridge() auto-connect
|
||||
# - tengu_ccr_bridge_multi_session → multi-session remote control
|
||||
# - tengu_ccr_bundle_seed_enabled → CCR bundle seed
|
||||
# - tengu_harbor → plugin marketplace
|
||||
# None of these are things we want off. Replace the whole body to return !0.
|
||||
# Safe because Av() never writes telemetry — it only reads cached flag state.
|
||||
|
||||
patch(
|
||||
"Av() force-true for telemetry-off builds",
|
||||
# Negative lookahead keeps the body match from extending past the end of Av
|
||||
# into the next function definition (a previous version matched `async
|
||||
# function Bb8(...)` and spanned through Av's tail, obliterating both).
|
||||
# The inner resolver name (Irq → aeq → ...) rotates across versions, so
|
||||
# capture it rather than pinning to a literal.
|
||||
rb"async function (" + W + rb")\(H\)\{(?:(?!async function ).){60,400}?return " + W + rb"\(H,!1,!0\)\}",
|
||||
lambda m: b"async function " + m[1] + b"(H){return !0}",
|
||||
)
|
||||
|
||||
# --- Restore 1h prompt cache TTL when telemetry is off ---
|
||||
# https://github.com/anthropics/claude-code/issues/45381
|
||||
# The GrowthBook allowlist for "ttl":"1h" cache_control falls back to the
|
||||
# default object when telemetry is off. Anthropic now ships
|
||||
# {allowlist:["repl_main_thread*","sdk","auto_mode"]} as the default (up
|
||||
# from the broken {} in earlier versions), so the TUI and SDK already get
|
||||
# 1h TTL — but batch agents and less-common query sources still miss.
|
||||
# Widen the default to ["*"] so everything matches.
|
||||
|
||||
patch(
|
||||
"1h prompt cache TTL fallback",
|
||||
rb'(' + W + rb')\("tengu_prompt_cache_1h_config",\{allowlist:\[[^\]]+\]\}\)\.allowlist\?\?\[\]',
|
||||
lambda m: m[1] + b'("tengu_prompt_cache_1h_config",{allowlist:["*"]}).allowlist??[]',
|
||||
)
|
||||
|
||||
# --- Disable tengu_keybindings_dom (new chord dispatcher) ---
|
||||
# 2.1.118 introduced a DOM-style chord/focus keybinding system behind this
|
||||
# gate. The gate defaults !0 (on). The new system wraps the TUI in a
|
||||
# programmatic focus manager (gt()-guarded useLayoutEffect subscribes to
|
||||
# activeElement and re-focuses a tabIndex ref). During /rewind the message
|
||||
# selector unmounts and remounts in a sequence where the focus target goes
|
||||
# null long enough that keystrokes stop routing — stdin ends up paused,
|
||||
# fd 0 drops out of epoll, and Ctrl-C (a raw 0x03 byte in raw mode) has no
|
||||
# reader. Wedges the TUI hard; only `kill` from another terminal recovers.
|
||||
# The old 117-era dispatcher is still present as the `: old_path` branch
|
||||
# of every gt()?new:old site; flipping the default reverts to it.
|
||||
|
||||
patch(
|
||||
"disable new keybindings dispatcher (causes /rewind hang in 2.1.118)",
|
||||
rb'(' + W + rb')\("tengu_keybindings_dom",!0\)',
|
||||
lambda m: m[1] + b'("tengu_keybindings_dom",!1)',
|
||||
)
|
||||
|
||||
# --- Fix Deno-compile bridge spawn ---
|
||||
# Deno-compiled binaries eat --flags as V8 args, so we route spawns through
|
||||
# env(1) to pass them as normal CLI flags instead.
|
||||
|
||||
patch(
|
||||
"deno bridge spawn fix",
|
||||
rb"let (" + W + rb")=(" + Q + rb")\((" + W + rb")\.execPath,(" + W + rb"),",
|
||||
lambda m: (
|
||||
b"let "
|
||||
+ m[1]
|
||||
+ b"="
|
||||
+ m[2]
|
||||
+ b'("env",["--",'
|
||||
+ m[3]
|
||||
+ b".execPath,..."
|
||||
+ m[4]
|
||||
+ b"],"
|
||||
),
|
||||
)
|
||||
|
||||
# --- Flip feature gates ---
|
||||
# DISABLE_TELEMETRY=1 prevents GrowthBook feature flag resolution, so all gates
|
||||
# fall back to their hardcoded defaults (false). Flip them to true.
|
||||
|
||||
Gate = tuple[bytes, str]
|
||||
|
||||
core_gates: list[Gate] = [
|
||||
(b"tengu_ccr_bridge", "remote control"),
|
||||
(b"tengu_bridge_system_init", "bridge SDK init on connect"),
|
||||
(b"tengu_bridge_client_presence_enabled", "bridge presence heartbeats"),
|
||||
(b"tengu_bridge_requires_action_details", "bridge rich tool-use payloads"),
|
||||
(b"tengu_remote_backend", "remote backend"),
|
||||
(b"tengu_immediate_model_command", "instant /model switching"),
|
||||
(b"tengu_fgts", "fine-grained tool streaming"),
|
||||
(b"tengu_auto_background_agents", "background agent timeout"),
|
||||
(b"tengu_plan_mode_interview_phase", "plan mode interview"),
|
||||
(b"tengu_surreal_dali", "scheduled agents/cron"),
|
||||
]
|
||||
|
||||
memory_gates: list[Gate] = [
|
||||
# (b"tengu_session_memory", "session memory"), # auto-memory; pollutes unrelated convos
|
||||
(b"tengu_pebble_leaf_prune", "message pruning"),
|
||||
(b"tengu_herring_clock", "team memory directory"),
|
||||
(b"tengu_passport_quail", "typed combined memory prompts"),
|
||||
(b"tengu_paper_halyard", "memory dedup in nested dirs"),
|
||||
]
|
||||
|
||||
ux_gates: list[Gate] = [
|
||||
(b"tengu_coral_fern", "grep hints in prompt"),
|
||||
(b"tengu_kairos_brief", "brief output mode"),
|
||||
(b"tengu_destructive_command_warning", "destructive command warnings"),
|
||||
(b"tengu_amber_prism", "permission denial context"),
|
||||
(b"tengu_hawthorn_steeple", "context windowing"),
|
||||
(b"tengu_loud_sugary_rock", "Opus 4.7 terse output guidance"),
|
||||
(b"tengu_verified_vs_assumed", "verified-vs-assumed reporting"),
|
||||
(b"tengu_birch_compass", "/usage 'What's contributing' breakdown block"),
|
||||
# tengu_pewter_brook (fullscreen TUI default) disabled — Ink fullscreen
|
||||
# rendering drops memoized Text children in nested Box columns (/usage
|
||||
# loses its "What's contributing..." bold header, big vertical gaps).
|
||||
# Re-enable by setting `tui: "fullscreen"` in settings.json if desired.
|
||||
]
|
||||
|
||||
tool_gates: list[Gate] = [
|
||||
(b"tengu_chrome_auto_enable", "auto-enable chrome devtools"),
|
||||
(b"tengu_plum_vx3", "web search reranking"),
|
||||
# (b"tengu_moth_copse", "relevant memory recall"), # auto-recall; pollutes unrelated convos
|
||||
(b"tengu_cork_m4q", "batch command processing"),
|
||||
(b"tengu_harbor", "plugin marketplace"),
|
||||
(b"tengu_harbor_permissions", "plugin permissions"),
|
||||
(b"tengu_relay_chain_v1", "parallel command chaining guidance"),
|
||||
(b"tengu_edit_minimalanchor_jrn", "Edit tool minimal-anchor instructions"),
|
||||
(b"tengu_slate_reef", "Read tool clearer offset/limit docs"),
|
||||
(b"tengu_otk_slot_v1", "output-token escalation for complex tasks"),
|
||||
(b"tengu_onyx_basin_m1k", "subagent tool-result truncation"),
|
||||
(b"tengu_sub_nomdrep_q7k", "block subagent report .md writes"),
|
||||
(b"tengu_amber_sentinel", "Monitor tool for streaming bg scripts"),
|
||||
(b"tengu_miraculo_the_bard", "skip penguin-mode startup prefetch"),
|
||||
(b"tengu_noreread_q7m_velvet", "sharper 'wasted read' feedback"),
|
||||
]
|
||||
|
||||
flip_gates(core_gates + memory_gates + ux_gates + tool_gates)
|
||||
|
||||
# --- Bump background agent timeout from 120s to 240s ---
|
||||
|
||||
patch(
|
||||
"background agent timeout",
|
||||
rb'"tengu_auto_background_agents",![01]\)\)return 120000',
|
||||
lambda m: m[0].replace(b"120000", b"240000"),
|
||||
)
|
||||
|
||||
# --- Disable the claude-api bundled skill ---
|
||||
# Registered via vA({name:"claude-api",description:v4_,...}) at bundle-load
|
||||
# time. The description (v4_) is a ~200-token SDK/Bedrock usage matrix with
|
||||
# TRIGGER/SKIP rules that gets injected into every system prompt. We don't
|
||||
# write Anthropic SDK code in this environment, so cut it. Renamed from
|
||||
# `claude-developer-platform` in an earlier release — match on current name.
|
||||
|
||||
patch(
|
||||
"disable claude-api skill",
|
||||
rb'(' + W + rb')\(\{name:"claude-api",description:',
|
||||
lambda m: m[1] + b'({name:"claude-api",isEnabled:()=>!1,description:',
|
||||
)
|
||||
|
||||
# --- Replace usage fetch with self-contained OAuth implementation ---
|
||||
# FO()/eO() falls back to x-api-key when dA()/nA() returns false (telemetry off),
|
||||
# but /api/oauth/usage requires Bearer + oauth beta header. Replace the entire
|
||||
# function with a Deno-native implementation that reads credentials directly.
|
||||
|
||||
usage_fn_pat: bytes = (
|
||||
rb"async function (" + W + rb")\(\)\{"
|
||||
rb"(?:if\(!" + W + rb"\(\)\|\|!" + W + rb"\(\)\)return\{\};)?"
|
||||
rb"let " + W + rb"=" + W + rb"\(\);if\(" + W + rb"&&" + W + rb"\(" + W + rb"\." + W + rb"\)\)return null;"
|
||||
rb"let " + W + rb"=" + W + rb"\(\);if\(" + W + rb"\.error\)throw Error\(\x60Auth error: \x24\{" + W + rb"\.error\}\x60\);"
|
||||
rb"let " + W + rb"=\{[^}]+\}," + W + rb"=\x60\x24\{(" + W + rb")\(\)\.(" + W + rb")\}/api/oauth/usage\x60;"
|
||||
rb"return\(await (" + W + rb")\.get\(" + W + rb",\{headers:" + W + rb",timeout:5000\}\)\)\.data\}"
|
||||
)
|
||||
|
||||
usage_fn_match: re.Match[bytes] | None = re.search(usage_fn_pat, data)
|
||||
if usage_fn_match:
|
||||
fn_name: bytes = usage_fn_match.group(1)
|
||||
config_fn: bytes = usage_fn_match.group(2)
|
||||
base_url_key: bytes = usage_fn_match.group(3)
|
||||
http_client: bytes = usage_fn_match.group(4)
|
||||
replacement: bytes = (
|
||||
b"async function " + fn_name + b"(){"
|
||||
b"const _cd=(process.env.CLAUDE_CONFIG_DIR||"
|
||||
b'(Deno.env.get("HOME")+"/.config/claude"));'
|
||||
b"let _tk;"
|
||||
b'try{const _cr=JSON.parse(new TextDecoder().decode('
|
||||
b'Deno.readFileSync(_cd+"/.credentials.json")));'
|
||||
b"_tk=_cr?.claudeAiOauth?.accessToken}catch{return{}}"
|
||||
b"if(!_tk)return{};"
|
||||
b'const _cp="/tmp/.claude-usage-"+_tk.slice(-8)+".json";'
|
||||
b"try{const _s=Deno.statSync(_cp);"
|
||||
b"if(Date.now()-_s.mtime.getTime()<60000)"
|
||||
b'return JSON.parse(new TextDecoder().decode(Deno.readFileSync(_cp)))}catch{}'
|
||||
b"const _h={" + b'"Content-Type":"application/json",'
|
||||
b'"Authorization":"Bearer "+_tk,'
|
||||
b'"anthropic-beta":"oauth-2025-04-20"};'
|
||||
b"const _u=`''${" + config_fn + b"()." + base_url_key + b"}/api/oauth/usage`;"
|
||||
b"const _r=(await " + http_client + b".get(_u,{headers:_h,timeout:5000})).data;"
|
||||
b'try{Deno.writeTextFileSync(_cp,JSON.stringify(_r))}catch{}'
|
||||
b"return _r}"
|
||||
)
|
||||
data = data.replace(usage_fn_match[0], replacement)
|
||||
log("usage fetch: replaced")
|
||||
else:
|
||||
log("usage fetch: pattern NOT FOUND")
|
||||
|
||||
# --- grep/find/rg shim: delegate to absolute Nix store paths ---
|
||||
# claude-code ships a shell shim (o45/i45 → a38) that redefines
|
||||
# `grep`/`find`/`rg` as bash functions which re-exec the claude binary
|
||||
# with argv[0]=ugrep/bfs/rg. In Bun "ant-native" builds this dispatches
|
||||
# to bundled native tools. The Deno repack drops those, so invocations
|
||||
# fail with `error: unknown option '-G'`. Rewrite a38's emitted bash to
|
||||
# call the real tools directly via their Nix store paths (resolved at
|
||||
# build time), so the shim works regardless of PATH and whether the
|
||||
# cached binary is launched through the wrapper or standalone.
|
||||
|
||||
a38_pat: bytes = (
|
||||
rb"function (" + W + rb")\(H,_,q=\[\]\)\{"
|
||||
rb"let (" + W + rb")=q\.length>0\?\x60\$\{q\.join\(\" \"\)\} \"\$@\"\x60:'\"\$@\"';"
|
||||
rb"return\[[\s\S]*?\]\.join\(\x60\n\x60\)\}"
|
||||
)
|
||||
|
||||
|
||||
def a38_repl(m: re.Match[bytes]) -> bytes:
|
||||
fn_name: bytes = m.group(1)
|
||||
loc: bytes = m.group(2)
|
||||
return (
|
||||
b"function " + fn_name + b"(H,_,q=[]){"
|
||||
b"let " + loc + b'=q.length>0?\x60''${q.join(" ")} "$@"\x60:\'"$@"\';'
|
||||
b'let P=({ugrep:"${getExe' pkgs.ugrep "ugrep"}",'
|
||||
b'bfs:"${getExe pkgs.bfs}",'
|
||||
b'rg:"${getExe pkgs.ripgrep}"})[_]||_;'
|
||||
b"return\x60function ''${H} { "
|
||||
b'if ! [ -x ''${P} ]; then command ''${H} "$@"; return; fi; '
|
||||
b"''${P} ''${" + loc + b"}; }\x60}"
|
||||
)
|
||||
|
||||
|
||||
patch("grep/find/rg shim: use absolute store paths", a38_pat, a38_repl)
|
||||
|
||||
# --- Bun runtime polyfill ---
|
||||
# Since 2.1.128 the bundle calls Bun.* APIs unguarded (Bun.stringWidth,
|
||||
# Bun.semver, Bun.hash, Bun.spawn, Bun.YAML, Bun.Transpiler, Bun.listen,
|
||||
# Bun.which, Bun.wrapAnsi, Bun.stripANSI, Bun.embeddedFiles, Bun.gc,
|
||||
# Bun.generateHeapSnapshot, Bun.JSONL, Bun.Terminal, Bun.version). Under
|
||||
# Deno these throw `ReferenceError: Bun is not defined` at first use
|
||||
# (Bun.stringWidth fires in a column-width helper called during banner
|
||||
# render). Define globalThis.Bun upfront with Node-backed equivalents so
|
||||
# bare `Bun.X` lookups resolve.
|
||||
#
|
||||
# Bun.Terminal and Bun.JSONL are intentionally left absent: the bundle
|
||||
# already has fallback paths gated on `typeof Bun.Terminal<"u"` and
|
||||
# `Bun.JSONL?.parseChunk`, so leaving them undefined preserves the
|
||||
# built-in "running under Node?" degradation rather than half-emulating.
|
||||
|
||||
bun_shim: bytes = rb"""(()=>{if(typeof globalThis.Bun!=="undefined")return;
|
||||
const sw=require("string-width"),sa=require("strip-ansi"),wa=require("wrap-ansi");
|
||||
const sv=require("semver"),ya=require("yaml");
|
||||
const cp=require("child_process"),fs=require("fs"),path=require("path");
|
||||
const crypto=require("crypto"),net=require("net");
|
||||
function bunHash(input){const buf=Buffer.isBuffer(input)?input:Buffer.from(typeof input==="string"?input:String(input));return crypto.createHash("sha1").update(buf).digest().readBigUInt64LE(0);}
|
||||
function bunSpawn(cmd,opts){opts=opts||{};const[bin,...args]=cmd;const stdio=["pipe","pipe",opts.stderr==="ignore"?"ignore":"pipe"];const child=cp.spawn(bin,args,{cwd:opts.cwd,env:opts.env||process.env,stdio,argv0:opts.argv0});const exited=new Promise(r=>child.on("exit",c=>r(c==null?1:c)));return{pid:child.pid,stdin:child.stdin,stdout:child.stdout,stderr:child.stderr,exitCode:null,killed:false,kill(s){try{child.kill(s)}catch{}this.killed=true},async wait(){return await exited},exited};}
|
||||
function bunListen(opts){const h=opts.socket||{};const server=net.createServer(s=>{s.data=undefined;if(h.open)try{h.open(s)}catch{}s.on("data",d=>h.data&&h.data(s,d));s.on("close",()=>h.close&&h.close(s));s.on("error",e=>h.error&&h.error(s,e));});server.listen(opts.port||0,opts.hostname||"127.0.0.1");return server;}
|
||||
class BunTranspiler{constructor(o){this.opts=o}transformSync(s){return s}}
|
||||
globalThis.Bun={version:"1.3.13",embeddedFiles:[],stringWidth:(s,o)=>sw(String(s||""),o),stripANSI:s=>sa(String(s||"")),wrapAnsi:(s,w,o)=>wa(String(s||""),w,o),semver:{satisfies:(a,b)=>sv.satisfies(a,b),order:(a,b)=>sv.compare(a,b)},hash:bunHash,which(cmd){const dirs=(process.env.PATH||"").split(path.delimiter);for(const d of dirs){const f=path.join(d,cmd);try{fs.accessSync(f,fs.constants.X_OK);return f;}catch{}}return null;},spawn:bunSpawn,listen:bunListen,YAML:{parse:s=>ya.parse(s),stringify:(o,r,i)=>ya.stringify(o,r,i)},Transpiler:BunTranspiler,generateHeapSnapshot:()=>new ArrayBuffer(0),gc:()=>{}};
|
||||
})();
|
||||
"""
|
||||
|
||||
data = bun_shim + data
|
||||
log("Bun runtime polyfill: prepended")
|
||||
|
||||
Path(sys.argv[1]).write_bytes(data)
|
||||
'';
|
||||
|
||||
settings = {
|
||||
"$schema" = "https://json.schemastore.org/claude-code-settings.json";
|
||||
|
||||
env = {
|
||||
CLAUDE_BASH_NO_LOGIN = "1";
|
||||
CLAUDE_CODE_DISABLE_ADAPTIVE_THINKING = "1";
|
||||
CLAUDE_CODE_DISABLE_FEEDBACK_SURVEY = "1";
|
||||
CLAUDE_CODE_DISABLE_TERMINAL_TITLE = "1";
|
||||
CLAUDE_CODE_EAGER_FLUSH = "1";
|
||||
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS = "1";
|
||||
CLAUDE_CODE_FORCE_GLOBAL_CACHE = "1";
|
||||
CLAUDE_CODE_HIDE_ACCOUNT_INFO = "1";
|
||||
CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY = "20";
|
||||
CLAUDE_CODE_PLAN_V2_AGENT_COUNT = "5";
|
||||
CLAUDE_CODE_PLAN_V2_EXPLORE_AGENT_COUNT = "5";
|
||||
DISABLE_AUTO_COMPACT = "1";
|
||||
DISABLE_AUTOUPDATER = "1";
|
||||
DISABLE_COST_WARNINGS = "1";
|
||||
DISABLE_ERROR_REPORTING = "1";
|
||||
DISABLE_INSTALLATION_CHECKS = "1";
|
||||
DISABLE_TELEMETRY = "1";
|
||||
ENABLE_MCP_LARGE_OUTPUT_FILES = "1";
|
||||
ENABLE_TOOL_SEARCH = "auto:5";
|
||||
MAX_THINKING_TOKENS = "31999";
|
||||
MCP_CONNECTION_NONBLOCKING = "1";
|
||||
RTK_TELEMETRY_DISABLED = "1";
|
||||
USE_BUILTIN_RIPGREP = "0";
|
||||
UV_THREADPOOL_SIZE = "16";
|
||||
};
|
||||
|
||||
attribution.commit = "";
|
||||
attribution.pr = "";
|
||||
|
||||
permissions.allow = [
|
||||
"Read"
|
||||
];
|
||||
permissions.defaultMode = "bypassPermissions";
|
||||
|
||||
extraKnownMarketplaces.openai-codex.source = {
|
||||
source = "github";
|
||||
repo = "openai/codex-plugin-cc";
|
||||
};
|
||||
|
||||
enabledPlugins = {
|
||||
"clangd-lsp@claude-plugins-official" = true;
|
||||
"code-review@claude-plugins-official" = true;
|
||||
"code-simplifier@claude-plugins-official" = true;
|
||||
"codex@openai-codex" = true;
|
||||
"kotlin-lsp@claude-plugins-official" = true;
|
||||
"ralph-loop@claude-plugins-official" = true;
|
||||
"rust-analyzer-lsp@claude-plugins-official" = true;
|
||||
};
|
||||
|
||||
statusLine = {
|
||||
type = "command";
|
||||
command = getExe statusLine;
|
||||
};
|
||||
|
||||
skipWebFetchPreflight = true;
|
||||
|
||||
spinnerVerbs.mode = "replace";
|
||||
spinnerVerbs.verbs = [
|
||||
"Redeeming"
|
||||
"Clodding"
|
||||
"Tokenmaxxing"
|
||||
"Slopping"
|
||||
"Clanking"
|
||||
"Churning"
|
||||
"Forgetting"
|
||||
"Splurging"
|
||||
"Ignoring GPL"
|
||||
"Increasing ram prices"
|
||||
];
|
||||
|
||||
cleanupPeriodDays = 90;
|
||||
alwaysThinkingEnabled = true;
|
||||
remoteControlAtStartup = true;
|
||||
showClearContextOnPlanAccept = true;
|
||||
skipDangerousModePermissionPrompt = true;
|
||||
};
|
||||
|
||||
settingsJson = pkgs.writeText "claude-settings.json" (toJSON settings);
|
||||
|
||||
mkSlopLauncher =
|
||||
{
|
||||
name,
|
||||
versionUrl,
|
||||
versionParser,
|
||||
fetch,
|
||||
runtimeDeps ? [ ],
|
||||
preExec ? "",
|
||||
cacheSubdir ? name,
|
||||
}:
|
||||
pkgs.writeScriptBin name /* nu */ ''
|
||||
#!${getExe pkgs.nushell} --no-config-file
|
||||
|
||||
def detect-version [--cache: directory, --rebuild]: nothing -> string {
|
||||
let version_file = $cache | path join "latest-version"
|
||||
let rebuild = ($rebuild | default false)
|
||||
let stale = try { (date now) - (ls $version_file | get 0.modified) > 6hr } catch { true }
|
||||
|
||||
if not ($rebuild or $stale) {
|
||||
return (try {
|
||||
open $version_file
|
||||
} catch {
|
||||
print --stderr $"(ansi yellow_bold)warn:(ansi reset) failed to read latest fetched version"
|
||||
""
|
||||
})
|
||||
}
|
||||
|
||||
let version = try {
|
||||
http get --max-time 5sec ${versionUrl} | ${versionParser}
|
||||
} catch {
|
||||
print --stderr $"(ansi yellow_bold)warn:(ansi reset) can't fetch latest version"
|
||||
return ""
|
||||
}
|
||||
|
||||
try {
|
||||
$version_file | path parse | get parent | mkdir $in
|
||||
$version | save --force $version_file
|
||||
} catch {
|
||||
print --stderr $"(ansi yellow_bold)warn:(ansi reset) failed to save latest fetched version"
|
||||
}
|
||||
|
||||
$version
|
||||
}
|
||||
|
||||
# Find newest cached binary path. Returns "" if none. Includes
|
||||
# symlinks because some tools (opencode) cache binaries via
|
||||
# `ln -s` to a nix-build gcroot, where the mode field reads
|
||||
# `lrwxrwxrwx` and the exec-bit check would reject it. A broken
|
||||
# symlink would still fail at exec time.
|
||||
def latest-cached [--cache: directory]: nothing -> string {
|
||||
try {
|
||||
ls --long ($cache | path join "${name}-*")
|
||||
| where {
|
||||
($in.type == "file" and ($in.mode | str substring 2..<3) == "x")
|
||||
or ($in.type == "symlink")
|
||||
}
|
||||
| sort-by modified
|
||||
| last
|
||||
| get name
|
||||
} catch { "" }
|
||||
}
|
||||
|
||||
def fetch-binary [version: string, binary_path: string, cache: string] {
|
||||
${fetch}
|
||||
}
|
||||
|
||||
def --wrapped main [--rebuild, ...args] {
|
||||
let rebuild = ($rebuild | default false)
|
||||
let cache = $env
|
||||
| get --optional "XDG_CACHE_HOME"
|
||||
| default ($env.HOME | path join ".cache")
|
||||
| path join "${cacheSubdir}"
|
||||
mkdir $cache
|
||||
|
||||
let version = detect-version --cache $cache --rebuild=$rebuild
|
||||
|
||||
# Resolve to a binary path. Both branches converge on the same
|
||||
# PATH/preExec/exec sequence below so offline-fallback isn't a
|
||||
# special codepath that skips environment setup (config syncing,
|
||||
# hook installation, etc).
|
||||
let binary_path = if ($version | is-empty) {
|
||||
print --stderr $"(ansi yellow_bold)warn:(ansi reset) version probe failed, trying latest cached binary"
|
||||
latest-cached --cache $cache
|
||||
} else {
|
||||
let p = $cache | path join $"${name}-($version)"
|
||||
if not ($p | path exists) or $rebuild {
|
||||
fetch-binary $version $p $cache
|
||||
}
|
||||
$p
|
||||
}
|
||||
|
||||
if ($binary_path | is-empty) {
|
||||
print --stderr $"(ansi red_bold)error:(ansi reset) no binary available (network down + nothing cached)"
|
||||
exit 67
|
||||
}
|
||||
|
||||
$env.PATH = ($env.PATH | prepend ("${lib.makeBinPath runtimeDeps}" | split row ":"))
|
||||
${preExec}
|
||||
exec $binary_path ...$args
|
||||
}
|
||||
'';
|
||||
|
||||
mkClod =
|
||||
suffix:
|
||||
mkSlopLauncher {
|
||||
name = "claude-${suffix}";
|
||||
cacheSubdir = "claude-code";
|
||||
versionUrl = "https://registry.npmjs.org/@anthropic-ai/claude-code/latest";
|
||||
versionParser = "get version";
|
||||
runtimeDeps = [
|
||||
pkgs.procps
|
||||
pkgs.ripgrep
|
||||
pkgs.bubblewrap
|
||||
pkgs.socat
|
||||
];
|
||||
preExec = /* nu */ ''
|
||||
let config_dir = $env.HOME | path join ".claude-${suffix}"
|
||||
mkdir $config_dir
|
||||
|
||||
# Sync declarative settings into the writable config dir. Slop refuses
|
||||
# to read a read-only settings.json in place, so we have to copy.
|
||||
cp --force ${settingsJson} ($config_dir | path join "settings.json")
|
||||
|
||||
r#'${toJSON settings.env}'# | from json | load-env
|
||||
$env.CLAUDE_CONFIG_DIR = $config_dir
|
||||
'';
|
||||
fetch = /* nu */ ''
|
||||
let arch = match ($nu.os-info.arch | str downcase) {
|
||||
"x86_64" | "x64" | "amd64" => "x64"
|
||||
"aarch64" | "arm64" => "arm64"
|
||||
$arch => { error make { msg: $"unsupported arch: ($arch)" } }
|
||||
}
|
||||
|
||||
let platform = match ($nu.os-info.name | str downcase) {
|
||||
"linux" => $"linux-($arch)"
|
||||
"macos" | "darwin" => $"darwin-($arch)"
|
||||
$os => { error make { msg: $"unsupported os: ($os)" } }
|
||||
}
|
||||
|
||||
let pkg = $"@anthropic-ai/claude-code-($platform)"
|
||||
let tarball_url = $"https://registry.npmjs.org/($pkg)/-/claude-code-($platform)-($version).tgz"
|
||||
|
||||
let tgz_dir = $cache | path join "tgz"
|
||||
mkdir $tgz_dir
|
||||
let tgz = $tgz_dir | path join $"claude-code-($platform)-($version).tgz"
|
||||
|
||||
# Download to .tmp + atomic rename so an interrupted run doesn't
|
||||
# leave a partial tarball cached forever (sticky-bad).
|
||||
if not ($tgz | path exists) {
|
||||
let tmp = $"($tgz).tmp"
|
||||
print --stderr $"(ansi cyan)fetch:(ansi reset) ($tarball_url)"
|
||||
http get --raw $tarball_url | save --force --raw $tmp
|
||||
mv $tmp $tgz
|
||||
}
|
||||
|
||||
let workdir = $cache | path join $"build-($version)"
|
||||
rm -rf $workdir
|
||||
mkdir $workdir
|
||||
|
||||
^${getExe pkgs.gnutar} -xzf $tgz -C $workdir
|
||||
let native_bin = $workdir | path join "package" "claude"
|
||||
if not ($native_bin | path exists) {
|
||||
error make { msg: $"lift: ($native_bin) missing after tar extract" }
|
||||
}
|
||||
|
||||
let cli = $workdir | path join "cli.cjs"
|
||||
^${getExe lift} $native_bin $cli
|
||||
^${getExe patch} $cli
|
||||
|
||||
# Bun keeps a handful of http/ws/schema libs as runtime-external. Deno has
|
||||
# no equivalent — drop a package.json next to cli.cjs, resolve deps into
|
||||
# a local node_modules/, and bundle the tree into the executable via
|
||||
# --include.
|
||||
r#'${
|
||||
toJSON {
|
||||
name = "claude-code-lifted";
|
||||
type = "commonjs";
|
||||
dependencies = {
|
||||
ws = "^8";
|
||||
undici = "^6";
|
||||
node-fetch = "^3";
|
||||
ajv = "^8";
|
||||
ajv-formats = "^3";
|
||||
yaml = "^2";
|
||||
# Bun shim deps (see "Bun runtime polyfill" in patch-claude-code-src).
|
||||
# Pinned to CJS-compatible majors: ESM-only releases (string-width@5+,
|
||||
# strip-ansi@7+, wrap-ansi@8+) break require() inside cli.cjs.
|
||||
string-width = "^4";
|
||||
strip-ansi = "^6";
|
||||
wrap-ansi = "^7";
|
||||
semver = "^7";
|
||||
};
|
||||
}
|
||||
}'# | save --force ($workdir | path join "package.json")
|
||||
|
||||
cd $workdir
|
||||
$env.DENO_DIR = ($workdir | path join ".deno")
|
||||
^${getExe pkgs.deno} install --node-modules-dir=auto
|
||||
^${getExe pkgs.deno} compile --allow-all --no-check --node-modules-dir=auto --include=node_modules --output $binary_path "cli.cjs"
|
||||
|
||||
# nushell refuses to delete a directory you're currently inside
|
||||
cd $cache
|
||||
rm -rf $workdir
|
||||
'';
|
||||
};
|
||||
in
|
||||
{
|
||||
environment.systemPackages = [
|
||||
(mkClod "amaan")
|
||||
(mkClod "lillis")
|
||||
(mkClod "amaan-work")
|
||||
];
|
||||
|
||||
programs.nix-ld.enable = true;
|
||||
# environment.variables.CLAUDE_CONFIG_DIR = "$XDG_CONFIG_HOME/claude";
|
||||
}
|
||||
7
graphical/llm/codex.nix
Normal file
7
graphical/llm/codex.nix
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
{ pkgs, ... }:
|
||||
{
|
||||
environment.systemPackages = [
|
||||
pkgs.codex
|
||||
pkgs.opencode
|
||||
];
|
||||
}
|
||||
|
|
@ -1,126 +0,0 @@
|
|||
def detect-platform []: nothing -> string {
|
||||
let os = $nu.os-info.name | str downcase
|
||||
let arch = $nu.os-info.arch | str downcase
|
||||
|
||||
let norm_arch = match $arch {
|
||||
"x86_64" | "x64" | "amd64" => "x64"
|
||||
"aarch64" | "arm64" => "arm64"
|
||||
_ => { error make { msg: $"unsupported arch: ($arch)" } }
|
||||
}
|
||||
|
||||
match $os {
|
||||
"linux" => {
|
||||
let musl_ld = $"/lib/ld-musl-($arch).so.1"
|
||||
let suffix = if ($musl_ld | path exists) { "-musl" } else { "" }
|
||||
$"linux-($norm_arch)($suffix)"
|
||||
}
|
||||
"macos" | "darwin" => $"darwin-($norm_arch)"
|
||||
"windows" => { error make { msg: "windows unsupported by this launcher" } }
|
||||
_ => { error make { msg: $"unsupported os: ($os)" } }
|
||||
}
|
||||
}
|
||||
|
||||
def build-binary [version: string, binary_path: string, cache: string] {
|
||||
let platform = detect-platform
|
||||
let pkg = $"@anthropic-ai/claude-code-($platform)"
|
||||
let tarball_url = $"https://registry.npmjs.org/($pkg)/-/claude-code-($platform)-($version).tgz"
|
||||
|
||||
let tgz_dir = $cache | path join "tgz"
|
||||
mkdir $tgz_dir
|
||||
let tgz = $tgz_dir | path join $"claude-code-($platform)-($version).tgz"
|
||||
|
||||
if not ($tgz | path exists) {
|
||||
print --stderr $"(ansi cyan)fetch:(ansi reset) ($tarball_url)"
|
||||
http get --raw $tarball_url | save --force --raw $tgz
|
||||
}
|
||||
|
||||
let workdir = $cache | path join $"build-($version)"
|
||||
rm -rf $workdir
|
||||
mkdir $workdir
|
||||
|
||||
run-external $env._TAR "-xzf" $tgz "-C" $workdir
|
||||
let native_bin = $workdir | path join "package" "claude"
|
||||
if not ($native_bin | path exists) {
|
||||
error make { msg: $"lift: ($native_bin) missing after tar extract" }
|
||||
}
|
||||
|
||||
let cli = $workdir | path join "cli.cjs"
|
||||
run-external $env._LIFT_SCRIPT $native_bin $cli
|
||||
run-external $env._PATCH_SCRIPT $cli
|
||||
|
||||
# Bun's bundler keeps a handful of http/ws/schema libs as runtime-external.
|
||||
# Deno has no equivalent provision — drop a package.json next to cli.cjs,
|
||||
# resolve deps into a local node_modules/, and bundle that tree into the
|
||||
# executable via --include.
|
||||
cp --force $env._EXTERNAL_PACKAGE_JSON ($workdir | path join "package.json")
|
||||
|
||||
cd $workdir
|
||||
$env.DENO_DIR = ($workdir | path join ".deno")
|
||||
run-external $env._DENO "install" "--node-modules-dir=auto"
|
||||
run-external $env._DENO "compile" "--allow-all" "--no-check" "--node-modules-dir=auto" "--include=node_modules" "--output" $binary_path "cli.cjs"
|
||||
|
||||
# nushell refuses to delete a directory you're currently inside
|
||||
cd $cache
|
||||
rm -rf $workdir
|
||||
}
|
||||
|
||||
def main --wrapped [...args] {
|
||||
let cache = $env
|
||||
| get --optional "XDG_CACHE_HOME"
|
||||
| default ($env.HOME | path join ".cache")
|
||||
| path join "claude-code"
|
||||
mkdir $cache
|
||||
|
||||
let config_dir = $env | get --optional "CLAUDE_CONFIG_DIR" | default (
|
||||
$env
|
||||
| get --optional "XDG_CONFIG_HOME"
|
||||
| default ($env.HOME | path join ".config")
|
||||
| path join "claude"
|
||||
)
|
||||
mkdir $config_dir
|
||||
|
||||
# Sync declarative settings into writable config dir
|
||||
cp --force $env._SETTINGS_JSON ($config_dir | path join "settings.json")
|
||||
|
||||
let version = do {
|
||||
let version_file = $cache | path join "latest-version"
|
||||
let stale = try { (date now) - (ls $version_file | get 0.modified) > 6hr } catch { true }
|
||||
|
||||
if not $stale { return (try { open $version_file } catch { "" }) }
|
||||
|
||||
let version = try {
|
||||
http get --max-time 5sec https://registry.npmjs.org/@anthropic-ai/claude-code/latest
|
||||
| get version
|
||||
} catch {
|
||||
print --stderr $"(ansi yellow_bold)warn:(ansi reset) version cache stale, can't re-fetch"
|
||||
return ""
|
||||
}
|
||||
|
||||
try {
|
||||
$version | save --force $version_file
|
||||
}
|
||||
$version
|
||||
}
|
||||
|
||||
let binary_path = if ($version | is-empty) {
|
||||
print --stderr $"(ansi yellow_bold)warn:(ansi reset) falling back to latest binary"
|
||||
|
||||
try {
|
||||
glob ($cache | path join "claude-*") | sort | last
|
||||
} catch {
|
||||
print --stderr $"(ansi red_bold)error:(ansi reset) no binary found"
|
||||
exit 67
|
||||
}
|
||||
} else {
|
||||
$cache | path join $"claude-($version)"
|
||||
}
|
||||
|
||||
if not ($binary_path | path exists) {
|
||||
build-binary $version $binary_path $cache
|
||||
}
|
||||
|
||||
$env.PATH = ($env.PATH | prepend ($env._RUNTIME_DEPS | split row ":"))
|
||||
$env._ENV_JSON | load-env
|
||||
|
||||
exec $binary_path ...$args
|
||||
}
|
||||
|
|
@ -1,92 +0,0 @@
|
|||
from __future__ import annotations
|
||||
|
||||
# Extract the cli.js bundle from a bun --compile --bytecode executable.
|
||||
#
|
||||
# Starting with @anthropic-ai/claude-code 2.1.113 the npm package stopped
|
||||
# shipping cli.js and instead publishes platform-specific tarballs that contain
|
||||
# a bun-compiled ELF (~226 MB). The JavaScript is still fully embedded in the
|
||||
# binary as plaintext — the @bytecode marker just means a V8 parse-cache lives
|
||||
# alongside it, not instead of it.
|
||||
#
|
||||
# Layout of each CJS module inside the bun SEA payload:
|
||||
# // @bun[ @bytecode] @bun-cjs\n
|
||||
# (function(exports, require, module, __filename, __dirname) {<BODY>})\n
|
||||
# \x00/$bunfs/root/<next-module-name>\x00...
|
||||
#
|
||||
# Claude Code ships three real modules in the tail region (past 0x6000000):
|
||||
# the main cli (~12 MB), then two tiny native-loader stubs for the optional
|
||||
# image-processor.node and audio-capture.node. Only the first is interesting.
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Skip over .rodata / .text — those contain `// @bun` string literals (error
|
||||
# messages, help text) that would confuse the scanner. The first real module
|
||||
# sat at ~0xd333ec8 in 2.1.113; staying well below that survives future growth.
|
||||
SCAN_FROM: int = 0x6000000
|
||||
|
||||
HEADERS: list[bytes] = [
|
||||
b"// @bun @bytecode @bun-cjs\n(function(exports, require, module, __filename, __dirname) {",
|
||||
b"// @bun @bun-cjs\n(function(exports, require, module, __filename, __dirname) {",
|
||||
]
|
||||
|
||||
CJS_OPEN: bytes = b"(function(exports, require, module, __filename, __dirname) {"
|
||||
CJS_END: bytes = b"})\n\x00"
|
||||
|
||||
|
||||
def find_main_module(data: bytes) -> tuple[int, int]:
|
||||
for header in HEADERS:
|
||||
start = data.find(header, SCAN_FROM)
|
||||
if start >= 0:
|
||||
break
|
||||
else:
|
||||
sys.exit("lift: no bun CJS module header found past 0x6000000")
|
||||
|
||||
end = data.find(CJS_END, start)
|
||||
if end < 0:
|
||||
sys.exit("lift: could not find module terminator (})\\n\\x00)")
|
||||
return start, end + 3 # include })\n, exclude trailing NUL
|
||||
|
||||
|
||||
def unwrap(mod: bytes) -> bytes:
|
||||
nl = mod.find(b"\n")
|
||||
if nl < 0:
|
||||
sys.exit("lift: module has no header newline")
|
||||
body = mod[nl + 1 :]
|
||||
if not body.startswith(CJS_OPEN):
|
||||
sys.exit("lift: module does not open with expected CJS wrapper")
|
||||
body = body[len(CJS_OPEN) :]
|
||||
# tail is either `})\n` or `})`
|
||||
if body.endswith(b"})\n"):
|
||||
body = body[:-3]
|
||||
elif body.endswith(b"})"):
|
||||
body = body[:-2]
|
||||
else:
|
||||
sys.exit("lift: module does not end with `})` wrapper close")
|
||||
return body
|
||||
|
||||
|
||||
def main() -> None:
|
||||
if len(sys.argv) != 3:
|
||||
sys.exit("usage: lift-claude-bun <claude-binary> <output.cjs>")
|
||||
|
||||
binary = Path(sys.argv[1])
|
||||
output = Path(sys.argv[2])
|
||||
|
||||
data = binary.read_bytes()
|
||||
start, end = find_main_module(data)
|
||||
body = unwrap(data[start:end])
|
||||
|
||||
# Sanity: the real claude-code cli.js always contains this legal banner.
|
||||
if b"Anthropic" not in body[:4096]:
|
||||
sys.exit("lift: extracted body is missing Anthropic banner — layout changed?")
|
||||
|
||||
output.write_bytes(body)
|
||||
sys.stderr.write(
|
||||
f"lifted {len(body):,} bytes from {binary.name} "
|
||||
f"(module @ {start:#x}..{end:#x}) -> {output}\n"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -1,152 +0,0 @@
|
|||
{
|
||||
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
inherit (lib) optionals;
|
||||
settings = {
|
||||
"$schema" = "https://json.schemastore.org/claude-code-settings.json";
|
||||
|
||||
env = {
|
||||
CLAUDE_BASH_NO_LOGIN = "1";
|
||||
CLAUDE_CODE_DISABLE_ADAPTIVE_THINKING = "1";
|
||||
CLAUDE_CODE_DISABLE_FEEDBACK_SURVEY = "1";
|
||||
CLAUDE_CODE_DISABLE_TERMINAL_TITLE = "1";
|
||||
CLAUDE_CODE_EAGER_FLUSH = "1";
|
||||
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS = "1";
|
||||
CLAUDE_CODE_FORCE_GLOBAL_CACHE = "1";
|
||||
CLAUDE_CODE_HIDE_ACCOUNT_INFO = "1";
|
||||
CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY = "20";
|
||||
CLAUDE_CODE_PLAN_V2_AGENT_COUNT = "5";
|
||||
CLAUDE_CODE_PLAN_V2_EXPLORE_AGENT_COUNT = "5";
|
||||
DISABLE_AUTO_COMPACT = "1";
|
||||
DISABLE_AUTOUPDATER = "1";
|
||||
DISABLE_COST_WARNINGS = "1";
|
||||
DISABLE_ERROR_REPORTING = "1";
|
||||
DISABLE_INSTALLATION_CHECKS = "1";
|
||||
DISABLE_TELEMETRY = "1";
|
||||
ENABLE_MCP_LARGE_OUTPUT_FILES = "1";
|
||||
ENABLE_TOOL_SEARCH = "auto:5";
|
||||
MAX_THINKING_TOKENS = "31999";
|
||||
MCP_CONNECTION_NONBLOCKING = "1";
|
||||
UV_THREADPOOL_SIZE = "16";
|
||||
};
|
||||
|
||||
attribution = {
|
||||
commit = "";
|
||||
pr = "";
|
||||
};
|
||||
|
||||
permissions = {
|
||||
allow = [
|
||||
"Read"
|
||||
];
|
||||
defaultMode = "bypassPermissions";
|
||||
};
|
||||
|
||||
statusLine = {
|
||||
type = "command";
|
||||
command = "/etc/claude/statusline-command.nu";
|
||||
};
|
||||
|
||||
enabledPlugins = {
|
||||
"clangd-lsp@claude-plugins-official" = true;
|
||||
"rust-analyzer-lsp@claude-plugins-official" = true;
|
||||
"context7@claude-plugins-official" = true;
|
||||
"code-review@claude-plugins-official" = true;
|
||||
"linear@claude-plugins-official" = true;
|
||||
};
|
||||
|
||||
skipWebFetchPreflight = true;
|
||||
|
||||
spinnerVerbs = {
|
||||
mode = "replace";
|
||||
verbs = [
|
||||
"Redeeming"
|
||||
"Clodding"
|
||||
"Tokenmaxxing"
|
||||
"Slopping"
|
||||
"Clanking"
|
||||
"Churning"
|
||||
"Forgetting"
|
||||
"Splurging"
|
||||
"Ignoring GPL"
|
||||
"Increasing ram prices"
|
||||
];
|
||||
};
|
||||
|
||||
cleanupPeriodDays = 90;
|
||||
alwaysThinkingEnabled = true;
|
||||
remoteControlAtStartup = true;
|
||||
showClearContextOnPlanAccept = true;
|
||||
skipDangerousModePermissionPrompt = true;
|
||||
};
|
||||
|
||||
settingsJson = pkgs.writeText "claude-settings.json" (builtins.toJSON settings);
|
||||
|
||||
runtimeDeps = lib.makeBinPath ([
|
||||
pkgs.bash
|
||||
pkgs.procps
|
||||
pkgs.ripgrep
|
||||
pkgs.bubblewrap
|
||||
pkgs.socat
|
||||
]);
|
||||
|
||||
patchScript = pkgs.writeScript "patch-claude-src" ''
|
||||
#!${pkgs.python3}/bin/python3
|
||||
${builtins.readFile ./patch-claude-src.py}
|
||||
'';
|
||||
|
||||
liftScript = pkgs.writeScript "lift-claude-bun" ''
|
||||
#!${pkgs.python3}/bin/python3
|
||||
${builtins.readFile ./lift-claude-bun.py}
|
||||
'';
|
||||
|
||||
externalPackageJson = pkgs.writeText "claude-code-external-package.json" (
|
||||
builtins.toJSON {
|
||||
name = "claude-code-lifted";
|
||||
type = "commonjs";
|
||||
dependencies = {
|
||||
ws = "^8";
|
||||
undici = "^6";
|
||||
node-fetch = "^3";
|
||||
ajv = "^8";
|
||||
ajv-formats = "^3";
|
||||
yaml = "^2";
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
mkClod =
|
||||
suffix:
|
||||
let
|
||||
finalSuffix = "-${suffix}";
|
||||
in
|
||||
pkgs.writeScriptBin "claude${finalSuffix}" ''
|
||||
#!${pkgs.nushell}/bin/nu --no-config-file
|
||||
|
||||
$env._SETTINGS_JSON = "${settingsJson}"
|
||||
$env._DENO = "${pkgs.deno}/bin/deno"
|
||||
$env._PATCH_SCRIPT = "${patchScript}"
|
||||
$env._LIFT_SCRIPT = "${liftScript}"
|
||||
$env._EXTERNAL_PACKAGE_JSON = "${externalPackageJson}"
|
||||
$env._TAR = "${pkgs.gnutar}/bin/tar"
|
||||
$env._RUNTIME_DEPS = "${runtimeDeps}"
|
||||
$env._ENV_JSON = ${lib.strings.toJSON settings.env}
|
||||
$env.CLAUDE_CONFIG_DIR = ("~/.claude${finalSuffix}" | path expand)
|
||||
|
||||
${builtins.readFile ./launcher.nu}
|
||||
'';
|
||||
in
|
||||
{
|
||||
environment.systemPackages = [
|
||||
(mkClod "lillis")
|
||||
(mkClod "amaan")
|
||||
];
|
||||
|
||||
environment.etc."claude/statusline-command.nu".source = ./statusline-command.nu;
|
||||
|
||||
programs.nix-ld.enable = true;
|
||||
}
|
||||
|
|
@ -1,231 +0,0 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import sys
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from typing import Union
|
||||
|
||||
type Replacement = Union[bytes, Callable[[re.Match[bytes]], bytes]]
|
||||
|
||||
W: bytes = rb"[\w$]+"
|
||||
# Qualified name: matches `FN` and also `NS.FN` (e.g. `Lf.join`, `Oc7.spawn`).
|
||||
# Since 2.1.113 bun's bundler emits more member-style calls for path/spawn helpers.
|
||||
Q: bytes = rb"[\w$]+(?:\.[\w$]+)*"
|
||||
data: bytes = Path(sys.argv[1]).read_bytes()
|
||||
|
||||
SEARCH_WINDOW: int = 500
|
||||
|
||||
|
||||
def log(msg: str) -> None:
|
||||
sys.stderr.write(msg + "\n")
|
||||
|
||||
|
||||
def patch(label: str, pattern: bytes, replacement: Replacement) -> None:
|
||||
global data
|
||||
data, n = re.subn(pattern, replacement, data)
|
||||
log(f"{label} ({n})")
|
||||
|
||||
|
||||
def replace(label: str, old: bytes, new: bytes) -> None:
|
||||
global data
|
||||
n: int = data.count(old)
|
||||
if n == 0:
|
||||
log(f"{label}: NOT FOUND")
|
||||
return
|
||||
data = data.replace(old, new)
|
||||
log(f"{label} ({n})")
|
||||
|
||||
|
||||
def flip_gates(gates: list[tuple[bytes, str]]) -> None:
|
||||
"""Flip all gate defaults from false to true in a single regex pass."""
|
||||
global data
|
||||
gate_keys: list[bytes] = [g for g, _ in gates]
|
||||
labels: dict[bytes, str] = dict(gates)
|
||||
alternation: bytes = b"|".join(re.escape(g) for g in gate_keys)
|
||||
pat: bytes = W + rb'\("(' + alternation + rb')",!1\)'
|
||||
flipped: set[bytes] = set()
|
||||
|
||||
def replacer(m: re.Match[bytes]) -> bytes:
|
||||
flipped.add(m.group(1))
|
||||
return m[0].replace(b",!1)", b",!0)")
|
||||
|
||||
data, n = re.subn(pat, replacer, data)
|
||||
log(f"feature gates: {n} flipped across {len(flipped)} gates")
|
||||
for key in gate_keys:
|
||||
status = "ok" if key in flipped else "MISSED"
|
||||
log(f" {labels[key]} [{status}]")
|
||||
|
||||
|
||||
# --- AGENTS.md support ---
|
||||
# The CLAUDE.md loader only reads CLAUDE.md. Patch it to also load AGENTS.md
|
||||
# from the same directories. Pattern: let VAR=ME(DIR,"CLAUDE.md");ARR.push(...await XE(VAR,"Project",ARG,BOOL))
|
||||
|
||||
agents_pat: bytes = (
|
||||
rb"let (" + W + rb")=(" + Q + rb")\((" + W + rb'),"CLAUDE\.md"\);'
|
||||
rb"(" + W + rb")\.push\(\.\.\.await (" + W + rb")\(\1,\"Project\",(" + W + rb"),(" + W + rb")\)\)"
|
||||
)
|
||||
|
||||
|
||||
def agents_repl(m: re.Match[bytes]) -> bytes:
|
||||
var, join_fn, dir_, arr, load_fn, arg, flag = [m.group(i) for i in range(1, 8)]
|
||||
return (
|
||||
b'for(let _f of["CLAUDE.md","AGENTS.md"]){let '
|
||||
+ var + b"=" + join_fn + b"(" + dir_ + b",_f);"
|
||||
+ arr + b".push(...await " + load_fn + b"(" + var + b',"Project",' + arg + b"," + flag + b"))}"
|
||||
)
|
||||
|
||||
|
||||
patch("agents.md loader", agents_pat, agents_repl)
|
||||
|
||||
# --- macOS config path ---
|
||||
|
||||
replace(
|
||||
"macOS config path",
|
||||
b'case"macos":return"/Library/Application Support/ClaudeCode"',
|
||||
b'case"macos":return"/etc/claude-code"',
|
||||
)
|
||||
|
||||
# --- Enable hard-disabled slash commands ---
|
||||
|
||||
slash_commands: list[tuple[bytes, str]] = [
|
||||
(b'name:"btw",description:"Ask a quick side question', "/btw"),
|
||||
(b'name:"bridge-kick",description:"Inject bridge failure states', "/bridge-kick"),
|
||||
(b'name:"files",description:"List all files currently in context"', "/files"),
|
||||
]
|
||||
|
||||
for anchor, label in slash_commands:
|
||||
pos: int = data.find(anchor)
|
||||
if pos < 0:
|
||||
log(f"slash command {label}: NOT FOUND")
|
||||
continue
|
||||
window: bytes = data[pos : pos + SEARCH_WINDOW]
|
||||
patched: bytes = window.replace(b"isEnabled:()=>!1", b"isEnabled:()=>!0", 1)
|
||||
if patched == window:
|
||||
log(f"slash command {label}: isEnabled not found in window")
|
||||
continue
|
||||
data = data[:pos] + patched + data[pos + SEARCH_WINDOW :]
|
||||
log(f"slash command {label}: enabled")
|
||||
|
||||
# --- Bypass telemetry gate in feature flag checker ---
|
||||
# The chain is: h8(featureGate) bails to default if !Qo(); Qo()=Ew6();
|
||||
# Ew6()=!Cq6(); Cq6() returns true when on bedrock/vertex/foundry OR when
|
||||
# user-facing telemetry is disabled (s_1()/equivalent). Drop the trailing
|
||||
# telemetry-disabled check so feature gates still resolve with
|
||||
# DISABLE_TELEMETRY=1 while preserving the bedrock/vertex/foundry detection.
|
||||
# Anchor on the stable env-var literal CLAUDE_CODE_USE_BEDROCK; the obfuscated
|
||||
# function name (Cq6) and the trailing wrapper name (s_1) both rotate.
|
||||
|
||||
patch(
|
||||
"telemetry gate (drop telemetry-disabled check)",
|
||||
(
|
||||
rb"function (" + W + rb")\(\)\{return (" + W + rb")\(process\.env\.CLAUDE_CODE_USE_BEDROCK\)"
|
||||
rb"\|\|\2\(process\.env\.CLAUDE_CODE_USE_VERTEX\)"
|
||||
rb"\|\|\2\(process\.env\.CLAUDE_CODE_USE_FOUNDRY\)"
|
||||
rb"\|\|" + W + rb"\(\)\}"
|
||||
),
|
||||
lambda m: re.sub(rb"\|\|" + W + rb"\(\)\}$", b"||!1}", m[0]),
|
||||
)
|
||||
|
||||
# --- Restore 1h prompt cache TTL when telemetry is off ---
|
||||
# https://github.com/anthropics/claude-code/issues/45381
|
||||
# The GrowthBook allowlist for "ttl":"1h" cache_control falls back to the
|
||||
# default object when telemetry is off. Anthropic now ships
|
||||
# {allowlist:["repl_main_thread*","sdk","auto_mode"]} as the default (up
|
||||
# from the broken {} in earlier versions), so the TUI and SDK already get
|
||||
# 1h TTL — but batch agents and less-common query sources still miss.
|
||||
# Widen the default to ["*"] so everything matches.
|
||||
|
||||
patch(
|
||||
"1h prompt cache TTL fallback",
|
||||
rb'(' + W + rb')\("tengu_prompt_cache_1h_config",\{allowlist:\[[^\]]+\]\}\)\.allowlist\?\?\[\]',
|
||||
lambda m: m[1] + b'("tengu_prompt_cache_1h_config",{allowlist:["*"]}).allowlist??[]',
|
||||
)
|
||||
|
||||
# --- Fix Deno-compile bridge spawn ---
|
||||
# Deno-compiled binaries eat --flags as V8 args, so we route spawns through
|
||||
# env(1) to pass them as normal CLI flags instead.
|
||||
|
||||
patch(
|
||||
"deno bridge spawn fix",
|
||||
rb"let (" + W + rb")=(" + Q + rb")\((" + W + rb")\.execPath,(" + W + rb"),",
|
||||
lambda m: (
|
||||
b"let "
|
||||
+ m[1]
|
||||
+ b"="
|
||||
+ m[2]
|
||||
+ b'("env",["--",'
|
||||
+ m[3]
|
||||
+ b".execPath,..."
|
||||
+ m[4]
|
||||
+ b"],"
|
||||
),
|
||||
)
|
||||
|
||||
# --- Flip feature gates ---
|
||||
# DISABLE_TELEMETRY=1 prevents GrowthBook feature flag resolution, so all gates
|
||||
# fall back to their hardcoded defaults (false). Flip them to true.
|
||||
|
||||
Gate = tuple[bytes, str]
|
||||
|
||||
core_gates: list[Gate] = [
|
||||
(b"tengu_ccr_bridge", "remote control"),
|
||||
(b"tengu_bridge_system_init", "bridge SDK init on connect"),
|
||||
(b"tengu_bridge_client_presence_enabled", "bridge presence heartbeats"),
|
||||
(b"tengu_bridge_requires_action_details", "bridge rich tool-use payloads"),
|
||||
(b"tengu_remote_backend", "remote backend"),
|
||||
(b"tengu_immediate_model_command", "instant /model switching"),
|
||||
(b"tengu_fgts", "fine-grained tool streaming"),
|
||||
(b"tengu_auto_background_agents", "background agent timeout"),
|
||||
(b"tengu_plan_mode_interview_phase", "plan mode interview"),
|
||||
(b"tengu_surreal_dali", "scheduled agents/cron"),
|
||||
]
|
||||
|
||||
memory_gates: list[Gate] = [
|
||||
# (b"tengu_session_memory", "session memory"), # auto-memory; pollutes unrelated convos
|
||||
(b"tengu_pebble_leaf_prune", "message pruning"),
|
||||
(b"tengu_herring_clock", "team memory directory"),
|
||||
(b"tengu_passport_quail", "typed combined memory prompts"),
|
||||
(b"tengu_paper_halyard", "memory dedup in nested dirs"),
|
||||
]
|
||||
|
||||
ux_gates: list[Gate] = [
|
||||
(b"tengu_coral_fern", "grep hints in prompt"),
|
||||
(b"tengu_kairos_brief", "brief output mode"),
|
||||
(b"tengu_destructive_command_warning", "destructive command warnings"),
|
||||
(b"tengu_amber_prism", "permission denial context"),
|
||||
(b"tengu_hawthorn_steeple", "context windowing"),
|
||||
(b"tengu_loud_sugary_rock", "Opus 4.7 terse output guidance"),
|
||||
(b"tengu_verified_vs_assumed", "verified-vs-assumed reporting"),
|
||||
(b"tengu_pewter_brook", "fullscreen TUI default"),
|
||||
]
|
||||
|
||||
tool_gates: list[Gate] = [
|
||||
(b"tengu_chrome_auto_enable", "auto-enable chrome devtools"),
|
||||
(b"tengu_plum_vx3", "web search reranking"),
|
||||
# (b"tengu_moth_copse", "relevant memory recall"), # auto-recall; pollutes unrelated convos
|
||||
(b"tengu_cork_m4q", "batch command processing"),
|
||||
(b"tengu_harbor", "plugin marketplace"),
|
||||
(b"tengu_harbor_permissions", "plugin permissions"),
|
||||
(b"tengu_relay_chain_v1", "parallel command chaining guidance"),
|
||||
(b"tengu_edit_minimalanchor_jrn", "Edit tool minimal-anchor instructions"),
|
||||
(b"tengu_slate_reef", "Read tool clearer offset/limit docs"),
|
||||
(b"tengu_otk_slot_v1", "output-token escalation for complex tasks"),
|
||||
(b"tengu_onyx_basin_m1k", "subagent tool-result truncation"),
|
||||
(b"tengu_sub_nomdrep_q7k", "block subagent report .md writes"),
|
||||
(b"tengu_amber_sentinel", "Monitor tool for streaming bg scripts"),
|
||||
(b"tengu_miraculo_the_bard", "skip penguin-mode startup prefetch"),
|
||||
(b"tengu_noreread_q7m_velvet", "sharper 'wasted read' feedback"),
|
||||
]
|
||||
|
||||
flip_gates(core_gates + memory_gates + ux_gates + tool_gates)
|
||||
|
||||
# --- Bump background agent timeout from 120s to 240s ---
|
||||
|
||||
patch(
|
||||
"background agent timeout",
|
||||
rb'"tengu_auto_background_agents",![01]\)\)return 120000',
|
||||
lambda m: m[0].replace(b"120000", b"240000"),
|
||||
)
|
||||
|
||||
Path(sys.argv[1]).write_bytes(data)
|
||||
|
|
@ -1,137 +0,0 @@
|
|||
#!/usr/bin/env -S nu --no-config-file
|
||||
|
||||
def format-duration [ms: int] {
|
||||
let total_s = $ms // 1000
|
||||
let h = $total_s // 3600
|
||||
let m = ($total_s mod 3600) // 60
|
||||
let s = $total_s mod 60
|
||||
if $h > 0 {
|
||||
$"($h)h($m | fill -a r -w 2 -c '0')m($s | fill -a r -w 2 -c '0')s"
|
||||
} else if $m > 0 {
|
||||
$"($m)m($s | fill -a r -w 2 -c '0')s"
|
||||
} else {
|
||||
$"($s)s"
|
||||
}
|
||||
}
|
||||
|
||||
def color-for-pct [pct: number] {
|
||||
let pct_int = $pct | math floor | into int
|
||||
if $pct_int >= 80 {
|
||||
"\e[31m"
|
||||
} else if $pct_int >= 50 {
|
||||
"\e[33m"
|
||||
} else {
|
||||
"\e[32m"
|
||||
}
|
||||
}
|
||||
|
||||
def format-rate-limits [input: record] {
|
||||
let session_pct = try { $input | get rate_limits.five_hour.used_percentage } catch { null }
|
||||
let week_pct = try { $input | get rate_limits.seven_day.used_percentage } catch { null }
|
||||
|
||||
let session_part = if $session_pct != null {
|
||||
let c = color-for-pct $session_pct
|
||||
let v = $session_pct | math round --precision 0 | into int
|
||||
$"session: ($c)($v)%\e[0m"
|
||||
} else { "" }
|
||||
let week_part = if $week_pct != null {
|
||||
let c = color-for-pct $week_pct
|
||||
let v = $week_pct | math round --precision 0 | into int
|
||||
$"week: ($c)($v)%\e[0m"
|
||||
} else { "" }
|
||||
|
||||
[$session_part $week_part] | where {|x| $x | is-not-empty} | str join " "
|
||||
}
|
||||
|
||||
|
||||
# --- Main ---
|
||||
let input = (^cat | from json)
|
||||
|
||||
let usage_info = format-rate-limits $input
|
||||
|
||||
let model_name = ($input | get model?.display_name? | default ($input | get model?.id? | default "unknown"))
|
||||
let used_pct = ($input | get context_window?.used_percentage? | default null)
|
||||
let total_cost = ($input | get cost?.total_cost_usd? | default 0)
|
||||
let total_input = ($input | get context_window?.s_in? | default ($input | get context_window?.total_input_tokens? | default 0))
|
||||
let total_output = ($input | get context_window?.s_out? | default ($input | get context_window?.total_output_tokens? | default 0))
|
||||
let duration_ms = ($input | get cost?.total_duration_ms? | default 0)
|
||||
let api_duration_ms = ($input | get cost?.total_api_duration_ms? | default 0)
|
||||
let lines_added = ($input | get cost?.total_lines_added? | default 0)
|
||||
let lines_removed = ($input | get cost?.total_lines_removed? | default 0)
|
||||
let exceeds_200k = ($input | get exceeds_200k_tokens? | default false)
|
||||
|
||||
let cache_read = ($input | get context_window?.cache_read_tokens? | default 0)
|
||||
let cache_create = ($input | get context_window?.cache_creation_tokens? | default 0)
|
||||
|
||||
let total_tokens = $total_input + $total_output
|
||||
|
||||
def format-tokens [n: int] {
|
||||
if $n >= 1_000_000 {
|
||||
$"($n / 1_000_000.0 | math round --precision 1)M"
|
||||
} else if $n >= 1_000 {
|
||||
$"($n / 1_000.0 | math round --precision 1)k"
|
||||
} else {
|
||||
$"($n)"
|
||||
}
|
||||
}
|
||||
|
||||
let in_display = (format-tokens ($total_input | into int))
|
||||
let out_display = (format-tokens ($total_output | into int))
|
||||
let tok_display = $"($in_display)/($out_display)"
|
||||
|
||||
let cache_total = $cache_read + $cache_create
|
||||
let cache_display = if $cache_total > 0 {
|
||||
let cache_pct = ($cache_read * 100 / $cache_total | math round --precision 0 | into int)
|
||||
let cache_color = if $cache_pct >= 70 {
|
||||
"\e[32m"
|
||||
} else if $cache_pct >= 40 {
|
||||
"\e[33m"
|
||||
} else {
|
||||
"\e[31m"
|
||||
}
|
||||
$" cache:($cache_color)($cache_pct)%\e[0m"
|
||||
} else { "" }
|
||||
|
||||
let context_display = if $used_pct != null {
|
||||
let color = color-for-pct $used_pct
|
||||
let pct_str = $used_pct | math round --precision 1
|
||||
$"($color)($pct_str)%\e[0m"
|
||||
} else { "--" }
|
||||
|
||||
let cost_cents = ($total_cost * 100 | math round | into int)
|
||||
let cost_dollars = $cost_cents // 100
|
||||
let cost_frac = ($cost_cents mod 100 | math abs | into string | fill -a r -w 2 -c '0')
|
||||
let cost_display = $"$($cost_dollars).($cost_frac)"
|
||||
let elapsed_display = (format-duration ($duration_ms | into int))
|
||||
let wait_display = (format-duration ($api_duration_ms | into int))
|
||||
let churn_display = $"\e[32m+($lines_added)\e[0m/\e[31m-($lines_removed)\e[0m"
|
||||
let marker_200k = if $exceeds_200k { " | \e[31m!200k\e[0m" } else { "" }
|
||||
def format-cwd [dir: string] {
|
||||
if ($dir | is-empty) { return "" }
|
||||
let home = ($env.HOME? | default "")
|
||||
let display = if ($home | is-not-empty) and ($dir | str starts-with $home) {
|
||||
let rel = ($dir | str replace $home "" | str trim -l -c '/')
|
||||
$"~/($rel)"
|
||||
} else {
|
||||
$dir
|
||||
}
|
||||
let parts = ($display | split row "/")
|
||||
let shortened = if ($parts | length) <= 5 {
|
||||
$display
|
||||
} else {
|
||||
let tail = ($parts | last 5 | str join "/")
|
||||
$"…/($tail)"
|
||||
}
|
||||
$shortened
|
||||
}
|
||||
|
||||
let cwd_raw = ($input | get workspace?.current_dir? | default "")
|
||||
let cwd_display = if ($cwd_raw | is-not-empty) {
|
||||
let formatted = (format-cwd $cwd_raw)
|
||||
$" | ($formatted)"
|
||||
} else { "" }
|
||||
let quota_section = if ($usage_info | is-not-empty) {
|
||||
" | (usage) " + $usage_info
|
||||
} else { "" }
|
||||
|
||||
print -n $"($model_name) | Ctx: ($context_display) | ($tok_display)($cache_display) | ($cost_display) | t:($elapsed_display) w:($wait_display) | ($churn_display)($marker_200k)($quota_section)($cwd_display)"
|
||||
12
graphical/login.nix
Normal file
12
graphical/login.nix
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
lib,
|
||||
inputs,
|
||||
getFlakePkg,
|
||||
...
|
||||
}:
|
||||
{
|
||||
services.greetd = {
|
||||
enable = true;
|
||||
settings.default_session.command = "${lib.getExe (getFlakePkg inputs.tuigreet)} --sessions /etc/greetd/wayland-sessions --remember-session --background doom";
|
||||
};
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
config,
|
||||
pkgs,
|
||||
mkWrappers,
|
||||
scope,
|
||||
...
|
||||
}:
|
||||
let
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
config,
|
||||
lib,
|
||||
mainUser,
|
||||
scope,
|
||||
...
|
||||
}:
|
||||
let
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
{ ... }: scope "services.syncthing.enable" <| true
|
||||
{ scope, ... }: scope "services.syncthing.enable" <| true
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
{
|
||||
pkgs,
|
||||
lib,
|
||||
config,
|
||||
mkWrappers,
|
||||
inputs,
|
||||
scope,
|
||||
...
|
||||
}:
|
||||
let
|
||||
|
|
@ -60,16 +61,19 @@ let
|
|||
tab-width=180
|
||||
tab-padding=10
|
||||
margin=8
|
||||
|
||||
[key-bindings]
|
||||
tab-overview=Control+Alt+Tab
|
||||
'';
|
||||
|
||||
footPatched = pkgs.foot.overrideAttrs (old: {
|
||||
patches = (old.patches or [ ]) ++ [ ./foot-tabs.patch ];
|
||||
});
|
||||
toesPkg = pkgs.foot.overrideAttrs {
|
||||
src = inputs.toes;
|
||||
};
|
||||
|
||||
foot = wrap {
|
||||
toes = wrap {
|
||||
name = "foot";
|
||||
pkg = footPatched;
|
||||
pkg = toesPkg;
|
||||
args = [ "--config=${footConfig}" ];
|
||||
};
|
||||
in
|
||||
scope "apps.terminal" foot
|
||||
scope "apps.terminal" toes
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue