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