@x12i/ask-cli

Natural-language input
for your CLI. No LLM required.

Accept phrases like "install, build and test all @x12i packages" and resolve them to structured command plans — deterministic catalog matching, offline, testable.

TypeScript
import { createAskCli } from "@x12i/ask-cli";

const askCli = createAskCli({
  catalog: [{
    id:       "xnpm.install.all",
    phrases: ["install all packages", "install everything"],
    command: { base: "xnpm", args: [] },
    approval: "never",
  }],
});

const result = await askCli.resolve({
  input:   "install everything",
  context: { commandName: "xnpm" },
});

if (result.matched && result.command) {
  // validate, prompt for approval if needed, execute
}
npm install @x12i/ask-cli

How it works

Input in. Structured command out.

Every call to resolve() runs the same pipeline. No network, no model, no state — pure function, fully testable.

01
normalize
Lowercase, trim, punctuation strip, &and, everythingall, action reorder
02
exact match
Compare normalized input against catalog phrase list first — fastest path, highest confidence
03
slot match
Match typed slot patterns — {scope}, {repoName}, etc. Extract and validate values
04
alias expand
Optional deterministic alias expansion before match — e.g. scope shorthand to full filter
05
result
Matched: structured command + explanation + approval policy. Unmatched: suggestions from catalog, no execution

Features

Built for real CLI safety constraints.

The library resolves. The host CLI validates, confirms, and executes. That boundary is intentional — it means the library is testable in isolation and your CLI keeps full control.

NORM

Input normalization

Handles the real-world mess of user input: mixed case, punctuation, synonyms (everything / all), conjunction variants (& / and), and action word reordering.

MATCH

Exact + slot matching

Exact phrase matching runs first for speed and confidence. Slot patterns handle parameterized input like "all @x12i packages" where @x12i is a typed {scope} slot.

SLOTS

Built-in slot types

Five built-in types: scope, packageName, repoName, enum, string. Each validates on extraction — unknown or malformed values fail the match cleanly.

SAFE

Approval metadata

Each catalog record declares its approval policy: never, always, or when-destructive. The host CLI reads this and renders the confirmation prompt — the library never prompts directly.

MISS

Safe on no match

Unmatched input returns a structured miss result with suggestions from the catalog. It never falls back to arbitrary command execution. No match = no action, always.

HOOK

Host validation hooks

Pass a validate hook to run your own checks on the resolved command before it's returned — check context, permissions, state — without forking the library.

TEST

Fully testable

No side effects. resolve() is a pure async function. Pass a catalog, pass an input string, assert the result. No mocking, no CLI harness, no process spawning.

ALIAS

Deterministic alias expansion

Optional pre-match alias map expands shorthand consistently before matching — "x12i packages""all @x12i/* packages" — so your catalog stays clean.

TS

TypeScript-first

Fully typed catalog records, resolve results, slot values, and hooks. AskCliCatalogRecord and AskCliResolveResult are exported for use in host CLI types.


Catalog

Define phrases once.
Reuse across your CLI.

Each catalog record maps one intent to phrases, a resolved command, human-readable explanation, and an approval policy. The library owns the matching; your CLI owns what happens next.

AskCliCatalogRecord
{
  id:          "xnpm.publish.scope",
  intent:      "publish",
  phrases:     [
    "publish all {scope} packages",
    "release {scope} and push",
  ],
  slots:       {
    scope: { type: "scope" },
  },
  command:     {
    base: "xnpm",
    args: ["--filter", "{scope}/*",
            "--build", "--test",
            "--publish", "--push"],
  },
  explanation: [
    "Select packages matching {scope}/*",
    "Run install, build, and test",
    "Publish in dependency-aware order",
    "Push git changes",
  ],
  risks:       ["Publish is irreversible",
               "Push is irreversible"],
  approval:    "always",
}

Built-in slot types

TypeValidates
scopenpm scope — must start with @
packageNamevalid npm package name, scoped or unscoped
repoNamealphanumeric + hyphens, no slashes
enumvalue must be in the declared allowed list
stringany non-empty string — least restrictive

Approval policy

ValueWhen the host should prompt
neversafe to run immediately — no confirmation needed
alwaysalways ask, regardless of what the command does
when-destructiveprompt only when the resolved command touches publish, push, or remote creation

Consumers

The engine behind ask subcommands.

Any CLI that wants a natural-language entry point can ship one catalog-backed ask subcommand. The library handles the resolution; each CLI handles validation and execution.

@x12i/npm
xnpm ask "..."
@x12i/doctor-x
doctor-x ask "..."
@x12i/funcx
funcx ask "..."
host CLI integration pattern
// 1. resolve — library's job
const result = await askCli.resolve({ input, context });

if (!result.matched) {
  showSuggestions(result.suggestions);  // host renders
  return;
}

// 2. explain + confirm — host CLI's job
showPlan(result.explanation, result.risks);

if (requiresApproval(result.approval)) {
  const ok = await prompt("Execute? [y/N]");
  if (!ok) return;
}

// 3. execute — host CLI's job
await run(result.command);

Install

Add to your project.

Install as a dependency in any Node.js CLI package. No peer dependencies, no runtime requirements beyond Node.js ≥ 20.

npm install @x12i/ask-cli

Node.js ≥ 20 required  ·  npmjs.com/package/@x12i/ask-cli ↗