npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

feral-agent

v2026.7.5

Published

Feral Agent — proactive, portable AI agent with a native security sandbox

Readme

Feral Agent

A proactive, portable AI agent with a native security sandbox, built in TypeScript on Bun. Agent layer for the Feral desktop app (Rust + Tauri v2 + Leptos) — runs the same core on any transport via an adapter pattern.


Prerequisites

| Tool | Version | Install | |---|---|---| | Bun | ≥ 1.1 | curl -fsSL https://bun.sh/install \| bash | | Ollama | any | ollama.com/download | | Git | any | pre-installed on most systems |

Ollama is required only for the live agent loop. All tests and the sandbox run without it.


Quick start

git clone https://github.com/bloom500/feral-agent.git
cd feral-agent
bun install

Pull the default model (first time only, ~4 GB):

ollama pull qwen2.5:7b

Start Ollama in the background (if not already running):

ollama serve

Run the agent (waits for newline-delimited JSON on stdin):

bun run src/index.ts

Testing locally

1. Run the test suite (no Ollama needed)

The full suite — sandbox guarantees, inference router, memory, mood, and the end-to-end integration test — runs with mocked network, no LLM required:

bun test
# → 53 pass, 0 fail

Typecheck only:

bunx tsc --noEmit

2. Smoke test without Ollama (sandbox + transport only)

Verify the agent boots, responds to ping, and handles a message gracefully when Ollama is not reachable:

PowerShell (Windows):

@'
{"type":"ping"}
{"type":"message","id":"m1","content":"hello","sessionId":"test"}
{"type":"shutdown"}
'@ | FERAL_DB=":memory:" bun run src/index.ts

Bash / Git Bash:

printf '{"type":"ping"}\n{"type":"message","id":"m1","content":"hello","sessionId":"test"}\n{"type":"shutdown"}\n' \
  | FERAL_DB=":memory:" bun run src/index.ts

Expected output (no Ollama running):

{"type":"pong"}
{"type":"error","id":"m1","message":"Inference unavailable: primary inference failed ..."}

3. Full conversation with Ollama running

Start Ollama (ollama serve), then:

PowerShell:

@'
{"type":"ping"}
{"type":"message","id":"1","content":"Ce poti face?","sessionId":"sess1"}
{"type":"message","id":"2","content":"Cauta pe web: Bun runtime","sessionId":"sess1"}
{"type":"shutdown"}
'@ | bun run src/index.ts

Bash:

printf '%s\n' \
  '{"type":"ping"}' \
  '{"type":"message","id":"1","content":"What can you do?","sessionId":"sess1"}' \
  '{"type":"message","id":"2","content":"Search the web: Bun runtime","sessionId":"sess1"}' \
  '{"type":"shutdown"}' \
  | bun run src/index.ts

Expected: pong, then streaming chunkdone events. Web search triggers tool_start / tool_done events before the final answer.

4. Inspect the audit log

Every action is written to SQLite. After a session:

# Open the DB (requires sqlite3 CLI)
sqlite3 data/feral.db "SELECT timestamp, action_type, tool_name, result FROM audit_log ORDER BY id;"

5. Build the Tauri sidecar binary

bun run build
# → dist/feral-agent (single executable, no Node/Bun needed at runtime)

Configuration

Set via environment variables (or a .env file — Bun reads it automatically):

| Variable | Default | Purpose | |---|---|---| | FERAL_DB | data/feral.db | SQLite path (:memory: for ephemeral) | | FERAL_WORKSPACE | cwd | root directory read_file may access | | FERAL_MODEL | qwen2.5:7b | primary model name | | FERAL_BASE_URL | http://localhost:11434 | primary inference endpoint | | FERAL_PROVIDER | ollama | ollama or any OpenAI-compatible API | | FERAL_FALLBACK_MODEL | — | enables a fallback target when set | | FERAL_FALLBACK_BASE_URL | http://localhost:11434 | fallback endpoint | | FERAL_TRUSTED_BASE_URLS | configured targets | comma-separated inference allowlist | | FERAL_BUDGET_CONVERSATION | 50000 | per-conversation token cap | | FERAL_BUDGET_DAY | 500000 | per-day token cap | | FERAL_BUDGET_POLICY | compress_and_continue | or stop | | FERAL_FETCH_DOMAINS | — | comma-separated domain allowlist for fetch_url (e.g. example.com,api.github.com) | | FERAL_INNER_THOUGHTS_ENABLED | false | true to enable proactive loop (V2) | | FERAL_THOUGHTS_INTERVAL_MS | 300000 | inner-thoughts tick interval |

Example .env for development:

FERAL_DB=data/dev.db
FERAL_MODEL=qwen2.5:7b
FERAL_BASE_URL=http://localhost:11434
FERAL_WORKSPACE=/Users/you/projects
FERAL_BUDGET_CONVERSATION=100000

Architecture (4 layers)

src/
├── core/
│   ├── agent-loop.ts       Layer 1 — prompt → infer → tools → loop
│   ├── mood.ts             Layer 1 — in-memory emotional state (4 dimensions)
│   └── inner-thoughts.ts   Layer 1 — proactive background loop (V2, flagged off)
├── memory/
│   ├── working.ts          Layer 2 — in-session transcript, auto-compresses
│   ├── episodic.ts         Layer 2 — FTS5 searchable event history
│   ├── semantic.ts         Layer 2 — persistent key-value user model
│   └── recall.ts           Layer 2 — unified retrieval injected before inference
├── sandbox/                Layer 3 — SECURITY (constructed before everything else)
│   ├── audit-log.ts        every action → SQLite audit_log row
│   ├── tool-permissions.ts manifest validation + path containment
│   ├── egress-proxy.ts     feralFetch(): domain whitelist, SSRF guard, rate limit
│   └── inference-router.ts single LLM choke point: budgets, allowlist, fallback
├── transports/             Layer 4 — transport-agnostic core
│   ├── interface.ts        Transport contract
│   ├── tauri.ts            stdin/stdout newline-delimited JSON (V1, active)
│   ├── telegram.ts         V2 stub
│   └── whatsapp.ts         V2 stub
├── tools/
│   ├── registry.ts         single gate for every tool call
│   └── builtin/
│       ├── read-file.ts    fs:read, allowedPaths enforced
│       └── web-search.ts   network:outbound, DuckDuckGo, no API key
├── db.ts                   bun:sqlite — schema, migrations, WAL mode
├── types.ts                shared interfaces (no runtime code)
└── index.ts                wires all layers, starts transport

Security guarantees (enforced, not optional)

  • Every tool must declare a manifest before registration; undeclared permissions are blocked at call time and written to the audit log.
  • No network request reaches the wire without passing through feralFetch() — loopback, private, and link-local ranges are blocked (SSRF guard), domains are whitelisted per-tool, and a rolling-window rate limit applies.
  • Every LLM call goes through the inference router — per-conversation and per-day token budgets enforced, inference endpoints validated against a trusted allowlist (port-normalized, checked at construction and at call time).
  • Every action — tool call, inference, network request, memory write, or blocked attempt — produces an audit_log row. The logger never throws to callers; an audit failure is reported to stderr but does not crash the agent.

IPC protocol (Tauri sidecar)

Newline-delimited JSON over stdin/stdout. stdout is reserved for protocol traffic only — all diagnostics go to stderr.

Inbound (stdin → agent):

{ "type": "message", "id": "uuid", "content": "user text", "sessionId": "sess_1" }
{ "type": "ping" }
{ "type": "shutdown" }

Outbound (agent → stdout):

{ "type": "chunk",      "id": "uuid", "content": "partial response" }
{ "type": "done",       "id": "uuid", "content": "full response" }
{ "type": "tool_start", "tool": "web_search", "args": { "query": "..." } }
{ "type": "tool_done",  "tool": "web_search", "result": { ... } }
{ "type": "proactive",  "content": "agent-initiated message" }
{ "type": "pong" }
{ "type": "error",      "id": "uuid", "message": "..." }

Roadmap

V1 — done ✓

  • [x] Sandbox: audit log, egress proxy (SSRF + whitelist), tool-permission manifests, inference router with token budgets and trusted-endpoint allowlist
  • [x] Memory: working (auto-compress), episodic (FTS5), semantic (key-value user model), recall (injected before every inference turn)
  • [x] Agent loop: prompt → LLM → tool calls → sandbox enforce → loop
  • [x] Tauri transport: stdin/stdout newline-delimited JSON, drain-on-exit
  • [x] Built-in tools: read_file (fs:read), web_search (DuckDuckGo)
  • [x] 53 tests — sandbox, inference router, memory, mood, full pipeline

V2 — next

  • [ ] Inner thoughts — enable the proactive loop; add mood decay + shouldBeProactive() gate (code exists, flagged off with FERAL_INNER_THOUGHTS_ENABLED=true)
  • [ ] Vector searchsqlite-vec for semantic similarity in recall (schema-compatible slot already in semantic.ts)
  • [ ] Additional transports — Telegram, WhatsApp (stubs exist in src/transports/)
  • [ ] Iteration enginesrc/core/iteration.ts, auto-iterate complex multi-step tasks with token budget management
  • [ ] Cloud inference + credential broker — host holds API keys, sandbox gets short-lived tokens (pattern from NemoClaw reference)
  • [ ] Per-path access modes — read vs write per allowedPaths entry (currently flat list, pattern from openclaw reference)
  • [ ] DNS rebind protection — resolve-then-pin for cloud inference endpoints (V1 note: safe for local Ollama, needed before cloud)
  • [ ] Real tokenizer — replace length/4 estimate with tiktoken or provider tokenizer for accurate budget tracking on non-English text