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

@lifeprompt/acli

v0.7.3

Published

acli - Agent CLI protocol for AI agents on MCP

Readme

ACLI - Agent CLI

CI npm version License: MIT

ACLI (Agent CLI) is a lightweight CLI protocol for AI agents built on top of MCP (Model Context Protocol).

Why ACLI?

Traditional MCP tool definitions require extensive schema for each tool, consuming valuable context window space. ACLI solves this by:

  • Single Tool per Domain: One MCP tool (e.g., math, calendar) handles related commands
  • Dynamic Discovery: Agents learn commands via help and schema
  • Shell-less Security: No shell execution, preventing injection attacks
  • Type-safe Arguments: Zod-based validation with full TypeScript inference
  • CLI & MCP Dual Support: Use as MCP tool or standalone CLI

Installation

npm install @lifeprompt/acli zod
# or
pnpm add @lifeprompt/acli zod

https://github.com/user-attachments/assets/c4b2a395-446c-4178-b552-9868ee40403c

Quick Start

MCP Server Integration

import { z } from "zod"
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"
import { registerAcli, defineCommand, arg } from "@lifeprompt/acli"

// Use defineCommand() for full type inference in handlers
const add = defineCommand({
  description: "Add two numbers",
  args: {
    a: arg(z.coerce.number(), { positional: 0 }),
    b: arg(z.coerce.number(), { positional: 1 }),
  },
  handler: async ({ a, b }) => ({ result: a + b }),  // a, b are inferred as number
})

const multiply = defineCommand({
  description: "Multiply two numbers",
  args: {
    a: arg(z.coerce.number(), { positional: 0 }),
    b: arg(z.coerce.number(), { positional: 1 }),
  },
  handler: async ({ a, b }) => ({ result: a * b }),
})

const commands = { add, multiply }

const server = new McpServer({ name: "my-server", version: "1.0.0" })

// Register as "math" tool
registerAcli(server, "math", commands, "Mathematical operations.")
// → Tool description: "Mathematical operations. Commands: add, multiply. Run 'help' for details."

How AI Agents Call ACLI Tools

Once registered, AI agents (like Claude) call the tool with a command string:

// Tool call from AI agent
{
  "name": "math",
  "arguments": {
    "command": "add 10 20"
  }
}

// Response
{
  "content": [{ "type": "text", "text": "{\"result\":30}" }]
}
// Discovery - agents can explore available commands
{ "name": "math", "arguments": { "command": "help" } }
{ "name": "math", "arguments": { "command": "help add" } }
{ "name": "math", "arguments": { "command": "schema" } }

Standalone CLI

#!/usr/bin/env node
import { z } from "zod"
import { defineCommand, runCli, arg } from "@lifeprompt/acli"

const greet = defineCommand({
  description: "Say hello",
  args: {
    name: arg(z.string(), { positional: 0 }),
  },
  handler: async ({ name }) => ({ message: `Hello, ${name}!` }),  // name is inferred as string
})

runCli({ commands: { greet } })
node my-cli.mjs greet World
# → { "message": "Hello, World!" }

Interactive REPL

Export commands from a file and explore them interactively — like dropping into a Docker container:

// tools.ts
import { z } from "zod"
import { defineCommand, arg } from "@lifeprompt/acli"

export const add = defineCommand({
  description: "Add two numbers",
  args: {
    a: arg(z.number(), { positional: 0 }),
    b: arg(z.number(), { positional: 1 }),
  },
  handler: async ({ a, b }) => ({ result: a + b }),
})

export const greet = defineCommand({
  description: "Say hello",
  args: {
    name: arg(z.string(), { positional: 0 }),
    shout: arg(z.boolean().default(false)),
  },
  handler: async ({ name, shout = false }) => {
    const msg = `Hello, ${name}!`
    return { message: shout ? msg.toUpperCase() : msg }
  },
})
npx @lifeprompt/acli repl ./tools.ts

  acli v0.7.3 — Interactive REPL
  Loaded 2 command(s) from ./tools.ts
  Type 'help' for commands, '.exit' to quit

acli> add 10 20
{ "result": 30 }

acli> greet Alice --shout
{ "message": "HELLO, ALICE!" }

acli> help
{ "commands": [{ "name": "add", ... }, { "name": "greet", ... }] }

acli> exit
Bye!

Single command execution (useful for scripting):

npx @lifeprompt/acli exec ./tools.ts "add 1 2"
# → { "result": 3 }

TypeScript support: Works natively on Node.js 22.6+, Bun, and Deno. For older Node.js, install jiti: npm install -D jiti


Argument Definition

ACLI uses Zod for type-safe argument parsing with rich validation.

arg(schema, meta?)

Wraps a Zod schema with CLI metadata:

import { z } from "zod"
import { arg } from "@lifeprompt/acli"

// Basic types
arg(z.string())                           // Required string
arg(z.coerce.number())                    // Number (coerced from string)
arg(z.coerce.number().int())              // Integer
arg(z.boolean().default(false))           // Flag (presence = true)
arg(z.array(z.string()))                  // Array (--tag a --tag b → ["a", "b"])
arg(z.coerce.date())                      // Date (ISO8601 string → Date)

// Validation
arg(z.string().min(1).max(100))           // Length validation
arg(z.coerce.number().min(0).max(100))    // Range validation
arg(z.enum(["json", "csv", "table"]))     // Enum validation
arg(z.string().email())                   // Email validation
arg(z.string().regex(/^[a-z]+$/))         // Regex validation

// Optional & defaults
arg(z.string().optional())                // Optional
arg(z.string().default("hello"))          // With default

// Metadata
arg(z.string(), { positional: 0 })        // Positional argument
arg(z.string(), { short: 'n' })           // Short alias (-n)
arg(z.string(), { description: "Name" })  // Help text
arg(z.string(), { examples: ["foo"] })    // Example values

InferArgs<T>

Infers the parsed argument types from an args definition:

const myArgs = {
  name: arg(z.string()),
  count: arg(z.coerce.number().default(10)),
  active: arg(z.boolean().optional()),
}

type MyArgs = InferArgs<typeof myArgs>
// { name: string; count: number; active?: boolean }

Command Definition

Structure

import { z } from "zod"
import { defineCommand, arg, type InferArgs } from "@lifeprompt/acli"

interface CommandDefinition<TArgs extends ArgsDefinition> {
  description: string                        // Required
  args?: TArgs                               // Zod-based arguments
  handler?: (args: InferArgs<TArgs>) => Promise<unknown>
  subcommands?: CommandRegistry              // Nested commands
}

Example with Subcommands

Use cmd() (alias for defineCommand) inside subcommands to enable type inference:

import { z } from "zod"
import { defineCommand, cmd, arg } from "@lifeprompt/acli"

const calendar = defineCommand({
  description: "Calendar management",
  subcommands: {
    events: cmd({
      description: "Manage events",
      subcommands: {
        list: cmd({
          description: "List events",
          args: {
            from: arg(z.coerce.date().optional()),
            limit: arg(z.coerce.number().int().default(10)),
          },
          handler: async ({ from, limit }) => {
            // from: Date | undefined, limit: number (types inferred!)
            return { events: await fetchEvents({ from, limit }) }
          },
        }),
        create: cmd({
          description: "Create event",
          args: {
            title: arg(z.string().min(1)),
            date: arg(z.coerce.date()),
          },
          handler: async ({ title, date }) => {
            // title: string, date: Date (types inferred!)
            return { event: await createEvent({ title, date }) }
          },
        }),
      },
    }),
  },
})

// Use directly: registerAcli(server, "cli", { calendar })

Note: Without cmd(), inline subcommand handlers receive unknown types due to TypeScript's type inference limitations. Always wrap subcommands with cmd() for full type safety.

Usage:

calendar events list --from 2026-02-01 --limit 5
calendar events create --title "Meeting" --date 2026-02-02T10:00:00Z

Positional Arguments

Positional arguments allow cleaner syntax:

const add = defineCommand({
  description: "Add numbers",
  args: {
    a: arg(z.coerce.number(), { positional: 0 }),
    b: arg(z.coerce.number(), { positional: 1 }),
  },
  handler: async ({ a, b }) => ({ result: a + b }),
})

// Use: registerAcli(server, "math", { add })

All syntaxes work:

add 10 20          # Positional
add --a 10 --b 20  # Named

To use short options like -a, define them explicitly with the short metadata:

const add = defineCommand({
  description: "Add numbers",
  args: {
    a: arg(z.coerce.number(), { positional: 0, short: 'a' }),
    b: arg(z.coerce.number(), { positional: 1, short: 'b' }),
  },
  handler: async ({ a, b }) => ({ result: a + b }),
})

// Now supports: add -a 10 -b 20

Flag Negation (--no- prefix)

Boolean flags can be explicitly set to false using the --no- prefix:

command --no-verbose    # verbose = false
command --no-color      # color = false

Repeated Options (Arrays)

Arguments defined with z.array(...) accumulate values from repeated options:

const search = defineCommand({
  description: "Search files",
  args: {
    ext: arg(z.array(z.string()), { short: 'e', description: "File extensions" }),
  },
  handler: async ({ ext }) => ({ extensions: ext }),
})

// search --ext .ts --ext .tsx  → ext: [".ts", ".tsx"]
// search -e .ts -e .tsx        → ext: [".ts", ".tsx"]

Built-in Commands

These commands are automatically available:

| Command | Description | |------------|--------------------------------------| | help | List all commands | | help <cmd> | Show command details | | schema | JSON schema for all commands | | schema <cmd> | JSON schema for specific command | | version | Show ACLI version |


Response Format

ACLI uses MCP-native response format for seamless integration.

Handler Return Values

Handlers can return values in two ways:

// 1. Simple object (auto-wrapped to MCP format)
handler: async () => ({ result: 123 })
// → { content: [{ type: "text", text: '{"result":123}' }] }

// 2. MCP native format (passed through as-is)
handler: async () => ({
  content: [
    { type: "text", text: "Hello" },
    { type: "image", data: "base64...", mimeType: "image/png" },
  ]
})
// → passed through unchanged

Error Codes

| Code | Description | |--------------------|------------------------------------------| | COMMAND_NOT_FOUND| Command does not exist | | VALIDATION_ERROR | Invalid arguments or missing required | | EXECUTION_ERROR | Handler threw an error | | PARSE_ERROR | Malformed command string | | PERMISSION_DENIED| Authorization failed |


Security

ACLI is designed with security in mind:

  • No Shell Execution: Commands are parsed and executed directly in-process
  • Command Whitelist: Only registered commands can be executed
  • Argument Validation: Zod validation before handler execution
  • DoS Prevention: Length and count limits on commands and arguments

API Reference

registerAcli(server, name, commands, description?)

Register commands as an MCP tool.

registerAcli(server, "tool_name", commands)

// With description
registerAcli(server, "tool_name", commands, "Base description.")
// → "Base description. Commands: cmd1, cmd2. Run 'help' for details."

runCli({ commands, args? })

Run as standalone CLI.

runCli({ commands })                          // Uses process.argv
runCli({ commands, args: ["add", "1", "2"] }) // Custom args

createAcli(commands)

Create a tool definition for manual integration.

const tool = createAcli(commands)
const result = await tool.execute({ command: "add 1 2" })

CLI (npx @lifeprompt/acli)

npx @lifeprompt/acli repl <file>           # Interactive REPL
npx @lifeprompt/acli exec <file> <command>  # Single command execution
npx @lifeprompt/acli --help                 # Show help
npx @lifeprompt/acli --version              # Show version

The <file> should export ACLI commands via default export, named commands export, or individual named exports.


TypeScript Types

All types are exported:

import type {
  // Argument types
  ArgSchema,
  ArgMeta,
  ArgsDefinition,
  InferArgs,
  // Command types
  CommandDefinition,
  CommandRegistry,
  // MCP migration types
  McpToolLike,
  // MCP response types
  CallToolResult,
  TextContent,
  ImageContent,
  // Error types
  AcliError,
  AcliErrorCode,
  // Options
  AcliToolOptions,
  CliOptions,
} from "@lifeprompt/acli"

// Helper functions
import { arg, defineCommand, cmd, aclify } from "@lifeprompt/acli"
// cmd is an alias for defineCommand - use inside subcommands for type inference
// aclify converts MCP-style tool definitions to ACLI CommandRegistry

Documentation


Contributing

See CONTRIBUTING.md for development setup, release flow, and guidelines.


License

MIT