pupt-react 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,15 +1,16 @@
1
1
  # pupt-react
2
2
 
3
- A headless React component library for integrating [pupt-lib](https://github.com/apowers313/pupt-lib) into web applications. Provides hooks and render-prop components for prompt transformation, rendering, and user input collection.
3
+ Headless React components and hooks for rendering [pupt-lib](https://github.com/apowers313/pupt-lib) prompts.
4
4
 
5
5
  ## Features
6
6
 
7
- - **Headless Components**: Full UI flexibility with render-prop patterns
8
- - **React Hooks**: Access pupt-lib functionality with idiomatic React APIs
9
- - **Ask System Integration**: Collect and validate user inputs with type-safe forms
10
- - **Search Support**: Built-in prompt search functionality
11
- - **Post-Actions**: Handle post-execution actions from prompts
12
- - **TypeScript**: Full type definitions included
7
+ - **Headless render-prop components** — full UI flexibility, zero styling opinions
8
+ - **Five React hooks** `usePromptRender`, `useAskIterator`, `usePromptSearch`, `usePostActions`, `usePupt`
9
+ - **Ask system** collect and validate user inputs with type-safe forms
10
+ - **Environment context** adapt prompts to model, language, runtime, and output format
11
+ - **Post-execution actions** — manage follow-up actions (open URLs, review files, run commands)
12
+ - **Prompt search** debounced full-text search across prompt libraries
13
+ - **Full TypeScript types** — all props, render props, and return values are typed
13
14
 
14
15
  ## Installation
15
16
 
@@ -17,50 +18,91 @@ A headless React component library for integrating [pupt-lib](https://github.com
17
18
  npm install pupt-react pupt-lib react react-dom
18
19
  ```
19
20
 
20
- ## Quick Start
21
+ **Peer dependencies:** React 18+ or 19+, pupt-lib ^1.2.1
21
22
 
22
- ### Basic Setup
23
+ ## Quick Start
23
24
 
24
- Wrap your application with `PuptProvider` to enable pupt-lib integration:
25
+ ### Minimal render a prompt
25
26
 
26
27
  ```tsx
27
28
  import { PuptProvider, PromptRenderer } from "pupt-react";
28
29
 
29
30
  function App() {
31
+ const source = `<Prompt name="greeting">
32
+ <Task>Say hello to the user.</Task>
33
+ </Prompt>`;
34
+
30
35
  return (
31
36
  <PuptProvider>
32
- <MyPromptComponent />
37
+ <PromptRenderer source={source}>
38
+ {({ output, isLoading, error }) => (
39
+ <div>
40
+ {isLoading && <p>Rendering...</p>}
41
+ {error && <p>Error: {error.message}</p>}
42
+ {output && <pre>{output}</pre>}
43
+ </div>
44
+ )}
45
+ </PromptRenderer>
33
46
  </PuptProvider>
34
47
  );
35
48
  }
36
49
  ```
37
50
 
38
- ### Rendering a Prompt
51
+ `PromptRenderer` auto-renders by default (`autoRender={true}`), so the prompt is rendered as soon as it transforms.
52
+
53
+ ### With clipboard
54
+
55
+ ```tsx
56
+ <PromptRenderer source={source}>
57
+ {({ output, isReady, copyToClipboard, isCopied }) => (
58
+ <div>
59
+ {isReady && (
60
+ <>
61
+ <pre>{output}</pre>
62
+ <button onClick={copyToClipboard}>
63
+ {isCopied ? "Copied!" : "Copy"}
64
+ </button>
65
+ </>
66
+ )}
67
+ </div>
68
+ )}
69
+ </PromptRenderer>
70
+ ```
71
+
72
+ `isReady` is `true` when there is output and no pending inputs remain.
73
+
74
+ ### With user inputs
39
75
 
40
- Use `PromptRenderer` with the render-prop pattern for full control over rendering:
76
+ Prompts can request user input via `<Ask.*>` components. When they do, `pendingInputs` lists what's needed:
41
77
 
42
78
  ```tsx
79
+ import { useState } from "react";
43
80
  import { PromptRenderer } from "pupt-react";
44
81
 
45
- function MyPromptComponent() {
46
- const source = `<Prompt name="greeting">
47
- <Task>Say hello to the user.</Task>
82
+ function PromptWithInputs() {
83
+ const [inputs, setInputs] = useState<Map<string, unknown>>(new Map());
84
+
85
+ const source = `<Prompt name="coder">
86
+ <Ask.Text name="language" label="Language" description="Programming language" />
87
+ <Task>Write a hello world program in {language}.</Task>
48
88
  </Prompt>`;
49
89
 
50
90
  return (
51
- <PromptRenderer source={source} autoRender>
52
- {({ output, error, isRendering, copyToClipboard }) => (
91
+ <PromptRenderer source={source} inputs={inputs}>
92
+ {({ output, isReady, pendingInputs }) => (
53
93
  <div>
54
- {isRendering ? (
55
- <p>Rendering...</p>
56
- ) : error ? (
57
- <p>Error: {error.message}</p>
58
- ) : (
59
- <>
60
- <pre>{output}</pre>
61
- <button onClick={copyToClipboard}>Copy</button>
62
- </>
63
- )}
94
+ {pendingInputs.map((input) => (
95
+ <label key={input.name}>
96
+ {input.label}
97
+ <input
98
+ type="text"
99
+ onChange={(e) =>
100
+ setInputs(new Map(inputs).set(input.name, e.target.value))
101
+ }
102
+ />
103
+ </label>
104
+ ))}
105
+ {isReady && <pre>{output}</pre>}
64
106
  </div>
65
107
  )}
66
108
  </PromptRenderer>
@@ -68,207 +110,713 @@ function MyPromptComponent() {
68
110
  }
69
111
  ```
70
112
 
71
- ### Using Hooks Directly
113
+ ---
72
114
 
73
- For more control, use the hooks directly:
115
+ ## Guide: Core Concepts
74
116
 
75
- ```tsx
76
- import { usePupt, usePromptRender } from "pupt-react";
117
+ ### Headless render-prop pattern
77
118
 
78
- function MyComponent() {
79
- const { transformer, registry } = usePupt();
80
- const { output, render, isRendering, error } = usePromptRender({
81
- source: { type: "source", value: "<Prompt><Task>Hello</Task></Prompt>" },
82
- autoRender: true,
83
- });
119
+ 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.
84
120
 
85
- return <div>{output}</div>;
86
- }
121
+ ```tsx
122
+ <PromptRenderer source={source}>
123
+ {(props) => {
124
+ // props contains: output, isLoading, isReady, error, pendingInputs,
125
+ // postActions, copyToClipboard, isCopied, render
126
+ return <YourCustomUI {...props} />;
127
+ }}
128
+ </PromptRenderer>
87
129
  ```
88
130
 
89
- ## Components
131
+ ### The prompt pipeline
132
+
133
+ Prompts go through two phases:
90
134
 
91
- ### PuptProvider
135
+ 1. **Transform** — source code (JSX string) is parsed into a `PuptElement` tree
136
+ 2. **Render** — the element tree is rendered to text output, resolving inputs and environment context
92
137
 
93
- Context provider that initializes pupt-lib and provides configuration to child components.
138
+ `PromptRenderer` handles both phases. For lower-level control, use `usePromptRender` which exposes `transform()` and `render()` separately.
139
+
140
+ ### Environment context
141
+
142
+ Environment context lets prompts adapt to the current model, output format, language, and runtime. Set defaults on the provider and override per-component:
94
143
 
95
144
  ```tsx
96
145
  <PuptProvider
97
- registry={customRegistry} // Optional custom ComponentRegistry
98
- searchEngine={searchEngine} // Optional SearchEngine instance
146
+ environment={{
147
+ llm: { model: "claude-sonnet-4-5-20250929", provider: "anthropic" },
148
+ output: { format: "markdown" },
149
+ }}
99
150
  >
100
- {children}
151
+ {/* All prompts inherit these defaults */}
152
+ <PromptRenderer
153
+ source={source}
154
+ environment={{ output: { format: "xml" } }}
155
+ >
156
+ {/* This prompt renders as XML */}
157
+ {(props) => <div>{props.output}</div>}
158
+ </PromptRenderer>
101
159
  </PuptProvider>
102
160
  ```
103
161
 
162
+ The full `EnvironmentContext` shape:
163
+
164
+ ```ts
165
+ interface EnvironmentContext {
166
+ llm: {
167
+ model: string;
168
+ provider: "anthropic" | "openai" | "google" | "meta" | "mistral"
169
+ | "deepseek" | "xai" | "cohere" | "unspecified";
170
+ maxTokens?: number;
171
+ temperature?: number;
172
+ };
173
+ output: {
174
+ format: "xml" | "markdown" | "json" | "text" | "unspecified";
175
+ trim: boolean;
176
+ indent: string;
177
+ };
178
+ code: {
179
+ language: string;
180
+ highlight?: boolean;
181
+ };
182
+ user: {
183
+ editor: string;
184
+ };
185
+ runtime: {
186
+ hostname?: string;
187
+ username?: string;
188
+ cwd?: string;
189
+ platform?: string;
190
+ os?: string;
191
+ locale?: string;
192
+ timestamp?: number;
193
+ date?: string;
194
+ time?: string;
195
+ uuid?: string;
196
+ };
197
+ }
198
+ ```
199
+
200
+ ### The Ask system
201
+
202
+ 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:
203
+
204
+ - **`PromptRenderer`** — `pendingInputs` render prop + `inputs` prop for values
205
+ - **`AskHandler`** — wizard-style step-through with validation
206
+ - **`useAskIterator`** — hook for custom input collection flows
207
+
208
+ Each `InputRequirement` includes `name`, `label`, `description`, `type`, `required`, optional `default`, `min`, `max`, `options`, and a `schema` for validation.
209
+
210
+ ---
211
+
212
+ ## Guide: Component Examples
213
+
104
214
  ### PromptRenderer
105
215
 
106
- Headless component for transforming and rendering prompts.
216
+ The primary component for rendering prompts. Accepts a source string or `PromptSource` object and provides all render state:
107
217
 
108
218
  ```tsx
109
219
  <PromptRenderer
110
- source={source} // Prompt source string
111
- inputs={inputsMap} // Map of user input values
112
- autoRender={true} // Auto-render when source changes
220
+ source={source}
221
+ inputs={inputs}
222
+ autoRender={true}
223
+ renderOptions={{ format: "markdown", trim: true }}
224
+ environment={{ llm: { model: "claude-sonnet-4-5-20250929" } }}
113
225
  >
114
- {(props) => (
115
- // props: output, error, isRendering, pendingInputs, copyToClipboard, isCopied
226
+ {({
227
+ output,
228
+ isReady,
229
+ isLoading,
230
+ error,
231
+ pendingInputs,
232
+ postActions,
233
+ copyToClipboard,
234
+ isCopied,
235
+ render,
236
+ }) => (
237
+ <div>
238
+ {isLoading && <Spinner />}
239
+ {error && <ErrorBanner message={error.message} />}
240
+ {pendingInputs.length > 0 && <InputForm inputs={pendingInputs} />}
241
+ {isReady && (
242
+ <>
243
+ <pre>{output}</pre>
244
+ <button onClick={copyToClipboard}>
245
+ {isCopied ? "Copied!" : "Copy"}
246
+ </button>
247
+ <button onClick={render}>Re-render</button>
248
+ {postActions.map((action) => (
249
+ <PostActionButton key={action.type} action={action} />
250
+ ))}
251
+ </>
252
+ )}
253
+ </div>
116
254
  )}
117
255
  </PromptRenderer>
118
256
  ```
119
257
 
120
258
  ### PromptEditor
121
259
 
122
- Headless component for editing prompt source with transformation preview.
260
+ A headless editor component that transforms source on the fly with debouncing. Pair it with `PromptRenderer` by passing the `element`:
123
261
 
124
262
  ```tsx
125
- <PromptEditor
126
- defaultValue={source}
127
- onChange={handleChange}
128
- debounce={300}
129
- >
130
- {({ inputProps, value, error, isTransforming }) => (
131
- <textarea {...inputProps} />
263
+ <PromptEditor defaultValue={initialSource} debounce={300}>
264
+ {({ inputProps, element, error, isTransforming }) => (
265
+ <div>
266
+ <textarea {...inputProps} rows={10} />
267
+ {isTransforming && <p>Parsing...</p>}
268
+ {error && <p>Syntax error: {error.message}</p>}
269
+
270
+ {element && (
271
+ <PromptRenderer source={{ type: "element", value: element }}>
272
+ {({ output, isLoading }) => (
273
+ <div>
274
+ <h3>Preview</h3>
275
+ {isLoading ? <Spinner /> : <pre>{output}</pre>}
276
+ </div>
277
+ )}
278
+ </PromptRenderer>
279
+ )}
280
+ </div>
132
281
  )}
133
282
  </PromptEditor>
134
283
  ```
135
284
 
136
285
  ### AskHandler
137
286
 
138
- Headless component for handling Ask input requirements.
287
+ Wizard-style input collection with validation, progress tracking, and navigation. `submit` validates the value and auto-advances to the next input on success:
139
288
 
140
289
  ```tsx
141
290
  <AskHandler
142
291
  element={element}
143
- onComplete={(inputs) => console.log(inputs)}
292
+ onComplete={(values) => console.log("Collected:", values)}
293
+ initialValues={new Map()}
144
294
  >
145
- {({ requirements, current, progress, getInputProps, next, previous }) => (
146
- // Render your custom form UI
147
- )}
295
+ {({
296
+ current,
297
+ currentIndex,
298
+ totalInputs,
299
+ progress,
300
+ isDone,
301
+ isLoading,
302
+ values,
303
+ submit,
304
+ previous,
305
+ goTo,
306
+ reset,
307
+ getInputProps,
308
+ }) => {
309
+ if (isLoading) return <Spinner />;
310
+ if (isDone) return <p>All inputs collected!</p>;
311
+ if (!current) return null;
312
+
313
+ const { inputProps, value, setValue, errors } = getInputProps(current.name);
314
+
315
+ return (
316
+ <div>
317
+ <progress value={progress} max={100} />
318
+ <p>Step {currentIndex + 1} of {totalInputs}</p>
319
+
320
+ <label htmlFor={inputProps.id}>{current.label}</label>
321
+ <p>{current.description}</p>
322
+ <input
323
+ {...inputProps}
324
+ value={String(value ?? "")}
325
+ onChange={(e) => setValue(e.target.value)}
326
+ />
327
+ {errors.map((err) => (
328
+ <p key={err} style={{ color: "red" }}>{err}</p>
329
+ ))}
330
+
331
+ <button onClick={previous} disabled={currentIndex === 0}>Back</button>
332
+ <button onClick={() => submit(value)}>Next</button>
333
+ <button onClick={reset}>Reset</button>
334
+ </div>
335
+ );
336
+ }}
148
337
  </AskHandler>
149
338
  ```
150
339
 
151
- ## Hooks
152
-
153
- ### usePupt
154
-
155
- Access the PuptProvider context:
340
+ ---
156
341
 
157
- ```tsx
158
- const { transformer, registry, searchEngine, isLoading, error } = usePupt();
159
- ```
342
+ ## Guide: Hook Examples
160
343
 
161
344
  ### usePromptRender
162
345
 
163
- Transform and render prompts:
346
+ 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):
164
347
 
165
348
  ```tsx
166
- const {
167
- source,
168
- setSource,
169
- element,
170
- output,
171
- error,
172
- isTransforming,
173
- isRendering,
174
- inputRequirements,
175
- render,
176
- transform,
177
- } = usePromptRender({
178
- source: { type: "source", value: promptString },
179
- inputs: inputsMap,
180
- autoRender: true,
181
- });
349
+ import { usePromptRender } from "pupt-react";
350
+
351
+ function CustomRenderer() {
352
+ const {
353
+ source,
354
+ setSource,
355
+ element,
356
+ output,
357
+ error,
358
+ renderErrors,
359
+ isTransforming,
360
+ isRendering,
361
+ isLoading,
362
+ inputRequirements,
363
+ postActions,
364
+ render,
365
+ transform,
366
+ } = usePromptRender({
367
+ source: { type: "source", value: "<Prompt><Task>Hello</Task></Prompt>" },
368
+ inputs: new Map(),
369
+ autoRender: true,
370
+ environment: { llm: { model: "claude-sonnet-4-5-20250929" } },
371
+ });
372
+
373
+ return (
374
+ <div>
375
+ {isLoading && <p>Working...</p>}
376
+ {output && <pre>{output}</pre>}
377
+ <button onClick={render}>Re-render</button>
378
+ </div>
379
+ );
380
+ }
182
381
  ```
183
382
 
184
383
  ### useAskIterator
185
384
 
186
- Iterate through Ask inputs and collect validated responses:
385
+ Hook for custom input collection flows outside `AskHandler`:
187
386
 
188
387
  ```tsx
189
- const {
190
- current,
191
- currentIndex,
192
- totalInputs,
193
- inputs,
194
- isDone,
195
- submit,
196
- next,
197
- previous,
198
- reset,
199
- getValue,
200
- setValue,
201
- } = useAskIterator({
202
- element,
203
- onComplete: (inputs) => console.log("All inputs collected", inputs),
204
- });
388
+ import { useAskIterator } from "pupt-react";
389
+
390
+ function CustomInputCollector({ element }) {
391
+ const {
392
+ requirements,
393
+ current,
394
+ currentIndex,
395
+ totalInputs,
396
+ isDone,
397
+ isLoading,
398
+ inputs,
399
+ submit,
400
+ previous,
401
+ goTo,
402
+ reset,
403
+ setValue,
404
+ getValue,
405
+ } = useAskIterator({
406
+ element,
407
+ onComplete: (inputs) => console.log("Done:", inputs),
408
+ initialValues: new Map(),
409
+ });
410
+
411
+ if (isDone) return <p>All {totalInputs} inputs collected.</p>;
412
+ // Render your custom UI...
413
+ }
205
414
  ```
206
415
 
207
416
  ### usePromptSearch
208
417
 
209
- Search through available prompts:
418
+ Build search UIs for prompt libraries. Requires `PuptProvider` to be configured with `prompts`:
210
419
 
211
420
  ```tsx
212
- const { query, setQuery, results, isSearching, clear } = usePromptSearch({
213
- debounce: 300,
214
- limit: 10,
215
- });
421
+ import { PuptProvider, usePromptSearch } from "pupt-react";
422
+
423
+ function App() {
424
+ return (
425
+ <PuptProvider prompts={myPromptLibrary}>
426
+ <PromptSearchUI />
427
+ </PuptProvider>
428
+ );
429
+ }
430
+
431
+ function PromptSearchUI() {
432
+ const { query, setQuery, results, isSearching, allTags, clear } =
433
+ usePromptSearch({ debounce: 200, limit: 10 });
434
+
435
+ return (
436
+ <div>
437
+ <input
438
+ value={query}
439
+ onChange={(e) => setQuery(e.target.value)}
440
+ placeholder="Search prompts..."
441
+ />
442
+ <button onClick={clear}>Clear</button>
443
+
444
+ {isSearching && <p>Searching...</p>}
445
+
446
+ <div>
447
+ {allTags.map((tag) => (
448
+ <button key={tag} onClick={() => setQuery(tag)}>{tag}</button>
449
+ ))}
450
+ </div>
451
+
452
+ <ul>
453
+ {results.map((result) => (
454
+ <li key={result.prompt.name}>
455
+ <strong>{result.prompt.name}</strong>
456
+ <span> — {result.prompt.description}</span>
457
+ <span> (score: {result.score.toFixed(2)})</span>
458
+ </li>
459
+ ))}
460
+ </ul>
461
+ </div>
462
+ );
463
+ }
216
464
  ```
217
465
 
218
466
  ### usePostActions
219
467
 
220
- Handle post-execution actions:
468
+ Manage post-execution actions with custom handlers:
221
469
 
222
470
  ```tsx
223
- const { pendingActions, executedActions, execute, executeAll, dismiss } =
224
- usePostActions({
471
+ import { usePostActions } from "pupt-react";
472
+
473
+ function PostActionPanel({ actions }) {
474
+ const {
475
+ pendingActions,
476
+ executedActions,
477
+ dismissedActions,
478
+ allDone,
479
+ execute,
480
+ dismiss,
481
+ executeAll,
482
+ dismissAll,
483
+ reset,
484
+ } = usePostActions({
225
485
  actions,
226
486
  handlers: {
227
- openUrl: (url) => window.open(url),
487
+ openUrl: (action) => window.open(action.url),
488
+ reviewFile: (action) => openInEditor(action.file),
489
+ runCommand: (action) => runInTerminal(action.command),
228
490
  },
229
491
  });
492
+
493
+ return (
494
+ <div>
495
+ <h3>Actions ({pendingActions.length} pending)</h3>
496
+ {pendingActions.map((action) => (
497
+ <div key={`${action.type}-${JSON.stringify(action)}`}>
498
+ <span>{action.type}</span>
499
+ <button onClick={() => execute(action)}>Execute</button>
500
+ <button onClick={() => dismiss(action)}>Dismiss</button>
501
+ </div>
502
+ ))}
503
+ {!allDone && (
504
+ <>
505
+ <button onClick={executeAll}>Execute All</button>
506
+ <button onClick={dismissAll}>Dismiss All</button>
507
+ </>
508
+ )}
509
+ {allDone && <p>All actions handled.</p>}
510
+ </div>
511
+ );
512
+ }
230
513
  ```
231
514
 
232
- ## Demo
515
+ ### usePupt
516
+
517
+ Access the `PuptProvider` context directly. Most commonly used internally by other hooks, but useful when you need the search engine or shared configuration:
233
518
 
234
- A demo website showcasing pupt-react capabilities is included. To run locally:
519
+ ```tsx
520
+ import { usePupt } from "pupt-react";
235
521
 
236
- ```bash
237
- npm run dev:demo
238
- ```
522
+ function DebugPanel() {
523
+ const { searchEngine, renderOptions, environment, isLoading, error } =
524
+ usePupt();
239
525
 
240
- Or build for production:
526
+ if (isLoading) return <p>Initializing...</p>;
527
+ if (error) return <p>Init error: {error.message}</p>;
241
528
 
242
- ```bash
243
- npm run build:demo
529
+ return (
530
+ <pre>{JSON.stringify({ renderOptions, environment }, null, 2)}</pre>
531
+ );
532
+ }
244
533
  ```
245
534
 
246
- ## Development
535
+ ---
247
536
 
248
- ```bash
249
- # Install dependencies
250
- npm install
537
+ ## API Reference
538
+
539
+ ### Components
251
540
 
252
- # Run tests
253
- npm test
541
+ #### PuptProvider
254
542
 
255
- # Run tests with coverage
256
- npm run test:coverage
543
+ Context provider that initializes pupt-lib and provides shared configuration.
257
544
 
258
- # Lint code
259
- npm run lint
545
+ | Prop | Type | Default | Description |
546
+ |------|------|---------|-------------|
547
+ | `children` | `ReactNode` | required | Child components |
548
+ | `prompts` | `SearchablePrompt[]` | `undefined` | Prompts to index for search |
549
+ | `renderOptions` | `Partial<RenderOptions>` | `{}` | Default render options for all renders |
550
+ | `environment` | `Partial<EnvironmentContext>` | `{}` | Default environment context |
260
551
 
261
- # Build library
262
- npm run build
552
+ #### PromptRenderer
263
553
 
264
- # Build demo
265
- npm run build:demo
554
+ Headless component for transforming and rendering prompts.
555
+
556
+ **Props:**
557
+
558
+ | Prop | Type | Default | Description |
559
+ |------|------|---------|-------------|
560
+ | `children` | `(props: PromptRendererRenderProps) => ReactNode` | required | Render function |
561
+ | `source` | `string \| PromptSource` | required | Prompt source code or pre-parsed source |
562
+ | `autoRender` | `boolean` | `true` | Auto-render when source/inputs change |
563
+ | `inputs` | `Map<string, unknown>` | `undefined` | Values for Ask components |
564
+ | `renderOptions` | `Partial<RenderOptions>` | `undefined` | Render options (merged with provider defaults) |
565
+ | `environment` | `Partial<EnvironmentContext>` | `undefined` | Environment overrides (merged with provider defaults) |
566
+
567
+ **Render props:**
568
+
569
+ | Prop | Type | Description |
570
+ |------|------|-------------|
571
+ | `output` | `string \| null` | Rendered text output |
572
+ | `isReady` | `boolean` | `true` when output exists and no pending inputs remain |
573
+ | `isLoading` | `boolean` | `true` while transforming or rendering |
574
+ | `error` | `Error \| null` | Transformation or rendering error |
575
+ | `pendingInputs` | `InputRequirement[]` | Unsatisfied input requirements from Ask components |
576
+ | `postActions` | `PostExecutionAction[]` | Post-execution actions from the prompt |
577
+ | `copyToClipboard` | `() => Promise<void>` | Copy output to clipboard |
578
+ | `isCopied` | `boolean` | `true` briefly after a successful copy |
579
+ | `render` | `() => Promise<void>` | Manually trigger re-render |
580
+
581
+ #### PromptEditor
582
+
583
+ Headless component for editing prompt source with live transformation preview.
584
+
585
+ **Props:**
586
+
587
+ | Prop | Type | Default | Description |
588
+ |------|------|---------|-------------|
589
+ | `children` | `(props: PromptEditorRenderProps) => ReactNode` | required | Render function |
590
+ | `defaultValue` | `string` | `undefined` | Initial source value |
591
+ | `onChange` | `(value: string) => void` | `undefined` | Called when value changes |
592
+ | `debounce` | `number` | `300` | Debounce delay for transformation (ms) |
593
+
594
+ **Render props:**
595
+
596
+ | Prop | Type | Description |
597
+ |------|------|-------------|
598
+ | `inputProps` | `{ value, onChange }` | Props to spread onto a textarea/input |
599
+ | `value` | `string` | Current source value |
600
+ | `setValue` | `(value: string) => void` | Set source value directly |
601
+ | `element` | `PuptElement \| null` | Transformed element (null if pending/failed) |
602
+ | `error` | `Error \| null` | Transformation error |
603
+ | `isTransforming` | `boolean` | `true` while transformation is in progress |
604
+
605
+ #### AskHandler
606
+
607
+ Headless component for wizard-style input collection with validation.
608
+
609
+ **Props:**
610
+
611
+ | Prop | Type | Default | Description |
612
+ |------|------|---------|-------------|
613
+ | `children` | `(props: AskHandlerRenderProps) => ReactNode` | required | Render function |
614
+ | `element` | `PuptElement \| null` | required | Element containing Ask components |
615
+ | `onComplete` | `(values: Map<string, unknown>) => void` | `undefined` | Called when all inputs are collected |
616
+ | `initialValues` | `Map<string, unknown>` | `undefined` | Pre-supplied input values |
617
+
618
+ **Render props:**
619
+
620
+ | Prop | Type | Description |
621
+ |------|------|-------------|
622
+ | `requirements` | `InputRequirement[]` | All input requirements |
623
+ | `current` | `InputRequirement \| null` | Current requirement being collected |
624
+ | `currentIndex` | `number` | Current step index |
625
+ | `totalInputs` | `number` | Total number of inputs |
626
+ | `progress` | `number` | Progress percentage (0–100) |
627
+ | `isDone` | `boolean` | `true` when all inputs are collected |
628
+ | `isLoading` | `boolean` | `true` while initializing |
629
+ | `values` | `Map<string, unknown>` | All collected values |
630
+ | `submit` | `(value: unknown) => Promise<ValidationResult>` | Submit value for current input (auto-advances on success) |
631
+ | `previous` | `() => void` | Go to previous input |
632
+ | `goTo` | `(index: number) => void` | Go to specific input by index |
633
+ | `reset` | `() => void` | Reset all inputs |
634
+ | `getInputProps` | `(name: string) => AskInputProps` | Get props helper for a specific input |
635
+
636
+ **AskInputProps** (returned by `getInputProps`):
637
+
638
+ | Prop | Type | Description |
639
+ |------|------|-------------|
640
+ | `inputProps` | `{ id, name, type, required, "aria-label" }` | Props to spread onto an input element |
641
+ | `requirement` | `InputRequirement` | The input requirement metadata |
642
+ | `value` | `unknown` | Current value for this input |
643
+ | `setValue` | `(value: unknown) => void` | Set the value |
644
+ | `errors` | `string[]` | Validation errors |
645
+
646
+ ### Hooks
647
+
648
+ #### usePupt
649
+
650
+ Access the PuptProvider context.
651
+
652
+ **Returns:**
653
+
654
+ | Property | Type | Description |
655
+ |----------|------|-------------|
656
+ | `searchEngine` | `SearchEngine \| null` | Search engine (null if no prompts provided) |
657
+ | `renderOptions` | `Partial<RenderOptions>` | Default render options |
658
+ | `environment` | `Partial<EnvironmentContext>` | Default environment context |
659
+ | `isLoading` | `boolean` | `true` during initialization |
660
+ | `error` | `Error \| null` | Initialization error |
661
+
662
+ #### usePromptRender
663
+
664
+ Transform and render prompts with full control.
665
+
666
+ **Options:**
667
+
668
+ | Option | Type | Default | Description |
669
+ |--------|------|---------|-------------|
670
+ | `source` | `PromptSource` | `undefined` | Initial source (`{ type: "source" \| "element", value }`) |
671
+ | `inputs` | `Map<string, unknown>` | `undefined` | Input values for Ask components |
672
+ | `environment` | `Partial<EnvironmentContext>` | `undefined` | Environment context |
673
+ | `renderOptions` | `Partial<RenderOptions>` | `undefined` | Render options |
674
+ | `autoRender` | `boolean` | `false` | Auto-render after transformation |
675
+
676
+ **Returns:**
677
+
678
+ | Property | Type | Description |
679
+ |----------|------|-------------|
680
+ | `source` | `PromptSource \| null` | Current source |
681
+ | `setSource` | `(source: PromptSource) => void` | Set a new source |
682
+ | `element` | `PuptElement \| null` | Transformed element |
683
+ | `output` | `string \| null` | Rendered text output |
684
+ | `error` | `Error \| null` | Transformation or rendering error |
685
+ | `renderErrors` | `RenderError[]` | Detailed render errors |
686
+ | `isTransforming` | `boolean` | `true` during transformation |
687
+ | `isRendering` | `boolean` | `true` during rendering |
688
+ | `isLoading` | `boolean` | `true` during transformation or rendering |
689
+ | `inputRequirements` | `InputRequirement[]` | Input requirements from Ask components |
690
+ | `postActions` | `PostExecutionAction[]` | Post-execution actions |
691
+ | `render` | `() => Promise<void>` | Trigger rendering |
692
+ | `transform` | `(sourceCode?: string) => Promise<PuptElement \| null>` | Trigger transformation |
693
+
694
+ #### useAskIterator
695
+
696
+ Iterate through Ask inputs and collect validated responses.
697
+
698
+ **Options:**
699
+
700
+ | Option | Type | Default | Description |
701
+ |--------|------|---------|-------------|
702
+ | `element` | `PuptElement \| null` | required | Element containing Ask components |
703
+ | `onComplete` | `(values: Map<string, unknown>) => void` | `undefined` | Callback when all inputs collected |
704
+ | `initialValues` | `Map<string, unknown>` | `undefined` | Pre-supplied values |
705
+
706
+ **Returns:**
707
+
708
+ | Property | Type | Description |
709
+ |----------|------|-------------|
710
+ | `requirements` | `InputRequirement[]` | All input requirements |
711
+ | `current` | `InputRequirement \| null` | Current requirement |
712
+ | `currentIndex` | `number` | Current index |
713
+ | `totalInputs` | `number` | Total count |
714
+ | `isDone` | `boolean` | `true` when all collected |
715
+ | `isLoading` | `boolean` | `true` while initializing |
716
+ | `inputs` | `Map<string, unknown>` | Collected values |
717
+ | `submit` | `(value: unknown) => Promise<ValidationResult>` | Submit and validate |
718
+ | `previous` | `() => void` | Go back |
719
+ | `goTo` | `(index: number) => void` | Jump to index |
720
+ | `reset` | `() => void` | Reset all |
721
+ | `setValue` | `(name: string, value: unknown) => void` | Set value by name |
722
+ | `getValue` | `(name: string) => unknown` | Get value by name |
723
+
724
+ #### usePromptSearch
725
+
726
+ Search through prompts indexed by PuptProvider.
727
+
728
+ **Options:**
729
+
730
+ | Option | Type | Default | Description |
731
+ |--------|------|---------|-------------|
732
+ | `debounce` | `number` | `200` | Debounce delay (ms) |
733
+ | `limit` | `number` | `undefined` | Max results |
734
+
735
+ **Returns:**
736
+
737
+ | Property | Type | Description |
738
+ |----------|------|-------------|
739
+ | `query` | `string` | Current query |
740
+ | `setQuery` | `(query: string) => void` | Set query |
741
+ | `results` | `SearchResult[]` | Search results |
742
+ | `isSearching` | `boolean` | `true` while searching |
743
+ | `allTags` | `string[]` | All tags from indexed prompts |
744
+ | `clear` | `() => void` | Clear query and results |
745
+
746
+ #### usePostActions
747
+
748
+ Manage post-execution actions.
749
+
750
+ **Options:**
751
+
752
+ | Option | Type | Default | Description |
753
+ |--------|------|---------|-------------|
754
+ | `actions` | `PostExecutionAction[]` | required | Actions to manage |
755
+ | `handlers` | `Partial<Record<PostExecutionAction["type"], PostActionHandler>>` | `undefined` | Custom handlers by action type |
756
+
757
+ **Returns:**
758
+
759
+ | Property | Type | Description |
760
+ |----------|------|-------------|
761
+ | `pendingActions` | `PostExecutionAction[]` | Not yet handled |
762
+ | `executedActions` | `PostExecutionAction[]` | Successfully executed |
763
+ | `dismissedActions` | `PostExecutionAction[]` | Dismissed without executing |
764
+ | `allDone` | `boolean` | `true` when none pending |
765
+ | `execute` | `(action: PostExecutionAction) => Promise<void>` | Execute one action |
766
+ | `dismiss` | `(action: PostExecutionAction) => void` | Dismiss one action |
767
+ | `executeAll` | `() => Promise<void>` | Execute all pending |
768
+ | `dismissAll` | `() => void` | Dismiss all pending |
769
+ | `reset` | `() => void` | Reset all to pending |
770
+
771
+ ### Types
772
+
773
+ All types are exported from the package entry point. Key types re-exported from pupt-lib:
774
+
775
+ | Type | Description |
776
+ |------|-------------|
777
+ | `PuptElement` | Parsed prompt element tree |
778
+ | `PromptSource` | `{ type: "source" \| "element"; value: string \| PuptElement }` |
779
+ | `InputRequirement` | Describes a user input required by an Ask component |
780
+ | `ValidationResult` | Result of validating a user input (`{ valid, errors, warnings }`) |
781
+ | `PostExecutionAction` | Union of `ReviewFileAction \| OpenUrlAction \| RunCommandAction` |
782
+ | `SearchablePrompt` | Prompt metadata for indexing (`{ name, description?, tags, library, content? }`) |
783
+ | `SearchResult` | Search hit (`{ prompt, score, matches }`) |
784
+ | `RenderOptions` | Options for rendering (`{ format?, trim?, indent?, maxDepth?, inputs?, env? }`) |
785
+ | `RenderResult` | Discriminated union: `RenderSuccess \| RenderFailure` |
786
+ | `RenderError` | Validation/runtime error during rendering |
787
+ | `EnvironmentContext` | Full environment configuration (see [Environment context](#environment-context)) |
788
+
789
+ ## Browser Usage
790
+
791
+ When using pupt-lib in the browser (e.g., for a demo app), an import map is required for dynamic ES module evaluation:
792
+
793
+ ```html
794
+ <script type="importmap">
795
+ {
796
+ "imports": {
797
+ "pupt-lib": "https://unpkg.com/pupt-lib@VERSION/dist/index.js",
798
+ "pupt-lib/jsx-runtime": "https://unpkg.com/pupt-lib@VERSION/dist/jsx-runtime/index.js",
799
+ "zod": "https://unpkg.com/zod@3.24.2/lib/index.mjs",
800
+ "minisearch": "https://unpkg.com/minisearch@7.1.1/dist/es/index.js"
801
+ }
802
+ }
803
+ </script>
266
804
  ```
267
805
 
268
- ## Requirements
806
+ Replace `VERSION` with the pupt-lib version you are using.
807
+
808
+ ## Development
269
809
 
270
- - React 18.0.0 or later
271
- - pupt-lib (peer dependency)
810
+ ```bash
811
+ npm run build # Build the library (outputs to dist/)
812
+ npm run build:demo # Build the demo website (outputs to dist-demo/)
813
+ npm run dev:demo # Start demo dev server
814
+ npm run lint # ESLint + TypeScript type checking
815
+ npm run lint:fix # Auto-fix linting issues
816
+ npm run test # Run all tests
817
+ npm run test:watch # Watch mode
818
+ npm run test:coverage # Run tests with coverage
819
+ ```
272
820
 
273
821
  ## License
274
822