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

pi-namespace

v0.2.1

Published

Namespace tools and skills in pi — group tools by extension with configurable prefix rewriting

Readme

🏷️ pi-namespace

Namespace tools and skills in pi

Group tools by extension with configurable prefix rewriting — flat tool names become deploy:push, fs:read, msg:send.

pi extension license


The Problem

Pi registers all tools in a flat namespace. When multiple extensions register tools, colliding names silently shadow each other — the first registration wins. There's no grouping to tell the LLM that push, status, and rollback all belong to the deploy extension.

Available tools
- read         ← built-in? extension? override?
- push         ← what does this do? where from?
- status       ← deploy status? git status? system status?
- search       ← brave? vector? code?
- deploy       ← the tool? or a namespace collision?

Skills are similarly flat — every skill appears in the system prompt without provenance or grouping.


The Solution

pi-namespace patches ExtensionRunner.prototype.getAllRegisteredTools — the single chokepoint where pi's AgentSession pulls tool definitions before they're frozen into the LLM schema, system prompt, and TUI.

| Before | After (default :) | After ("separator": "__") | |--------|--------|--------| | push | deploy:push | deploy__push | | status | deploy:status | deploy__status | | search | msg:search | msg__search | | read | fs:read (with builtinNamespace) | fs__read |

Namespaced names flow through to everything: LLM tool schemas, system prompt snippets, setActiveTools(), --tools, --exclude-tools, and the TUI.

Tip: Some LLM providers (notably Anthropic) reject colons in tool names. If you see invalid_request_error: tools.X.custom.name: String should match pattern '^[a-zA-Z0-9_-]{1,128}$', set "separator": "__" in your config to use underscores instead.

Available tools
- fs:read         ← built-in (namespaced)
- fs:bash         ← built-in (namespaced)
- fs:edit         ← built-in (namespaced)
- fs:write        ← built-in (namespaced)
- deploy:push     ← from pi-deploy
- deploy:status   ← from pi-deploy
- deploy:rollback  ← from pi-deploy
- msg:search      ← from pi-messenger
- msg:send        ← from pi-messenger

Install

With pi install (recommended):

pi install https://github.com/monotykamary/pi-namespace

Manual — copy to global extensions:

cp namespace.ts ~/.pi/agent/extensions/

Project-local:

mkdir -p .pi/extensions
cp namespace.ts .pi/extensions/

Quick test:

pi -e ./namespace.ts

Reload with /reload after any install method.


Configuration

Create ~/.pi/agent/namespace.json (global) or .pi/namespace.json (project):

{
  "namespaces": {
    "deploy-extension": "deploy",
    "pi-messenger": "msg",
    "pi-code-previews": "preview"
  },
  "builtinNamespace": "fs",
  "separator": "__",
  "autoNamespace": false
}

| Option | Type | Default | Description | |--------|------|---------|-------------| | namespaces | Record<string, string> | {} | Map of extension sourceInfo.path (or substring) → namespace prefix | | builtinNamespace | string | — | Optional prefix for built-in tools (read, bash, edit, write, grep, find, ls) | | separator | string | ":" | Separator between prefix and tool name. Use "__" for providers that reject colons (e.g. Anthropic: ^[a-zA-Z0-9_-]{1,128}$) | | autoNamespace | boolean | false | Auto-derive namespace from extension directory/package name |

Namespace matching

The key in namespaces is matched against each tool's sourceInfo.path:

  1. Exact match — key equals the full path
  2. Substring match — key is contained within the path
"deploy-extension" matches:
  ✓ /home/user/.pi/agent/extensions/deploy-extension/index.ts
  ✓ /home/user/.npm-global/lib/node_modules/@foo/deploy-extension/dist/index.js
  ✗ /home/user/.pi/agent/extensions/my-deploy/index.ts

Auto-namespace

When autoNamespace is true, tools from extensions without an explicit mapping get a namespace derived from:

| Source format | Example | Derived namespace | |---|---|---| | File path with pi- segment | /home/user/.pi/agent/extensions/pi-messenger/index.ts | messenger | | npm: package | npm:@foo/pi-deploy | deploy | | git: package | git:github.com/user/pi-tools | tools | | No pi- prefix, falls back | /home/user/.pi/agent/extensions/myext/index.ts | myext |


Commands

| Command | Description | |---------|-------------| | /namespace | Show namespaced tool count and config summary | | /namespace list | Same as above | | /namespace config | Show the full namespace config JSON | | /namespace map | Show per-tool namespace mapping with source paths |

Example output

/namespace map

  read → fs:read (<builtin:read>)
  bash → fs:bash (<builtin:bash>)
  edit → fs:edit (<builtin:edit>)
  write → fs:write (<builtin:write>)
  push → deploy:push (/home/user/.pi/agent/extensions/deploy-extension/index.ts)
  status → deploy:status (/home/user/.pi/agent/extensions/deploy-extension/index.ts)
  search (no namespace, local)

How It Works

┌─────────────────────────────────────────────────────────────┐
│  session_start                                              │
│                                                             │
│  Patch ExtensionRunner.prototype.getAllRegisteredTools      │
│  (once per process — all future calls go through the patch) │
│                                                             │
│  Build reverse namespace map: namespaced → original         │
│  (e.g. "deploy:push" → "push")                              │
└────────────────────────┬────────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────────────┐
│  AgentSession._refreshToolRegistry()                        │
│                                                             │
│  Calls getAllRegisteredTools() ──→ PATCH INTERCEPTS ──→     │
│                                                             │
│  1. resolveNamespace(): find matching prefix from config    │
│  2. applyNamespace(): rewrite name "push" → "deploy:push"   │
│  3. Object.create(): delegate to original definition        │
│     → execute() inherited untouched                         │
│     → only name, promptSnippet, promptGuidelines overridden │
│                                                             │
│  Namespaced names flow into:                                │
│    _toolDefinitions → _toolRegistry → system prompt → TUI   │
│    setActiveTools(), --tools, --exclude-tools               │
└────────────────────────┬────────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────────────┐
│  before_agent_start                                         │
│                                                             │
│  Rewrite <skill name="..."> attributes in system prompt     │
│  e.g. <skill name="brave-search"> → <skill name="web:brave- │
│  search">                                                   │
└─────────────────────────────────────────────────────────────┘
                         │
                         ▼
               LLM calls "deploy:push"
               → matches namespaced tool in registry
               → original execute() runs via Object.create delegation

Built-in tool override safety

If an extension overrides a built-in tool by name (like pi-code-previews overrides read), and that extension also has a namespace, pi-namespace skips the rewrite — namespacing it would break the override mechanism:

pi-code-previews registers tool { name: "read" }  (override)
  → namespace config maps pi-code-previews → "preview"
  → BUT "read" is a built-in override → SKIP
  → tool stays as "read", not "preview:read"

With explicit builtinNamespace: "fs":
  → built-in "read" → "fs:read"
  → override "read" from pi-code-previews → still "read" (override wins)

Only use builtinNamespace when you want to namespace all built-in tools explicitly. It intentionally does not affect extension overrides of those built-ins.

Object.create delegation

The patch does not copy tool definitions. It creates a delegating object:

const namespacedDef = Object.create(tool.definition);
namespacedDef.name = "deploy:push";                    // override name
namespacedDef.promptSnippet = "Use deploy:push to..."; // override snippet
namespacedDef.promptGuidelines = ["Use deploy:push"];  // override guidelines
// execute(), parameters(), renderCall(), renderResult() — all inherited

This means the original execute function runs with zero wrapping overhead. No proxy, no shim — just prototype chain delegation.


Known Limitations

| Limitation | Details | |------------|---------| | Built-in type guards break | isToolCallEventType("bash", event) won't match fs:bash. Use stripNamespace() to resolve back to bash. | | Tool override by name | Registering shell:bash won't override built-in bash since the names differ. | | Fragile to pi internals | A pi update could change ExtensionRunner API. The patch targets a single stable method. | | Skill namespacing is prompt-only | Skills are XML text in the system prompt — we rewrite <skill name> attributes but can't intercept /skill:ns:name invocations. |


Development

npm install            # install dev dependencies
npm test               # run tests (25 tests)
npm run typecheck      # TypeScript validation
npm run lint:dead      # dead code detection (knip)
npm run test:coverage  # tests with coverage

Related Projects

| Project | Description | |---------|-------------| | pi-tool-repair | Fix common LLM tool-call mistakes before tools execute | | pi-lazy-extensions | Lazy-load extensions on demand via ToolSearch-style proxy | | pi-startup-tracer | Trace per-extension load and handler timing | | pi-toggle-skills | Toggle skill visibility in the system prompt | | pi-trust-defer | Skip the startup trust prompt |

License

MIT