@hmcs/sdk 1.0.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.
@@ -0,0 +1,852 @@
1
+ import { ZodType } from 'zod';
2
+ import { EventSource } from 'eventsource';
3
+
4
+ /**
5
+ * Mathematical types and interfaces for 3D graphics and spatial calculations.
6
+ *
7
+ * This module provides type definitions for common mathematical concepts used
8
+ * throughout the Desktop Homunculus SDK, including transforms, vectors, and
9
+ * domain-specific request/response types.
10
+ * These types are designed to be compatible with Bevy's math system.
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * // Working with transforms
15
+ * const transform: TransformArgs = {
16
+ * translation: [0, 100, 0],
17
+ * rotation: [0, 0, 0, 1],
18
+ * scale: [1, 1, 1]
19
+ * };
20
+ *
21
+ * // Working with vectors
22
+ * const position: Vec3 = [10, 20, 30];
23
+ * const screenPos: Vec2 = [1920, 1080];
24
+ * ```
25
+ */
26
+ /**
27
+ * Represents a 3D transformation containing position, rotation, and scale.
28
+ *
29
+ * This is the core type for positioning objects in 3D space. All spatial
30
+ * operations in Desktop Homunculus use this transform representation,
31
+ * which is compatible with Bevy's Transform component.
32
+ *
33
+ * @example
34
+ * ```typescript
35
+ * const identity: Transform = {
36
+ * translation: [0, 0, 0],
37
+ * rotation: [0, 0, 0, 1],
38
+ * scale: [1, 1, 1]
39
+ * };
40
+ * ```
41
+ */
42
+ interface Transform {
43
+ /**
44
+ * The position of the entity in world space.
45
+ * Format: [x, y, z] where Y is typically up in Bevy's coordinate system.
46
+ */
47
+ translation: [number, number, number];
48
+ /**
49
+ * The rotation of the entity in world space, represented as a quaternion.
50
+ * Format: [x, y, z, w] where [0, 0, 0, 1] represents no rotation (identity).
51
+ */
52
+ rotation: [number, number, number, number];
53
+ /**
54
+ * The scale of the entity in world space.
55
+ * Format: [x, y, z] where [1, 1, 1] represents normal size.
56
+ */
57
+ scale: [number, number, number];
58
+ }
59
+ /**
60
+ * Represents a 3D vector as [x, y, z].
61
+ * Used for 3D positions, directions, and mathematical calculations.
62
+ * Compatible with Bevy's Vec3 serialization format.
63
+ */
64
+ type Vec3 = [number, number, number];
65
+ /**
66
+ * Represents a quaternion rotation as [x, y, z, w].
67
+ * Compatible with Bevy's Quat serialization format.
68
+ */
69
+ type Quat = [number, number, number, number];
70
+ /** Transform arguments for API requests. Partial version of Transform. */
71
+ interface TransformArgs {
72
+ translation?: Vec3;
73
+ rotation?: Quat;
74
+ scale?: Vec3;
75
+ }
76
+
77
+ /** Global viewport coordinates (screen-space position) as [x, y]. */
78
+ type GlobalViewport = [number, number];
79
+
80
+ /**
81
+ * Big Five personality traits (OCEAN model).
82
+ *
83
+ * @example
84
+ * ```typescript
85
+ * const ocean: Ocean = {
86
+ * openness: 0.8,
87
+ * conscientiousness: 0.6,
88
+ * extraversion: 0.7,
89
+ * };
90
+ * ```
91
+ */
92
+ interface Ocean {
93
+ /** Openness (0.0=conservative, 1.0=curious) */
94
+ openness?: number;
95
+ /** Conscientiousness (0.0=spontaneous, 1.0=organized) */
96
+ conscientiousness?: number;
97
+ /** Extraversion (0.0=introverted, 1.0=extroverted) */
98
+ extraversion?: number;
99
+ /** Agreeableness (0.0=independent, 1.0=cooperative) */
100
+ agreeableness?: number;
101
+ /** Neuroticism (0.0=stable, 1.0=sensitive) */
102
+ neuroticism?: number;
103
+ }
104
+ /**
105
+ * Persona data for a VRM character.
106
+ *
107
+ * @example
108
+ * ```typescript
109
+ * const persona: Persona = {
110
+ * profile: "A cheerful virtual assistant",
111
+ * personality: "Friendly and helpful",
112
+ * ocean: { openness: 0.8, extraversion: 0.7 },
113
+ * metadata: {},
114
+ * };
115
+ * ```
116
+ */
117
+ interface Persona {
118
+ /** Character profile/background description. */
119
+ profile: string;
120
+ /** Personality description in natural language. */
121
+ personality?: string | null;
122
+ /** Big Five personality parameters. */
123
+ ocean: Ocean;
124
+ /** Extension metadata for MODs. */
125
+ metadata: Record<string, unknown>;
126
+ }
127
+ /** Override type for expression override settings. */
128
+ type OverrideType = "none" | "blend" | "block";
129
+ /**
130
+ * Information about a single VRM expression.
131
+ *
132
+ * @example
133
+ * ```typescript
134
+ * const vrm = await Vrm.findByName("MyAvatar");
135
+ * const { expressions } = await vrm.expressions();
136
+ * for (const expr of expressions) {
137
+ * console.log(`${expr.name}: weight=${expr.weight}, binary=${expr.isBinary}`);
138
+ * }
139
+ * ```
140
+ */
141
+ interface ExpressionInfo {
142
+ /** Expression name (e.g. "happy", "aa", "blink"). */
143
+ name: string;
144
+ /** Current weight value (0.0-1.0). */
145
+ weight: number;
146
+ /** Whether this expression is binary (snaps to 0 or 1). */
147
+ isBinary: boolean;
148
+ /** Override type for blink expressions. */
149
+ overrideBlink: OverrideType;
150
+ /** Override type for lookAt expressions. */
151
+ overrideLookAt: OverrideType;
152
+ /** Override type for mouth expressions. */
153
+ overrideMouth: OverrideType;
154
+ }
155
+ /** Response for VRM expression queries. */
156
+ interface ExpressionsResponse {
157
+ expressions: ExpressionInfo[];
158
+ }
159
+ /** Spring bone physics properties. */
160
+ interface SpringBoneProps {
161
+ stiffness: number;
162
+ dragForce: number;
163
+ gravityPower: number;
164
+ gravityDir: [number, number, number];
165
+ hitRadius: number;
166
+ }
167
+ /** A single spring bone chain. */
168
+ interface SpringBoneChain {
169
+ entity: number;
170
+ joints: string[];
171
+ props: SpringBoneProps;
172
+ }
173
+ /** Response for spring bone chains query. */
174
+ interface SpringBoneChainsResponse {
175
+ chains: SpringBoneChain[];
176
+ }
177
+ /** Repeat settings for VRMA playback. */
178
+ interface VrmaRepeat {
179
+ type: "forever" | "never" | "count";
180
+ count?: number;
181
+ }
182
+ /** Request body for playing a VRMA animation. */
183
+ interface VrmaPlayRequest {
184
+ asset: string;
185
+ transitionSecs?: number;
186
+ repeat?: VrmaRepeat;
187
+ waitForCompletion?: boolean;
188
+ /** If true, resets SpringBone velocities to prevent bouncing during animation transitions. */
189
+ resetSpringBones?: boolean;
190
+ }
191
+ /** State of a VRMA animation. */
192
+ interface VrmaState {
193
+ playing: boolean;
194
+ repeat: string;
195
+ speed: number;
196
+ elapsedSecs: number;
197
+ }
198
+ /** Info about a VRMA animation entity. */
199
+ interface VrmaInfo {
200
+ entity: number;
201
+ name: string;
202
+ playing: boolean;
203
+ }
204
+ /** Current look-at state of a VRM. */
205
+ type LookAtState = {
206
+ type: "cursor";
207
+ } | {
208
+ type: "target";
209
+ entity: number;
210
+ };
211
+ /**
212
+ * Snapshot of a VRM instance with full runtime state.
213
+ *
214
+ * @example
215
+ * ```typescript
216
+ * const snapshots = await Vrm.findAllDetailed();
217
+ * for (const s of snapshots) {
218
+ * console.log(`${s.name}: ${s.state} at (${s.globalViewport?.[0]}, ${s.globalViewport?.[1]})`);
219
+ * }
220
+ * ```
221
+ */
222
+ interface VrmSnapshot {
223
+ entity: number;
224
+ name: string;
225
+ state: string;
226
+ transform: Transform;
227
+ globalViewport: GlobalViewport | null;
228
+ expressions: ExpressionsResponse;
229
+ animations: VrmaInfo[];
230
+ lookAt: LookAtState | null;
231
+ linkedWebviews: number[];
232
+ persona: Persona;
233
+ }
234
+ /**
235
+ * Response from the VRM position endpoint.
236
+ *
237
+ * @example
238
+ * ```ts
239
+ * const vrm = await Vrm.findByName("MyCharacter");
240
+ * const pos = await vrm.position();
241
+ * console.log(`Screen: (${pos.globalViewport?.[0]}, ${pos.globalViewport?.[1]})`);
242
+ * console.log(`World: (${pos.world[0]}, ${pos.world[1]}, ${pos.world[2]})`);
243
+ * ```
244
+ */
245
+ interface PositionResponse {
246
+ /** Global screen coordinates (multi-monitor origin at leftmost screen). Null if not visible. */
247
+ globalViewport: GlobalViewport | null;
248
+ /** Bevy world coordinates. */
249
+ world: Vec3;
250
+ }
251
+ interface SpawnVrmOptions {
252
+ transform?: TransformArgs;
253
+ persona?: Persona;
254
+ }
255
+ /**
256
+ * A single keyframe in a speech timeline.
257
+ */
258
+ interface TimelineKeyframe {
259
+ /**
260
+ * Duration of this keyframe in seconds.
261
+ */
262
+ duration: number;
263
+ /**
264
+ * Expression targets to set during this keyframe.
265
+ * Keys are expression names (e.g. "aa", "ih", "happy"), values are weights (0.0-1.0).
266
+ */
267
+ targets?: Record<string, number>;
268
+ }
269
+ /**
270
+ * Options for the timeline speech API.
271
+ */
272
+ interface SpeakTimelineOptions {
273
+ /**
274
+ * If true, the method will wait for the speech to complete.
275
+ * Defaults to true.
276
+ */
277
+ waitForCompletion?: boolean;
278
+ /**
279
+ * Duration in seconds for smoothstep blending between adjacent keyframes.
280
+ * Defaults to 0.05 (50ms). Clamped to 40% of each keyframe's duration.
281
+ */
282
+ transitionDuration?: number;
283
+ }
284
+ interface VrmPointerEvent {
285
+ /**
286
+ * The cursor position in the global viewport.
287
+ */
288
+ globalViewport: [number, number];
289
+ }
290
+ interface VrmDragEvent extends VrmPointerEvent {
291
+ /**
292
+ * The change in cursor position since the last event.
293
+ */
294
+ delta: [number, number];
295
+ }
296
+ type Button = "Primary" | "Secondary" | "Middle";
297
+ interface VrmMouseEvent extends VrmPointerEvent {
298
+ /**
299
+ * The button that was pressed or released.
300
+ */
301
+ button: Button;
302
+ }
303
+ interface VrmStateChangeEvent {
304
+ /**
305
+ * The new state of the VRM.
306
+ */
307
+ state: string;
308
+ }
309
+ interface PersonaChangeEvent {
310
+ /**
311
+ * The updated persona.
312
+ */
313
+ persona: Persona;
314
+ }
315
+ type EventMap = {
316
+ "drag-start": VrmPointerEvent;
317
+ "drag": VrmDragEvent;
318
+ "drag-end": VrmPointerEvent;
319
+ "pointer-press": VrmMouseEvent;
320
+ "pointer-click": VrmMouseEvent;
321
+ "pointer-release": VrmMouseEvent;
322
+ "pointer-over": VrmPointerEvent;
323
+ "pointer-out": VrmPointerEvent;
324
+ "pointer-cancel": VrmPointerEvent;
325
+ "pointer-move": VrmPointerEvent;
326
+ "state-change": VrmStateChangeEvent;
327
+ "expression-change": VrmStateChangeEvent;
328
+ "vrma-play": VrmStateChangeEvent;
329
+ "vrma-finish": VrmStateChangeEvent;
330
+ "persona-change": PersonaChangeEvent;
331
+ };
332
+ interface VrmMetadata {
333
+ name: string;
334
+ entity: number;
335
+ }
336
+ type Bones = "hips" | "spine" | "chest" | "neck" | "head" | "leftShoulder" | "leftArm" | "leftForeArm" | "leftHand" | "rightShoulder" | "rightArm" | "rightForeArm" | "rightHand" | "leftUpLeg" | "leftLeg" | "leftFoot" | "rightUpLeg" | "rightLeg" | "rightFoot";
337
+ declare class VrmEventSource implements Disposable {
338
+ readonly eventSource: EventSource;
339
+ constructor(eventSource: EventSource);
340
+ /**
341
+ * Registers an event listener for the specified event type.
342
+ */
343
+ on<K extends keyof EventMap>(event: K, callback: (event: EventMap[K]) => (void | Promise<void>)): void;
344
+ /**
345
+ * Closes the EventSource connection.
346
+ */
347
+ close(): void;
348
+ [Symbol.dispose](): void;
349
+ }
350
+ declare class Vrm {
351
+ readonly entity: number;
352
+ constructor(entity: number);
353
+ /**
354
+ * Returns an EventSource for receiving events related to this VRM entity.
355
+ */
356
+ events(): VrmEventSource;
357
+ /**
358
+ * Returns the current state of the VRM.
359
+ */
360
+ state(): Promise<string>;
361
+ /**
362
+ * Sets the state of the VRM.
363
+ *
364
+ * @param state The new state to set.
365
+ */
366
+ setState(state: string): Promise<void>;
367
+ /**
368
+ * Returns the persona of the VRM.
369
+ *
370
+ * @example
371
+ * ```typescript
372
+ * const vrm = await Vrm.findByName("MyAvatar");
373
+ * const persona = await vrm.persona();
374
+ * console.log(persona.profile);
375
+ * ```
376
+ */
377
+ persona(): Promise<Persona>;
378
+ /**
379
+ * Sets the persona of the VRM.
380
+ *
381
+ * @param persona The persona data to set.
382
+ *
383
+ * @example
384
+ * ```typescript
385
+ * const vrm = await Vrm.findByName("MyAvatar");
386
+ * await vrm.setPersona({
387
+ * profile: "A cheerful assistant",
388
+ * ocean: { openness: 0.8, extraversion: 0.7 },
389
+ * metadata: {},
390
+ * });
391
+ * ```
392
+ */
393
+ setPersona(persona: Persona): Promise<void>;
394
+ /**
395
+ * Returns the name of the VRM avatar.
396
+ */
397
+ name(): Promise<string>;
398
+ /**
399
+ * Finds the entity ID of a bone by its name.
400
+ */
401
+ findBoneEntity(bone: Bones): Promise<number>;
402
+ /**
403
+ * Despawns this VRM entity.
404
+ */
405
+ despawn(): Promise<void>;
406
+ /**
407
+ * Gets the current position of this VRM in both screen and world coordinates.
408
+ *
409
+ * @example
410
+ * ```ts
411
+ * const vrm = await Vrm.findByName("MyCharacter");
412
+ * const pos = await vrm.position();
413
+ * console.log(`Screen: (${pos.globalViewport?.[0]}, ${pos.globalViewport?.[1]})`);
414
+ * console.log(`World: (${pos.world[0]}, ${pos.world[1]}, ${pos.world[2]})`);
415
+ * ```
416
+ */
417
+ position(): Promise<PositionResponse>;
418
+ /**
419
+ * Gets all expressions and their current weights, including metadata
420
+ * such as binary status and override settings.
421
+ *
422
+ * @example
423
+ * ```typescript
424
+ * const vrm = await Vrm.findByName("MyAvatar");
425
+ * const { expressions } = await vrm.expressions();
426
+ * for (const expr of expressions) {
427
+ * console.log(`${expr.name}: ${expr.weight}`);
428
+ * }
429
+ * ```
430
+ */
431
+ expressions(): Promise<ExpressionsResponse>;
432
+ /**
433
+ * Sets expression weights, replacing all previous overrides.
434
+ * Expressions not included will return to VRMA animation control.
435
+ *
436
+ * @param weights A record of expression names to weight values (0.0-1.0).
437
+ *
438
+ * @example
439
+ * ```typescript
440
+ * const vrm = await Vrm.findByName("MyAvatar");
441
+ * await vrm.setExpressions({ happy: 1.0, blink: 0.5 });
442
+ * ```
443
+ */
444
+ setExpressions(weights: Record<string, number>): Promise<void>;
445
+ /**
446
+ * Modifies specific expression weights without affecting others (partial update).
447
+ * Existing overrides not mentioned remain unchanged.
448
+ *
449
+ * @param weights A record of expression names to weight values (0.0-1.0).
450
+ *
451
+ * @example
452
+ * ```typescript
453
+ * const vrm = await Vrm.findByName("MyAvatar");
454
+ * // Only modifies "happy", leaves other overrides intact
455
+ * await vrm.modifyExpressions({ happy: 1.0 });
456
+ * ```
457
+ */
458
+ modifyExpressions(weights: Record<string, number>): Promise<void>;
459
+ /**
460
+ * Clears all expression overrides, returning control to VRMA animation.
461
+ *
462
+ * @example
463
+ * ```typescript
464
+ * const vrm = await Vrm.findByName("MyAvatar");
465
+ * await vrm.clearExpressions();
466
+ * ```
467
+ */
468
+ clearExpressions(): Promise<void>;
469
+ /**
470
+ * Modifies mouth expression weights for lip-sync.
471
+ * Unspecified mouth expressions are reset to 0.0.
472
+ * Non-mouth expression overrides are preserved.
473
+ *
474
+ * @param weights A record of mouth expression names to weight values (0.0-1.0).
475
+ *
476
+ * @example
477
+ * ```typescript
478
+ * const vrm = await Vrm.findByName("MyAvatar");
479
+ * await vrm.modifyMouth({ aa: 0.8, oh: 0.2 });
480
+ * ```
481
+ */
482
+ modifyMouth(weights: Record<string, number>): Promise<void>;
483
+ /**
484
+ * Gets all spring bone chains.
485
+ */
486
+ springBones(): Promise<SpringBoneChainsResponse>;
487
+ /**
488
+ * Gets a single spring bone chain by entity ID.
489
+ *
490
+ * @param chainId The chain entity ID.
491
+ */
492
+ springBone(chainId: number): Promise<SpringBoneChain>;
493
+ /**
494
+ * Updates spring bone properties for a chain.
495
+ *
496
+ * @param chainId The chain entity ID.
497
+ * @param props Partial properties to update.
498
+ */
499
+ setSpringBone(chainId: number, props: Partial<SpringBoneProps>): Promise<void>;
500
+ /**
501
+ * Gets all VRMA animations for this VRM.
502
+ */
503
+ listVrma(): Promise<VrmaInfo[]>;
504
+ /**
505
+ * Plays a VRMA animation.
506
+ *
507
+ * @param options Play request options including asset, repeat, transition.
508
+ */
509
+ playVrma(options: VrmaPlayRequest): Promise<void>;
510
+ /**
511
+ * Stops a VRMA animation.
512
+ *
513
+ * @param asset The asset ID of the VRMA animation to stop.
514
+ */
515
+ stopVrma(asset: string): Promise<void>;
516
+ /**
517
+ * Gets the state of a VRMA animation.
518
+ *
519
+ * @param asset The asset ID of the VRMA animation to query.
520
+ */
521
+ vrmaState(asset: string): Promise<VrmaState>;
522
+ /**
523
+ * Sets the playback speed of a VRMA animation.
524
+ *
525
+ * @param asset The asset ID of the VRMA animation.
526
+ * @param speed The playback speed.
527
+ */
528
+ setVrmaSpeed(asset: string, speed: number): Promise<void>;
529
+ /**
530
+ * Speaks using pre-generated audio with a timeline of expression keyframes.
531
+ * This allows any TTS engine to be used — the engine receives WAV audio and
532
+ * frame-synchronized lip-sync data.
533
+ *
534
+ * @param audio - WAV audio data as ArrayBuffer or Uint8Array.
535
+ * @param keyframes - Timeline keyframes specifying expression targets and durations.
536
+ * @param options - Optional settings (e.g. waitForCompletion).
537
+ *
538
+ * @example
539
+ * ```typescript
540
+ * const vrm = await Vrm.findByName("MyAvatar");
541
+ * const wavData = await fetchWavFromTTS("Hello world");
542
+ * await vrm.speakWithTimeline(wavData, [
543
+ * { duration: 0.1, targets: { aa: 1.0 } },
544
+ * { duration: 0.05 },
545
+ * { duration: 0.12, targets: { oh: 1.0, happy: 0.5 } },
546
+ * ]);
547
+ * ```
548
+ */
549
+ speakWithTimeline(audio: ArrayBuffer | Uint8Array, keyframes: TimelineKeyframe[], options?: SpeakTimelineOptions): Promise<void>;
550
+ /**
551
+ * Looks at the mouse cursor.
552
+ */
553
+ lookAtCursor(): Promise<void>;
554
+ /**
555
+ * Sets the VRM's look-at target to a specific entity.
556
+ *
557
+ * @param target The entity ID to look at.
558
+ */
559
+ lookAtTarget(target: number): Promise<void>;
560
+ /**
561
+ * Disables the VRM's look-at functionality.
562
+ */
563
+ unlook(): Promise<void>;
564
+ /**
565
+ * Spawns a new VRM instance from the given mod asset ID.
566
+ */
567
+ static spawn(asset: string, options?: SpawnVrmOptions): Promise<Vrm>;
568
+ /**
569
+ * Finds a VRM instance by its name.
570
+ *
571
+ * @param vrmName VRM avatar name
572
+ */
573
+ static findByName(vrmName: string): Promise<Vrm>;
574
+ /**
575
+ * Waits for a VRM instance to be spawned and initialized by its name.
576
+ *
577
+ * @param vrmName VRM avatar name
578
+ */
579
+ static waitLoadByName(vrmName: string): Promise<Vrm>;
580
+ /**
581
+ * Returns entity IDs of all currently loaded VRM instances.
582
+ *
583
+ * @example
584
+ * ```typescript
585
+ * const entities = await Vrm.findAllEntities();
586
+ * console.log(`Found ${entities.length} VRM entities`);
587
+ * ```
588
+ */
589
+ static findAllEntities(): Promise<number[]>;
590
+ /**
591
+ * Returns detailed snapshot of all VRM instances.
592
+ *
593
+ * @example
594
+ * ```typescript
595
+ * const snapshots = await Vrm.findAllDetailed();
596
+ * for (const s of snapshots) {
597
+ * console.log(`${s.name}: ${s.state} at (${s.globalViewport?.[0]}, ${s.globalViewport?.[1]})`);
598
+ * }
599
+ * ```
600
+ */
601
+ static findAllDetailed(): Promise<VrmSnapshot[]>;
602
+ static streamMetadata(f: (vrm: VrmMetadata) => (void | Promise<void>)): EventSource;
603
+ /**
604
+ * Streams all currently existing VRM instances and any VRM instances that will be created in the future.
605
+ * @param f
606
+ */
607
+ static stream(f: (vrm: Vrm) => (void | Promise<void>)): EventSource;
608
+ /**
609
+ * Returns all VRM instances that are currently loaded.
610
+ */
611
+ static findAll(): Promise<Vrm[]>;
612
+ private fetch;
613
+ private post;
614
+ private put;
615
+ private patch;
616
+ private delete;
617
+ }
618
+
619
+ /**
620
+ * Command script utilities for Desktop Homunculus mods.
621
+ *
622
+ * This module provides helpers for parsing stdin input and writing structured
623
+ * output in mod command scripts (`bin/` scripts invoked via the HTTP command
624
+ * execution API).
625
+ *
626
+ * **Input:** {@link input.parse} / {@link input.parseMenu} / {@link input.read}
627
+ * **Output:** {@link output.succeed} / {@link output.fail} / {@link output.write} / {@link output.writeError}
628
+ *
629
+ * @remarks
630
+ * This module uses Node.js APIs (`process.stdin`, `fs.writeFileSync`) and is
631
+ * not browser-compatible. Import from `@hmcs/sdk/commands` — it is intentionally
632
+ * not re-exported from the main `@hmcs/sdk` entry point.
633
+ *
634
+ * @example
635
+ * ```typescript
636
+ * import { z } from "zod";
637
+ * import { input, output, StdinParseError } from "@hmcs/sdk/commands";
638
+ *
639
+ * const schema = z.object({ name: z.string() });
640
+ *
641
+ * try {
642
+ * const data = await input.parse(schema);
643
+ * output.succeed({ greeting: `Hello, ${data.name}!` });
644
+ * } catch (err) {
645
+ * output.fail("GREET_FAILED", (err as Error).message);
646
+ * }
647
+ * ```
648
+ *
649
+ * @packageDocumentation
650
+ */
651
+
652
+ /**
653
+ * Error thrown by {@link input.parse} when stdin is empty, contains invalid JSON,
654
+ * or fails Zod schema validation.
655
+ *
656
+ * @example
657
+ * ```typescript
658
+ * import { input, StdinParseError } from "@hmcs/sdk/commands";
659
+ *
660
+ * try {
661
+ * const data = await input.parse(schema);
662
+ * } catch (err) {
663
+ * if (err instanceof StdinParseError) {
664
+ * console.error(JSON.stringify({ code: err.code, message: err.message }));
665
+ * process.exit(1);
666
+ * }
667
+ * throw err;
668
+ * }
669
+ * ```
670
+ */
671
+ declare class StdinParseError extends Error {
672
+ /** Structured error code identifying the failure stage. */
673
+ readonly code: "EMPTY_STDIN" | "INVALID_JSON" | "VALIDATION_ERROR";
674
+ /** For `VALIDATION_ERROR`, contains the `ZodError` instance. */
675
+ readonly details?: unknown | undefined;
676
+ readonly name = "StdinParseError";
677
+ constructor(
678
+ /** Structured error code identifying the failure stage. */
679
+ code: "EMPTY_STDIN" | "INVALID_JSON" | "VALIDATION_ERROR", message: string,
680
+ /** For `VALIDATION_ERROR`, contains the `ZodError` instance. */
681
+ details?: unknown | undefined);
682
+ }
683
+ /**
684
+ * Input helpers for reading and parsing stdin in bin command scripts.
685
+ *
686
+ * @example
687
+ * ```typescript
688
+ * import { z } from "zod";
689
+ * import { input } from "@hmcs/sdk/commands";
690
+ *
691
+ * const data = await input.parse(
692
+ * z.object({ name: z.string(), count: z.number() })
693
+ * );
694
+ * ```
695
+ */
696
+ declare namespace input {
697
+ /**
698
+ * Read all of stdin as a UTF-8 string.
699
+ *
700
+ * Consumes the entire `process.stdin` stream via async iteration and returns
701
+ * the concatenated result. Useful when you need the raw string without JSON
702
+ * parsing or validation.
703
+ *
704
+ * @example
705
+ * ```typescript
706
+ * import { input } from "@hmcs/sdk/commands";
707
+ *
708
+ * const raw = await input.read();
709
+ * console.log("Received:", raw);
710
+ * ```
711
+ */
712
+ function read(): Promise<string>;
713
+ /**
714
+ * Read JSON from stdin and validate it against a Zod schema.
715
+ *
716
+ * Performs three steps:
717
+ * 1. Reads all of stdin via {@link input.read}
718
+ * 2. Parses the raw string as JSON
719
+ * 3. Validates the parsed object against the provided Zod schema
720
+ *
721
+ * @typeParam T - The output type inferred from the Zod schema
722
+ * @param schema - A Zod schema to validate the parsed JSON against
723
+ * @returns The validated and typed input object
724
+ * @throws {StdinParseError} With `code: "EMPTY_STDIN"` if stdin is empty or whitespace-only
725
+ * @throws {StdinParseError} With `code: "INVALID_JSON"` if stdin is not valid JSON
726
+ * @throws {StdinParseError} With `code: "VALIDATION_ERROR"` if the JSON does not match the schema
727
+ *
728
+ * @example
729
+ * ```typescript
730
+ * import { z } from "zod";
731
+ * import { input } from "@hmcs/sdk/commands";
732
+ *
733
+ * const data = await input.parse(
734
+ * z.object({
735
+ * entity: z.number(),
736
+ * text: z.union([z.string(), z.array(z.string())]),
737
+ * speaker: z.number().default(0),
738
+ * })
739
+ * );
740
+ * ```
741
+ */
742
+ function parse<T>(schema: ZodType<T>): Promise<T>;
743
+ /**
744
+ * Parse menu command stdin and return the linked VRM instance.
745
+ *
746
+ * Menu commands receive `{ "linkedVrm": <entityId> }` on stdin from the
747
+ * menu UI. This helper validates the input and returns a ready-to-use
748
+ * {@link Vrm} instance.
749
+ *
750
+ * @returns A {@link Vrm} instance for the linked entity
751
+ * @throws {StdinParseError} With `code: "EMPTY_STDIN"` if stdin is empty
752
+ * @throws {StdinParseError} With `code: "INVALID_JSON"` if stdin is not valid JSON
753
+ * @throws {StdinParseError} With `code: "VALIDATION_ERROR"` if `linkedVrm` is missing or not a number
754
+ *
755
+ * @example
756
+ * ```typescript
757
+ * import { input } from "@hmcs/sdk/commands";
758
+ *
759
+ * const vrm = await input.parseMenu();
760
+ * await vrm.setExpressions({ happy: 1.0 });
761
+ * ```
762
+ */
763
+ function parseMenu(): Promise<Vrm>;
764
+ }
765
+ /**
766
+ * Output helpers for writing structured results and errors in bin command scripts.
767
+ *
768
+ * @example
769
+ * ```typescript
770
+ * import { output } from "@hmcs/sdk/commands";
771
+ *
772
+ * output.succeed({ count: 42, status: "done" });
773
+ * ```
774
+ */
775
+ declare namespace output {
776
+ /**
777
+ * Write a JSON-serialized result to stdout (fd 1).
778
+ *
779
+ * Serializes `data` with `JSON.stringify` and writes it followed by a newline
780
+ * to file descriptor 1 using synchronous I/O. This ensures the output is
781
+ * flushed before the process exits.
782
+ *
783
+ * @param data - The value to serialize as JSON and write to stdout
784
+ *
785
+ * @example
786
+ * ```typescript
787
+ * import { output } from "@hmcs/sdk/commands";
788
+ *
789
+ * output.write({ count: 42, status: "done" });
790
+ * // stdout receives: {"count":42,"status":"done"}\n
791
+ * ```
792
+ */
793
+ function write(data: unknown): void;
794
+ /**
795
+ * Write a structured JSON error to stderr (fd 2).
796
+ *
797
+ * Serializes an object with `code` and `message` fields and writes it followed
798
+ * by a newline to file descriptor 2 using synchronous I/O.
799
+ *
800
+ * @param code - A machine-readable error code (e.g., `"NOT_FOUND"`, `"TIMEOUT"`)
801
+ * @param message - A human-readable error description
802
+ *
803
+ * @example
804
+ * ```typescript
805
+ * import { output } from "@hmcs/sdk/commands";
806
+ *
807
+ * output.writeError("NOT_FOUND", "Entity 42 does not exist");
808
+ * // stderr receives: {"code":"NOT_FOUND","message":"Entity 42 does not exist"}\n
809
+ * ```
810
+ */
811
+ function writeError(code: string, message: string): void;
812
+ /**
813
+ * Write a JSON result to stdout and exit the process with code 0.
814
+ *
815
+ * This is a convenience wrapper that calls {@link output.write} followed by
816
+ * `process.exit(0)`. Use this as the final call in a successful bin command.
817
+ *
818
+ * @param data - The value to serialize as JSON and write to stdout
819
+ *
820
+ * @example
821
+ * ```typescript
822
+ * import { input, output } from "@hmcs/sdk/commands";
823
+ *
824
+ * const data = await input.parse(schema);
825
+ * const result = await doWork(data);
826
+ * output.succeed({ processed: result.count });
827
+ * ```
828
+ */
829
+ function succeed(data: unknown): never;
830
+ /**
831
+ * Write a structured error to stderr and exit the process.
832
+ *
833
+ * This is a convenience wrapper that calls {@link output.writeError} followed by
834
+ * `process.exit(exitCode)`. Use this when a bin command encounters a fatal error.
835
+ *
836
+ * @param code - A machine-readable error code (e.g., `"NOT_FOUND"`, `"TIMEOUT"`)
837
+ * @param message - A human-readable error description
838
+ * @param exitCode - Process exit code (default: `1`)
839
+ *
840
+ * @example
841
+ * ```typescript
842
+ * import { output } from "@hmcs/sdk/commands";
843
+ *
844
+ * if (!response.ok) {
845
+ * output.fail("API_ERROR", `Server returned ${response.status}`);
846
+ * }
847
+ * ```
848
+ */
849
+ function fail(code: string, message: string, exitCode?: number): never;
850
+ }
851
+
852
+ export { StdinParseError, input, output };