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

pupt-react

v1.1.3

Published

Headless React component library for pupt-lib integration

Downloads

596

Readme

pupt-react

Headless React components and hooks for rendering pupt-lib prompts.

Features

  • Headless render-prop components — full UI flexibility, zero styling opinions
  • Five React hooksusePromptRender, useAskIterator, usePromptSearch, usePostActions, usePupt
  • Ask system — collect and validate user inputs with type-safe forms
  • Environment context — adapt prompts to model, language, runtime, and output format
  • Post-execution actions — manage follow-up actions (open URLs, review files, run commands)
  • Prompt search — debounced full-text search across prompt libraries
  • Full TypeScript types — all props, render props, and return values are typed

Installation

npm install pupt-react pupt-lib react react-dom

Peer dependencies: React 18+ or 19+, pupt-lib ^1.2.1

Quick Start

Minimal — render a prompt

import { PuptProvider, PromptRenderer } from "pupt-react";

function App() {
  const source = `<Prompt name="greeting">
  <Task>Say hello to the user.</Task>
</Prompt>`;

  return (
    <PuptProvider>
      <PromptRenderer source={source}>
        {({ output, isLoading, error }) => (
          <div>
            {isLoading && <p>Rendering...</p>}
            {error && <p>Error: {error.message}</p>}
            {output && <pre>{output}</pre>}
          </div>
        )}
      </PromptRenderer>
    </PuptProvider>
  );
}

PromptRenderer auto-renders by default (autoRender={true}), so the prompt is rendered as soon as it transforms.

With clipboard

<PromptRenderer source={source}>
  {({ output, isReady, copyToClipboard, isCopied }) => (
    <div>
      {isReady && (
        <>
          <pre>{output}</pre>
          <button onClick={copyToClipboard}>
            {isCopied ? "Copied!" : "Copy"}
          </button>
        </>
      )}
    </div>
  )}
</PromptRenderer>

isReady is true when there is output and no pending inputs remain.

With user inputs

Prompts can request user input via <Ask.*> components. When they do, pendingInputs lists what's needed:

import { useState } from "react";
import { PromptRenderer } from "pupt-react";

function PromptWithInputs() {
  const [inputs, setInputs] = useState<Map<string, unknown>>(new Map());

  const source = `<Prompt name="coder">
  <Ask.Text name="language" label="Language" description="Programming language" />
  <Task>Write a hello world program in {language}.</Task>
</Prompt>`;

  return (
    <PromptRenderer source={source} inputs={inputs}>
      {({ output, isReady, pendingInputs }) => (
        <div>
          {pendingInputs.map((input) => (
            <label key={input.name}>
              {input.label}
              <input
                type="text"
                onChange={(e) =>
                  setInputs(new Map(inputs).set(input.name, e.target.value))
                }
              />
            </label>
          ))}
          {isReady && <pre>{output}</pre>}
        </div>
      )}
    </PromptRenderer>
  );
}

Guide: Core Concepts

Headless render-prop pattern

Every component in pupt-react is headless: it manages state and logic, then passes everything to your render function. You control all markup and styling.

<PromptRenderer source={source}>
  {(props) => {
    // props contains: output, isLoading, isReady, error, pendingInputs,
    //                 postActions, copyToClipboard, isCopied, render
    return <YourCustomUI {...props} />;
  }}
</PromptRenderer>

The prompt pipeline

Prompts go through two phases:

  1. Transform — source code (JSX string) is parsed into a PuptElement tree
  2. Render — the element tree is rendered to text output, resolving inputs and environment context

PromptRenderer handles both phases. For lower-level control, use usePromptRender which exposes transform() and render() separately.

Environment context

Environment context lets prompts adapt to the current model, output format, language, and runtime. Set defaults on the provider and override per-component:

<PuptProvider
  environment={{
    llm: { model: "claude-sonnet-4-5-20250929", provider: "anthropic" },
    output: { format: "markdown" },
  }}
>
  {/* All prompts inherit these defaults */}
  <PromptRenderer
    source={source}
    environment={{ output: { format: "xml" } }}
  >
    {/* This prompt renders as XML */}
    {(props) => <div>{props.output}</div>}
  </PromptRenderer>
</PuptProvider>

The full EnvironmentContext shape:

interface EnvironmentContext {
  llm: {
    model: string;
    provider: "anthropic" | "openai" | "google" | "meta" | "mistral"
            | "deepseek" | "xai" | "cohere" | "unspecified";
    maxTokens?: number;
    temperature?: number;
  };
  output: {
    format: "xml" | "markdown" | "json" | "text" | "unspecified";
    trim: boolean;
    indent: string;
  };
  code: {
    language: string;
    highlight?: boolean;
  };
  user: {
    editor: string;
  };
  runtime: {
    hostname?: string;
    username?: string;
    cwd?: string;
    platform?: string;
    os?: string;
    locale?: string;
    timestamp?: number;
    date?: string;
    time?: string;
    uuid?: string;
  };
}

The Ask system

pupt-lib prompts can declare inputs with <Ask.*> components (e.g., <Ask.Text>, <Ask.Number>, <Ask.Select>). These surface as InputRequirement objects that you can collect via:

  • PromptRendererpendingInputs render prop + inputs prop for values
  • AskHandler — wizard-style step-through with validation
  • useAskIterator — hook for custom input collection flows

Each InputRequirement includes name, label, description, type, required, optional default, min, max, options, and a schema for validation.


Guide: Component Examples

PromptRenderer

The primary component for rendering prompts. Accepts a source string or PromptSource object and provides all render state:

<PromptRenderer
  source={source}
  inputs={inputs}
  autoRender={true}
  renderOptions={{ format: "markdown", trim: true }}
  environment={{ llm: { model: "claude-sonnet-4-5-20250929" } }}
>
  {({
    output,
    isReady,
    isLoading,
    error,
    pendingInputs,
    postActions,
    copyToClipboard,
    isCopied,
    render,
  }) => (
    <div>
      {isLoading && <Spinner />}
      {error && <ErrorBanner message={error.message} />}
      {pendingInputs.length > 0 && <InputForm inputs={pendingInputs} />}
      {isReady && (
        <>
          <pre>{output}</pre>
          <button onClick={copyToClipboard}>
            {isCopied ? "Copied!" : "Copy"}
          </button>
          <button onClick={render}>Re-render</button>
          {postActions.map((action) => (
            <PostActionButton key={action.type} action={action} />
          ))}
        </>
      )}
    </div>
  )}
</PromptRenderer>

PromptEditor

A headless editor component that transforms source on the fly with debouncing. Pair it with PromptRenderer by passing the element:

<PromptEditor defaultValue={initialSource} debounce={300}>
  {({ inputProps, element, error, isTransforming }) => (
    <div>
      <textarea {...inputProps} rows={10} />
      {isTransforming && <p>Parsing...</p>}
      {error && <p>Syntax error: {error.message}</p>}

      {element && (
        <PromptRenderer source={{ type: "element", value: element }}>
          {({ output, isLoading }) => (
            <div>
              <h3>Preview</h3>
              {isLoading ? <Spinner /> : <pre>{output}</pre>}
            </div>
          )}
        </PromptRenderer>
      )}
    </div>
  )}
</PromptEditor>

AskHandler

Wizard-style input collection with validation, progress tracking, and navigation. submit validates the value and auto-advances to the next input on success:

<AskHandler
  element={element}
  onComplete={(values) => console.log("Collected:", values)}
  initialValues={new Map()}
>
  {({
    current,
    currentIndex,
    totalInputs,
    progress,
    isDone,
    isLoading,
    values,
    submit,
    previous,
    goTo,
    reset,
    getInputProps,
  }) => {
    if (isLoading) return <Spinner />;
    if (isDone) return <p>All inputs collected!</p>;
    if (!current) return null;

    const { inputProps, value, setValue, errors } = getInputProps(current.name);

    return (
      <div>
        <progress value={progress} max={100} />
        <p>Step {currentIndex + 1} of {totalInputs}</p>

        <label htmlFor={inputProps.id}>{current.label}</label>
        <p>{current.description}</p>
        <input
          {...inputProps}
          value={String(value ?? "")}
          onChange={(e) => setValue(e.target.value)}
        />
        {errors.map((err) => (
          <p key={err} style={{ color: "red" }}>{err}</p>
        ))}

        <button onClick={previous} disabled={currentIndex === 0}>Back</button>
        <button onClick={() => submit(value)}>Next</button>
        <button onClick={reset}>Reset</button>
      </div>
    );
  }}
</AskHandler>

Guide: Hook Examples

usePromptRender

Lower-level hook for when you need direct control over transformation and rendering. Unlike PromptRenderer, autoRender defaults to false and the source must be a PromptSource object (not a plain string):

import { usePromptRender } from "pupt-react";

function CustomRenderer() {
  const {
    source,
    setSource,
    element,
    output,
    error,
    renderErrors,
    isTransforming,
    isRendering,
    isLoading,
    inputRequirements,
    postActions,
    render,
    transform,
  } = usePromptRender({
    source: { type: "source", value: "<Prompt><Task>Hello</Task></Prompt>" },
    inputs: new Map(),
    autoRender: true,
    environment: { llm: { model: "claude-sonnet-4-5-20250929" } },
  });

  return (
    <div>
      {isLoading && <p>Working...</p>}
      {output && <pre>{output}</pre>}
      <button onClick={render}>Re-render</button>
    </div>
  );
}

useAskIterator

Hook for custom input collection flows outside AskHandler:

import { useAskIterator } from "pupt-react";

function CustomInputCollector({ element }) {
  const {
    requirements,
    current,
    currentIndex,
    totalInputs,
    isDone,
    isLoading,
    inputs,
    submit,
    previous,
    goTo,
    reset,
    setValue,
    getValue,
  } = useAskIterator({
    element,
    onComplete: (inputs) => console.log("Done:", inputs),
    initialValues: new Map(),
  });

  if (isDone) return <p>All {totalInputs} inputs collected.</p>;
  // Render your custom UI...
}

usePromptSearch

Build search UIs for prompt libraries. Requires PuptProvider to be configured with prompts:

import { PuptProvider, usePromptSearch } from "pupt-react";

function App() {
  return (
    <PuptProvider prompts={myPromptLibrary}>
      <PromptSearchUI />
    </PuptProvider>
  );
}

function PromptSearchUI() {
  const { query, setQuery, results, isSearching, allTags, clear } =
    usePromptSearch({ debounce: 200, limit: 10 });

  return (
    <div>
      <input
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder="Search prompts..."
      />
      <button onClick={clear}>Clear</button>

      {isSearching && <p>Searching...</p>}

      <div>
        {allTags.map((tag) => (
          <button key={tag} onClick={() => setQuery(tag)}>{tag}</button>
        ))}
      </div>

      <ul>
        {results.map((result) => (
          <li key={result.prompt.name}>
            <strong>{result.prompt.name}</strong>
            <span> — {result.prompt.description}</span>
            <span> (score: {result.score.toFixed(2)})</span>
          </li>
        ))}
      </ul>
    </div>
  );
}

usePostActions

Manage post-execution actions with custom handlers:

import { usePostActions } from "pupt-react";

function PostActionPanel({ actions }) {
  const {
    pendingActions,
    executedActions,
    dismissedActions,
    allDone,
    execute,
    dismiss,
    executeAll,
    dismissAll,
    reset,
  } = usePostActions({
    actions,
    handlers: {
      openUrl: (action) => window.open(action.url),
      reviewFile: (action) => openInEditor(action.file),
      runCommand: (action) => runInTerminal(action.command),
    },
  });

  return (
    <div>
      <h3>Actions ({pendingActions.length} pending)</h3>
      {pendingActions.map((action) => (
        <div key={`${action.type}-${JSON.stringify(action)}`}>
          <span>{action.type}</span>
          <button onClick={() => execute(action)}>Execute</button>
          <button onClick={() => dismiss(action)}>Dismiss</button>
        </div>
      ))}
      {!allDone && (
        <>
          <button onClick={executeAll}>Execute All</button>
          <button onClick={dismissAll}>Dismiss All</button>
        </>
      )}
      {allDone && <p>All actions handled.</p>}
    </div>
  );
}

usePupt

Access the PuptProvider context directly. Most commonly used internally by other hooks, but useful when you need the search engine or shared configuration:

import { usePupt } from "pupt-react";

function DebugPanel() {
  const { searchEngine, renderOptions, environment, isLoading, error } =
    usePupt();

  if (isLoading) return <p>Initializing...</p>;
  if (error) return <p>Init error: {error.message}</p>;

  return (
    <pre>{JSON.stringify({ renderOptions, environment }, null, 2)}</pre>
  );
}

API Reference

Components

PuptProvider

Context provider that initializes pupt-lib and provides shared configuration.

| Prop | Type | Default | Description | |------|------|---------|-------------| | children | ReactNode | required | Child components | | prompts | SearchablePrompt[] | undefined | Prompts to index for search | | renderOptions | Partial<RenderOptions> | {} | Default render options for all renders | | environment | Partial<EnvironmentContext> | {} | Default environment context |

PromptRenderer

Headless component for transforming and rendering prompts.

Props:

| Prop | Type | Default | Description | |------|------|---------|-------------| | children | (props: PromptRendererRenderProps) => ReactNode | required | Render function | | source | string \| PromptSource | required | Prompt source code or pre-parsed source | | autoRender | boolean | true | Auto-render when source/inputs change | | inputs | Map<string, unknown> | undefined | Values for Ask components | | renderOptions | Partial<RenderOptions> | undefined | Render options (merged with provider defaults) | | environment | Partial<EnvironmentContext> | undefined | Environment overrides (merged with provider defaults) |

Render props:

| Prop | Type | Description | |------|------|-------------| | output | string \| null | Rendered text output | | isReady | boolean | true when output exists and no pending inputs remain | | isLoading | boolean | true while transforming or rendering | | error | Error \| null | Transformation or rendering error | | pendingInputs | InputRequirement[] | Unsatisfied input requirements from Ask components | | postActions | PostExecutionAction[] | Post-execution actions from the prompt | | copyToClipboard | () => Promise<void> | Copy output to clipboard | | isCopied | boolean | true briefly after a successful copy | | render | () => Promise<void> | Manually trigger re-render |

PromptEditor

Headless component for editing prompt source with live transformation preview.

Props:

| Prop | Type | Default | Description | |------|------|---------|-------------| | children | (props: PromptEditorRenderProps) => ReactNode | required | Render function | | defaultValue | string | undefined | Initial source value | | onChange | (value: string) => void | undefined | Called when value changes | | debounce | number | 300 | Debounce delay for transformation (ms) |

Render props:

| Prop | Type | Description | |------|------|-------------| | inputProps | { value, onChange } | Props to spread onto a textarea/input | | value | string | Current source value | | setValue | (value: string) => void | Set source value directly | | element | PuptElement \| null | Transformed element (null if pending/failed) | | error | Error \| null | Transformation error | | isTransforming | boolean | true while transformation is in progress |

AskHandler

Headless component for wizard-style input collection with validation.

Props:

| Prop | Type | Default | Description | |------|------|---------|-------------| | children | (props: AskHandlerRenderProps) => ReactNode | required | Render function | | element | PuptElement \| null | required | Element containing Ask components | | onComplete | (values: Map<string, unknown>) => void | undefined | Called when all inputs are collected | | initialValues | Map<string, unknown> | undefined | Pre-supplied input values |

Render props:

| Prop | Type | Description | |------|------|-------------| | requirements | InputRequirement[] | All input requirements | | current | InputRequirement \| null | Current requirement being collected | | currentIndex | number | Current step index | | totalInputs | number | Total number of inputs | | progress | number | Progress percentage (0–100) | | isDone | boolean | true when all inputs are collected | | isLoading | boolean | true while initializing | | values | Map<string, unknown> | All collected values | | submit | (value: unknown) => Promise<ValidationResult> | Submit value for current input (auto-advances on success) | | previous | () => void | Go to previous input | | goTo | (index: number) => void | Go to specific input by index | | reset | () => void | Reset all inputs | | getInputProps | (name: string) => AskInputProps | Get props helper for a specific input |

AskInputProps (returned by getInputProps):

| Prop | Type | Description | |------|------|-------------| | inputProps | { id, name, type, required, "aria-label" } | Props to spread onto an input element | | requirement | InputRequirement | The input requirement metadata | | value | unknown | Current value for this input | | setValue | (value: unknown) => void | Set the value | | errors | string[] | Validation errors |

Hooks

usePupt

Access the PuptProvider context.

Returns:

| Property | Type | Description | |----------|------|-------------| | searchEngine | SearchEngine \| null | Search engine (null if no prompts provided) | | renderOptions | Partial<RenderOptions> | Default render options | | environment | Partial<EnvironmentContext> | Default environment context | | isLoading | boolean | true during initialization | | error | Error \| null | Initialization error |

usePromptRender

Transform and render prompts with full control.

Options:

| Option | Type | Default | Description | |--------|------|---------|-------------| | source | PromptSource | undefined | Initial source ({ type: "source" \| "element", value }) | | inputs | Map<string, unknown> | undefined | Input values for Ask components | | environment | Partial<EnvironmentContext> | undefined | Environment context | | renderOptions | Partial<RenderOptions> | undefined | Render options | | autoRender | boolean | false | Auto-render after transformation |

Returns:

| Property | Type | Description | |----------|------|-------------| | source | PromptSource \| null | Current source | | setSource | (source: PromptSource) => void | Set a new source | | element | PuptElement \| null | Transformed element | | output | string \| null | Rendered text output | | error | Error \| null | Transformation or rendering error | | renderErrors | RenderError[] | Detailed render errors | | isTransforming | boolean | true during transformation | | isRendering | boolean | true during rendering | | isLoading | boolean | true during transformation or rendering | | inputRequirements | InputRequirement[] | Input requirements from Ask components | | postActions | PostExecutionAction[] | Post-execution actions | | render | () => Promise<void> | Trigger rendering | | transform | (sourceCode?: string) => Promise<PuptElement \| null> | Trigger transformation |

useAskIterator

Iterate through Ask inputs and collect validated responses.

Options:

| Option | Type | Default | Description | |--------|------|---------|-------------| | element | PuptElement \| null | required | Element containing Ask components | | onComplete | (values: Map<string, unknown>) => void | undefined | Callback when all inputs collected | | initialValues | Map<string, unknown> | undefined | Pre-supplied values |

Returns:

| Property | Type | Description | |----------|------|-------------| | requirements | InputRequirement[] | All input requirements | | current | InputRequirement \| null | Current requirement | | currentIndex | number | Current index | | totalInputs | number | Total count | | isDone | boolean | true when all collected | | isLoading | boolean | true while initializing | | inputs | Map<string, unknown> | Collected values | | submit | (value: unknown) => Promise<ValidationResult> | Submit and validate | | previous | () => void | Go back | | goTo | (index: number) => void | Jump to index | | reset | () => void | Reset all | | setValue | (name: string, value: unknown) => void | Set value by name | | getValue | (name: string) => unknown | Get value by name |

usePromptSearch

Search through prompts indexed by PuptProvider.

Options:

| Option | Type | Default | Description | |--------|------|---------|-------------| | debounce | number | 200 | Debounce delay (ms) | | limit | number | undefined | Max results |

Returns:

| Property | Type | Description | |----------|------|-------------| | query | string | Current query | | setQuery | (query: string) => void | Set query | | results | SearchResult[] | Search results | | isSearching | boolean | true while searching | | allTags | string[] | All tags from indexed prompts | | clear | () => void | Clear query and results |

usePostActions

Manage post-execution actions.

Options:

| Option | Type | Default | Description | |--------|------|---------|-------------| | actions | PostExecutionAction[] | required | Actions to manage | | handlers | Partial<Record<PostExecutionAction["type"], PostActionHandler>> | undefined | Custom handlers by action type |

Returns:

| Property | Type | Description | |----------|------|-------------| | pendingActions | PostExecutionAction[] | Not yet handled | | executedActions | PostExecutionAction[] | Successfully executed | | dismissedActions | PostExecutionAction[] | Dismissed without executing | | allDone | boolean | true when none pending | | execute | (action: PostExecutionAction) => Promise<void> | Execute one action | | dismiss | (action: PostExecutionAction) => void | Dismiss one action | | executeAll | () => Promise<void> | Execute all pending | | dismissAll | () => void | Dismiss all pending | | reset | () => void | Reset all to pending |

Types

All types are exported from the package entry point. Key types re-exported from pupt-lib:

| Type | Description | |------|-------------| | PuptElement | Parsed prompt element tree | | PromptSource | { type: "source" \| "element"; value: string \| PuptElement } | | InputRequirement | Describes a user input required by an Ask component | | ValidationResult | Result of validating a user input ({ valid, errors, warnings }) | | PostExecutionAction | Union of ReviewFileAction \| OpenUrlAction \| RunCommandAction | | SearchablePrompt | Prompt metadata for indexing ({ name, description?, tags, library, content? }) | | SearchResult | Search hit ({ prompt, score, matches }) | | RenderOptions | Options for rendering ({ format?, trim?, indent?, maxDepth?, inputs?, env? }) | | RenderResult | Discriminated union: RenderSuccess \| RenderFailure | | RenderError | Validation/runtime error during rendering | | EnvironmentContext | Full environment configuration (see Environment context) |

Browser Usage

When using pupt-lib in the browser (e.g., for a demo app), an import map is required for dynamic ES module evaluation:

<script type="importmap">
  {
    "imports": {
      "pupt-lib": "https://unpkg.com/pupt-lib@VERSION/dist/index.js",
      "pupt-lib/jsx-runtime": "https://unpkg.com/pupt-lib@VERSION/dist/jsx-runtime/index.js",
      "zod": "https://unpkg.com/[email protected]/lib/index.mjs",
      "minisearch": "https://unpkg.com/[email protected]/dist/es/index.js"
    }
  }
</script>

Replace VERSION with the pupt-lib version you are using.

Development

npm run build        # Build the library (outputs to dist/)
npm run build:demo   # Build the demo website (outputs to dist-demo/)
npm run dev:demo     # Start demo dev server
npm run lint         # ESLint + TypeScript type checking
npm run lint:fix     # Auto-fix linting issues
npm run test         # Run all tests
npm run test:watch   # Watch mode
npm run test:coverage # Run tests with coverage

License

MIT