How to List Files in a Directory Using Bash: The pi‑web Approach

The pi‑web repository provides two built‑in methods for listing directory contents: a server‑side Node.js utility (listDirectories) and a REST API endpoint (GET /api/files/[…path]), both of which can be invoked from Bash scripts.

The agegr/pi‑web project implements a secure, consistent file‑browsing system that you can tap into from the command line. Whether you need to list files in a directory using Bash for automation, monitoring, or integration tasks, pi‑web offers both direct library access and an HTTP interface. Both approaches share the same core logic in lib/directory-browser.ts, ensuring identical behavior and security policies.

Method 1: Query the HTTP API from Bash

The simplest way to list files in a directory using Bash is to call the built‑in REST endpoint. This requires no Node.js knowledge and works from any shell environment.

The endpoint GET /api/files/[…path]?type=list performs path normalization, symlink resolution, and security filtering before returning a JSON array of directory entries. It automatically excludes common directories like node_modules and .git.

#!/usr/bin/env bash

# List directory contents via pi‑web HTTP API

BASE_URL="http://localhost:30141/api/files"
TARGET="${1:-~}"   # First argument or home directory

# URL‑encode the path (strip leading slash)

ENCODED_PATH=$(python3 -c "
import urllib.parse, sys
print(urllib.parse.quote(sys.argv[1].lstrip('/')))
" "$TARGET")

curl -s "${BASE_URL}/${ENCODED_PATH}?type=list" |
  jq -r '.entries[] | "\(.isDir?"📁":"📄") \(.name)"'

Save and run: chmod +x list-files.sh && ./list-files.sh /var/log

Security Features of the API Route

The API route at app/api/files/[...path]/route.ts implements several protections:

  • Path validation via resolveDirectory() from lib/directory-browser.ts
  • Symlink resolution to prevent directory‑traversal attacks
  • Root allow‑list enforcement through lib/file-access.ts
  • Ignored pattern filtering (.git, node_modules, __pycache__, etc.)

Method 2: Invoke the Node.js Library Directly

For server‑side scripts or custom tooling written in TypeScript, call listDirectories() directly. This bypasses HTTP overhead and provides typed results.

import { resolveDirectory, listDirectories } from '@/lib/directory-browser';

// Resolve and validate the path
const dir = await resolveDirectory('~/projects');

// Get typed array of sub‑directories
const subDirs = await listDirectories(dir);

for (const { name, path, isDirectory } of subDirs) {
  console.log(`${isDirectory ? 'dir' : 'file'}: ${name} at ${path}`);
}

The listDirectories function in lib/directory-browser.ts returns objects with these properties:

  • name — entry basename
  • path — absolute resolved path
  • isDirectory — boolean from resolveDirentIsDirectory helper

Core Implementation Files

Understanding these source files helps you extend or debug pi‑web's file‑listing behavior:

File Purpose
lib/directory-browser.ts Core utilities: resolveDirectory(), listDirectories(), resolveDirentIsDirectory()
app/api/files/[...path]/route.ts HTTP handler for ?type=list requests; delegates to directory‑browser
lib/file-access.ts Allowed‑roots configuration and path‑validation guards
hooks/useAgentSession.ts Client‑side fetcher for UI file explorer (calls same API)

Comparison: Which Method to Use

Approach Best For Latency Dependencies
HTTP API Bash scripts, external tools, cron jobs Higher (network) curl, jq
Direct listDirectories Server‑side TypeScript, custom integrations Lower (in‑process) Node.js runtime

Both methods respect the same ignore patterns and security constraints defined in the source code.

Summary

  • Primary method for Bash: Use curl against GET /api/files/[path]?type=list with URL‑encoded paths
  • Server‑side alternative: Import listDirectories from lib/directory-browser.ts for typed, synchronous‑style enumeration
  • Security consistency: Both paths invoke resolveDirectory() and resolveDirentIsDirectory() from the same core module
  • Data filtering: Automatic exclusion of node_modules, .git, and configurable junk patterns

Frequently Asked Questions

How do I handle paths with spaces when calling the pi‑web API from Bash?

Quote the argument when invoking your script: ./list-files.sh "/path/with spaces". The Python urllib.parse.quote() in the example properly encodes spaces as %20, ensuring the URL remains valid. The route.ts handler decodes these automatically before validation.

Can I list files recursively instead of just one directory?

The built‑in ?type=list parameter returns a single level. For recursion, either iterate in your Bash script (making successive API calls for each subdirectory) or extend lib/directory-browser.ts to expose a recursive variant. The resolveDirentIsDirectory helper already provides the isDirectory flag needed for traversal logic.

What ports and authentication does the HTTP API require?

By default, pi‑web listens on port 30141 as shown in the examples. Authentication depends on your deployment; the useAgentSession.ts hook suggests session‑based auth for browser clients. For programmatic Bash access, check your lib/file-access.ts configuration—it may allow localhost requests without credentials or require a bearer token in the Authorization header.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →