dfhack-remote-node 2.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.
package/LICENSE.md ADDED
@@ -0,0 +1,16 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2020, Alexander Chandel (original browser client)
4
+ Copyright (c) 2026, Alexander Olvera (TypeScript/Node port)
5
+
6
+ Permission to use, copy, modify, and/or distribute this software for any
7
+ purpose with or without fee is hereby granted, provided that the above
8
+ copyright notice and this permission notice appear in all copies.
9
+
10
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,126 @@
1
+ # dfhack-remote-node
2
+
3
+ A small, strictly-typed Node client for the [DFHack](https://github.com/DFHack/dfhack)
4
+ Remote RPC protocol — protobuf over raw TCP (`127.0.0.1:5000`). It lets a Node
5
+ process read from (and, later, act on) a live Dwarf Fortress fort without
6
+ compiling against DFHack's C++.
7
+
8
+ This is a TypeScript port of the original browser client
9
+ ([alexchandel/dfhack-remote](https://github.com/alexchandel/dfhack-remote), ISC).
10
+ The protocol codec is carried over; the transport was rewritten from
11
+ WebSocket + `websockify` to Node `net.Socket`, `RunCommand` console output is now
12
+ captured instead of discarded, and methods bind lazily on first use.
13
+
14
+ Built primarily as the RPC layer for a DFHack MCP server.
15
+
16
+ ## Install
17
+
18
+ ```sh
19
+ npm install dfhack-remote-node
20
+ ```
21
+
22
+ The published package ships prebuilt (`dist/` ESM + type declarations, proto
23
+ bundle inlined), so no build step is needed to consume it.
24
+
25
+ To work on the library from a clone instead:
26
+
27
+ ```sh
28
+ npm install
29
+ npm run build # regenerates build/proto.json and bundles dist/
30
+ ```
31
+
32
+ `build/proto.json` (the compiled protobuf bundle) is committed, so the source
33
+ runs immediately; `npm run build` produces the distributable `dist/` (ESM +
34
+ type declarations, with the proto bundle inlined).
35
+
36
+ ## Usage
37
+
38
+ ```js
39
+ import { DwarfClient } from 'dfhack-remote-node';
40
+
41
+ const df = new DwarfClient(); // defaults to 127.0.0.1:5000
42
+ await df.connect(); // handshake
43
+
44
+ console.log(await df.getVersion()); // DFHack version string
45
+ const world = await df.getWorldInfo(); // decoded GetWorldInfoOut
46
+ console.log(world.worldName?.englishName);
47
+
48
+ // Arbitrary Lua snippet; returns whatever it prints (captured console output):
49
+ const text = await df.runLuaSnippet('print(#df.global.world.units.active)');
50
+
51
+ // Any method in the METHODS table, by name:
52
+ const map = await df.call('GetMapInfo');
53
+
54
+ df.close();
55
+ ```
56
+
57
+ Every reply object also carries `_text` — the concatenated console output from
58
+ any TEXT frames the call produced.
59
+
60
+ ## Package layout
61
+
62
+ `src/` is split by concern (all TypeScript ESM):
63
+
64
+ | File | Responsibility |
65
+ | --------------- | ----------------------------------------------------------------------------------- |
66
+ | `codec.ts` | wire framing: magic bytes, frame ids, `encodeMessage`, `readHandshake`, `readFrame` |
67
+ | `errors.ts` | `RpcError`, `ProtocolError` |
68
+ | `connection.ts` | `DfConnection` — the `net.Socket` transport + call queue |
69
+ | `methods.ts` | the `METHODS` table (fully-qualified protobuf type names) |
70
+ | `proto.ts` | loads the committed proto bundle as a JSON module import |
71
+ | `client.ts` | `DwarfClient` — the high-level API |
72
+ | `index.ts` | public exports |
73
+
74
+ The source imports the proto bundle as a JSON module
75
+ (`import protoJson from '../build/proto.json' with { type: 'json' }`), so the
76
+ bundler inlines it into `dist/` — there is no runtime file lookup.
77
+
78
+ ## Protos
79
+
80
+ > The `.proto` files are pinned to **DFHack `53.15-r2`** (pulled from
81
+ > [DFHack/dfhack](https://github.com/DFHack/dfhack) at the commit the running
82
+ > build reports via `getGitDescription`). To retarget another version, replace
83
+ > the files from the matching tag and re-run `npm run gen-proto`. Method
84
+ > input/output types in `METHODS` (`src/methods.ts`) are fully-qualified names
85
+ > that must match DFHack's registration, or `BindMethod` reports a "wrong
86
+ > signature" at runtime — which makes drift detectable per method.
87
+
88
+ ## Scripts
89
+
90
+ ```sh
91
+ npm run build # gen-proto + tsup -> dist/ (ESM + .d.ts, proto inlined)
92
+ npm run gen-proto # regenerate build/proto.json from proto/*.proto
93
+ npm run typecheck # tsc --noEmit
94
+ npm run lint # eslint (flat config)
95
+ npm run format # prettier --write
96
+ npm test # offline: mock server speaks the real wire format, no game needed
97
+ npm run spike # live: connect to a running fort, print version + world + fort name
98
+ ```
99
+
100
+ `npm test` validates handshake, framing, lazy binding, protobuf round-trip, and
101
+ TEXT-frame capture against an in-process mock. `npm run spike` (M0) requires
102
+ Dwarf Fortress running with DFHack and a fort loaded.
103
+
104
+ ## Protocol notes
105
+
106
+ - Handshake: client sends `DFHack?\n` + `i32(1)`, server replies `DFHack!\n` + `i32(1)`.
107
+ - Message header: `id:i16, pad:u16, size:i32` (little-endian), then `size` body bytes.
108
+ - A call's reply is zero or more `TEXT` frames (console output) followed by one
109
+ `RESULT` frame (reply protobuf) or one `FAIL` frame (errno in the size field).
110
+ - Calls are serialized — DFHack handles one at a time per socket.
111
+ - `GetBlockList` only returns blocks _changed since the last call_ — don't build a
112
+ full-map snapshot on it naively (use `ResetMapHashes` to force a resend).
113
+
114
+ ## Contributing
115
+
116
+ Issues and PRs welcome. Please keep the module boundaries above intact, run
117
+ `npm run typecheck`, `npm run lint`, and `npm test` before opening a PR, and add
118
+ new RPC methods to `src/methods.ts` with their fully-qualified protobuf type
119
+ names. Changes touching the live protocol should be verified against a running
120
+ fort with `npm run spike`.
121
+
122
+ ## License
123
+
124
+ ISC. Ported from [alexchandel/dfhack-remote](https://github.com/alexchandel/dfhack-remote)
125
+ (Copyright © 2020 Alexander Chandel); the original copyright and license are
126
+ preserved in [LICENSE.md](./LICENSE.md).