Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
487 changes: 479 additions & 8 deletions Cargo.lock

Large diffs are not rendered by default.

57 changes: 54 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,38 @@ lowfat ls -la
{ "shellCommandPrefix": "eval \"$(lowfat shell-init zsh)\"; " }
```

**Codex, Grok Build, Antigravity** — direct prefixing works in all:

```sh
lowfat git status
lowfat docker ps
```

Shell integration auto-activates for Codex when `CODEX_ENV` is set (or force with `LOWFAT_ENABLE=1`).

Non-interactive runs often need bypass/approval flags:

- Codex: `codex exec --dangerously-bypass-approvals-and-sandbox ...`
- Grok Build: `grok --single --always-approve --permission-mode bypassPermissions ...`

Antigravity (`agy`) supports direct prefixing inside sessions; non-interactive terminal execution is inconsistent.

**Discoverability skills** are installed for autonomous agent use:

- Claude Code: `~/.claude/skills/lowfat/SKILL.md`
- Codex: `~/.codex/skills/lowfat/SKILL.md`
- .agents / Devin-style: `~/.agents/skills/lowfat/SKILL.md`
- Antigravity (Google): `~/.gemini/config/plugins/google-antigravity-sdk/skills/lowfat/SKILL.md`

**Tested agents (one-shot verification)**

| Assistant | Result | Notes |
|---------------|----------|-------|
| Claude Code | PASS | Pre-existing hooks; clean `--print` execution |
| Codex | PASS | Used bypass flags for non-interactive exec |
| Grok Build | PASS | `--single` + bypass/always-approve |
| Antigravity | PARTIAL | Direct prefix works in-session; `--print` terminal invocation is unreliable |

### Usage highlights

```sh
Expand Down Expand Up @@ -206,9 +238,10 @@ core you extend yourself.
| Custom filters | `.lf` DSL + shell + Python (PEP 723/uv) | TOML DSL |
| Levels | `lite` / `full` / `ultra` | `-l aggressive`, `--ultra-compact` |
| File-content filtering | `post-read` hook (code, markdown, HTML, data, lock files) | `rtk read` / `smart` (signatures, summaries) |
| Agent integrations | Claude Code, OpenCode, shell, Pi | 14 tools (Claude Code, Copilot, Gemini, Codex, …) |
| Agent integrations | Claude Code, Codex, Grok Build, Antigravity, OpenCode, shell, Pi | 14 tools (Claude Code, Copilot, Gemini, Codex, …) |
| Telemetry | None — local-only | Opt-in, off by default (anonymous aggregate) |
| Savings analytics | `lowfat stats` / `history` (local) | `rtk gain` / `discover` (local) |
| Optional remote log | Opt-in to your Supabase observatory (eng_evaluations) | — |

### Token savings, head-to-head

Expand All @@ -222,13 +255,31 @@ counted with `tiktoken` (`cl100k_base`); savings are vs the raw command output:
| `git log` | 3350 | -93% | -97% | -56% |
| `ls -la` | 153 | -77% | -89% | -86% |
| `find` | 535 | -0% | -58% | -66% |

Honest read: lowfat compresses git harder; rtk edges out `find`; `ls` is close.
| Honest read: lowfat compresses git harder; rtk edges out `find`; `ls` is close.

@zdk zdk Jul 6, 2026

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would turn line into a broken table row.

`find` only engages at lowfat's `ultra` level. rtk's `--ultra-compact` gave
near-identical numbers to its default here, so the default is shown. This is a
single small run on one repo — directional, not a benchmark; measure on your own
workload before trusting any of it.

### Optional Supabase observatory integration

lowfat can optionally log its own token savings to a personal Supabase
"observatory" (tables `eng_technologies` + `eng_evaluations`). This is
**completely optional**, best-effort, and non-blocking.

- Only activates when `LOWFAT_SUPABASE_URL` + `LOWFAT_SUPABASE_KEY` (or the
generic `SUPABASE_URL` / `SUPABASE_SERVICE_ROLE_KEY`) are present.
- Logs aggregates only: command, subcommand, raw/filtered/saved tokens,
savings %, had_plugin, reduced, exit code, elapsed ms. **No raw output or
full arguments** are sent.
- Records under technology slug `lowfat` (category `tool`).
- `lowfat stats` / `history` / `info` remain fully local and work offline.
- Enabled status appears in `lowfat info` when active.
- Debug failures with `LOWFAT_OBSERVATORY_DEBUG=1`.

This respects the local-first design: the remote path is fire-and-forget and
never affects stdout, exit code, or local tracking.

## License

Apache-2.0
Expand Down
1 change: 1 addition & 0 deletions crates/lowfat/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ clap = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
tempfile = "3"
ureq = { version = "2", default-features = false, features = ["json", "tls"] }
10 changes: 6 additions & 4 deletions crates/lowfat/src/commands/hook.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,16 @@ pub fn run() -> Result<()> {
None => return Ok(()),
};

let mut updated_input = json!({ "command": rewritten });
if let Some(description) = payload["tool_input"]["description"].as_str() {
updated_input["description"] = json!(description);
}

let output = json!({
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"updatedInput": {
"command": rewritten,
"description": payload["tool_input"]["description"]
}
"updatedInput": updated_input
}
});

Expand Down
78 changes: 63 additions & 15 deletions crates/lowfat/src/commands/run.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::filters;
use crate::observatory::{log_savings_if_enabled, SavingsData};
use lowfat_core::config::RunfConfig;
use lowfat_core::db::{Db, InvocationRecord, TrackRecord};
use lowfat_core::pipeline::{Pipeline, StageType};
Expand Down Expand Up @@ -51,19 +52,33 @@ pub fn run(args: &[String]) -> i32 {
// Skip filtering for tiny outputs — overhead exceeds any possible savings.
const MIN_FILTER_TOKENS: usize = 128;
if lowfat_core::tokens::estimate_tokens(&raw) < MIN_FILTER_TOKENS {
let tokens = lowfat_core::tokens::estimate_tokens(&raw) as u64;
if let Ok(db) = Db::open(&config.data_dir) {
let _ = db.record_invocation(&InvocationRecord {
command: cmd.clone(),
subcommand: subcommand.clone(),
raw_tokens: lowfat_core::tokens::estimate_tokens(&raw) as u64,
filtered_tokens: lowfat_core::tokens::estimate_tokens(&raw) as u64,
raw_tokens: tokens,
filtered_tokens: tokens,
had_plugin: false,
in_scope: false,
reduced: false,
is_external_plugin: false,
exit_code,
});
}
log_savings_if_enabled(
&config.data_dir,
SavingsData {
command: cmd.clone(),
subcommand: subcommand.clone(),
raw_tokens: tokens,
filtered_tokens: tokens,
had_plugin: false,
reduced: false,
exit_code,
exec_time_ms: None,
},
);
print!("{raw}");
return exit_code;
}
Expand Down Expand Up @@ -129,6 +144,17 @@ pub fn run(args: &[String]) -> i32 {

let elapsed = start.elapsed().as_millis() as u64;

// Compute usage history values outside the DB block so they are also
// available for the optional remote observatory log.
let known = known_subcommands(cmd, &plugin_map, &external_plugins, &external_map);
let raw_tokens = lowfat_core::tokens::estimate_tokens(&raw) as u64;
let filtered_tokens = lowfat_core::tokens::estimate_tokens(&filtered) as u64;
let in_scope =
filter_name.is_some() && (known.is_empty() || known.iter().any(|s| s == &subcommand));
let had_plugin = filter_name.is_some();
let reduced = filtered_tokens < raw_tokens;
let hist_sub = history_subcommand(&subcommand, &known);

// Track
if let Ok(db) = Db::open(&config.data_dir) {
let args_str = cmd_args.join(" ");
Expand All @@ -143,27 +169,34 @@ pub fn run(args: &[String]) -> i32 {
.map(|p| p.to_string_lossy().to_string())
.unwrap_or_default(),
});
// Usage history — command+subcommand only, no args. Powers `lowfat history`.
let known = known_subcommands(cmd, &plugin_map, &external_plugins, &external_map);
// in_scope = plugin is declared to handle *this* subcommand. Empty
// `known` means "no subcommand restriction" — treat as universal.
let in_scope =
filter_name.is_some() && (known.is_empty() || known.iter().any(|s| s == &subcommand));
let raw_tokens = lowfat_core::tokens::estimate_tokens(&raw) as u64;
let filtered_tokens = lowfat_core::tokens::estimate_tokens(&filtered) as u64;
let _ = db.record_invocation(&InvocationRecord {
command: cmd.clone(),
subcommand: history_subcommand(&subcommand, &known),
subcommand: hist_sub.clone(),
raw_tokens,
filtered_tokens,
had_plugin: filter_name.is_some(),
had_plugin,
in_scope,
reduced: filtered_tokens < raw_tokens,
reduced,
is_external_plugin,
exit_code,
});
}

// Remote observatory (best-effort, non-blocking)
log_savings_if_enabled(
&config.data_dir,
SavingsData {
command: cmd.clone(),
subcommand: hist_sub,
raw_tokens,
filtered_tokens,
had_plugin,
reduced,
exit_code,
exec_time_ms: Some(elapsed),
},
);

// Tee on failure
let tee_dir = config.data_dir.join("tee");
tee::save_on_failure(&tee_dir, &format!("{cmd}_{subcommand}"), &raw, exit_code);
Expand Down Expand Up @@ -362,6 +395,8 @@ fn passthrough(cmd: &str, args: &[String], config: &RunfConfig) -> i32 {
};

let elapsed = start.elapsed().as_millis() as u64;
let subcommand = args.first().cloned().unwrap_or_default();
let tokens = lowfat_core::tokens::estimate_tokens(&raw) as u64;

if let Ok(db) = Db::open(&config.data_dir) {
let args_str = args.join(" ");
Expand All @@ -375,8 +410,6 @@ fn passthrough(cmd: &str, args: &[String], config: &RunfConfig) -> i32 {
.map(|p| p.to_string_lossy().to_string())
.unwrap_or_default(),
});
let subcommand = args.first().cloned().unwrap_or_default();
let tokens = lowfat_core::tokens::estimate_tokens(&raw) as u64;
let _ = db.record_invocation(&InvocationRecord {
command: cmd.to_string(),
subcommand: history_subcommand(&subcommand, &[]),
Expand All @@ -390,6 +423,21 @@ fn passthrough(cmd: &str, args: &[String], config: &RunfConfig) -> i32 {
});
}

// Remote observatory (best-effort, non-blocking)
log_savings_if_enabled(
&config.data_dir,
SavingsData {
command: cmd.to_string(),
subcommand: history_subcommand(&subcommand, &[]),
raw_tokens: tokens,
filtered_tokens: tokens,
had_plugin: false,
reduced: false,
exit_code,
exec_time_ms: Some(elapsed),
},
);

print!("{raw}");
exit_code
}
Expand Down
4 changes: 4 additions & 0 deletions crates/lowfat/src/commands/status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ pub fn run() -> Result<()> {
println!();
println!(" {Y}💰{R} {D}saved:{R} {B}{input}{R} {D}→{R} {G}{B}{output}{R} {D}tokens{R}");
println!(" {Y}⚡{R} {D}speed:{R} {B}{time_without:.1}s{R} {D}→{R} {G}{B}{time_s:.1}s{R}");

if crate::observatory::enabled() {
println!(" {D}🌐 observatory:{R} {G}on{R}");
}
println!();

Ok(())
Expand Down
1 change: 1 addition & 0 deletions crates/lowfat/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod commands;
mod filters;
mod observatory;

use clap::{Parser, Subcommand};

Expand Down
Loading