@fungi.computer/whistle 0.0.0-stage → 0.0.1

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.
@@ -0,0 +1,459 @@
1
+ import * as z from "zod";
2
+ import { type WhistleCommand, type WhistleJsonObject, type WhistleJsonValue } from "./index.js";
3
+ import type { ReadonlySchemaValue } from "./wire.js";
4
+ /** The one catalog protocol this Whistle understands. */
5
+ export declare const whistleCatalogProtocol = "whistle.catalog/v1";
6
+ /**
7
+ * A backend's advertised command catalog: data-only command definitions a
8
+ * client turns into Whistle commands. Every command runs through the backend's
9
+ * own call door; the client never names a command in code.
10
+ */
11
+ /** Call one advertised command at its backend; resolves to a raw step. */
12
+ export type WhistleCatalogInvoke = (commandId: string, args: WhistleJsonObject, signal: AbortSignal) => Promise<WhistleJsonValue>;
13
+ /** Validate one complete Whistle catalog and its unique command ids. */
14
+ export declare const WhistleCatalogSchema: z.ZodPipe<z.ZodObject<{
15
+ protocol: z.ZodLiteral<"whistle.catalog/v1">;
16
+ commands: z.ZodArray<z.ZodObject<{
17
+ id: z.ZodString;
18
+ title: z.ZodString;
19
+ category: z.ZodString;
20
+ description: z.ZodOptional<z.ZodString>;
21
+ projections: z.ZodOptional<z.ZodObject<{
22
+ bindings: z.ZodOptional<z.ZodObject<{
23
+ order: z.ZodOptional<z.ZodNumber>;
24
+ values: z.ZodArray<z.ZodString>;
25
+ }, z.core.$strict>>;
26
+ agent: z.ZodOptional<z.ZodObject<{
27
+ keybind: z.ZodOptional<z.ZodString>;
28
+ order: z.ZodOptional<z.ZodNumber>;
29
+ }, z.core.$strict>>;
30
+ help: z.ZodOptional<z.ZodObject<{
31
+ order: z.ZodOptional<z.ZodNumber>;
32
+ }, z.core.$strict>>;
33
+ menu: z.ZodOptional<z.ZodObject<{
34
+ order: z.ZodOptional<z.ZodNumber>;
35
+ }, z.core.$strict>>;
36
+ palette: z.ZodOptional<z.ZodObject<{
37
+ order: z.ZodOptional<z.ZodNumber>;
38
+ }, z.core.$strict>>;
39
+ slash: z.ZodOptional<z.ZodObject<{
40
+ aliases: z.ZodArray<z.ZodString>;
41
+ order: z.ZodOptional<z.ZodNumber>;
42
+ arguments: z.ZodOptional<z.ZodObject<{
43
+ hint: z.ZodPipe<z.ZodString, z.ZodString>;
44
+ words: z.ZodOptional<z.ZodArray<z.ZodObject<{
45
+ word: z.ZodString;
46
+ description: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodString>>;
47
+ arguments: z.ZodRecord<z.ZodString, z.ZodJSONSchema>;
48
+ }, z.core.$strict>>>;
49
+ text: z.ZodOptional<z.ZodObject<{
50
+ field: z.ZodPipe<z.ZodString, z.ZodString>;
51
+ arguments: z.ZodRecord<z.ZodString, z.ZodJSONSchema>;
52
+ requestIdField: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodString>>;
53
+ }, z.core.$strict>>;
54
+ }, z.core.$strict>>;
55
+ }, z.core.$strict>>;
56
+ }, z.core.$strict>>;
57
+ action: z.ZodOptional<z.ZodObject<{
58
+ inputSchema: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodJSONSchema>>;
59
+ outputSchema: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodJSONSchema>>;
60
+ presentation: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
61
+ type: z.ZodLiteral<"action">;
62
+ }, z.core.$strict>, z.ZodObject<{
63
+ type: z.ZodLiteral<"custom">;
64
+ data: z.ZodRecord<z.ZodString, z.ZodJSONSchema>;
65
+ }, z.core.$strict>, z.ZodObject<{
66
+ type: z.ZodLiteral<"choice">;
67
+ options: z.ZodArray<z.ZodObject<{
68
+ value: z.ZodJSONSchema;
69
+ label: z.ZodString;
70
+ description: z.ZodOptional<z.ZodString>;
71
+ }, z.core.$strip>>;
72
+ }, z.core.$strict>, z.ZodObject<{
73
+ type: z.ZodLiteral<"form">;
74
+ submitLabel: z.ZodOptional<z.ZodString>;
75
+ }, z.core.$strict>, z.ZodObject<{
76
+ type: z.ZodLiteral<"confirmation">;
77
+ message: z.ZodString;
78
+ confirmLabel: z.ZodOptional<z.ZodString>;
79
+ cancelLabel: z.ZodOptional<z.ZodString>;
80
+ }, z.core.$strict>]>>;
81
+ }, z.core.$strict>>;
82
+ availability: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
83
+ status: z.ZodLiteral<"available">;
84
+ }, z.core.$strict>, z.ZodObject<{
85
+ status: z.ZodLiteral<"unavailable">;
86
+ reason: z.ZodString;
87
+ }, z.core.$strict>], "status">>;
88
+ }, z.core.$strict>>;
89
+ }, z.core.$strict>, z.ZodTransform<Readonly<{
90
+ protocol: "whistle.catalog/v1";
91
+ commands: readonly {
92
+ id: string;
93
+ title: string;
94
+ category: string;
95
+ description?: string | undefined;
96
+ projections?: {
97
+ bindings?: {
98
+ values: string[];
99
+ order?: number | undefined;
100
+ } | undefined;
101
+ agent?: {
102
+ keybind?: string | undefined;
103
+ order?: number | undefined;
104
+ } | undefined;
105
+ help?: {
106
+ order?: number | undefined;
107
+ } | undefined;
108
+ menu?: {
109
+ order?: number | undefined;
110
+ } | undefined;
111
+ palette?: {
112
+ order?: number | undefined;
113
+ } | undefined;
114
+ slash?: {
115
+ aliases: string[];
116
+ order?: number | undefined;
117
+ arguments?: {
118
+ hint: string;
119
+ words?: {
120
+ word: string;
121
+ arguments: Record<string, z.core.util.JSONType>;
122
+ description?: string | undefined;
123
+ }[] | undefined;
124
+ text?: {
125
+ field: string;
126
+ arguments: Record<string, z.core.util.JSONType>;
127
+ requestIdField?: string | undefined;
128
+ } | undefined;
129
+ } | undefined;
130
+ } | undefined;
131
+ } | undefined;
132
+ action?: {
133
+ inputSchema?: Record<string, z.core.util.JSONType> | undefined;
134
+ outputSchema?: Record<string, z.core.util.JSONType> | undefined;
135
+ presentation?: {
136
+ type: "action";
137
+ } | {
138
+ type: "custom";
139
+ data: Record<string, z.core.util.JSONType>;
140
+ } | {
141
+ type: "choice";
142
+ options: {
143
+ value: z.core.util.JSONType;
144
+ label: string;
145
+ description?: string | undefined;
146
+ }[];
147
+ } | {
148
+ type: "form";
149
+ submitLabel?: string | undefined;
150
+ } | {
151
+ type: "confirmation";
152
+ message: string;
153
+ confirmLabel?: string | undefined;
154
+ cancelLabel?: string | undefined;
155
+ } | undefined;
156
+ } | undefined;
157
+ availability?: {
158
+ status: "available";
159
+ } | {
160
+ status: "unavailable";
161
+ reason: string;
162
+ } | undefined;
163
+ }[];
164
+ }>, {
165
+ protocol: "whistle.catalog/v1";
166
+ commands: {
167
+ id: string;
168
+ title: string;
169
+ category: string;
170
+ description?: string | undefined;
171
+ projections?: {
172
+ bindings?: {
173
+ values: string[];
174
+ order?: number | undefined;
175
+ } | undefined;
176
+ agent?: {
177
+ keybind?: string | undefined;
178
+ order?: number | undefined;
179
+ } | undefined;
180
+ help?: {
181
+ order?: number | undefined;
182
+ } | undefined;
183
+ menu?: {
184
+ order?: number | undefined;
185
+ } | undefined;
186
+ palette?: {
187
+ order?: number | undefined;
188
+ } | undefined;
189
+ slash?: {
190
+ aliases: string[];
191
+ order?: number | undefined;
192
+ arguments?: {
193
+ hint: string;
194
+ words?: {
195
+ word: string;
196
+ arguments: Record<string, z.core.util.JSONType>;
197
+ description?: string | undefined;
198
+ }[] | undefined;
199
+ text?: {
200
+ field: string;
201
+ arguments: Record<string, z.core.util.JSONType>;
202
+ requestIdField?: string | undefined;
203
+ } | undefined;
204
+ } | undefined;
205
+ } | undefined;
206
+ } | undefined;
207
+ action?: {
208
+ inputSchema?: Record<string, z.core.util.JSONType> | undefined;
209
+ outputSchema?: Record<string, z.core.util.JSONType> | undefined;
210
+ presentation?: {
211
+ type: "action";
212
+ } | {
213
+ type: "custom";
214
+ data: Record<string, z.core.util.JSONType>;
215
+ } | {
216
+ type: "choice";
217
+ options: {
218
+ value: z.core.util.JSONType;
219
+ label: string;
220
+ description?: string | undefined;
221
+ }[];
222
+ } | {
223
+ type: "form";
224
+ submitLabel?: string | undefined;
225
+ } | {
226
+ type: "confirmation";
227
+ message: string;
228
+ confirmLabel?: string | undefined;
229
+ cancelLabel?: string | undefined;
230
+ } | undefined;
231
+ } | undefined;
232
+ availability?: {
233
+ status: "available";
234
+ } | {
235
+ status: "unavailable";
236
+ reason: string;
237
+ } | undefined;
238
+ }[];
239
+ }>>;
240
+ /** An advertised command catalog decoded by the maintained catalog schema. */
241
+ export type WhistleCatalogV1 = ReadonlySchemaValue<z.output<typeof WhistleCatalogSchema>>;
242
+ /** Parse an advertised catalog at the client boundary. */
243
+ export declare const parseCatalog: typeof WhistleCatalogSchema.parse;
244
+ /** Validate one response step before it reaches an interaction renderer. */
245
+ export declare const WhistleCatalogStepSchema: z.ZodPipe<z.ZodDiscriminatedUnion<[z.ZodObject<{
246
+ type: z.ZodLiteral<"done">;
247
+ notice: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
248
+ type: z.ZodLiteral<"info">;
249
+ message: z.ZodPipe<z.ZodString, z.ZodString>;
250
+ links: z.ZodOptional<z.ZodArray<z.ZodObject<{
251
+ url: z.ZodURL;
252
+ label: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodString>>;
253
+ }, z.core.$strict>>>;
254
+ }, z.core.$strict>, z.ZodObject<{
255
+ type: z.ZodLiteral<"progress">;
256
+ message: z.ZodPipe<z.ZodString, z.ZodString>;
257
+ }, z.core.$strict>, z.ZodObject<{
258
+ type: z.ZodLiteral<"handoff">;
259
+ url: z.ZodURL;
260
+ code: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodString>>;
261
+ message: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodString>>;
262
+ }, z.core.$strict>, z.ZodObject<{
263
+ type: z.ZodLiteral<"success">;
264
+ message: z.ZodPipe<z.ZodString, z.ZodString>;
265
+ }, z.core.$strict>], "type">>;
266
+ result: z.ZodOptional<z.ZodJSONSchema>;
267
+ }, z.core.$strict>, z.ZodObject<{
268
+ type: z.ZodLiteral<"prompt">;
269
+ prompt: z.ZodDiscriminatedUnion<[z.ZodObject<{
270
+ type: z.ZodLiteral<"choice">;
271
+ message: z.ZodPipe<z.ZodString, z.ZodString>;
272
+ collapsedGroup: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodString>>;
273
+ options: z.ZodArray<z.ZodObject<{
274
+ value: z.ZodPipe<z.ZodString, z.ZodString>;
275
+ label: z.ZodPipe<z.ZodString, z.ZodString>;
276
+ description: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodString>>;
277
+ group: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodString>>;
278
+ }, z.core.$strict>>;
279
+ }, z.core.$strict>, z.ZodObject<{
280
+ type: z.ZodLiteral<"text">;
281
+ message: z.ZodPipe<z.ZodString, z.ZodString>;
282
+ placeholder: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodString>>;
283
+ secret: z.ZodOptional<z.ZodBoolean>;
284
+ }, z.core.$strict>, z.ZodObject<{
285
+ type: z.ZodLiteral<"confirmation">;
286
+ message: z.ZodPipe<z.ZodString, z.ZodString>;
287
+ confirmLabel: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodString>>;
288
+ cancelLabel: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodString>>;
289
+ }, z.core.$strict>], "type">;
290
+ notice: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
291
+ type: z.ZodLiteral<"info">;
292
+ message: z.ZodPipe<z.ZodString, z.ZodString>;
293
+ links: z.ZodOptional<z.ZodArray<z.ZodObject<{
294
+ url: z.ZodURL;
295
+ label: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodString>>;
296
+ }, z.core.$strict>>>;
297
+ }, z.core.$strict>, z.ZodObject<{
298
+ type: z.ZodLiteral<"progress">;
299
+ message: z.ZodPipe<z.ZodString, z.ZodString>;
300
+ }, z.core.$strict>, z.ZodObject<{
301
+ type: z.ZodLiteral<"handoff">;
302
+ url: z.ZodURL;
303
+ code: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodString>>;
304
+ message: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodString>>;
305
+ }, z.core.$strict>, z.ZodObject<{
306
+ type: z.ZodLiteral<"success">;
307
+ message: z.ZodPipe<z.ZodString, z.ZodString>;
308
+ }, z.core.$strict>], "type">>;
309
+ next: z.ZodObject<{
310
+ arguments: z.ZodRecord<z.ZodString, z.ZodJSONSchema>;
311
+ answer: z.ZodString;
312
+ }, z.core.$strict>;
313
+ }, z.core.$strict>], "type">, z.ZodTransform<Readonly<{
314
+ type: "done";
315
+ notice?: {
316
+ type: "info";
317
+ message: string;
318
+ links?: {
319
+ url: string;
320
+ label?: string | undefined;
321
+ }[] | undefined;
322
+ } | {
323
+ type: "progress";
324
+ message: string;
325
+ } | {
326
+ type: "handoff";
327
+ url: string;
328
+ code?: string | undefined;
329
+ message?: string | undefined;
330
+ } | {
331
+ type: "success";
332
+ message: string;
333
+ } | undefined;
334
+ result?: z.core.util.JSONType | undefined;
335
+ }> | Readonly<{
336
+ type: "prompt";
337
+ prompt: {
338
+ type: "choice";
339
+ message: string;
340
+ options: {
341
+ value: string;
342
+ label: string;
343
+ description?: string | undefined;
344
+ group?: string | undefined;
345
+ }[];
346
+ collapsedGroup?: string | undefined;
347
+ } | {
348
+ type: "text";
349
+ message: string;
350
+ placeholder?: string | undefined;
351
+ secret?: boolean | undefined;
352
+ } | {
353
+ type: "confirmation";
354
+ message: string;
355
+ confirmLabel?: string | undefined;
356
+ cancelLabel?: string | undefined;
357
+ };
358
+ next: {
359
+ arguments: Record<string, z.core.util.JSONType>;
360
+ answer: string;
361
+ };
362
+ notice?: {
363
+ type: "info";
364
+ message: string;
365
+ links?: {
366
+ url: string;
367
+ label?: string | undefined;
368
+ }[] | undefined;
369
+ } | {
370
+ type: "progress";
371
+ message: string;
372
+ } | {
373
+ type: "handoff";
374
+ url: string;
375
+ code?: string | undefined;
376
+ message?: string | undefined;
377
+ } | {
378
+ type: "success";
379
+ message: string;
380
+ } | undefined;
381
+ }>, {
382
+ type: "done";
383
+ notice?: {
384
+ type: "info";
385
+ message: string;
386
+ links?: {
387
+ url: string;
388
+ label?: string | undefined;
389
+ }[] | undefined;
390
+ } | {
391
+ type: "progress";
392
+ message: string;
393
+ } | {
394
+ type: "handoff";
395
+ url: string;
396
+ code?: string | undefined;
397
+ message?: string | undefined;
398
+ } | {
399
+ type: "success";
400
+ message: string;
401
+ } | undefined;
402
+ result?: z.core.util.JSONType | undefined;
403
+ } | {
404
+ type: "prompt";
405
+ prompt: {
406
+ type: "choice";
407
+ message: string;
408
+ options: {
409
+ value: string;
410
+ label: string;
411
+ description?: string | undefined;
412
+ group?: string | undefined;
413
+ }[];
414
+ collapsedGroup?: string | undefined;
415
+ } | {
416
+ type: "text";
417
+ message: string;
418
+ placeholder?: string | undefined;
419
+ secret?: boolean | undefined;
420
+ } | {
421
+ type: "confirmation";
422
+ message: string;
423
+ confirmLabel?: string | undefined;
424
+ cancelLabel?: string | undefined;
425
+ };
426
+ next: {
427
+ arguments: Record<string, z.core.util.JSONType>;
428
+ answer: string;
429
+ };
430
+ notice?: {
431
+ type: "info";
432
+ message: string;
433
+ links?: {
434
+ url: string;
435
+ label?: string | undefined;
436
+ }[] | undefined;
437
+ } | {
438
+ type: "progress";
439
+ message: string;
440
+ } | {
441
+ type: "handoff";
442
+ url: string;
443
+ code?: string | undefined;
444
+ message?: string | undefined;
445
+ } | {
446
+ type: "success";
447
+ message: string;
448
+ } | undefined;
449
+ }>>;
450
+ /** A decoded catalog response step derived from its maintained schema. */
451
+ export type WhistleCatalogStepV1 = ReadonlySchemaValue<z.output<typeof WhistleCatalogStepSchema>>;
452
+ /** Parse one backend reply before any of it reaches a renderer. */
453
+ export declare const parseCatalogStep: typeof WhistleCatalogStepSchema.parse;
454
+ /**
455
+ * Turn an advertised catalog into Whistle commands. Each handler runs the
456
+ * backend's step conversation through the caller's interaction; an Agent,
457
+ * which has no interaction, can run only commands that finish in one step.
458
+ */
459
+ export declare function catalogCommands(catalog: WhistleCatalogV1, invoke: WhistleCatalogInvoke): readonly WhistleCommand<never>[];
@@ -0,0 +1,147 @@
1
+ import * as z from "zod";
2
+ import { WhistleActionError, } from "./index.js";
3
+ import { parseJsonObject, WhistleCommandDefinitionSchema, WhistleJsonObjectSchema, WhistleJsonValueSchema, WhistleNoticeSchema, WhistlePromptSchema, } from "./wire.js";
4
+ /** The one catalog protocol this Whistle understands. */
5
+ export const whistleCatalogProtocol = "whistle.catalog/v1";
6
+ const maximumCatalogCommands = 128;
7
+ const maximumSteps = 16;
8
+ /** Validate one complete Whistle catalog and its unique command ids. */
9
+ export const WhistleCatalogSchema = z
10
+ .object({
11
+ protocol: z.literal(whistleCatalogProtocol),
12
+ commands: z
13
+ .array(WhistleCommandDefinitionSchema)
14
+ .max(maximumCatalogCommands),
15
+ })
16
+ .strict()
17
+ .transform((catalog) => {
18
+ const ids = new Set();
19
+ for (const command of catalog.commands) {
20
+ if (ids.has(command.id))
21
+ throw new Error(`Catalog command ${command.id} is repeated`);
22
+ ids.add(command.id);
23
+ }
24
+ return Object.freeze({
25
+ protocol: whistleCatalogProtocol,
26
+ commands: Object.freeze(catalog.commands),
27
+ });
28
+ });
29
+ /** Parse an advertised catalog at the client boundary. */
30
+ export const parseCatalog = (value) => WhistleCatalogSchema.parse(value);
31
+ /** Validate one response step before it reaches an interaction renderer. */
32
+ export const WhistleCatalogStepSchema = z
33
+ .discriminatedUnion("type", [
34
+ z
35
+ .object({
36
+ type: z.literal("done"),
37
+ notice: WhistleNoticeSchema.optional(),
38
+ result: WhistleJsonValueSchema.optional(),
39
+ })
40
+ .strict(),
41
+ z
42
+ .object({
43
+ type: z.literal("prompt"),
44
+ prompt: WhistlePromptSchema,
45
+ notice: WhistleNoticeSchema.optional(),
46
+ next: z
47
+ .object({
48
+ arguments: WhistleJsonObjectSchema,
49
+ answer: z.string().min(1).max(120),
50
+ })
51
+ .strict(),
52
+ })
53
+ .strict(),
54
+ ])
55
+ .transform((step) => Object.freeze(step));
56
+ /** Parse one backend reply before any of it reaches a renderer. */
57
+ export const parseCatalogStep = (value) => WhistleCatalogStepSchema.parse(value);
58
+ function initialArguments(definition, input) {
59
+ if (input.arguments !== undefined) {
60
+ try {
61
+ return parseJsonObject(input.arguments);
62
+ }
63
+ catch {
64
+ throw new WhistleActionError("invalid_arguments", `${definition.title}: arguments must be an object`);
65
+ }
66
+ }
67
+ const presentation = definition.action?.presentation;
68
+ const interaction = input.interaction;
69
+ if (interaction === undefined || presentation === undefined)
70
+ return {};
71
+ if (presentation.type === "choice") {
72
+ const options = presentation.options;
73
+ return interaction
74
+ .prompt({
75
+ type: "choice",
76
+ message: definition.title,
77
+ options: options.map((option, index) => {
78
+ const choice = {
79
+ value: String(index),
80
+ label: option.label,
81
+ };
82
+ return option.description === undefined
83
+ ? choice
84
+ : { ...choice, description: option.description };
85
+ }),
86
+ })
87
+ .then((answer) => parseJsonObject(options[Number(answer)]?.value));
88
+ }
89
+ if (presentation.type === "confirmation") {
90
+ const confirmation = {
91
+ type: "confirmation",
92
+ message: presentation.message,
93
+ };
94
+ if (presentation.confirmLabel !== undefined)
95
+ confirmation.confirmLabel = presentation.confirmLabel;
96
+ if (presentation.cancelLabel !== undefined)
97
+ confirmation.cancelLabel = presentation.cancelLabel;
98
+ return interaction
99
+ .prompt(confirmation)
100
+ .then((confirmed) => (confirmed ? {} : undefined));
101
+ }
102
+ return {};
103
+ }
104
+ /**
105
+ * Turn an advertised catalog into Whistle commands. Each handler runs the
106
+ * backend's step conversation through the caller's interaction; an Agent,
107
+ * which has no interaction, can run only commands that finish in one step.
108
+ */
109
+ export function catalogCommands(catalog, invoke) {
110
+ return catalog.commands.map((definition) => {
111
+ const { availability, ...rest } = definition;
112
+ const command = {
113
+ id: rest.id,
114
+ title: rest.title,
115
+ category: rest.category,
116
+ handler: async (input) => {
117
+ const signal = input.signal ?? new AbortController().signal;
118
+ const interaction = input.interaction;
119
+ let args = await initialArguments(definition, input);
120
+ if (args === undefined)
121
+ return { cancelled: true };
122
+ for (let step = 0; step < maximumSteps; step += 1) {
123
+ signal.throwIfAborted();
124
+ const reply = parseCatalogStep(await invoke(definition.id, args, signal));
125
+ if (reply.notice !== undefined)
126
+ interaction?.notify(reply.notice);
127
+ if (reply.type === "done")
128
+ return reply.result;
129
+ if (interaction === undefined)
130
+ throw new WhistleActionError("interaction_required", `${definition.title} needs a person to answer: ${reply.prompt.message}`);
131
+ const answer = await interaction.prompt(reply.prompt);
132
+ args = { ...reply.next.arguments, [reply.next.answer]: answer };
133
+ }
134
+ throw new WhistleActionError("flow_too_long", `${definition.title} asked too many questions.`);
135
+ },
136
+ };
137
+ if (rest.description !== undefined)
138
+ command.description = rest.description;
139
+ if (rest.projections !== undefined)
140
+ command.projections = rest.projections;
141
+ if (rest.action !== undefined)
142
+ command.action = rest.action;
143
+ if (availability !== undefined)
144
+ command.availability = () => availability;
145
+ return command;
146
+ });
147
+ }
package/dist/html.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ import { createDefaultHtmlKeymap } from "@opentui/keymap/html";
2
+ import { type WhistleExecutionOrigin, type WhistleRuntime } from "./index.js";
3
+ /** Maintained document keymap plus disposable Whistle command projection. */
4
+ export type HtmlWhistleBinding = Readonly<{
5
+ /** The host keymap, also available for the application's other layers. */
6
+ keymap: ReturnType<typeof createDefaultHtmlKeymap>;
7
+ /** Remove Whistle's layer and subscription; the HTML host belongs to the document. */
8
+ dispose: () => void;
9
+ }>;
10
+ /**
11
+ * Bind Whistle commands to the maintained OpenTUI HTML keymap. The root owns
12
+ * the host's document lifetime; dispose removes the command projection only.
13
+ */
14
+ export declare function bindHtmlKeymap(root: HTMLElement, whistle: Pick<WhistleRuntime, "snapshot" | "subscribe" | "execute">, origin: WhistleExecutionOrigin): HtmlWhistleBinding;
package/dist/html.js ADDED
@@ -0,0 +1,11 @@
1
+ import { createDefaultHtmlKeymap } from "@opentui/keymap/html";
2
+ import { createWhistleKeymapAdapter, } from "./index.js";
3
+ /**
4
+ * Bind Whistle commands to the maintained OpenTUI HTML keymap. The root owns
5
+ * the host's document lifetime; dispose removes the command projection only.
6
+ */
7
+ export function bindHtmlKeymap(root, whistle, origin) {
8
+ const keymap = createDefaultHtmlKeymap(root);
9
+ const adapter = createWhistleKeymapAdapter(keymap, whistle, origin);
10
+ return { keymap, dispose: adapter.cleanup };
11
+ }