Open source · MIT · Single Go binary

Observe the filesystem.
Typed JSON output.

loupe replaces ls, find, and stat with structured JSON output — a depth-aware, schema-defined contract agents can actually use. Pipe to jq or connect an MCP client; the output is the same either way.

go install github.com/norlinga/loupe@latest

~/myapp  —  loupe
# replace ls / find / stat with typed JSON loupe . --depth 1 { "path": ".", "name": "myapp", "type": "directory", "entry_count": 3, "entries": [ { "name": "main.go", "type": "file", "size_bytes": 3072 }, { "name": "go.mod", "type": "file", "size_bytes": 312 }, { "name": "internal", "type": "directory", "entry_count": 4 } ] } # pipe to jq — same contract, every time loupe . | jq '.entries[] | select(.type == "file")'

The problem

ls is for humans. Agents need a contract.

When an agent calls ls or find, it gets back formatted text designed for terminal output — columns, locale-dependent strings, no types. Parsing that is fragile. Change a flag or locale and the parsing breaks. Agents end up with brittle regex where they should have a schema.

The idea

A loupe is a precision lens — it reveals what the naked eye misses. This tool applies that same principle to filesystem state: the same data ls shows, restructured into a typed contract that machines can rely on.

The output schema is versioned, documented, and available via loupe --schema. Every field has a type. Every traversal is depth-bounded. No locale hazards, no column alignment, no parsing guesswork.

Schema-first

A typed contract, not formatted text

Output conforms to a published JSON Schema. Run loupe --schema from any distributed binary to inspect the contract — no docs required.

Depth-aware

Recursion is nested and bounded

Ask for two levels, get exactly two levels. Subdirectory entries nest inside their parent, matching the structure agents actually need to reason about.

Jq-native

Filter and transform without glue code

JSON output pipes directly to jq. Filter by type, select recently modified, extract paths — no intermediate parsing step.

What loupe does

Three ways to observe.

A CLI tool, a stdio MCP server, and an agent notes layer — the same typed output, accessible from wherever agents live.

CLI

loupe <path> [flags] emits typed JSON to stdout. Depth, type filtering, hidden files, and project context are all flags. Combine with jq for any query.

  • Works on files, directories, and symlinks
  • Lexical ordering, deterministic output
  • --human for a minimal tree view

MCP server

loupe --mcp starts a stdio MCP server. Three tools expose observe, schema, and notes-schema — no additional process, no daemon, just the binary.

  • loupe_observe — observe any local path
  • loupe_output_schema — fetch the contract
  • loupe_notes_schema — authoring spec

Agent notes

A .loupe/notes.json file at the git root carries per-repo context across agent sessions. Surface it with --context alongside VCS state and recently modified files.

  • Gotchas, invariants, known issues
  • Schema via loupe --notes-schema
  • Written by agents, read by agents

Zero config

Point at any path and JSON comes out. No init step, no manifest, no configuration file required.

Schema-documented

loupe --schema prints the full JSON Schema from any binary. Agents can self-orient without external docs.

Symlink-correct

Broken symlinks are reported with their unresolved target path — no silent omissions, no traversal errors on broken links.

Quickstart

Install, observe, pipe.

Build or install the binary, then point it at any path. No setup required.

01

Install the binary

Build from source with make build, or install directly once a tagged release exists. Verify with --schema to confirm the output contract is available.

install
# build from source make build VERSION=dev ./bin/loupe --version loupe dev # after a tagged release exists go install github.com/norlinga/loupe@latest # verify the output contract is accessible loupe --schema { "$schema": "...", "title": "LoupeOutput", ... }
02

Observe a path

Pass any path. Use --depth to control recursion. Filter by type, recency, or hidden status. The output contract is the same for files and directories.

observe
# observe a directory one level deep loupe . --depth 1 # observe a single file loupe ./main.go # files modified in the last 5 minutes loupe . --depth 2 --type file --newer-than 300 # pipe to jq — filter large files loupe ./internal | jq '.entries[] | select(.size_bytes > 4096) | .name'
03

Add project context

--context enriches output with VCS state, project type, and recently modified files. Agent notes in .loupe/notes.json are included when present.

context
loupe . --context | jq '.context' { "vcs": "git", "project_type": "go", "recent_files": [ { "path": "main.go", "modified_unix": 1718123000 }, { "path": "internal/parser.go", "modified_unix": 1718122800 } ], "notes": [ { "kind": "gotcha", "summary": "Tests need a local Postgres instance" } ] }
04

Human-readable tree

When you need a quick visual rather than structured data, --human prints a minimal tree. Same traversal rules apply — depth, type filter, and hidden-file exclusion all work.

human
loupe . --depth 2 --human . ├── internal/ │ ├── parser.go 4.1 KB │ └── schema.go 1.0 KB ├── main.go 3.0 KB └── go.mod 312 B loupe . --no-hidden --human # hidden entries excluded

Output & flags

The whole surface, on one page.

Every entry carries path, name, type, size, modified time, and permissions. Directories add entry_count and a nested entries array up to the requested depth.

F Flags

--depth N

Recurse N levels deep. Defaults to 1 for directories, 0 for files.

--type file|dir|symlink

Filter emitted entries by type. Only matching entries appear in the output.

--newer-than N

Emit only entries modified within the last N seconds.

--no-hidden

Exclude dotfile entries from traversal and output.

--context

Include project context: VCS, project type, recent files, and agent notes from the nearest git root.

--human

Print a minimal human-readable tree instead of JSON.

--schema  ·  --notes-schema  ·  --version

Print the output JSON Schema, the notes JSON Schema, or the build version, then exit.

O Output shape

output schema
{ "path": "/repo", "name": "repo", "type": "directory", "size_bytes": 4096, "modified_unix": 1718123456, "permissions": "755", "entry_count": 1, "entries": [ { "path": "/repo/main.go", "name": "main.go", "type": "file", "size_bytes": 2048, "modified_unix": 1718120000, "permissions": "644", "extension": ".go" } ] }

Guarantees

Ordered
Entries are lexical by filename — deterministic across runs.
Complete
If traversal encounters an unreadable path, loupe returns an error — never partial JSON.
Symlinks
Reported as "type": "symlink" with their target path, even when broken.
Full schema in docs/schema.md

MCP integration

Built-in MCP server.
No extra process.

loupe --mcp starts a stdio MCP server using the same binary. Add it to any MCP client config and agents get loupe_observe as a first-class tool.

Config

Client configuration

Add loupe to your MCP client's server list. Use an absolute path to the binary. During local development, point at the workspace build in bin/loupe.

mcp config
{ "mcpServers": { "loupe": { "command": "/usr/local/bin/loupe", "args": ["--mcp"] } } }
Tools

Available MCP tools

Three tools are exposed. Only path is required for loupe_observe — all other arguments mirror the CLI flags.

loupe_observe

Observe a local filesystem path. Returns the same typed JSON as the CLI. Accepts path, depth, type, newer_than, no_hidden, and context.

loupe_output_schema

Returns the JSON Schema for the observe output. Agents can call this once to learn the full output contract.

loupe_notes_schema

Returns the JSON Schema for .loupe/notes.json. Agents use this to author valid notes files.

Notes

Authoring agent notes

Place .loupe/notes.json at the git root. loupe surfaces the notes in the context field when --context is passed. Malformed notes files are skipped silently — the rest of the output is unaffected.

.loupe/notes.json
{ "schema_version": 1, "written_by": "agent-session-abc123", "written_at": 1718123456, "notes": [ { "kind": "gotcha", "summary": "Integration tests require a local Postgres instance", "paths": ["internal/db"] } ] }

One binary. Any path. Typed JSON.

loupe is MIT-licensed, small enough to read end-to-end, and designed to slot into any agent workflow without configuration.