@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,298 @@
1
+ 'use strict';
2
+
3
+ var node_fs = require('node:fs');
4
+ var zod = require('zod');
5
+ var vrm = require('./vrm.cjs');
6
+
7
+ /**
8
+ * Command script utilities for Desktop Homunculus mods.
9
+ *
10
+ * This module provides helpers for parsing stdin input and writing structured
11
+ * output in mod command scripts (`bin/` scripts invoked via the HTTP command
12
+ * execution API).
13
+ *
14
+ * **Input:** {@link input.parse} / {@link input.parseMenu} / {@link input.read}
15
+ * **Output:** {@link output.succeed} / {@link output.fail} / {@link output.write} / {@link output.writeError}
16
+ *
17
+ * @remarks
18
+ * This module uses Node.js APIs (`process.stdin`, `fs.writeFileSync`) and is
19
+ * not browser-compatible. Import from `@hmcs/sdk/commands` — it is intentionally
20
+ * not re-exported from the main `@hmcs/sdk` entry point.
21
+ *
22
+ * @example
23
+ * ```typescript
24
+ * import { z } from "zod";
25
+ * import { input, output, StdinParseError } from "@hmcs/sdk/commands";
26
+ *
27
+ * const schema = z.object({ name: z.string() });
28
+ *
29
+ * try {
30
+ * const data = await input.parse(schema);
31
+ * output.succeed({ greeting: `Hello, ${data.name}!` });
32
+ * } catch (err) {
33
+ * output.fail("GREET_FAILED", (err as Error).message);
34
+ * }
35
+ * ```
36
+ *
37
+ * @packageDocumentation
38
+ */
39
+ /**
40
+ * Safely serialize a value to JSON. Returns a fallback error JSON string
41
+ * if serialization fails (e.g., circular references or BigInt values).
42
+ */
43
+ function safeStringify(data) {
44
+ try {
45
+ return JSON.stringify(data);
46
+ }
47
+ catch {
48
+ return '{"code":"SERIALIZE_ERROR","message":"Failed to serialize output"}';
49
+ }
50
+ }
51
+ /**
52
+ * Error thrown by {@link input.parse} when stdin is empty, contains invalid JSON,
53
+ * or fails Zod schema validation.
54
+ *
55
+ * @example
56
+ * ```typescript
57
+ * import { input, StdinParseError } from "@hmcs/sdk/commands";
58
+ *
59
+ * try {
60
+ * const data = await input.parse(schema);
61
+ * } catch (err) {
62
+ * if (err instanceof StdinParseError) {
63
+ * console.error(JSON.stringify({ code: err.code, message: err.message }));
64
+ * process.exit(1);
65
+ * }
66
+ * throw err;
67
+ * }
68
+ * ```
69
+ */
70
+ class StdinParseError extends Error {
71
+ code;
72
+ details;
73
+ name = "StdinParseError";
74
+ constructor(
75
+ /** Structured error code identifying the failure stage. */
76
+ code, message,
77
+ /** For `VALIDATION_ERROR`, contains the `ZodError` instance. */
78
+ details) {
79
+ super(message);
80
+ this.code = code;
81
+ this.details = details;
82
+ }
83
+ }
84
+ /**
85
+ * Input helpers for reading and parsing stdin in bin command scripts.
86
+ *
87
+ * @example
88
+ * ```typescript
89
+ * import { z } from "zod";
90
+ * import { input } from "@hmcs/sdk/commands";
91
+ *
92
+ * const data = await input.parse(
93
+ * z.object({ name: z.string(), count: z.number() })
94
+ * );
95
+ * ```
96
+ */
97
+ exports.input = void 0;
98
+ (function (input) {
99
+ /**
100
+ * Read all of stdin as a UTF-8 string.
101
+ *
102
+ * Consumes the entire `process.stdin` stream via async iteration and returns
103
+ * the concatenated result. Useful when you need the raw string without JSON
104
+ * parsing or validation.
105
+ *
106
+ * @example
107
+ * ```typescript
108
+ * import { input } from "@hmcs/sdk/commands";
109
+ *
110
+ * const raw = await input.read();
111
+ * console.log("Received:", raw);
112
+ * ```
113
+ */
114
+ async function read() {
115
+ const chunks = [];
116
+ for await (const chunk of process.stdin) {
117
+ chunks.push(chunk);
118
+ }
119
+ return Buffer.concat(chunks).toString("utf-8");
120
+ }
121
+ input.read = read;
122
+ /**
123
+ * Read JSON from stdin and validate it against a Zod schema.
124
+ *
125
+ * Performs three steps:
126
+ * 1. Reads all of stdin via {@link input.read}
127
+ * 2. Parses the raw string as JSON
128
+ * 3. Validates the parsed object against the provided Zod schema
129
+ *
130
+ * @typeParam T - The output type inferred from the Zod schema
131
+ * @param schema - A Zod schema to validate the parsed JSON against
132
+ * @returns The validated and typed input object
133
+ * @throws {StdinParseError} With `code: "EMPTY_STDIN"` if stdin is empty or whitespace-only
134
+ * @throws {StdinParseError} With `code: "INVALID_JSON"` if stdin is not valid JSON
135
+ * @throws {StdinParseError} With `code: "VALIDATION_ERROR"` if the JSON does not match the schema
136
+ *
137
+ * @example
138
+ * ```typescript
139
+ * import { z } from "zod";
140
+ * import { input } from "@hmcs/sdk/commands";
141
+ *
142
+ * const data = await input.parse(
143
+ * z.object({
144
+ * entity: z.number(),
145
+ * text: z.union([z.string(), z.array(z.string())]),
146
+ * speaker: z.number().default(0),
147
+ * })
148
+ * );
149
+ * ```
150
+ */
151
+ async function parse(schema) {
152
+ const raw = await read();
153
+ if (raw.trim().length === 0) {
154
+ throw new StdinParseError("EMPTY_STDIN", "No input received on stdin");
155
+ }
156
+ let json;
157
+ try {
158
+ json = JSON.parse(raw);
159
+ }
160
+ catch {
161
+ throw new StdinParseError("INVALID_JSON", `Invalid JSON: ${raw.slice(0, 200)}`);
162
+ }
163
+ const result = schema.safeParse(json);
164
+ if (!result.success) {
165
+ throw new StdinParseError("VALIDATION_ERROR", `Validation failed: ${result.error.message}`, result.error);
166
+ }
167
+ return result.data;
168
+ }
169
+ input.parse = parse;
170
+ /**
171
+ * Parse menu command stdin and return the linked VRM instance.
172
+ *
173
+ * Menu commands receive `{ "linkedVrm": <entityId> }` on stdin from the
174
+ * menu UI. This helper validates the input and returns a ready-to-use
175
+ * {@link Vrm} instance.
176
+ *
177
+ * @returns A {@link Vrm} instance for the linked entity
178
+ * @throws {StdinParseError} With `code: "EMPTY_STDIN"` if stdin is empty
179
+ * @throws {StdinParseError} With `code: "INVALID_JSON"` if stdin is not valid JSON
180
+ * @throws {StdinParseError} With `code: "VALIDATION_ERROR"` if `linkedVrm` is missing or not a number
181
+ *
182
+ * @example
183
+ * ```typescript
184
+ * import { input } from "@hmcs/sdk/commands";
185
+ *
186
+ * const vrm = await input.parseMenu();
187
+ * await vrm.setExpressions({ happy: 1.0 });
188
+ * ```
189
+ */
190
+ async function parseMenu() {
191
+ const parsed = await parse(zod.z.object({ linkedVrm: zod.z.number() }));
192
+ return new vrm.Vrm(parsed.linkedVrm);
193
+ }
194
+ input.parseMenu = parseMenu;
195
+ })(exports.input || (exports.input = {}));
196
+ /**
197
+ * Output helpers for writing structured results and errors in bin command scripts.
198
+ *
199
+ * @example
200
+ * ```typescript
201
+ * import { output } from "@hmcs/sdk/commands";
202
+ *
203
+ * output.succeed({ count: 42, status: "done" });
204
+ * ```
205
+ */
206
+ exports.output = void 0;
207
+ (function (output) {
208
+ /**
209
+ * Write a JSON-serialized result to stdout (fd 1).
210
+ *
211
+ * Serializes `data` with `JSON.stringify` and writes it followed by a newline
212
+ * to file descriptor 1 using synchronous I/O. This ensures the output is
213
+ * flushed before the process exits.
214
+ *
215
+ * @param data - The value to serialize as JSON and write to stdout
216
+ *
217
+ * @example
218
+ * ```typescript
219
+ * import { output } from "@hmcs/sdk/commands";
220
+ *
221
+ * output.write({ count: 42, status: "done" });
222
+ * // stdout receives: {"count":42,"status":"done"}\n
223
+ * ```
224
+ */
225
+ function write(data) {
226
+ node_fs.writeFileSync(1, safeStringify(data) + "\n");
227
+ }
228
+ output.write = write;
229
+ /**
230
+ * Write a structured JSON error to stderr (fd 2).
231
+ *
232
+ * Serializes an object with `code` and `message` fields and writes it followed
233
+ * by a newline to file descriptor 2 using synchronous I/O.
234
+ *
235
+ * @param code - A machine-readable error code (e.g., `"NOT_FOUND"`, `"TIMEOUT"`)
236
+ * @param message - A human-readable error description
237
+ *
238
+ * @example
239
+ * ```typescript
240
+ * import { output } from "@hmcs/sdk/commands";
241
+ *
242
+ * output.writeError("NOT_FOUND", "Entity 42 does not exist");
243
+ * // stderr receives: {"code":"NOT_FOUND","message":"Entity 42 does not exist"}\n
244
+ * ```
245
+ */
246
+ function writeError(code, message) {
247
+ node_fs.writeFileSync(2, JSON.stringify({ code, message }) + "\n");
248
+ }
249
+ output.writeError = writeError;
250
+ /**
251
+ * Write a JSON result to stdout and exit the process with code 0.
252
+ *
253
+ * This is a convenience wrapper that calls {@link output.write} followed by
254
+ * `process.exit(0)`. Use this as the final call in a successful bin command.
255
+ *
256
+ * @param data - The value to serialize as JSON and write to stdout
257
+ *
258
+ * @example
259
+ * ```typescript
260
+ * import { input, output } from "@hmcs/sdk/commands";
261
+ *
262
+ * const data = await input.parse(schema);
263
+ * const result = await doWork(data);
264
+ * output.succeed({ processed: result.count });
265
+ * ```
266
+ */
267
+ function succeed(data) {
268
+ write(data);
269
+ process.exit(0);
270
+ }
271
+ output.succeed = succeed;
272
+ /**
273
+ * Write a structured error to stderr and exit the process.
274
+ *
275
+ * This is a convenience wrapper that calls {@link output.writeError} followed by
276
+ * `process.exit(exitCode)`. Use this when a bin command encounters a fatal error.
277
+ *
278
+ * @param code - A machine-readable error code (e.g., `"NOT_FOUND"`, `"TIMEOUT"`)
279
+ * @param message - A human-readable error description
280
+ * @param exitCode - Process exit code (default: `1`)
281
+ *
282
+ * @example
283
+ * ```typescript
284
+ * import { output } from "@hmcs/sdk/commands";
285
+ *
286
+ * if (!response.ok) {
287
+ * output.fail("API_ERROR", `Server returned ${response.status}`);
288
+ * }
289
+ * ```
290
+ */
291
+ function fail(code, message, exitCode = 1) {
292
+ writeError(code, message);
293
+ process.exit(exitCode);
294
+ }
295
+ output.fail = fail;
296
+ })(exports.output || (exports.output = {}));
297
+
298
+ exports.StdinParseError = StdinParseError;