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 +689 -141
- package/dist/index.cjs +239 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +262 -1
- package/dist/index.js +210 -13
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
# pupt-react
|
|
2
2
|
|
|
3
|
-
|
|
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
|
|
8
|
-
- **React
|
|
9
|
-
- **Ask
|
|
10
|
-
- **
|
|
11
|
-
- **Post-
|
|
12
|
-
- **
|
|
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
|
-
|
|
21
|
+
**Peer dependencies:** React 18+ or 19+, pupt-lib ^1.2.1
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
## Quick Start
|
|
23
24
|
|
|
24
|
-
|
|
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
|
-
<
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
46
|
-
const
|
|
47
|
-
|
|
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}
|
|
52
|
-
{({ output,
|
|
91
|
+
<PromptRenderer source={source} inputs={inputs}>
|
|
92
|
+
{({ output, isReady, pendingInputs }) => (
|
|
53
93
|
<div>
|
|
54
|
-
{
|
|
55
|
-
<
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
113
|
+
---
|
|
72
114
|
|
|
73
|
-
|
|
115
|
+
## Guide: Core Concepts
|
|
74
116
|
|
|
75
|
-
|
|
76
|
-
import { usePupt, usePromptRender } from "pupt-react";
|
|
117
|
+
### Headless render-prop pattern
|
|
77
118
|
|
|
78
|
-
function
|
|
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
|
-
|
|
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
|
-
|
|
131
|
+
### The prompt pipeline
|
|
132
|
+
|
|
133
|
+
Prompts go through two phases:
|
|
90
134
|
|
|
91
|
-
|
|
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
|
-
|
|
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
|
-
|
|
98
|
-
|
|
146
|
+
environment={{
|
|
147
|
+
llm: { model: "claude-sonnet-4-5-20250929", provider: "anthropic" },
|
|
148
|
+
output: { format: "markdown" },
|
|
149
|
+
}}
|
|
99
150
|
>
|
|
100
|
-
{
|
|
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
|
-
|
|
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}
|
|
111
|
-
inputs={
|
|
112
|
-
autoRender={true}
|
|
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
|
-
{(
|
|
115
|
-
|
|
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
|
-
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
>
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
|
|
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={(
|
|
292
|
+
onComplete={(values) => console.log("Collected:", values)}
|
|
293
|
+
initialValues={new Map()}
|
|
144
294
|
>
|
|
145
|
-
{({
|
|
146
|
-
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
### usePupt
|
|
154
|
-
|
|
155
|
-
Access the PuptProvider context:
|
|
340
|
+
---
|
|
156
341
|
|
|
157
|
-
|
|
158
|
-
const { transformer, registry, searchEngine, isLoading, error } = usePupt();
|
|
159
|
-
```
|
|
342
|
+
## Guide: Hook Examples
|
|
160
343
|
|
|
161
344
|
### usePromptRender
|
|
162
345
|
|
|
163
|
-
|
|
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
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
-
|
|
385
|
+
Hook for custom input collection flows outside `AskHandler`:
|
|
187
386
|
|
|
188
387
|
```tsx
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
-
|
|
418
|
+
Build search UIs for prompt libraries. Requires `PuptProvider` to be configured with `prompts`:
|
|
210
419
|
|
|
211
420
|
```tsx
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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
|
-
|
|
468
|
+
Manage post-execution actions with custom handlers:
|
|
221
469
|
|
|
222
470
|
```tsx
|
|
223
|
-
|
|
224
|
-
|
|
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: (
|
|
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
|
-
|
|
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
|
-
|
|
519
|
+
```tsx
|
|
520
|
+
import { usePupt } from "pupt-react";
|
|
235
521
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
522
|
+
function DebugPanel() {
|
|
523
|
+
const { searchEngine, renderOptions, environment, isLoading, error } =
|
|
524
|
+
usePupt();
|
|
239
525
|
|
|
240
|
-
|
|
526
|
+
if (isLoading) return <p>Initializing...</p>;
|
|
527
|
+
if (error) return <p>Init error: {error.message}</p>;
|
|
241
528
|
|
|
242
|
-
|
|
243
|
-
|
|
529
|
+
return (
|
|
530
|
+
<pre>{JSON.stringify({ renderOptions, environment }, null, 2)}</pre>
|
|
531
|
+
);
|
|
532
|
+
}
|
|
244
533
|
```
|
|
245
534
|
|
|
246
|
-
|
|
535
|
+
---
|
|
247
536
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
537
|
+
## API Reference
|
|
538
|
+
|
|
539
|
+
### Components
|
|
251
540
|
|
|
252
|
-
|
|
253
|
-
npm test
|
|
541
|
+
#### PuptProvider
|
|
254
542
|
|
|
255
|
-
|
|
256
|
-
npm run test:coverage
|
|
543
|
+
Context provider that initializes pupt-lib and provides shared configuration.
|
|
257
544
|
|
|
258
|
-
|
|
259
|
-
|
|
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
|
-
|
|
262
|
-
npm run build
|
|
552
|
+
#### PromptRenderer
|
|
263
553
|
|
|
264
|
-
|
|
265
|
-
|
|
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
|
-
|
|
806
|
+
Replace `VERSION` with the pupt-lib version you are using.
|
|
807
|
+
|
|
808
|
+
## Development
|
|
269
809
|
|
|
270
|
-
|
|
271
|
-
|
|
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
|
|