diff --git a/CHANGELOG.md b/CHANGELOG.md
index c6f8ab99..f32e5b5a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,12 @@ All notable changes to this project are documented here. The format follows
### Changed
+- **Statusline, cold-cache guard, and cheaper `/compact` are off by default.** `setup` /
+ `ensure` no longer first-install them. New machines get `/sub` (and routed subagents)
+ only. An already-owned status line or guard is still refreshed on upgrade; compact
+ model lists already in `config.toml` still apply. Opt in with `llmtrim statusline
+ install`, `llmtrim guard install`, or `llmtrim compact models haiku sonnet`.
+
- **Status watch SVGs show the 0.13 Sub tab.** The README hero
(`status-watch-dark.svg` / `status-watch-light.svg`) still rendered the
0.12 Always→Codex / kimi / grok presets. Tab 4 now matches CLIProxyAPI:
diff --git a/INSTALL.md b/INSTALL.md
index deb75e73..928c66a8 100644
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -100,20 +100,16 @@ curl and the npm one-liner above run this for you. After Cargo, Homebrew, Scoop,
`LLMTRIM_NO_SETUP=1`, run it yourself:
```bash
-llmtrim setup # CA + shell env + autostart + Claude Code integrations + daemon
+llmtrim setup # CA + shell env + autostart + Claude Code /sub + daemon
llmtrim status # live savings dashboard
```
Open a new terminal so tools inherit `HTTPS_PROXY`. Re-running `setup` is safe (idempotent).
-When Claude Code is present (`~/.claude`), `setup` also enables:
-
-- status line
-- cold-cache guard
-- window-local `/sub`
-- cheaper `/compact` model chain
-
-No separate install steps for those. Later upgrades refresh them via `update` / `ensure`.
+When Claude Code is present (`~/.claude`), `setup` also enables window-local `/sub`.
+Status line, cold-cache guard, and cheaper `/compact` are not installed (deprecated;
+opt in with `llmtrim statusline install`, `llmtrim guard install`, `llmtrim compact models …`).
+Existing installs of those keep working; `ensure` refreshes an already-owned status line or guard.
llmtrim is a local MITM proxy (plus optional Claude Code hooks).
`llmtrim uninstall` reverses it. How traffic reaches tools: [README](README.md#what-it-does).
diff --git a/README.md b/README.md
index 2f66b005..4bf845a2 100644
--- a/README.md
+++ b/README.md
@@ -17,9 +17,8 @@
- Using Claude Code? One install also gets you a live status line,
- a cold-cache guard, cheaper /compact,
- and /sub to serve it through CLIProxyAPI.
+ Using Claude Code? One install also gets you /sub
+ to serve it through CLIProxyAPI.
@@ -76,7 +75,7 @@ llmtrim sits on your machine as a local proxy, trims the waste, and forwards a s
Compression cannot raise your bill or break a request; worst case is zero savings. Everything runs locally, nothing is sent to us. [In action →](#in-action)
-For Claude Code the same install goes further: a status line with live trim % and rate limits, a guard that warns before an expired prompt cache re-bills your whole context, `/compact` on a cheaper model, and `/sub` to route sessions through another subscription. [Details →](#claude-code)
+For Claude Code the same install also wires `/sub` to route sessions through another subscription. [Details →](#claude-code)
---
@@ -88,7 +87,7 @@ npm install -g @llmtrim/cli@latest && llmtrim setup
llmtrim status
```
-That's it. `setup` starts a local proxy, wires your shell, and enables recoverable tool-output shaping. When Claude Code is present, it also turns on the status line, cold-cache guard, `/sub`, and cheaper `/compact`. You do not run a separate install for each of those.
+That's it. `setup` starts a local proxy, wires your shell, and enables recoverable tool-output shaping. When Claude Code is present, it also turns on `/sub`. You do not run a separate install for that.
| You want | Run |
|---|---|
@@ -165,7 +164,7 @@ llmtrim ensure # match the recommended install state on this machine
| Force one session through llmtrim | `llmtrim wrap claude` |
| Remove everything | `llmtrim uninstall` |
-After `setup`, `update`, or `ensure`, owned Claude Code pieces (status line, guard, `/sub`, compact defaults) stay in sync with the binary. You should not need `statusline install` or similar after an upgrade.
+After `setup`, `update`, or `ensure`, owned Claude Code `/sub` stays in sync with the binary.
Time series: `llmtrim status --daily` · `--weekly` · `--monthly` · `--json` · `--csv`.
@@ -242,69 +241,12 @@ Default `auto` enables each stage only where it pays. `safe` is lossless-only. [
## Claude Code
-When `~/.claude` exists, `setup`, `update`, and `ensure` wire these. No separate install commands.
+When `~/.claude` exists, `setup`, `update`, and `ensure` wire `/sub` (and routed subagents). No separate install command for that.
| Feature | What you get |
|---|---|
-| Status line | Model, context gauge, trim %, rate limits, cache warm/cold |
-| Guard | Blocks one turn if a cold-cache resume would rewrite a huge context (and bill for it) |
-| `/compact` models | Prefer Haiku → Sonnet before your selected model, but only when the prompt cache is cold |
| `/sub` | Per-window: `/sub on [optional:codex\|kimi\|grok]` · `/sub off` · `/sub status` |
-```text
-◆ Opus→gpt-5.6-terra ▓▓▓▓▓░░░ 142k ✂ 6.8% ◔ 3h·24% · 4d·12% ♻ 63% cached
-```
-
-
-Status line details
-
-Claude Code [custom status line](https://code.claude.com/docs/en/statusline). The arrow is the backend that answered the last turn (not merely what is configured). In `sub` fallback mode it stays off while Anthropic serves and shows up when a chain provider does.
-
-- `✂`: trim for this session (`✂ –` until something is saved)
-- `◔`: rate-limit windows (time left · % used) — Claude.ai when Anthropic is serving; under `/sub` the active plan's windows (Codex today: weekly always, 5h when the plan reports one). Kimi/Grok fill in when their usage APIs are wired
-- Context gauge: fill of the serving model's real window (green under 40%, orange 40-65%, red above)
-- `♻`: prompt-cache reuse; becomes `♻ cache cold` after the cache TTL
-
-Owned settings rewrite themselves when the binary path or payload changes. To opt out, leave your own status line in place, or uninstall ours (`llmtrim statusline uninstall`).
-
-
-
-
-Cold-cache guard
-
-Resuming a large session after the prompt-cache TTL rewrites the whole context at cache-write rates (often a few dollars) with no warning at the prompt.
-
-Guard is a free `UserPromptSubmit` hook. It blocks one turn, prints idle time, context size, estimated cost, and the draft you typed (Claude Code clears the input box and would otherwise drop it), then lets a resend through. If you type something else next, the blocked text is also reinjected as model context. `/compact` pays that cold write too, because it has to read the full context to summarize. Local-only slash commands that never hit the model (today: `/sub`) pass through without acking the gap, so the next real prompt still warns.
-
-Opt out: `llmtrim guard uninstall`. `ensure` remembers that choice.
-
-```text
-Idle 6h 19m, 347k tokens of context. The prompt cache has expired, so the next turn
-rewrites the whole context (about $3.47 before any work happens).
-```
-
-
-
-
-Cheaper `/compact`
-
-```bash
-llmtrim compact models haiku sonnet # setup already sets this by default
-llmtrim compact status
-llmtrim compact off
-```
-
-```toml
-[compact]
-models = ["haiku", "sonnet"]
-```
-
-Candidates run in order when they fit the compressed request. Claude's selected model is always the last fallback (do not put it in the list). Empty `models = []` records opt-out.
-
-The redirect only fires once the prompt cache has gone cold. `/compact` re-sends the conversation Claude Code has been caching against your selected model, so while that cache is warm a cache-read there costs less than a cold read on a smaller model, and the compact stays home. After the cache expires the cheaper model wins, so that is when the redirect takes over.
-
-
-
Subscription reroute (`sub`) (opt-in; may conflict with provider ToS)
@@ -540,8 +482,7 @@ These knobs are orthogonal to compression. Each resolves env-first, then from th
-Claude Code options (compact models, subscription reroute) are under
-[Claude Code](#claude-code).
+Claude Code options (subscription reroute) are under [Claude Code](#claude-code).
Upstream proxy (corporate egress or chaining local tools)
diff --git a/crates/llmtrim-cli/src/compact.rs b/crates/llmtrim-cli/src/compact.rs
index 0d594b23..b2cc6892 100644
--- a/crates/llmtrim-cli/src/compact.rs
+++ b/crates/llmtrim-cli/src/compact.rs
@@ -1,5 +1,6 @@
//! Claude Code compaction request detection and ordered model planning.
//!
+//! Opt-in via `[compact].models` / `llmtrim compact models` — `setup` does not write defaults.
//! Claude Code handles `/compact` locally and sends a normal Anthropic Messages request. The
//! internal summarization prompt is the protocol marker; ordinary user text mentioning `/compact`
//! must never trigger model substitution.
diff --git a/crates/llmtrim-cli/src/ensure.rs b/crates/llmtrim-cli/src/ensure.rs
index 8740bb67..dc13a71c 100644
--- a/crates/llmtrim-cli/src/ensure.rs
+++ b/crates/llmtrim-cli/src/ensure.rs
@@ -2,8 +2,10 @@
//!
//! Happy path: `llmtrim setup` / `llmtrim update` / `llmtrim doctor --fix` / status `f` all
//! land here. Power-user install/uninstall commands remain as escape hatches; humans should not
-//! need them after a release. Owned Claude Code hooks and the status line are rewritten in place
-//! when the binary path or feature payload changes so changelogs never say "re-run install".
+//! need them after a release. Owned `/sub` hooks (and a previously installed status line or
+//! guard) are rewritten in place when the binary path or feature payload changes so changelogs
+//! never say "re-run install". Statusline, guard, and cheaper `/compact` are deprecated: new
+//! installs do not wire them.
use std::path::{Path, PathBuf};
@@ -124,7 +126,7 @@ fn save_at(path: &Path, state: &State) -> Result<()> {
/// How ensure was invoked — controls prompting and how loud the report is.
#[derive(Debug, Clone, Copy)]
pub struct Options {
- /// May prompt on a TTY (first-run compact/tray choices). Non-interactive defaults to yes
+ /// May prompt on a TTY (first-run tray choice). Non-interactive defaults to yes
/// for recommended items, no for optional network downloads.
pub interactive: bool,
/// Skip the success panel (daemon start / quiet migrations).
@@ -134,7 +136,8 @@ pub struct Options {
/// Allow downloading the Linux tray binary when missing (interactive confirm, or forced).
pub download_tray: bool,
/// When false (quiet auto-heal), only refresh *owned* stale integrations and daemon skew —
- /// never first-install statusline/guard//sub/compact or enable tray autostart.
+ /// never first-install `/sub` / subagents or enable tray autostart. Statusline, guard, and
+ /// compact are never first-installed even when this is true (deprecated).
pub install_missing: bool,
}
@@ -185,36 +188,33 @@ fn probe_with(state: &State) -> Vec {
let mut gaps = Vec::new();
let claude = crate::statusline::claude_code_present();
- if claude && !state.opt_out.statusline {
- match crate::statusline::owned_status() {
- crate::statusline::OwnedStatus::Missing => gaps.push(Gap {
- id: "statusline",
- label: "Statusline".into(),
- detail: "not installed — recommended for Claude Code".into(),
- }),
- crate::statusline::OwnedStatus::Stale => gaps.push(Gap {
- id: "statusline",
- label: "Statusline".into(),
- detail: "stale (binary path or refresh settings)".into(),
- }),
- crate::statusline::OwnedStatus::Current | crate::statusline::OwnedStatus::Foreign => {}
- }
+ if claude
+ && !state.opt_out.statusline
+ && matches!(
+ crate::statusline::owned_status(),
+ crate::statusline::OwnedStatus::Stale
+ )
+ {
+ // Deprecated: missing is not a gap. Refresh only an already-owned stale line.
+ gaps.push(Gap {
+ id: "statusline",
+ label: "Statusline".into(),
+ detail: "stale (binary path or refresh settings)".into(),
+ });
}
- if claude && !state.opt_out.guard {
- match crate::guard::owned_status() {
- crate::guard::OwnedStatus::Missing => gaps.push(Gap {
- id: "guard",
- label: "Guard".into(),
- detail: "not installed — warns before cold-cache resumes".into(),
- }),
- crate::guard::OwnedStatus::Stale => gaps.push(Gap {
- id: "guard",
- label: "Guard".into(),
- detail: "stale (binary path)".into(),
- }),
- crate::guard::OwnedStatus::Current => {}
- }
+ if claude
+ && !state.opt_out.guard
+ && matches!(
+ crate::guard::owned_status(),
+ crate::guard::OwnedStatus::Stale
+ )
+ {
+ gaps.push(Gap {
+ id: "guard",
+ label: "Guard".into(),
+ detail: "stale (binary path)".into(),
+ });
}
if claude && !state.opt_out.window_sub {
@@ -250,14 +250,6 @@ fn probe_with(state: &State) -> Vec {
}
}
- if claude && !state.opt_out.compact && !llmtrim_core::config::compact_models_configured() {
- gaps.push(Gap {
- id: "compact",
- label: "Compact".into(),
- detail: "cheaper /compact models not configured".into(),
- });
- }
-
// Version skew: daemon older than this binary.
if let Some(d) = crate::daemon::running()
&& let Some(v) = d.version.as_deref()
@@ -316,8 +308,8 @@ pub fn apply(opts: Options) -> Result {
if claude && !state.opt_out.statusline {
let status = crate::statusline::owned_status();
- let skip_missing =
- !opts.install_missing && matches!(status, crate::statusline::OwnedStatus::Missing);
+ // Deprecated: never first-install. Refresh an already-owned (or foreign-skip) line.
+ let skip_missing = matches!(status, crate::statusline::OwnedStatus::Missing);
if !skip_missing {
match crate::statusline::sync_owned() {
Ok(crate::statusline::SyncOutcome::Installed) => {
@@ -363,8 +355,7 @@ pub fn apply(opts: Options) -> Result {
if claude && !state.opt_out.guard {
let gstatus = crate::guard::owned_status();
- let skip_missing =
- !opts.install_missing && matches!(gstatus, crate::guard::OwnedStatus::Missing);
+ let skip_missing = matches!(gstatus, crate::guard::OwnedStatus::Missing);
if !skip_missing {
match crate::guard::sync_owned() {
Ok(true) => {
@@ -469,43 +460,8 @@ pub fn apply(opts: Options) -> Result {
.push((ui::NOTE, "Subagents".into(), "opted out".into()));
}
- if claude
- && opts.install_missing
- && !state.opt_out.compact
- && !llmtrim_core::config::compact_models_configured()
- {
- let want = if opts.interactive {
- confirm_default_yes("Use cheaper models for Claude Code /compact (Haiku → Sonnet)?")
- } else {
- true
- };
- if want {
- match llmtrim_core::config::write_compact_models(&["haiku".into(), "sonnet".into()]) {
- Ok(()) => {
- report.applied.push("compact");
- report.rows.push((
- ui::OK,
- "Compact".into(),
- "Haiku → Sonnet → original model".into(),
- ));
- }
- Err(e) => {
- report
- .rows
- .push((ui::WARN, "Compact".into(), format!("not configured: {e:#}")))
- }
- }
- } else {
- // Remember opt-out as empty models list + flag.
- let _ = llmtrim_core::config::write_compact_models(&[]);
- state.opt_out.compact = true;
- report.rows.push((
- ui::OK,
- "Compact".into(),
- "original model only (remembered)".into(),
- ));
- }
- } else if claude && llmtrim_core::config::compact_models_configured() {
+ // Deprecated: never write `[compact].models` on setup/ensure. Leave an existing list alone.
+ if claude && llmtrim_core::config::compact_models_configured() {
report
.rows
.push((ui::OK, "Compact".into(), "configured".into()));
@@ -700,7 +656,8 @@ pub fn maybe_auto() -> Result {
.iter()
.any(|g| g.id == "daemon" || g.detail.contains("stale"));
// Quiet path never first-installs missing integrations — only refresh owned stale
- // pieces / daemon skew, or stamp the version after a binary bump.
+ // pieces / daemon skew, or stamp the version after a binary bump. Statusline/guard/compact
+ // are never first-installed on any path.
if !version_mismatch && !needs_refresh {
return Ok(false);
}
@@ -1006,6 +963,15 @@ mod tests {
assert!(!s.opt_out.window_sub);
}
+ #[test]
+ fn probe_never_flags_unconfigured_compact() {
+ let gaps = probe_with(&State::default());
+ assert!(
+ gaps.iter().all(|g| g.id != "compact"),
+ "compact is deprecated and must not be a recommended gap: {gaps:?}"
+ );
+ }
+
#[cfg(feature = "intercept")]
#[test]
fn route_agents_opt_out_is_sidecar_not_optout_field() {
diff --git a/crates/llmtrim-cli/src/guard.rs b/crates/llmtrim-cli/src/guard.rs
index beb8316e..c4d6562a 100644
--- a/crates/llmtrim-cli/src/guard.rs
+++ b/crates/llmtrim-cli/src/guard.rs
@@ -544,7 +544,8 @@ pub enum OwnedStatus {
Current,
}
-/// Whether ensure should wire / refresh the cold-cache guard.
+/// Ownership of the guard hook relative to this binary. `ensure` refreshes Stale only;
+/// it does not first-install Missing (deprecated).
pub fn owned_status() -> OwnedStatus {
let Ok(path) = claude_settings_path() else {
return OwnedStatus::Missing;
diff --git a/crates/llmtrim-cli/src/main.rs b/crates/llmtrim-cli/src/main.rs
index e5ad6b98..d0d94cc9 100644
--- a/crates/llmtrim-cli/src/main.rs
+++ b/crates/llmtrim-cli/src/main.rs
@@ -132,11 +132,11 @@ enum Commands {
#[command(subcommand)]
action: AgentsCmd,
},
- /// Configure cheaper models for Claude Code `/compact`
+ /// Configure cheaper models for Claude Code `/compact` (deprecated; opt-in)
///
- /// Configured alternatives are tried in order when they fit the request. The model Claude Code
- /// originally requested is always the implicit final fallback. Prefer `llmtrim ensure` /
- /// `setup` for the recommended defaults — this is the power-user escape hatch.
+ /// Not installed by `setup` / `ensure`. Configured alternatives are tried in order when they
+ /// fit the request. The model Claude Code originally requested is always the implicit final
+ /// fallback.
#[command(hide = true)]
Compact {
#[command(subcommand)]
@@ -167,8 +167,8 @@ enum Commands {
///
/// The fastest path from install to compressing: ensures the local CA, sets
/// HTTPS_PROXY + CA trust in your environment (shell profile on POSIX,
- /// HKCU\Environment on Windows), enables run-at-login, wires Claude Code
- /// integrations (statusline, guard, /sub, compact), and starts the interceptor.
+ /// HKCU\Environment on Windows), enables run-at-login, wires Claude Code `/sub`,
+ /// and starts the interceptor.
/// Idempotent — re-running reuses the same port and won't restart a healthy daemon.
/// No sudo.
Setup {
@@ -187,9 +187,10 @@ enum Commands {
},
/// Bring this machine to the recommended current state
///
- /// Idempotent: rewrites owned Claude Code hooks/statusline when stale, installs
- /// recommended integrations you have not opted out of, and restarts a version-skewed
- /// daemon. Same engine `setup` and `update` use — the one verb after a release.
+ /// Idempotent: rewrites owned Claude Code `/sub` (and a previously installed statusline
+ /// or guard) when stale, installs `/sub` / subagents you have not opted out of, and
+ /// restarts a version-skewed daemon. Same engine `setup` and `update` use — the one verb
+ /// after a release. Does not install statusline, guard, or cheaper `/compact`.
Ensure {
/// Suppress the summary panel (for scripts / postinstall hooks).
#[arg(long, short = 'q')]
@@ -307,20 +308,20 @@ enum Commands {
#[command(subcommand)]
action: Option,
},
- /// Render Claude Code's custom status line (hook entrypoint; prefer `llmtrim ensure`)
+ /// Render Claude Code's custom status line (deprecated; opt-in)
///
/// With no subcommand, reads Claude Code's JSON session blob on stdin and prints one
- /// elegant line. `install` / `uninstall` are power-user escape hatches — `setup`,
- /// `update`, and `ensure` keep the status line current automatically.
+ /// elegant line. Not installed by `setup` / `ensure`. `install` wires it; `ensure` only
+ /// refreshes an already-owned line.
#[command(hide = true)]
Statusline {
#[command(subcommand)]
action: Option,
},
- /// Cold-cache resume warning hook (prefer `llmtrim ensure`)
+ /// Cold-cache resume warning hook (deprecated; opt-in)
///
- /// With no subcommand, acts as Claude Code's `UserPromptSubmit` hook. `install` /
- /// `uninstall` are escape hatches — `setup` / `update` / `ensure` wire this for you.
+ /// With no subcommand, acts as Claude Code's `UserPromptSubmit` hook. Not installed by
+ /// `setup` / `ensure`. `install` wires it; `ensure` only refreshes an already-owned hook.
#[command(hide = true)]
Guard {
#[command(subcommand)]
diff --git a/crates/llmtrim-cli/src/setup.rs b/crates/llmtrim-cli/src/setup.rs
index bc033b96..e67a83c6 100644
--- a/crates/llmtrim-cli/src/setup.rs
+++ b/crates/llmtrim-cli/src/setup.rs
@@ -540,9 +540,10 @@ pub fn run(requested: Option, force: bool) -> Result<()> {
Err(e) => rows.push((ui::WARN, "Autostart".into(), format!("not enabled: {e}"))),
}
- // 3b. Claude Code integrations + tray: one ensure pass (statusline, guard, /sub, compact,
- // tray autostart). Idempotent; honors remembered opt-outs. `--force` / non-TTY take
- // recommended defaults without asking.
+ // 3b. Claude Code integrations + tray: one ensure pass (`/sub`, subagents, tray autostart).
+ // Statusline, guard, and cheaper `/compact` are not first-installed (deprecated).
+ // Idempotent; honors remembered opt-outs. `--force` / non-TTY take recommended
+ // defaults without asking.
let ensure_report = crate::ensure::apply(crate::ensure::Options {
interactive: !force && std::io::IsTerminal::is_terminal(&std::io::stdin()),
quiet: true,
@@ -662,8 +663,7 @@ pub fn run(requested: Option, force: bool) -> Result<()> {
" {} llmtrim status",
ui::paint(color, Tone::Dim, "watch savings")
);
- // Claude Code integrations (statusline, guard, /sub, compact) are applied by ensure above —
- // no separate install homework.
+ // Claude Code `/sub` is applied by ensure above — no separate install homework.
#[cfg(windows)]
println!(
"{}",
diff --git a/crates/llmtrim-cli/src/statusline.rs b/crates/llmtrim-cli/src/statusline.rs
index bdab5d4d..e256df31 100644
--- a/crates/llmtrim-cli/src/statusline.rs
+++ b/crates/llmtrim-cli/src/statusline.rs
@@ -898,8 +898,7 @@ pub fn run() -> Result<()> {
// ── install / uninstall (wire ~/.claude/settings.json) ────────────────────────────
/// Whether Claude Code appears to be installed (its `~/.claude` config dir exists). Used by
-/// `setup` to hint at the status line for Claude Code users only, not users of other agents —
-/// setup itself is client-agnostic and never writes this file.
+/// `ensure` to decide whether to wire `/sub` (and to refresh a previously owned status line).
pub fn claude_code_present() -> bool {
claude_settings_path()
.ok()
@@ -1095,7 +1094,7 @@ pub enum OwnedStatus {
Foreign,
}
-/// Whether ensure should install / refresh the status line.
+/// Whether this binary owns the current `statusLine` (Missing is not first-installed by ensure).
pub fn owned_status() -> OwnedStatus {
let Ok(path) = claude_settings_path() else {
return OwnedStatus::Missing;
diff --git a/crates/llmtrim-core/src/config.rs b/crates/llmtrim-core/src/config.rs
index 99d0ec15..62032a60 100644
--- a/crates/llmtrim-core/src/config.rs
+++ b/crates/llmtrim-core/src/config.rs
@@ -1496,7 +1496,7 @@ pub fn write_compact_models(models: &[String]) -> Result<()> {
}
/// Whether the config explicitly contains `[compact].models`, including an empty list used to
-/// remember that setup's recommendation was declined.
+/// remember a prior opt-out. `setup` / `ensure` no longer write this key.
pub fn compact_models_configured() -> bool {
let Some(path) = config_path() else {
return false;
diff --git a/scripts/test-ensure-sandboxes.sh b/scripts/test-ensure-sandboxes.sh
index da3e96fe..1f2dfe5e 100755
--- a/scripts/test-ensure-sandboxes.sh
+++ b/scripts/test-ensure-sandboxes.sh
@@ -102,7 +102,7 @@ with_sandbox s1 with-claude -- bash -c '
exit 0
' && ok "s1 ensure + opt-out" || bad "s1 ensure + opt-out"
-# ── Step 2a: setup wires integrations ────────────────────────────────────────
+# ── Step 2a: setup wires /sub, not deprecated extras ────────────────────────
with_sandbox s2a with-claude -- bash -c '
set -e
'"$BIN"' setup --force /tmp/llmtrim-setup-out.$$ 2>&1 || {
@@ -111,27 +111,26 @@ with_sandbox s2a with-claude -- bash -c '
}
rm -f /tmp/llmtrim-setup-out.$$
test -f "$LLMTRIM_HOME/integrations.json"
- jq -e ".statusLine.command | test(\"statusline\")" "$HOME/.claude/settings.json" >/dev/null
- jq -e ".statusLine.refreshInterval == 300" "$HOME/.claude/settings.json" >/dev/null
- jq -e ".. | strings | select(test(\"guard\"))" "$HOME/.claude/settings.json" >/dev/null
+ jq -e "has(\"statusLine\") | not" "$HOME/.claude/settings.json" >/dev/null
+ if jq -e ".. | strings | select(test(\" guard$\"))" "$HOME/.claude/settings.json" >/dev/null 2>&1; then
+ exit 1
+ fi
test -f "$HOME/.claude/skills/sub/SKILL.md"
grep -q "llmtrim-owned-window-sub" "$HOME/.claude/skills/sub/SKILL.md"
- # compact configured somewhere under XDG
found=0
while IFS= read -r -d "" f; do
if grep -q "\[compact\]" "$f" 2>/dev/null; then found=1; break; fi
done < <(find "$XDG_CONFIG_HOME" -name "config.toml" -print0 2>/dev/null || true)
- # also check HOME/.config
while IFS= read -r -d "" f; do
if grep -q "\[compact\]" "$f" 2>/dev/null; then found=1; break; fi
done < <(find "$HOME" -name "config.toml" -print0 2>/dev/null || true)
- test "$found" = 1
-' && ok "s2a setup wires integrations" || bad "s2a setup wires integrations"
+ test "$found" = 0
+' && ok "s2a setup wires /sub only" || bad "s2a setup wires /sub only"
-# ── Step 2b: stale statusline rewrite ────────────────────────────────────────
+# ── Step 2b: stale statusline rewrite (already owned) ────────────────────────
with_sandbox s2b with-claude -- bash -c '
set -e
- '"$BIN"' ensure -q /dev/null
jq ".statusLine = {
\"type\": \"command\",
\"command\": \"/old/cellar/llmtrim statusline\",
@@ -144,14 +143,13 @@ with_sandbox s2b with-claude -- bash -c '
jq -e ".statusLine.refreshInterval == 300" "$HOME/.claude/settings.json" >/dev/null
' && ok "s2b stale statusline rewrite" || bad "s2b stale statusline rewrite"
-# ── Step 3: ensure installs statusline; foreign left alone ───────────────────
+# ── Step 3: ensure does not first-install statusline; foreign left alone ─────
with_sandbox s3a with-claude -- bash -c '
set -e
echo "{}" >"$HOME/.claude/settings.json"
'"$BIN"' ensure -q /dev/null
- jq -e ".statusLine.command | test(\"statusline\")" "$HOME/.claude/settings.json" >/dev/null
-' && ok "s3a statusline install" || bad "s3a statusline install"
+ jq -e "has(\"statusLine\") | not" "$HOME/.claude/settings.json" >/dev/null
+' && ok "s3a statusline not first-installed" || bad "s3a statusline not first-installed"
with_sandbox s3b with-claude -- bash -c '
set -e
@@ -172,7 +170,7 @@ with_sandbox s4 with-claude -- bash -c '
set -e
test "$d1" -ne 0
'"$BIN"' doctor --fix /dev/null 2>&1 || true
- jq -e ".statusLine.command | test(\"statusline\")" "$HOME/.claude/settings.json" >/dev/null
+ jq -e "has(\"statusLine\") | not" "$HOME/.claude/settings.json" >/dev/null
test -f "$HOME/.claude/skills/sub/SKILL.md"
' && ok "s4 doctor --fix" || bad "s4 doctor --fix"
@@ -226,8 +224,10 @@ with_sandbox s8a no-claude -- bash -c '
with_sandbox s8b with-claude -- bash -c '
set -e
'"$BIN"' ensure /dev/null
- jq -e ".statusLine.refreshInterval == 300" "$HOME/.claude/settings.json" >/dev/null
- jq -e ".. | strings | select(test(\"guard\"))" "$HOME/.claude/settings.json" >/dev/null
+ jq -e "has(\"statusLine\") | not" "$HOME/.claude/settings.json" >/dev/null
+ if jq -e ".. | strings | select(test(\" guard$\"))" "$HOME/.claude/settings.json" >/dev/null 2>&1; then
+ exit 1
+ fi
test -f "$HOME/.claude/skills/sub/SKILL.md"
test -f "$LLMTRIM_HOME/integrations.json"
# update reminder path (package channel) should not crash