@pupt/react 0.0.1 → 1.2.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,45 +1,823 @@
1
- # @pupt/react
1
+ # pupt-react
2
2
 
3
- ## ⚠️ IMPORTANT NOTICE ⚠️
3
+ Headless React components and hooks for rendering [pupt-lib](https://github.com/apowers313/pupt-lib) prompts.
4
4
 
5
- **This package is created solely for the purpose of setting up OIDC (OpenID Connect) trusted publishing with npm.**
5
+ ## Features
6
6
 
7
- This is **NOT** a functional package and contains **NO** code or functionality beyond the OIDC setup configuration.
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
8
14
 
9
- ## Purpose
15
+ ## Installation
10
16
 
11
- This package exists to:
12
- 1. Configure OIDC trusted publishing for the package name `@pupt/react`
13
- 2. Enable secure, token-less publishing from CI/CD workflows
14
- 3. Establish provenance for packages published under this name
17
+ ```bash
18
+ npm install pupt-react pupt-lib react react-dom
19
+ ```
15
20
 
16
- ## What is OIDC Trusted Publishing?
21
+ **Peer dependencies:** React 18+ or 19+, pupt-lib ^1.2.1
17
22
 
18
- OIDC trusted publishing allows package maintainers to publish packages directly from their CI/CD workflows without needing to manage npm access tokens. Instead, it uses OpenID Connect to establish trust between the CI/CD provider (like GitHub Actions) and npm.
23
+ ## Quick Start
19
24
 
20
- ## Setup Instructions
25
+ ### Minimal — render a prompt
21
26
 
22
- To properly configure OIDC trusted publishing for this package:
27
+ ```tsx
28
+ import { PuptProvider, PromptRenderer } from "pupt-react";
23
29
 
24
- 1. Go to [npmjs.com](https://www.npmjs.com/) and navigate to your package settings
25
- 2. Configure the trusted publisher (e.g., GitHub Actions)
26
- 3. Specify the repository and workflow that should be allowed to publish
27
- 4. Use the configured workflow to publish your actual package
30
+ function App() {
31
+ const source = `<Prompt name="greeting">
32
+ <Task>Say hello to the user.</Task>
33
+ </Prompt>`;
28
34
 
29
- ## DO NOT USE THIS PACKAGE
35
+ return (
36
+ <PuptProvider>
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>
46
+ </PuptProvider>
47
+ );
48
+ }
49
+ ```
30
50
 
31
- This package is a placeholder for OIDC configuration only. It:
32
- - Contains no executable code
33
- - Provides no functionality
34
- - Should not be installed as a dependency
35
- - Exists only for administrative purposes
51
+ `PromptRenderer` auto-renders by default (`autoRender={true}`), so the prompt is rendered as soon as it transforms.
36
52
 
37
- ## More Information
53
+ ### With clipboard
38
54
 
39
- For more details about npm's trusted publishing feature, see:
40
- - [npm Trusted Publishing Documentation](https://docs.npmjs.com/generating-provenance-statements)
41
- - [GitHub Actions OIDC Documentation](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect)
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
75
+
76
+ Prompts can request user input via `<Ask.*>` components. When they do, `pendingInputs` lists what's needed:
77
+
78
+ ```tsx
79
+ import { useState } from "react";
80
+ import { PromptRenderer } from "pupt-react";
81
+
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>
88
+ </Prompt>`;
89
+
90
+ return (
91
+ <PromptRenderer source={source} inputs={inputs}>
92
+ {({ output, isReady, pendingInputs }) => (
93
+ <div>
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>}
106
+ </div>
107
+ )}
108
+ </PromptRenderer>
109
+ );
110
+ }
111
+ ```
112
+
113
+ ---
114
+
115
+ ## Guide: Core Concepts
116
+
117
+ ### Headless render-prop pattern
118
+
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.
120
+
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>
129
+ ```
130
+
131
+ ### The prompt pipeline
132
+
133
+ Prompts go through two phases:
134
+
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
137
+
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:
143
+
144
+ ```tsx
145
+ <PuptProvider
146
+ environment={{
147
+ llm: { model: "claude-sonnet-4-5-20250929", provider: "anthropic" },
148
+ output: { format: "markdown" },
149
+ }}
150
+ >
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>
159
+ </PuptProvider>
160
+ ```
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
+
214
+ ### PromptRenderer
215
+
216
+ The primary component for rendering prompts. Accepts a source string or `PromptSource` object and provides all render state:
217
+
218
+ ```tsx
219
+ <PromptRenderer
220
+ source={source}
221
+ inputs={inputs}
222
+ autoRender={true}
223
+ renderOptions={{ format: "markdown", trim: true }}
224
+ environment={{ llm: { model: "claude-sonnet-4-5-20250929" } }}
225
+ >
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>
254
+ )}
255
+ </PromptRenderer>
256
+ ```
257
+
258
+ ### PromptEditor
259
+
260
+ A headless editor component that transforms source on the fly with debouncing. Pair it with `PromptRenderer` by passing the `element`:
261
+
262
+ ```tsx
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>
281
+ )}
282
+ </PromptEditor>
283
+ ```
284
+
285
+ ### AskHandler
286
+
287
+ Wizard-style input collection with validation, progress tracking, and navigation. `submit` validates the value and auto-advances to the next input on success:
288
+
289
+ ```tsx
290
+ <AskHandler
291
+ element={element}
292
+ onComplete={(values) => console.log("Collected:", values)}
293
+ initialValues={new Map()}
294
+ >
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
+ }}
337
+ </AskHandler>
338
+ ```
339
+
340
+ ---
341
+
342
+ ## Guide: Hook Examples
343
+
344
+ ### usePromptRender
345
+
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):
347
+
348
+ ```tsx
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
+ }
381
+ ```
382
+
383
+ ### useAskIterator
384
+
385
+ Hook for custom input collection flows outside `AskHandler`:
386
+
387
+ ```tsx
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
+ }
414
+ ```
415
+
416
+ ### usePromptSearch
417
+
418
+ Build search UIs for prompt libraries. Requires `PuptProvider` to be configured with `prompts`:
419
+
420
+ ```tsx
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
+ }
464
+ ```
465
+
466
+ ### usePostActions
467
+
468
+ Manage post-execution actions with custom handlers:
469
+
470
+ ```tsx
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({
485
+ actions,
486
+ handlers: {
487
+ openUrl: (action) => window.open(action.url),
488
+ reviewFile: (action) => openInEditor(action.file),
489
+ runCommand: (action) => runInTerminal(action.command),
490
+ },
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
+ }
513
+ ```
514
+
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:
518
+
519
+ ```tsx
520
+ import { usePupt } from "pupt-react";
521
+
522
+ function DebugPanel() {
523
+ const { searchEngine, renderOptions, environment, isLoading, error } =
524
+ usePupt();
525
+
526
+ if (isLoading) return <p>Initializing...</p>;
527
+ if (error) return <p>Init error: {error.message}</p>;
528
+
529
+ return (
530
+ <pre>{JSON.stringify({ renderOptions, environment }, null, 2)}</pre>
531
+ );
532
+ }
533
+ ```
42
534
 
43
535
  ---
44
536
 
45
- **Maintained for OIDC setup purposes only**
537
+ ## API Reference
538
+
539
+ ### Components
540
+
541
+ #### PuptProvider
542
+
543
+ Context provider that initializes pupt-lib and provides shared configuration.
544
+
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 |
551
+
552
+ #### PromptRenderer
553
+
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>
804
+ ```
805
+
806
+ Replace `VERSION` with the pupt-lib version you are using.
807
+
808
+ ## Development
809
+
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
+ ```
820
+
821
+ ## License
822
+
823
+ MIT