@forgeax/engine-remote 0.1.26 → 0.1.28

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 CHANGED
@@ -130,7 +130,7 @@ world.despawn(h);
130
130
  if (rhiCapture === undefined) return { ok: false, error: { code: 'capture-unavailable' } };
131
131
  const capture = await rhiCapture.captureFrame();
132
132
  if (!capture.ok) return capture;
133
- // Pass this single artifact to `forgeax run rhi.summary` or `rhi.inspect`.
133
+ // Pass this single artifact to `forgeax debug rhi summary` or `rhi.inspect`.
134
134
  return { kind: capture.value.kind, digest: capture.value.digest };
135
135
  ```
136
136
 
@@ -213,9 +213,9 @@ flowchart TD
213
213
  |:--|:--|:--|
214
214
  | In-process | `const result = await client.eval('world.inspect().entityCount')` | Host self-inspection; zero network cost |
215
215
  | WebSocket | `ws://localhost:5732` send `{"method":"eval","params":{"script":"..."}}` | External AI agents / CLI tools attaching to a running **Node / dawn-node** app |
216
- | Browser loopback relay (**remote-live**) | `POST http://127.0.0.1:5733/eval {"code":"..."}` → page dials the relay | Driving a **live browser** engine (`pnpm --filter <app> dev`, :5173) where no WS server can bind |
216
+ | Persistent DevKit owner (**`forgeax dev`**) | `forgeax dev eval --root <project> --instance-id <id> --world-identity <id> --code "..."` | Driving a **live browser** engine while preserving one Page and one actual World |
217
217
 
218
- > **Browsers cannot host a WS server.** `startServer` uses `ws.WebSocketServer` (a Node listening socket), so it never starts in a browser `createApp` catches the failure and `app.remote` stays `undefined`. To reach a running browser engine, `createApp` mounts a DEV-only bridge that dials OUT to a loopback relay and runs the ws-free eval core (`@forgeax/engine-remote/execute`) in the page realm. Start it with `node scripts/dev-live.mjs <app>` and drive it with `node skills/forgeax-engine-cli/scripts/remote-live.mjs "<code>"`. On by default in dev; opt out with `VITE_FORGEAX_ENGINE_BRIDGE=0`. Full recipe + security notes: the `forgeax-engine-cli` skill (§remote-live). This path is additive — it does not change the WS-server path or `app.remote` semantics.
218
+ > **Browsers cannot host a WS server.** `startServer` remains a Node/dawn-node transport. Browser development uses the DevKit owner: `forgeax dev start` keeps the project process and controlled Page, and its App bridge evaluates code in the actual main or Worker realm. `forgeax dev status` is the readiness and identity authority; stale `instanceId`, `loadId`, or `worldIdentity` requests fail instead of silently switching pages.
219
219
 
220
220
  The wire protocol exposes **two** JSON-RPC methods: `eval` (the single capability above) and `introspect`. Send `{"method":"introspect"}` to get an OpenRPC L2 subset document listing the available methods (`eval` / `introspect`) and the eval-scope live roots — an AI agent can self-describe the surface without reading source. Recoverable failures map to JSON-RPC error codes `-32001..-32005` (the 5-member `RemoteErrorCode` union; see above).
221
221
 
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAE,WAAW,EAAE,KAAK,eAAe,EAAE,MAAM,UAAU,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAE,WAAW,EAAE,KAAK,eAAe,EAAE,MAAM,UAAU,CAAC"}
package/package.json CHANGED
@@ -1,14 +1,11 @@
1
1
  {
2
2
  "name": "@forgeax/engine-remote",
3
- "version": "0.1.26",
3
+ "version": "0.1.28",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
7
7
  "sideEffects": false,
8
- "description": "ForgeaX live eval/introspection transport with structural World, renderer, assets, profiler, and execution roots plus CLI.",
9
- "bin": {
10
- "forgeax-engine-remote": "./dist/cli.mjs"
11
- },
8
+ "description": "ForgeaX live eval/introspection transport with structural World, renderer, assets, profiler, and execution roots. DevKit owns the public dev command surface.",
12
9
  "exports": {
13
10
  ".": {
14
11
  "types": "./dist/index.d.ts",
@@ -41,8 +38,8 @@
41
38
  "LICENSE"
42
39
  ],
43
40
  "dependencies": {
44
- "@forgeax/engine-profiler": "0.1.26",
45
- "@forgeax/engine-types": "0.1.26",
41
+ "@forgeax/engine-profiler": "0.1.28",
42
+ "@forgeax/engine-types": "0.1.28",
46
43
  "ws": "^8.20.0"
47
44
  },
48
45
  "devDependencies": {
@@ -1,5 +1,5 @@
1
1
  // @forgeax/engine-remote/src/__tests__/execute-browser-safe — the ./execute
2
- // subpath is the eval core reused by the browser remote-live bridge (createApp
2
+ // subpath is the eval core reused by the browser DevKit live bridge (createApp
3
3
  // dials a loopback relay; the page realm runs executeScript directly because a
4
4
  // browser cannot bind a Node WS server). It MUST stay ws-free / node-built-in-
5
5
  // free at the SOURCE level, else importing it in a browser bundle throws on the
@@ -16,7 +16,7 @@ import { describe, expect, it } from 'vitest';
16
16
 
17
17
  const EXECUTE_SRC = fileURLToPath(new URL('../execute.ts', import.meta.url));
18
18
 
19
- describe('execute.ts browser safety (remote-live bridge)', () => {
19
+ describe('execute.ts browser safety (DevKit live bridge)', () => {
20
20
  const source = readFileSync(EXECUTE_SRC, 'utf8');
21
21
  // Match only real import statements, not prose in comments.
22
22
  const importLines = source
package/src/index.ts CHANGED
@@ -1,9 +1,7 @@
1
- // @forgeax/engine-remote - inspector P0 server + CLI dual-exit package.
1
+ // @forgeax/engine-remote - inspector transport and eval core.
2
2
  //
3
- // Single entry facade (charter proposition 1 progressive disclosure). The
4
- // runtime server lives under the ./server sub-path; the standalone CLI binary
5
- // ships via `bin.forgeax` -> dist/cli.mjs. AI users import the shared error
6
- // model from this top entry:
3
+ // The runtime server lives under the ./server sub-path. DevKit owns the public
4
+ // command tree; AI users import the shared error model from this top entry:
7
5
  //
8
6
  // import { RemoteError, type RemoteErrorCode } from '@forgeax/engine-remote';
9
7
  //
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=cli-defaults.unit.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"cli-defaults.unit.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/cli-defaults.unit.test.ts"],"names":[],"mappings":""}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=console.unit.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"console.unit.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/console.unit.test.ts"],"names":[],"mappings":""}
package/dist/cli.d.ts DELETED
@@ -1,15 +0,0 @@
1
- #!/usr/bin/env node
2
- import { type ConnectFn, defaultConnect, type InspectorClient } from '@forgeax/engine-types/inspector-client';
3
- import { type SubcommandSpec } from './defineSubcommand';
4
- export type { ConnectFn, InspectorClient };
5
- export { defaultConnect };
6
- export declare const FORGEAX_CLI_SPEC: SubcommandSpec;
7
- export interface DispatchOptions {
8
- readonly argv: readonly string[];
9
- readonly stdoutWrite: (line: string) => void;
10
- readonly stderrWrite: (line: string) => void;
11
- readonly connect: ConnectFn;
12
- readonly fileReader?: (path: string) => Promise<string>;
13
- }
14
- export declare function dispatch(opts: DispatchOptions): Promise<number>;
15
- //# sourceMappingURL=cli.d.ts.map
package/dist/cli.d.ts.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AA0BA,OAAO,EACL,KAAK,SAAS,EACd,cAAc,EAGd,KAAK,eAAe,EACrB,MAAM,wCAAwC,CAAC;AAChD,OAAO,EAAgC,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEvF,YAAY,EAAE,SAAS,EAAE,eAAe,EAAE,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,CAAC;AAK1B,eAAO,MAAM,gBAAgB,EAAE,cAyC7B,CAAC;AAgDH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,QAAQ,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C,QAAQ,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAC;IAC5B,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CACzD;AAMD,wBAAsB,QAAQ,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CA2DrE"}
package/dist/cli.mjs DELETED
@@ -1,336 +0,0 @@
1
- #!/usr/bin/env node
2
- import { realpath, readFile } from 'fs/promises';
3
- import { fileURLToPath } from 'url';
4
- import { INSPECTOR_DEFAULT_PORT, INSPECTOR_DEFAULT_HOST, defaultConnect } from '@forgeax/engine-types/inspector-client';
5
- export { defaultConnect } from '@forgeax/engine-types/inspector-client';
6
-
7
- // src/defineSubcommand.ts
8
- var GAP = 4;
9
- function defineSubcommand(spec) {
10
- if (typeof spec.name !== "string" || spec.name.length === 0) {
11
- throw new Error("defineSubcommand: spec.name must be a non-empty string");
12
- }
13
- return spec;
14
- }
15
- function resolvePath(root, path) {
16
- let current = root;
17
- const consumed = [];
18
- for (const segment of path) {
19
- const next = current.subcommands?.find((s) => s.name === segment);
20
- if (next === void 0) break;
21
- current = next;
22
- consumed.push(segment);
23
- }
24
- return { spec: current, path: consumed };
25
- }
26
- function section(title, items) {
27
- if (items.length === 0) return [];
28
- let maxLen = 0;
29
- for (const [label] of items) {
30
- if (label.length > maxLen) maxLen = label.length;
31
- }
32
- const lines = [];
33
- lines.push(`${title}:`);
34
- for (const [label, body] of items) {
35
- const pad = " ".repeat(maxLen + GAP - label.length);
36
- lines.push(` ${label}${pad}${body}`);
37
- }
38
- lines.push("");
39
- return lines;
40
- }
41
- function renderHelp(root, path) {
42
- const { spec, path: consumed } = resolvePath(root, path);
43
- const fullPath = [root.name, ...consumed].join(" ");
44
- const out = [];
45
- out.push(`${fullPath} - ${spec.description}`);
46
- out.push("");
47
- const usagePieces = [fullPath];
48
- if (spec.subcommands && spec.subcommands.length > 0) {
49
- usagePieces.push("<subcommand>");
50
- } else {
51
- usagePieces.push("[options]");
52
- }
53
- out.push("Usage:");
54
- out.push(` ${usagePieces.join(" ")}`);
55
- out.push("");
56
- if (spec.subcommands && spec.subcommands.length > 0) {
57
- out.push(
58
- ...section(
59
- consumed.length === 0 ? "Sub-commands" : "Sub-targets",
60
- spec.subcommands.map((s) => [s.name, s.description])
61
- )
62
- );
63
- }
64
- if (spec.options && spec.options.length > 0) {
65
- out.push(
66
- ...section(
67
- "Options",
68
- spec.options.map((o) => [o.flag, o.description])
69
- )
70
- );
71
- }
72
- if (spec.examples && spec.examples.length > 0) {
73
- const exampleItems = spec.examples.map(
74
- (e) => [e.usage, e.description ?? ""]
75
- );
76
- out.push(...section("Examples", exampleItems));
77
- }
78
- if (spec.extraNotes && spec.extraNotes.length > 0) {
79
- out.push("Notes:");
80
- for (const note of spec.extraNotes) {
81
- out.push(` ${note}`);
82
- }
83
- out.push("");
84
- }
85
- return `${out.join("\n").trimEnd()}
86
- `;
87
- }
88
-
89
- // src/cli.ts
90
- var FORGEAX_CLI_SPEC = defineSubcommand({
91
- name: "forgeax-engine-remote",
92
- description: "remote eval CLI - drive a running forgeax engine via JSON-RPC over WS",
93
- options: [
94
- {
95
- flag: "--port <n>",
96
- description: `Inspector WebSocket port (default ${INSPECTOR_DEFAULT_PORT}; monitor uses 5731)`
97
- },
98
- { flag: "--host <s>", description: `Host name (default ${INSPECTOR_DEFAULT_HOST})` },
99
- { flag: "--help, -h", description: "Show this help and exit 0" }
100
- ],
101
- subcommands: [
102
- defineSubcommand({
103
- name: "script",
104
- description: "eval a script file against the live world/renderer/assets",
105
- options: [{ flag: "--help, -h", description: "Show this help and exit 0" }],
106
- examples: [
107
- {
108
- usage: "forgeax-engine-remote script ./inspect.mjs",
109
- description: "eval a local script file"
110
- }
111
- ]
112
- }),
113
- defineSubcommand({
114
- name: "eval",
115
- description: "evaluate an inline expression against the world",
116
- options: [{ flag: "--help, -h", description: "Show this help and exit 0" }],
117
- examples: [
118
- {
119
- usage: 'forgeax-engine-remote eval "world.inspect().entityCount"',
120
- description: "inline read of world.inspect()"
121
- }
122
- ]
123
- })
124
- ],
125
- extraNotes: [
126
- "eval is full read/write access to the live world/renderer/assets/debugAdapter; the only security boundary is whether the host started the server.",
127
- "Plugin discovery via PATH-prefix removed in M2 (routing layer deletion).",
128
- "See also: packages/remote/README.md (eval API, live roots, security model) + AI User Charter.",
129
- "Simulation inspection is read-only through eval; restore and replay are not Remote or CLI actions."
130
- ]
131
- });
132
- function renderTopLevelHelp() {
133
- const lines = [];
134
- lines.push(`${FORGEAX_CLI_SPEC.name} - ${FORGEAX_CLI_SPEC.description}`);
135
- lines.push("");
136
- lines.push("Usage:");
137
- lines.push(` ${FORGEAX_CLI_SPEC.name} <subcommand> [args]`);
138
- lines.push("");
139
- lines.push("Built-in commands:");
140
- const builtIns = FORGEAX_CLI_SPEC.subcommands ?? [];
141
- const builtInWidth = builtIns.reduce((m, s) => Math.max(m, s.name.length), 0);
142
- for (const s of builtIns) {
143
- const pad = " ".repeat(builtInWidth - s.name.length + 4);
144
- lines.push(` ${s.name}${pad}${s.description}`);
145
- }
146
- lines.push("");
147
- if (FORGEAX_CLI_SPEC.options && FORGEAX_CLI_SPEC.options.length > 0) {
148
- lines.push("Options:");
149
- const optWidth = FORGEAX_CLI_SPEC.options.reduce((m, o) => Math.max(m, o.flag.length), 0);
150
- for (const o of FORGEAX_CLI_SPEC.options) {
151
- const pad = " ".repeat(optWidth - o.flag.length + 4);
152
- lines.push(` ${o.flag}${pad}${o.description}`);
153
- }
154
- lines.push("");
155
- }
156
- if (FORGEAX_CLI_SPEC.extraNotes && FORGEAX_CLI_SPEC.extraNotes.length > 0) {
157
- lines.push("Notes:");
158
- for (const note of FORGEAX_CLI_SPEC.extraNotes) {
159
- lines.push(` ${note}`);
160
- }
161
- lines.push("");
162
- }
163
- return `${lines.join("\n").trimEnd()}
164
- `;
165
- }
166
- var defaultFileReader = async (path) => {
167
- return await readFile(path, "utf8");
168
- };
169
- async function dispatch(opts) {
170
- const { argv, stdoutWrite, stderrWrite, connect } = opts;
171
- const fileReader = opts.fileReader ?? defaultFileReader;
172
- const [, , subcommand, ...rest] = argv;
173
- if (subcommand === void 0 || subcommand === "--help" || subcommand === "-h") {
174
- stdoutWrite(renderTopLevelHelp());
175
- return 0;
176
- }
177
- let port = INSPECTOR_DEFAULT_PORT;
178
- let host = INSPECTOR_DEFAULT_HOST;
179
- const filteredRest = [];
180
- for (let i = 0; i < rest.length; i++) {
181
- const arg = rest[i];
182
- if (arg === "--port") {
183
- const next = rest[i + 1];
184
- if (typeof next === "string") {
185
- const parsed = Number(next);
186
- if (!Number.isNaN(parsed) && parsed > 0) {
187
- port = parsed;
188
- i++;
189
- continue;
190
- }
191
- }
192
- }
193
- if (arg === "--host") {
194
- const next = rest[i + 1];
195
- if (typeof next === "string") {
196
- host = next;
197
- i++;
198
- continue;
199
- }
200
- }
201
- if (typeof arg === "string") filteredRest.push(arg);
202
- }
203
- switch (subcommand) {
204
- case "script":
205
- return runScript(filteredRest, {
206
- stdoutWrite,
207
- stderrWrite,
208
- connect,
209
- port,
210
- host,
211
- fileReader
212
- });
213
- case "eval":
214
- return runEval(filteredRest, { stdoutWrite, stderrWrite, connect, port, host });
215
- default: {
216
- stderrWrite(
217
- `forgeax: unknown subcommand '${subcommand}'
218
- expected: subcommand is one of: script, eval
219
- hint: run 'forgeax-engine-remote --help' for usage
220
- detail: '${subcommand}' is not a built-in subcommand (plugin discovery removed in M2)
221
- `
222
- );
223
- return 1;
224
- }
225
- }
226
- }
227
- function inspectorErrorToStderr(e) {
228
- return [`forgeax: ${e.code}`, ` expected: ${e.expected}`, ` hint: ${e.hint}`].join("\n");
229
- }
230
- async function runScript(rest, ctx) {
231
- const [file] = rest;
232
- if (file === "--help" || file === "-h") {
233
- ctx.stdoutWrite(renderHelp(FORGEAX_CLI_SPEC, ["script"]));
234
- return 0;
235
- }
236
- if (typeof file !== "string") {
237
- ctx.stderrWrite(
238
- [
239
- "forgeax: script requires a <file> positional argument",
240
- " expected: forgeax-engine-remote script <path-to-js-file>",
241
- " hint: e.g. 'forgeax-engine-remote script ./inspect.mjs'"
242
- ].join("\n")
243
- );
244
- return 1;
245
- }
246
- let body;
247
- try {
248
- body = await ctx.fileReader(file);
249
- } catch (e) {
250
- const message = e instanceof Error ? e.message : String(e);
251
- ctx.stderrWrite(
252
- [
253
- `forgeax: script file unreadable: ${file}`,
254
- " expected: file exists and is readable",
255
- ` hint: check path; underlying error: ${message}`
256
- ].join("\n")
257
- );
258
- return 1;
259
- }
260
- return invokeExecute(body, ctx);
261
- }
262
- async function runEval(rest, ctx) {
263
- const [script] = rest;
264
- if (script === "--help" || script === "-h") {
265
- ctx.stdoutWrite(renderHelp(FORGEAX_CLI_SPEC, ["eval"]));
266
- return 0;
267
- }
268
- if (typeof script !== "string") {
269
- ctx.stderrWrite(
270
- [
271
- "forgeax: eval requires an inline <script> positional argument",
272
- ' expected: forgeax-engine-remote eval "<expression>"',
273
- ` hint: e.g. 'forgeax-engine-remote eval "world.inspect().entityCount"'`
274
- ].join("\n")
275
- );
276
- return 1;
277
- }
278
- return invokeExecute(script, ctx);
279
- }
280
- async function invokeExecute(script, ctx) {
281
- const url = `ws://${ctx.host}:${ctx.port}/inspector`;
282
- const connectResult = await ctx.connect(url);
283
- if (!connectResult.ok) {
284
- ctx.stderrWrite(inspectorErrorToStderr(connectResult.error));
285
- return 1;
286
- }
287
- const client = connectResult.value;
288
- try {
289
- const result = await client.eval(script);
290
- ctx.stdoutWrite(typeof result === "string" ? result : JSON.stringify(result, null, 2));
291
- return 0;
292
- } catch (e) {
293
- if (isRemoteError(e)) {
294
- ctx.stderrWrite(inspectorErrorToStderr(e));
295
- return 1;
296
- }
297
- const message = e instanceof Error ? e.message : String(e);
298
- ctx.stderrWrite(
299
- [
300
- "forgeax: execute failed",
301
- " expected: server-side execute() resolves Result.ok",
302
- ` hint: underlying: ${message}`
303
- ].join("\n")
304
- );
305
- return 1;
306
- } finally {
307
- await client.dispose();
308
- }
309
- }
310
- function isRemoteError(e) {
311
- return typeof e === "object" && e !== null && typeof e.code === "string" && typeof e.expected === "string" && typeof e.hint === "string";
312
- }
313
- var isBinEntry = await (async () => {
314
- const argv1 = process.argv[1];
315
- if (typeof argv1 !== "string") return false;
316
- const argv1Real = await realpath(argv1).catch(() => argv1);
317
- const selfReal = await realpath(fileURLToPath(import.meta.url)).catch(
318
- () => fileURLToPath(import.meta.url)
319
- );
320
- return argv1Real === selfReal;
321
- })();
322
- if (isBinEntry) {
323
- const exitCode = await dispatch({
324
- argv: process.argv,
325
- stdoutWrite: (line) => process.stdout.write(`${line}
326
- `),
327
- stderrWrite: (line) => process.stderr.write(`${line}
328
- `),
329
- connect: defaultConnect
330
- });
331
- process.exit(exitCode);
332
- }
333
-
334
- export { FORGEAX_CLI_SPEC, dispatch };
335
- //# sourceMappingURL=cli.mjs.map
336
- //# sourceMappingURL=cli.mjs.map
package/dist/cli.mjs.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/defineSubcommand.ts","../src/cli.ts"],"names":[],"mappings":"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","file":"cli.mjs","sourcesContent":["// @forgeax/engine-remote/src/defineSubcommand - sade utils.js form (~94-line\n// flat-dictionary + section primitive) DSL for the `forgeax-engine-console`\n// CLI help renderer. Plan-strategy D-4 + D-7 lock-in:\n//\n// - Single file, package-internal (NOT in package.json#exports).\n// `cli.ts` is the sole consumer; tsup inlines it into dist/cli.mjs.\n// - 94-line ceiling is a pattern target, not a hard cap; we trade a few\n// extra lines for explicit JSDoc that AI users read at edit time.\n// - Three render layers driven by `path` slicing:\n// path = [] -> top-level help\n// path = ['inspect'] -> subcommand help\n// path = ['inspect', 'entities'] -> sub-target help\n// - List-width pin: `maxLen + GAP=4` padding (R-4 mitigation; snapshot\n// test packages/console/src/__tests__/cli-help.test.ts guards drift).\n//\n// charter: proposition 1 (progressive disclosure — `path` is the navigator,\n// not a hidden config) + proposition 3 (machine-readable spec >>> hand-rolled\n// strings) + proposition 4 (explicit failure — render is total: any unknown\n// path returns the closest valid layer rather than throwing).\n\nconst GAP = 4;\n\n/** Single option entry: `--with <Name>`, `--port <number>` ... */\nexport interface OptionSpec {\n readonly flag: string;\n readonly description: string;\n readonly multiple?: boolean;\n readonly defaultValue?: string;\n}\n\n/** Single example block: usage line + free description. */\nexport interface ExampleSpec {\n readonly usage: string;\n readonly description?: string;\n}\n\n/** Subcommand spec — recursive (subcommands map to nested specs). */\nexport interface SubcommandSpec {\n readonly name: string;\n readonly description: string;\n readonly options?: ReadonlyArray<OptionSpec>;\n readonly subcommands?: ReadonlyArray<SubcommandSpec>;\n readonly examples?: ReadonlyArray<ExampleSpec>;\n readonly extraNotes?: ReadonlyArray<string>;\n}\n\n/**\n * Single-input wrapping helper: turns a sade-style descriptor into a\n * SubcommandSpec POD. Today the function is a near-identity (the spec is\n * already structurally a POD), but the wrapper preserves a single intercept\n * point for future validation (charter proposition 4: explicit failure on\n * malformed input — we can throw here rather than silently render garbage).\n */\nexport function defineSubcommand(spec: SubcommandSpec): SubcommandSpec {\n if (typeof spec.name !== 'string' || spec.name.length === 0) {\n throw new Error('defineSubcommand: spec.name must be a non-empty string');\n }\n return spec;\n}\n\n/**\n * Look up the descendant spec at `path`. Returns the closest matching\n * ancestor when the path is partially unknown so renderHelp degrades to the\n * deepest valid layer rather than throwing (charter proposition 4 explicit\n * failure: a wrong path is recoverable; a thrown render is not).\n */\nfunction resolvePath(\n root: SubcommandSpec,\n path: readonly string[],\n): { readonly spec: SubcommandSpec; readonly path: readonly string[] } {\n let current = root;\n const consumed: string[] = [];\n for (const segment of path) {\n const next = current.subcommands?.find((s) => s.name === segment);\n if (next === undefined) break;\n current = next;\n consumed.push(segment);\n }\n return { spec: current, path: consumed };\n}\n\n/**\n * Render a single section with `key` left-padded to `maxLen + GAP` columns.\n * `items` carries `[label, body]` pairs; both halves are flat strings.\n *\n * Skips emission entirely when `items` is empty so the rendered help body\n * has no orphan section headers (UX nit: AI users grep section headers as\n * anchors — emitting a header followed by nothing fools the grep).\n */\nfunction section(title: string, items: ReadonlyArray<readonly [string, string]>): string[] {\n if (items.length === 0) return [];\n let maxLen = 0;\n for (const [label] of items) {\n if (label.length > maxLen) maxLen = label.length;\n }\n const lines: string[] = [];\n lines.push(`${title}:`);\n for (const [label, body] of items) {\n const pad = ' '.repeat(maxLen + GAP - label.length);\n lines.push(` ${label}${pad}${body}`);\n }\n lines.push('');\n return lines;\n}\n\n/**\n * Render the help body for `path` against `root`. Always returns a non-empty\n * string ending in a single newline so callers can pipe to stdout without\n * post-processing.\n *\n * Layer 1 (root): title + Usage + Sub-commands + Options + extraNotes\n * Layer 2 (subcommand): title + Usage + Sub-targets (if any) + Options + Examples + extraNotes\n * Layer 3 (sub-target): title + Usage + Options + Examples + extraNotes\n */\nexport function renderHelp(root: SubcommandSpec, path: readonly string[]): string {\n const { spec, path: consumed } = resolvePath(root, path);\n const fullPath = [root.name, ...consumed].join(' ');\n const out: string[] = [];\n out.push(`${fullPath} - ${spec.description}`);\n out.push('');\n\n // Usage line — synthesised from `path` + the leaf's surface.\n const usagePieces: string[] = [fullPath];\n if (spec.subcommands && spec.subcommands.length > 0) {\n usagePieces.push('<subcommand>');\n } else {\n // Leaf nodes use a generic <args> token; concrete shape lives in\n // `examples` so the help body stays declarative rather than guessed.\n usagePieces.push('[options]');\n }\n out.push('Usage:');\n out.push(` ${usagePieces.join(' ')}`);\n out.push('');\n\n if (spec.subcommands && spec.subcommands.length > 0) {\n out.push(\n ...section(\n consumed.length === 0 ? 'Sub-commands' : 'Sub-targets',\n spec.subcommands.map((s) => [s.name, s.description] as const),\n ),\n );\n }\n\n if (spec.options && spec.options.length > 0) {\n out.push(\n ...section(\n 'Options',\n spec.options.map((o) => [o.flag, o.description] as const),\n ),\n );\n }\n\n if (spec.examples && spec.examples.length > 0) {\n const exampleItems: Array<readonly [string, string]> = spec.examples.map(\n (e) => [e.usage, e.description ?? ''] as const,\n );\n out.push(...section('Examples', exampleItems));\n }\n\n if (spec.extraNotes && spec.extraNotes.length > 0) {\n out.push('Notes:');\n for (const note of spec.extraNotes) {\n out.push(` ${note}`);\n }\n out.push('');\n }\n\n return `${out.join('\\n').trimEnd()}\\n`;\n}\n","#!/usr/bin/env node\n// @forgeax/engine-remote/src/cli - forgeax CLI binary entry (feat-20260517 D-3\n// + D-4: inspect-subcommand removed; only built-in `script` / `eval`\n// remain. M2 w8: plugin discovery (discoverPlugins) deleted alongside\n// routing layer removal. defaultConnect SSOT lives in\n// `@forgeax/engine-types/inspector-client` and is re-exported here\n// for the legacy import surface).\n//\n// Two-subcommand built-in dispatch:\n// - forgeax-engine-remote script <file>\n// - forgeax-engine-remote eval <inline-script>\n//\n// WebSocket client (D-3 / w18): the in-cli ~80-line `defaultConnect`\n// implementation was extracted to `@forgeax/engine-types/inspector-client`\n// so the engine-remote base CLI and the engine-ecs `cli-ecs` plugin bin\n// (M3) share one client recipe. The Result-form `eval(script)` /\n// `dispose()` surface replaces the legacy `request(method,params)` /\n// `close()` shape that lived inside cli.ts.\n//\n// Argparse via stdlib `node:util.parseArgs` (no commander / sade / cac\n// dep). Help body is produced by the package-internal `defineSubcommand`\n// DSL (plan-strategy D-4 + D-7).\n\nimport { readFile, realpath } from 'node:fs/promises';\nimport { fileURLToPath } from 'node:url';\nimport type { RemoteError as RemoteErrorShape } from '@forgeax/engine-types';\nimport {\n type ConnectFn,\n defaultConnect,\n INSPECTOR_DEFAULT_HOST,\n INSPECTOR_DEFAULT_PORT,\n type InspectorClient,\n} from '@forgeax/engine-types/inspector-client';\nimport { defineSubcommand, renderHelp, type SubcommandSpec } from './defineSubcommand';\n\nexport type { ConnectFn, InspectorClient };\nexport { defaultConnect };\n\n// w8: LEGACY_INSPECT_TARGETS removed alongside plugin discovery deletion.\n// ─── Subcommand spec tree (sade utils.js form) ───────────────────────────────\n\nexport const FORGEAX_CLI_SPEC: SubcommandSpec = defineSubcommand({\n name: 'forgeax-engine-remote',\n description: 'remote eval CLI - drive a running forgeax engine via JSON-RPC over WS',\n options: [\n {\n flag: '--port <n>',\n description: `Inspector WebSocket port (default ${INSPECTOR_DEFAULT_PORT}; monitor uses 5731)`,\n },\n { flag: '--host <s>', description: `Host name (default ${INSPECTOR_DEFAULT_HOST})` },\n { flag: '--help, -h', description: 'Show this help and exit 0' },\n ],\n subcommands: [\n defineSubcommand({\n name: 'script',\n description: 'eval a script file against the live world/renderer/assets',\n options: [{ flag: '--help, -h', description: 'Show this help and exit 0' }],\n examples: [\n {\n usage: 'forgeax-engine-remote script ./inspect.mjs',\n description: 'eval a local script file',\n },\n ],\n }),\n defineSubcommand({\n name: 'eval',\n description: 'evaluate an inline expression against the world',\n options: [{ flag: '--help, -h', description: 'Show this help and exit 0' }],\n examples: [\n {\n usage: 'forgeax-engine-remote eval \"world.inspect().entityCount\"',\n description: 'inline read of world.inspect()',\n },\n ],\n }),\n ],\n extraNotes: [\n 'eval is full read/write access to the live world/renderer/assets/debugAdapter; the only security boundary is whether the host started the server.',\n 'Plugin discovery via PATH-prefix removed in M2 (routing layer deletion).',\n 'See also: packages/remote/README.md (eval API, live roots, security model) + AI User Charter.',\n 'Simulation inspection is read-only through eval; restore and replay are not Remote or CLI actions.',\n ],\n});\n\n// w8: Plugin discovery (discoverPlugins) removed alongside routing layer deletion.\n// renderTopLevelHelp no longer takes plugins — only built-in commands displayed.\n\nfunction renderTopLevelHelp(): string {\n const lines: string[] = [];\n lines.push(`${FORGEAX_CLI_SPEC.name} - ${FORGEAX_CLI_SPEC.description}`);\n lines.push('');\n lines.push('Usage:');\n lines.push(` ${FORGEAX_CLI_SPEC.name} <subcommand> [args]`);\n lines.push('');\n\n lines.push('Built-in commands:');\n const builtIns = FORGEAX_CLI_SPEC.subcommands ?? [];\n const builtInWidth = builtIns.reduce((m, s) => Math.max(m, s.name.length), 0);\n for (const s of builtIns) {\n const pad = ' '.repeat(builtInWidth - s.name.length + 4);\n lines.push(` ${s.name}${pad}${s.description}`);\n }\n lines.push('');\n\n if (FORGEAX_CLI_SPEC.options && FORGEAX_CLI_SPEC.options.length > 0) {\n lines.push('Options:');\n const optWidth = FORGEAX_CLI_SPEC.options.reduce((m, o) => Math.max(m, o.flag.length), 0);\n for (const o of FORGEAX_CLI_SPEC.options) {\n const pad = ' '.repeat(optWidth - o.flag.length + 4);\n lines.push(` ${o.flag}${pad}${o.description}`);\n }\n lines.push('');\n }\n\n if (FORGEAX_CLI_SPEC.extraNotes && FORGEAX_CLI_SPEC.extraNotes.length > 0) {\n lines.push('Notes:');\n for (const note of FORGEAX_CLI_SPEC.extraNotes) {\n lines.push(` ${note}`);\n }\n lines.push('');\n }\n\n return `${lines.join('\\n').trimEnd()}\\n`;\n}\n\n// w8: renderConsoleStartupFailed (plugin-discovery error rendering) removed.\n// For unknown subcommands, a simple stderr fallback is used inline.\n\n// --- Dispatch (test-injectable) ---\n\nexport interface DispatchOptions {\n readonly argv: readonly string[];\n readonly stdoutWrite: (line: string) => void;\n readonly stderrWrite: (line: string) => void;\n readonly connect: ConnectFn;\n readonly fileReader?: (path: string) => Promise<string>;\n}\n\nconst defaultFileReader = async (path: string): Promise<string> => {\n return await readFile(path, 'utf8');\n};\n\nexport async function dispatch(opts: DispatchOptions): Promise<number> {\n const { argv, stdoutWrite, stderrWrite, connect } = opts;\n const fileReader = opts.fileReader ?? defaultFileReader;\n const [, , subcommand, ...rest] = argv;\n\n if (subcommand === undefined || subcommand === '--help' || subcommand === '-h') {\n stdoutWrite(renderTopLevelHelp());\n return 0;\n }\n\n let port = INSPECTOR_DEFAULT_PORT;\n let host = INSPECTOR_DEFAULT_HOST;\n const filteredRest: string[] = [];\n for (let i = 0; i < rest.length; i++) {\n const arg = rest[i];\n if (arg === '--port') {\n const next = rest[i + 1];\n if (typeof next === 'string') {\n const parsed = Number(next);\n if (!Number.isNaN(parsed) && parsed > 0) {\n port = parsed;\n i++;\n continue;\n }\n }\n }\n if (arg === '--host') {\n const next = rest[i + 1];\n if (typeof next === 'string') {\n host = next;\n i++;\n continue;\n }\n }\n if (typeof arg === 'string') filteredRest.push(arg);\n }\n\n switch (subcommand) {\n case 'script':\n return runScript(filteredRest, {\n stdoutWrite,\n stderrWrite,\n connect,\n port,\n host,\n fileReader,\n });\n case 'eval':\n return runEval(filteredRest, { stdoutWrite, stderrWrite, connect, port, host });\n default: {\n // CLI argument error (not a RemoteErrorCode — that closed union is the\n // wire/eval failure vocabulary, not a usage-error channel). Plain\n // usage message mirrors the script/eval missing-arg errors above.\n stderrWrite(\n `forgeax: unknown subcommand '${subcommand}'\\n expected: subcommand is one of: script, eval\\n hint: run 'forgeax-engine-remote --help' for usage\\n detail: '${subcommand}' is not a built-in subcommand (plugin discovery removed in M2)\\n`,\n );\n return 1;\n }\n }\n}\n\ninterface RunCtx {\n readonly stdoutWrite: (line: string) => void;\n readonly stderrWrite: (line: string) => void;\n readonly connect: ConnectFn;\n readonly port: number;\n readonly host: string;\n}\n\ninterface RunScriptCtx extends RunCtx {\n readonly fileReader: (path: string) => Promise<string>;\n}\n\nfunction inspectorErrorToStderr(e: RemoteErrorShape): string {\n return [`forgeax: ${e.code}`, ` expected: ${e.expected}`, ` hint: ${e.hint}`].join('\\n');\n}\n\nasync function runScript(rest: string[], ctx: RunScriptCtx): Promise<number> {\n const [file] = rest;\n if (file === '--help' || file === '-h') {\n ctx.stdoutWrite(renderHelp(FORGEAX_CLI_SPEC, ['script']));\n return 0;\n }\n if (typeof file !== 'string') {\n ctx.stderrWrite(\n [\n 'forgeax: script requires a <file> positional argument',\n ' expected: forgeax-engine-remote script <path-to-js-file>',\n \" hint: e.g. 'forgeax-engine-remote script ./inspect.mjs'\",\n ].join('\\n'),\n );\n return 1;\n }\n let body: string;\n try {\n body = await ctx.fileReader(file);\n } catch (e) {\n const message = e instanceof Error ? e.message : String(e);\n ctx.stderrWrite(\n [\n `forgeax: script file unreadable: ${file}`,\n ' expected: file exists and is readable',\n ` hint: check path; underlying error: ${message}`,\n ].join('\\n'),\n );\n return 1;\n }\n return invokeExecute(body, ctx);\n}\n\nasync function runEval(rest: string[], ctx: RunCtx): Promise<number> {\n const [script] = rest;\n if (script === '--help' || script === '-h') {\n ctx.stdoutWrite(renderHelp(FORGEAX_CLI_SPEC, ['eval']));\n return 0;\n }\n if (typeof script !== 'string') {\n ctx.stderrWrite(\n [\n 'forgeax: eval requires an inline <script> positional argument',\n ' expected: forgeax-engine-remote eval \"<expression>\"',\n ' hint: e.g. \\'forgeax-engine-remote eval \"world.inspect().entityCount\"\\'',\n ].join('\\n'),\n );\n return 1;\n }\n return invokeExecute(script, ctx);\n}\n\nasync function invokeExecute(script: string, ctx: RunCtx): Promise<number> {\n const url = `ws://${ctx.host}:${ctx.port}/inspector`;\n const connectResult = await ctx.connect(url);\n if (!connectResult.ok) {\n ctx.stderrWrite(inspectorErrorToStderr(connectResult.error));\n return 1;\n }\n const client = connectResult.value;\n try {\n const result = await client.eval(script);\n ctx.stdoutWrite(typeof result === 'string' ? result : JSON.stringify(result, null, 2));\n return 0;\n } catch (e) {\n if (isRemoteError(e)) {\n ctx.stderrWrite(inspectorErrorToStderr(e));\n return 1;\n }\n const message = e instanceof Error ? e.message : String(e);\n ctx.stderrWrite(\n [\n 'forgeax: execute failed',\n ' expected: server-side execute() resolves Result.ok',\n ` hint: underlying: ${message}`,\n ].join('\\n'),\n );\n return 1;\n } finally {\n await client.dispose();\n }\n}\n\nfunction isRemoteError(e: unknown): e is RemoteErrorShape {\n return (\n typeof e === 'object' &&\n e !== null &&\n typeof (e as { code?: unknown }).code === 'string' &&\n typeof (e as { expected?: unknown }).expected === 'string' &&\n typeof (e as { hint?: unknown }).hint === 'string'\n );\n}\n\n// ─── Bin entry — only runs when this module is the process entry ────────────\n\nconst isBinEntry = await (async () => {\n const argv1 = process.argv[1];\n if (typeof argv1 !== 'string') return false;\n const argv1Real = await realpath(argv1).catch(() => argv1);\n const selfReal = await realpath(fileURLToPath(import.meta.url)).catch(() =>\n fileURLToPath(import.meta.url),\n );\n return argv1Real === selfReal;\n})();\n\nif (isBinEntry) {\n const exitCode = await dispatch({\n argv: process.argv,\n stdoutWrite: (line: string) => process.stdout.write(`${line}\\n`),\n stderrWrite: (line: string) => process.stderr.write(`${line}\\n`),\n connect: defaultConnect,\n });\n process.exit(exitCode);\n}\n"]}
@@ -1,40 +0,0 @@
1
- /** Single option entry: `--with <Name>`, `--port <number>` ... */
2
- export interface OptionSpec {
3
- readonly flag: string;
4
- readonly description: string;
5
- readonly multiple?: boolean;
6
- readonly defaultValue?: string;
7
- }
8
- /** Single example block: usage line + free description. */
9
- export interface ExampleSpec {
10
- readonly usage: string;
11
- readonly description?: string;
12
- }
13
- /** Subcommand spec — recursive (subcommands map to nested specs). */
14
- export interface SubcommandSpec {
15
- readonly name: string;
16
- readonly description: string;
17
- readonly options?: ReadonlyArray<OptionSpec>;
18
- readonly subcommands?: ReadonlyArray<SubcommandSpec>;
19
- readonly examples?: ReadonlyArray<ExampleSpec>;
20
- readonly extraNotes?: ReadonlyArray<string>;
21
- }
22
- /**
23
- * Single-input wrapping helper: turns a sade-style descriptor into a
24
- * SubcommandSpec POD. Today the function is a near-identity (the spec is
25
- * already structurally a POD), but the wrapper preserves a single intercept
26
- * point for future validation (charter proposition 4: explicit failure on
27
- * malformed input — we can throw here rather than silently render garbage).
28
- */
29
- export declare function defineSubcommand(spec: SubcommandSpec): SubcommandSpec;
30
- /**
31
- * Render the help body for `path` against `root`. Always returns a non-empty
32
- * string ending in a single newline so callers can pipe to stdout without
33
- * post-processing.
34
- *
35
- * Layer 1 (root): title + Usage + Sub-commands + Options + extraNotes
36
- * Layer 2 (subcommand): title + Usage + Sub-targets (if any) + Options + Examples + extraNotes
37
- * Layer 3 (sub-target): title + Usage + Options + Examples + extraNotes
38
- */
39
- export declare function renderHelp(root: SubcommandSpec, path: readonly string[]): string;
40
- //# sourceMappingURL=defineSubcommand.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"defineSubcommand.d.ts","sourceRoot":"","sources":["../src/defineSubcommand.ts"],"names":[],"mappings":"AAsBA,kEAAkE;AAClE,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAChC;AAED,2DAA2D;AAC3D,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,qEAAqE;AACrE,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,CAAC,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;IAC7C,QAAQ,CAAC,WAAW,CAAC,EAAE,aAAa,CAAC,cAAc,CAAC,CAAC;IACrD,QAAQ,CAAC,QAAQ,CAAC,EAAE,aAAa,CAAC,WAAW,CAAC,CAAC;IAC/C,QAAQ,CAAC,UAAU,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CAC7C;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,cAAc,GAAG,cAAc,CAKrE;AA+CD;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAsDhF"}
@@ -1,69 +0,0 @@
1
- import { readFileSync } from 'node:fs';
2
- import { dirname, resolve } from 'node:path';
3
- import { fileURLToPath } from 'node:url';
4
- import {
5
- type ConnectFn,
6
- INSPECTOR_DEFAULT_HOST,
7
- INSPECTOR_DEFAULT_PORT,
8
- } from '@forgeax/engine-types/inspector-client';
9
- import { describe, expect, it } from 'vitest';
10
- import { dispatch } from '../cli';
11
-
12
- const source = readFileSync(
13
- resolve(dirname(fileURLToPath(import.meta.url)), '..', 'cli.ts'),
14
- 'utf8',
15
- );
16
-
17
- const client: ConnectFn = async () => ({
18
- ok: true,
19
- value: {
20
- eval: async () => null,
21
- dispose: async () => {},
22
- },
23
- });
24
-
25
- describe('remote inspector connection defaults', () => {
26
- it('uses the inspector-client owner instead of a local default ledger', () => {
27
- expect(source).toContain('INSPECTOR_DEFAULT_PORT');
28
- expect(source).toContain('INSPECTOR_DEFAULT_HOST');
29
- expect(source).not.toContain('const DEFAULT_PORT');
30
- expect(source).not.toContain('const DEFAULT_HOST');
31
- });
32
-
33
- it('builds the owned default target and preserves explicit overrides', async () => {
34
- let defaultUrl: string | undefined;
35
- const defaultExitCode = await dispatch({
36
- argv: ['node', 'forgeax', 'eval', 'world.inspect()'],
37
- stdoutWrite: () => {},
38
- stderrWrite: () => {},
39
- connect: async (url) => {
40
- defaultUrl = url;
41
- return client(url);
42
- },
43
- });
44
- expect(defaultExitCode).toBe(0);
45
- expect(defaultUrl).toBe(`ws://${INSPECTOR_DEFAULT_HOST}:${INSPECTOR_DEFAULT_PORT}/inspector`);
46
-
47
- let overriddenUrl: string | undefined;
48
- const overriddenExitCode = await dispatch({
49
- argv: [
50
- 'node',
51
- 'forgeax',
52
- 'eval',
53
- '--port',
54
- '6000',
55
- '--host',
56
- 'inspector.example',
57
- 'world.inspect()',
58
- ],
59
- stdoutWrite: () => {},
60
- stderrWrite: () => {},
61
- connect: async (url) => {
62
- overriddenUrl = url;
63
- return client(url);
64
- },
65
- });
66
- expect(overriddenExitCode).toBe(0);
67
- expect(overriddenUrl).toBe('ws://inspector.example:6000/inspector');
68
- });
69
- });