@oddessentials/agent-guild 1.28.0 → 1.30.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/bin/native/win32-arm64/agent-guild-report.exe +0 -0
- package/bin/native/win32-x64/agent-guild-report.exe +0 -0
- package/bin/windows/arm64/agent-guild-start.exe +0 -0
- package/bin/windows/x64/agent-guild-start.exe +0 -0
- package/docs/api.md +10 -2
- package/node_modules/@jsquash/jpeg/CHANGELOG.md +54 -0
- package/node_modules/@jsquash/jpeg/LICENSE +201 -0
- package/node_modules/@jsquash/jpeg/README.md +105 -0
- package/node_modules/@jsquash/jpeg/codec/LICENSE.codec.md +132 -0
- package/node_modules/@jsquash/jpeg/codec/dec/mozjpeg_dec.d.ts +7 -0
- package/node_modules/@jsquash/jpeg/codec/dec/mozjpeg_dec.js +15 -0
- package/node_modules/@jsquash/jpeg/codec/dec/mozjpeg_dec.wasm +0 -0
- package/node_modules/@jsquash/jpeg/codec/enc/mozjpeg_enc.d.ts +37 -0
- package/node_modules/@jsquash/jpeg/codec/enc/mozjpeg_enc.js +15 -0
- package/node_modules/@jsquash/jpeg/codec/enc/mozjpeg_enc.wasm +0 -0
- package/node_modules/@jsquash/jpeg/codec/pre.js +24 -0
- package/node_modules/@jsquash/jpeg/decode.d.ts +16 -0
- package/node_modules/@jsquash/jpeg/decode.js +36 -0
- package/node_modules/@jsquash/jpeg/emscripten-types.d.ts +137 -0
- package/node_modules/@jsquash/jpeg/encode.d.ts +21 -0
- package/node_modules/@jsquash/jpeg/encode.js +35 -0
- package/node_modules/@jsquash/jpeg/index.d.ts +3 -0
- package/node_modules/@jsquash/jpeg/index.js +2 -0
- package/node_modules/@jsquash/jpeg/meta.d.ts +24 -0
- package/node_modules/@jsquash/jpeg/meta.js +25 -0
- package/node_modules/@jsquash/jpeg/package.json +32 -0
- package/node_modules/@jsquash/jpeg/utils.d.ts +17 -0
- package/node_modules/@jsquash/jpeg/utils.js +31 -0
- package/node_modules/@jsquash/png/CHANGELOG.md +69 -0
- package/node_modules/@jsquash/png/LICENSE +201 -0
- package/node_modules/@jsquash/png/README.md +114 -0
- package/node_modules/@jsquash/png/codec/LICENSE.codec.md +29 -0
- package/node_modules/@jsquash/png/codec/README.md +5 -0
- package/node_modules/@jsquash/png/codec/pkg/README.md +5 -0
- package/node_modules/@jsquash/png/codec/pkg/squoosh_png.d.ts +71 -0
- package/node_modules/@jsquash/png/codec/pkg/squoosh_png.js +314 -0
- package/node_modules/@jsquash/png/codec/pkg/squoosh_png_bg.wasm +0 -0
- package/node_modules/@jsquash/png/codec/pkg/squoosh_png_bg.wasm.d.ts +13 -0
- package/node_modules/@jsquash/png/codec/pre.js +24 -0
- package/node_modules/@jsquash/png/decode.d.ts +29 -0
- package/node_modules/@jsquash/png/decode.js +35 -0
- package/node_modules/@jsquash/png/encode.d.ts +31 -0
- package/node_modules/@jsquash/png/encode.js +40 -0
- package/node_modules/@jsquash/png/index.d.ts +3 -0
- package/node_modules/@jsquash/png/index.js +2 -0
- package/node_modules/@jsquash/png/meta.d.ts +16 -0
- package/node_modules/@jsquash/png/meta.js +15 -0
- package/node_modules/@jsquash/png/package.json +34 -0
- package/node_modules/@jsquash/webp/CHANGELOG.md +48 -0
- package/node_modules/@jsquash/webp/LICENSE +201 -0
- package/node_modules/@jsquash/webp/README.md +100 -0
- package/node_modules/@jsquash/webp/codec/LICENSE.codec.md +29 -0
- package/node_modules/@jsquash/webp/codec/Makefile.animated.backup +88 -0
- package/node_modules/@jsquash/webp/codec/README.md +5 -0
- package/node_modules/@jsquash/webp/codec/dec/README.md +18 -0
- package/node_modules/@jsquash/webp/codec/dec/webp_dec.d.ts +7 -0
- package/node_modules/@jsquash/webp/codec/dec/webp_dec.js +16 -0
- package/node_modules/@jsquash/webp/codec/dec/webp_dec.wasm +0 -0
- package/node_modules/@jsquash/webp/codec/enc/README.md +22 -0
- package/node_modules/@jsquash/webp/codec/enc/webp_enc.d.ts +42 -0
- package/node_modules/@jsquash/webp/codec/enc/webp_enc.js +16 -0
- package/node_modules/@jsquash/webp/codec/enc/webp_enc.wasm +0 -0
- package/node_modules/@jsquash/webp/codec/enc/webp_enc_simd.d.ts +1 -0
- package/node_modules/@jsquash/webp/codec/enc/webp_enc_simd.js +16 -0
- package/node_modules/@jsquash/webp/codec/enc/webp_enc_simd.wasm +0 -0
- package/node_modules/@jsquash/webp/codec/pre.js +24 -0
- package/node_modules/@jsquash/webp/decode.d.ts +15 -0
- package/node_modules/@jsquash/webp/decode.js +34 -0
- package/node_modules/@jsquash/webp/emscripten-types.d.ts +137 -0
- package/node_modules/@jsquash/webp/encode.d.ts +23 -0
- package/node_modules/@jsquash/webp/encode.js +43 -0
- package/node_modules/@jsquash/webp/index.d.ts +3 -0
- package/node_modules/@jsquash/webp/index.js +2 -0
- package/node_modules/@jsquash/webp/meta.d.ts +19 -0
- package/node_modules/@jsquash/webp/meta.js +33 -0
- package/node_modules/@jsquash/webp/package.json +35 -0
- package/node_modules/@jsquash/webp/utils.d.ts +17 -0
- package/node_modules/@jsquash/webp/utils.js +31 -0
- package/node_modules/argparse/lib/argparse.js +1 -1
- package/node_modules/argparse/lib/textwrap.js +13 -2
- package/node_modules/argparse/package.json +1 -1
- package/node_modules/markdown-it/dist/browser/markdown-it.esm.min.mjs +737 -686
- package/node_modules/markdown-it/dist/browser/markdown-it.esm.min.mjs.map +1 -1
- package/node_modules/markdown-it/dist/browser/markdown-it.umd.min.js +8 -8
- package/node_modules/markdown-it/dist/browser/markdown-it.umd.min.js.map +1 -1
- package/node_modules/markdown-it/dist/markdown-it.cjs.js +660 -518
- package/node_modules/markdown-it/dist/markdown-it.cjs.js.map +1 -1
- package/node_modules/markdown-it/dist/markdown-it.d.mts +1 -0
- package/node_modules/markdown-it/dist/markdown-it.mjs +179 -124
- package/node_modules/markdown-it/dist/markdown-it.mjs.map +1 -1
- package/node_modules/markdown-it/package.json +11 -11
- package/node_modules/node-pty/prebuilds/win32-arm64/conpty.node +0 -0
- package/node_modules/node-pty/prebuilds/win32-arm64/conpty_console_list.node +0 -0
- package/node_modules/node-pty/prebuilds/win32-x64/conpty.node +0 -0
- package/node_modules/node-pty/prebuilds/win32-x64/conpty_console_list.node +0 -0
- package/node_modules/wasm-feature-detect/LICENSE +202 -0
- package/node_modules/wasm-feature-detect/README.md +109 -0
- package/node_modules/wasm-feature-detect/README.md.ejs +89 -0
- package/node_modules/wasm-feature-detect/dist/cjs/index.cjs +1 -0
- package/node_modules/wasm-feature-detect/dist/esm/index.js +1 -0
- package/node_modules/wasm-feature-detect/dist/index.d.cts +24 -0
- package/node_modules/wasm-feature-detect/dist/index.d.ts +24 -0
- package/node_modules/wasm-feature-detect/dist/umd/index.js +1 -0
- package/node_modules/wasm-feature-detect/package.json +53 -0
- package/node_modules/ws/README.md +21 -29
- package/node_modules/ws/lib/buffer-util.js +13 -2
- package/node_modules/ws/lib/sender.js +4 -1
- package/node_modules/ws/lib/websocket.js +8 -3
- package/node_modules/ws/package.json +1 -1
- package/package.json +7 -4
- package/src/manager/config.mjs +2 -0
- package/src/manager/folder-browser.mjs +39 -3
- package/src/manager/main.mjs +4 -0
- package/src/manager/server.mjs +32 -5
- package/src/manager/steam-workshop.mjs +64 -0
- package/src/manager/yard-art.mjs +1 -1
- package/src/manager/yard-piece-check.mjs +424 -0
- package/src/manager/yard-pieces.mjs +188 -0
- package/src/manager/yard-worlds.mjs +214 -0
- package/web/app.js +175 -11
- package/web/index.html +20 -2
- package/web/skins/grove/ui/companion-bird.webp +0 -0
- package/web/skins/guild/skin.css +2 -13
- package/web/styles.css +15 -1
- package/web/yard/art.json +1 -1
- package/web/yard/model.mjs +44 -2
- package/web/yard/photo.js +3 -4
- package/web/yard/renderer.js +150 -38
- package/web/yard/vendor/engine.js +500 -495
- package/web/yard/view.js +160 -14
- package/web/yard/yard.css +55 -12
- package/web/skins/grove/ui/install-now-dark.svg +0 -91
- package/web/skins/grove/ui/install-now-light.svg +0 -91
|
@@ -56,39 +56,27 @@ can use one of the many wrappers available on npm, like
|
|
|
56
56
|
npm install ws
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
-
###
|
|
59
|
+
### Legacy opt-in for performance
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
On older Node.js versions, ws can leverage optional binary modules to optimize
|
|
62
|
+
performance.
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
This is a binary addon that improves the performance of certain operations such
|
|
69
|
-
as masking and unmasking the data payload of the WebSocket frames. Prebuilt
|
|
70
|
-
binaries are available for the most popular platforms, so you don't necessarily
|
|
71
|
-
need to have a C++ compiler installed on your machine.
|
|
72
|
-
|
|
73
|
-
To force ws to not use bufferutil, use the
|
|
74
|
-
[`WS_NO_BUFFER_UTIL`](./doc/ws.md#ws_no_buffer_util) environment variable. This
|
|
75
|
-
can be useful to enhance security in systems where a user can put a package in
|
|
76
|
-
the package search path of an application of another user, due to how the
|
|
77
|
-
Node.js resolver algorithm works.
|
|
78
|
-
|
|
79
|
-
#### Legacy opt-in for performance
|
|
80
|
-
|
|
81
|
-
If you are running on an old version of Node.js (prior to v18.14.0), ws also
|
|
82
|
-
supports the [utf-8-validate][] module:
|
|
83
|
-
|
|
84
|
-
```
|
|
85
|
-
npm install --save-optional utf-8-validate
|
|
86
|
-
```
|
|
64
|
+
- `npm install --save-optional bufferutil`: Provides binary polyfills for
|
|
65
|
+
[`http.websocketMask()`][] and [`http.websocketUnmask()`][].
|
|
66
|
+
- `npm install --save-optional utf-8-validate`: Provides a binary polyfill for
|
|
67
|
+
[`buffer.isUtf8()`][].
|
|
87
68
|
|
|
88
|
-
|
|
69
|
+
On newer Node.js versions (v27.0.0-alpha.0.0.0 or later for bufferutil, and
|
|
70
|
+
v18.14.0 or later for utf-8-validate) that feature built-in alternatives, these
|
|
71
|
+
modules are not loaded even if installed.
|
|
89
72
|
|
|
90
|
-
To force ws not to use utf-8-validate
|
|
91
|
-
|
|
73
|
+
To force ws not to use [bufferutil][] or [utf-8-validate][] on the older Node.js
|
|
74
|
+
versions where they would otherwise be loaded, set the
|
|
75
|
+
[`WS_NO_BUFFER_UTIL`](./doc/ws.md#ws_no_buffer_util) and
|
|
76
|
+
[`WS_NO_UTF_8_VALIDATE`](./doc/ws.md#ws_no_utf_8_validate) environment
|
|
77
|
+
variables. This can be useful to enhance security in systems where a user can
|
|
78
|
+
put a package in the package search path of an application of another user, due
|
|
79
|
+
to how the Node.js resolver algorithm works.
|
|
92
80
|
|
|
93
81
|
## API docs
|
|
94
82
|
|
|
@@ -533,6 +521,10 @@ We're using the GitHub [releases][changelog] for changelog entries.
|
|
|
533
521
|
[MIT](LICENSE)
|
|
534
522
|
|
|
535
523
|
[`buffer.isutf8()`]: https://nodejs.org/api/buffer.html#bufferisutf8input
|
|
524
|
+
[`http.websocketMask()`]:
|
|
525
|
+
https://nodejs.org/api/http.html#httpwebsocketmasksource-mask-output-offset-length
|
|
526
|
+
[`http.websocketUnmask()`]:
|
|
527
|
+
https://nodejs.org/api/http.html#httpwebsocketunmaskbuffer-mask
|
|
536
528
|
[bufferutil]: https://github.com/websockets/bufferutil
|
|
537
529
|
[changelog]: https://github.com/websockets/ws/releases
|
|
538
530
|
[client-report]: http://websockets.github.io/ws/autobahn/clients/
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { websocketMask, websocketUnmask } = require('http');
|
|
4
|
+
|
|
3
5
|
const { EMPTY_BUFFER } = require('./constants');
|
|
4
6
|
|
|
5
7
|
const FastBuffer = Buffer[Symbol.species];
|
|
@@ -111,8 +113,17 @@ module.exports = {
|
|
|
111
113
|
unmask: _unmask
|
|
112
114
|
};
|
|
113
115
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
+
if (websocketMask) {
|
|
117
|
+
module.exports.mask = function (source, mask, output, offset, length) {
|
|
118
|
+
if (length < 24) _mask(source, mask, output, offset, length);
|
|
119
|
+
else websocketMask(source, mask, output, offset, length);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
module.exports.unmask = function (buffer, mask) {
|
|
123
|
+
if (buffer.length < 24) _unmask(buffer, mask);
|
|
124
|
+
else websocketUnmask(buffer, mask);
|
|
125
|
+
};
|
|
126
|
+
} /* istanbul ignore else */ else if (!process.env.WS_NO_BUFFER_UTIL) {
|
|
116
127
|
try {
|
|
117
128
|
const bufferUtil = require('bufferutil');
|
|
118
129
|
|
|
@@ -153,7 +153,9 @@ class Sender {
|
|
|
153
153
|
target.writeUIntBE(dataLength, 4, 6);
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
-
if (!options.mask)
|
|
156
|
+
if (!options.mask) {
|
|
157
|
+
return dataLength ? [target, data] : [target];
|
|
158
|
+
}
|
|
157
159
|
|
|
158
160
|
target[1] |= 0x80;
|
|
159
161
|
target[offset - 4] = mask[0];
|
|
@@ -161,6 +163,7 @@ class Sender {
|
|
|
161
163
|
target[offset - 2] = mask[2];
|
|
162
164
|
target[offset - 1] = mask[3];
|
|
163
165
|
|
|
166
|
+
if (!dataLength) return [target];
|
|
164
167
|
if (skipMasking) return [target, data];
|
|
165
168
|
|
|
166
169
|
if (merge) {
|
|
@@ -819,10 +819,15 @@ function initAsClient(websocket, address, protocols, options) {
|
|
|
819
819
|
}
|
|
820
820
|
|
|
821
821
|
if (isIpcUrl) {
|
|
822
|
-
const
|
|
822
|
+
const i = opts.path.indexOf(':');
|
|
823
823
|
|
|
824
|
-
|
|
825
|
-
|
|
824
|
+
if (i === -1) {
|
|
825
|
+
opts.socketPath = opts.path;
|
|
826
|
+
opts.path = '';
|
|
827
|
+
} else {
|
|
828
|
+
opts.socketPath = opts.path.slice(0, i);
|
|
829
|
+
opts.path = opts.path.slice(i + 1);
|
|
830
|
+
}
|
|
826
831
|
}
|
|
827
832
|
|
|
828
833
|
let req;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oddessentials/agent-guild",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.30.0",
|
|
4
4
|
"description": "Launch and watch AI coding-assistant terminal sessions from one local web page. A bundled session manager owns the terminals so the page can close and reconnect.",
|
|
5
5
|
"mcpName": "ai.oddessentials/agent-guild",
|
|
6
6
|
"license": "AGPL-3.0-only",
|
|
@@ -50,17 +50,20 @@
|
|
|
50
50
|
"test": "node --test --test-concurrency=1 \"tests/*.test.mjs\""
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
+
"@jsquash/jpeg": "1.6.0",
|
|
54
|
+
"@jsquash/png": "3.1.1",
|
|
55
|
+
"@jsquash/webp": "1.5.0",
|
|
53
56
|
"@noble/hashes": "2.4.0",
|
|
54
57
|
"@xterm/addon-fit": "^0.11.0",
|
|
55
58
|
"@xterm/addon-serialize": "^0.14.0",
|
|
56
59
|
"@xterm/addon-web-links": "^0.12.0",
|
|
57
60
|
"@xterm/headless": "^6.0.0",
|
|
58
61
|
"@xterm/xterm": "^6.0.0",
|
|
59
|
-
"markdown-it": "^15.0.
|
|
62
|
+
"markdown-it": "^15.0.3",
|
|
60
63
|
"node-pty": "^1.2.0-beta.15",
|
|
61
64
|
"qrcode-generator": "2.0.4",
|
|
62
65
|
"smol-toml": "1.9.1",
|
|
63
|
-
"ws": "^8.22.
|
|
66
|
+
"ws": "^8.22.1"
|
|
64
67
|
},
|
|
65
68
|
"bundleDependencies": true,
|
|
66
69
|
"publishConfig": {
|
|
@@ -74,6 +77,6 @@
|
|
|
74
77
|
"@gltf-transform/extensions": "4.5.1",
|
|
75
78
|
"@gltf-transform/functions": "4.5.1",
|
|
76
79
|
"esbuild": "0.28.2",
|
|
77
|
-
"three": "0.
|
|
80
|
+
"three": "0.187.0"
|
|
78
81
|
}
|
|
79
82
|
}
|
package/src/manager/config.mjs
CHANGED
|
@@ -56,6 +56,8 @@ export const paths = {
|
|
|
56
56
|
get multiplexers() { return path.join(dataDir(), 'multiplexers.json'); },
|
|
57
57
|
/** The Yard's art packs, one folder per content id. */
|
|
58
58
|
get yardArt() { return path.join(dataDir(), 'yard-art'); },
|
|
59
|
+
get yardWorlds() { return path.join(dataDir(), 'yard-worlds'); },
|
|
60
|
+
get yardPieces() { return path.join(dataDir(), 'yard-pieces'); },
|
|
59
61
|
};
|
|
60
62
|
|
|
61
63
|
function writePrivate(file, contents) {
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
+
import { readManifest } from './yard-pieces.mjs';
|
|
4
5
|
|
|
5
6
|
const failure = (status, code, message) => Object.assign(new Error(message), { status, code });
|
|
7
|
+
const PIECE_SCAN = 500;
|
|
6
8
|
const byName = (a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: 'base', numeric: true }) || (a.name < b.name ? -1 : 1);
|
|
7
9
|
|
|
8
10
|
export function folderSegments(dir, paths = path) {
|
|
@@ -30,7 +32,7 @@ export function folderNameProblem(name, platform = process.platform) {
|
|
|
30
32
|
return null;
|
|
31
33
|
}
|
|
32
34
|
|
|
33
|
-
export function createFolderBrowser({ platform = process.platform, home = os.homedir(), limit = 2000, driveTimeoutMs = 500, fsp = fs.promises } = {}) {
|
|
35
|
+
export function createFolderBrowser({ platform = process.platform, home = os.homedir(), limit = 2000, driveTimeoutMs = 500, fsp = fs.promises, workshop = null } = {}) {
|
|
34
36
|
const paths = platform === 'win32' ? path.win32 : path.posix;
|
|
35
37
|
|
|
36
38
|
async function roots() {
|
|
@@ -73,6 +75,37 @@ export function createFolderBrowser({ platform = process.platform, home = os.hom
|
|
|
73
75
|
return folders.filter(Boolean).sort(byName);
|
|
74
76
|
}
|
|
75
77
|
|
|
78
|
+
async function filesIn(dir, extension) {
|
|
79
|
+
const listed = await fsp.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
80
|
+
return listed.filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith(extension))
|
|
81
|
+
.map((entry) => ({ name: entry.name, path: paths.join(dir, entry.name) })).sort(byName);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function pieceIn(dir, kind) {
|
|
85
|
+
const file = paths.join(dir, 'piece.json');
|
|
86
|
+
try {
|
|
87
|
+
const stat = await fsp.lstat(file);
|
|
88
|
+
if (!stat.isFile() || stat.size > 16 * 1024) return null;
|
|
89
|
+
const bytes = await fsp.readFile(file), folder = paths.basename(dir);
|
|
90
|
+
try {
|
|
91
|
+
const { title } = readManifest(bytes, kind);
|
|
92
|
+
return { title: title || folder, folder, path: dir };
|
|
93
|
+
} catch (err) {
|
|
94
|
+
const meta = err.newer ? JSON.parse(bytes.toString('utf8')) : null;
|
|
95
|
+
if (meta?.kind !== kind) return null;
|
|
96
|
+
const title = typeof meta.title === 'string' && meta.title.length <= 128 && !/\p{Cc}/u.test(meta.title) ? meta.title.trim() : '';
|
|
97
|
+
return { title: title || folder, folder, path: dir, problem: err.message };
|
|
98
|
+
}
|
|
99
|
+
} catch {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function piecesIn(dir, folders, kind) {
|
|
105
|
+
const found = await Promise.all([dir, ...folders.slice(0, PIECE_SCAN).map((entry) => entry.path)].map((folder) => pieceIn(folder, kind)));
|
|
106
|
+
return found.filter(Boolean).sort((a, b) => a.title.localeCompare(b.title, undefined, { sensitivity: 'base', numeric: true }) || (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
|
|
107
|
+
}
|
|
108
|
+
|
|
76
109
|
const browser = {
|
|
77
110
|
async create(rawParent, rawName) {
|
|
78
111
|
if (typeof rawParent !== 'string' || !rawParent.trim() || rawParent.includes('\0')) throw failure(400, 'bad_path', 'The folder must be a path.');
|
|
@@ -97,7 +130,7 @@ export function createFolderBrowser({ platform = process.platform, home = os.hom
|
|
|
97
130
|
}
|
|
98
131
|
return browser.list(dir);
|
|
99
132
|
},
|
|
100
|
-
async list(raw) {
|
|
133
|
+
async list(raw, { extension = null, pieces = null } = {}) {
|
|
101
134
|
if (raw !== undefined && raw !== null && (typeof raw !== 'string' || raw.includes('\0'))) {
|
|
102
135
|
throw failure(400, 'bad_path', 'The folder must be a path.');
|
|
103
136
|
}
|
|
@@ -105,8 +138,9 @@ export function createFolderBrowser({ platform = process.platform, home = os.hom
|
|
|
105
138
|
if (asked === '~' || asked.startsWith('~/') || asked.startsWith('~\\')) asked = paths.join(home, asked.slice(1));
|
|
106
139
|
const wanted = asked ? paths.resolve(asked) : home;
|
|
107
140
|
const dir = await nearestFolder(wanted);
|
|
108
|
-
const [folders, drives] = await Promise.all([entries(dir), roots()]);
|
|
141
|
+
const [folders, drives, files] = await Promise.all([entries(dir), roots(), extension ? filesIn(dir, extension) : null]);
|
|
109
142
|
const parent = paths.dirname(dir);
|
|
143
|
+
const [found, steam] = pieces ? await Promise.all([piecesIn(dir, folders, pieces), workshop ? workshop() : null]) : [null, null];
|
|
110
144
|
return {
|
|
111
145
|
path: dir,
|
|
112
146
|
parent: parent === dir ? null : parent,
|
|
@@ -116,6 +150,8 @@ export function createFolderBrowser({ platform = process.platform, home = os.hom
|
|
|
116
150
|
entries: folders.slice(0, limit),
|
|
117
151
|
truncated: folders.length > limit,
|
|
118
152
|
note: dir === wanted ? null : wanted,
|
|
153
|
+
...(files ? { files: files.slice(0, limit) } : {}),
|
|
154
|
+
...(found ? { pieces: found, workshop: steam } : {}),
|
|
119
155
|
};
|
|
120
156
|
},
|
|
121
157
|
};
|
package/src/manager/main.mjs
CHANGED
|
@@ -17,6 +17,8 @@ import { Changelog } from './changelog.mjs';
|
|
|
17
17
|
import { GitHub } from './github.mjs';
|
|
18
18
|
import { createManagerServer } from './server.mjs';
|
|
19
19
|
import { YardArt } from './yard-art.mjs';
|
|
20
|
+
import { YardWorlds } from './yard-worlds.mjs';
|
|
21
|
+
import { YardPieces } from './yard-pieces.mjs';
|
|
20
22
|
import { loadPty } from './pty.mjs';
|
|
21
23
|
import { createNotesStore } from './notes.mjs';
|
|
22
24
|
import { createCardNames } from './card-names.mjs';
|
|
@@ -186,6 +188,8 @@ export async function startManager({ port = resolvePort(), host = DEFAULT_HOST,
|
|
|
186
188
|
notes: createNotesStore(paths.notes),
|
|
187
189
|
launcher: launcherPath(),
|
|
188
190
|
yardArt: new YardArt({ webDir, dir: paths.yardArt }),
|
|
191
|
+
yardWorlds: new YardWorlds({ dir: paths.yardWorlds }),
|
|
192
|
+
yardPieces: new YardPieces({ dir: paths.yardPieces }),
|
|
189
193
|
mcpConnect,
|
|
190
194
|
onShutdownRequest: ({ restart = false } = {}) => shutdown('requested via API', { restart }).then((code) => process.exit(code)),
|
|
191
195
|
});
|
package/src/manager/server.mjs
CHANGED
|
@@ -19,6 +19,9 @@ import { createOptionalCards } from './optional-cards.mjs';
|
|
|
19
19
|
import { MODES as AUTOSTART_MODES } from './autostart.mjs';
|
|
20
20
|
import { ptyRestartProblem } from './pty.mjs';
|
|
21
21
|
import { YardArt } from './yard-art.mjs';
|
|
22
|
+
import { YardWorlds } from './yard-worlds.mjs';
|
|
23
|
+
import { YardPieces } from './yard-pieces.mjs';
|
|
24
|
+
import { createWorkshopFinder } from './steam-workshop.mjs';
|
|
22
25
|
import { spokenText } from './spoken-text.mjs';
|
|
23
26
|
import { MCP_ACTIONS } from './mcp-connect.mjs';
|
|
24
27
|
|
|
@@ -69,8 +72,8 @@ const SECURITY_HEADERS = {
|
|
|
69
72
|
'Referrer-Policy': 'no-referrer',
|
|
70
73
|
'Cross-Origin-Resource-Policy': 'same-origin',
|
|
71
74
|
'Content-Security-Policy':
|
|
72
|
-
"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://i.ytimg.com; " +
|
|
73
|
-
"connect-src 'self' ws://127.0.0.1:* ws://localhost:*; frame-src https://www.youtube-nocookie.com; " +
|
|
75
|
+
"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https://i.ytimg.com; " +
|
|
76
|
+
"connect-src 'self' blob: ws://127.0.0.1:* ws://localhost:*; frame-src https://www.youtube-nocookie.com; " +
|
|
74
77
|
"frame-ancestors vscode-webview: vscode-file:; base-uri 'none'; form-action 'none'",
|
|
75
78
|
};
|
|
76
79
|
|
|
@@ -176,7 +179,7 @@ export function createManagerServer({
|
|
|
176
179
|
notes = createNotesStore(),
|
|
177
180
|
deviceSignIn = new DeviceSignIn({ token }),
|
|
178
181
|
folderOpener = createFolderOpener({ resolveCwd: (cwd) => manager.resolveCwd(cwd) }),
|
|
179
|
-
folderBrowser = createFolderBrowser(),
|
|
182
|
+
folderBrowser = createFolderBrowser({ workshop: createWorkshopFinder() }),
|
|
180
183
|
/** The double-click launcher file for this platform, or null when the package carries none. */
|
|
181
184
|
launcher = null,
|
|
182
185
|
/** @type {(opts: { restart: boolean }) => void} */
|
|
@@ -185,6 +188,8 @@ export function createManagerServer({
|
|
|
185
188
|
nextManagerProblem = ptyRestartProblem,
|
|
186
189
|
/** The Yard's art; without a data folder, only a checkout's. */
|
|
187
190
|
yardArt = new YardArt({ webDir }),
|
|
191
|
+
yardWorlds = new YardWorlds(),
|
|
192
|
+
yardPieces = new YardPieces(),
|
|
188
193
|
mcpConnect = null,
|
|
189
194
|
heartbeatMs = 30000,
|
|
190
195
|
}) {
|
|
@@ -292,6 +297,18 @@ export function createManagerServer({
|
|
|
292
297
|
throw new HttpError(400, 'malformed path', 'bad_path');
|
|
293
298
|
}
|
|
294
299
|
if (rel.includes('\0')) throw new HttpError(404, 'not found', 'not_found');
|
|
300
|
+
if (rel.startsWith('yard/worlds/')) {
|
|
301
|
+
const [skin, name, ...rest] = rel.slice('yard/worlds/'.length).split('/');
|
|
302
|
+
const world = rest.length ? null : yardWorlds.file(skin, name);
|
|
303
|
+
if (!world) throw new HttpError(404, 'not found', 'not_found');
|
|
304
|
+
return serveFile(req, res, world);
|
|
305
|
+
}
|
|
306
|
+
if (rel.startsWith('yard/pieces/')) {
|
|
307
|
+
const [skin, slot, name, ...rest] = rel.slice('yard/pieces/'.length).split('/');
|
|
308
|
+
const piece = rest.length ? null : yardPieces.file(skin, slot, name);
|
|
309
|
+
if (!piece) throw new HttpError(404, 'not found', 'not_found');
|
|
310
|
+
return serveFile(req, res, piece);
|
|
311
|
+
}
|
|
295
312
|
let root = webDir;
|
|
296
313
|
if (rel.startsWith('yard/assets/')) {
|
|
297
314
|
root = yardArt.assetsDir();
|
|
@@ -408,6 +425,14 @@ export function createManagerServer({
|
|
|
408
425
|
if (route === '/yard-art' && method === 'GET') return sendJson(res, 200, { yardArt: yardArt.snapshot() });
|
|
409
426
|
if (route === '/yard-art/download' && method === 'POST') return sendJson(res, 202, { yardArt: yardArt.start() });
|
|
410
427
|
if (route === '/yard-art/cancel' && method === 'POST') return sendJson(res, 200, { yardArt: yardArt.cancel() });
|
|
428
|
+
if (route === '/yard-worlds' && method === 'GET') return sendJson(res, 200, { yardWorlds: yardWorlds.snapshot() });
|
|
429
|
+
const yardWorld = route.match(/^\/yard-worlds\/([a-z]+)$/);
|
|
430
|
+
if (yardWorld && method === 'PUT') return sendJson(res, 200, { yardWorlds: await yardWorlds.replace(yardWorld[1], (await readJsonBody(req)).path) });
|
|
431
|
+
if (yardWorld && method === 'DELETE') return sendJson(res, 200, { yardWorlds: await yardWorlds.restore(yardWorld[1]) });
|
|
432
|
+
if (route === '/yard-pieces' && method === 'GET') return sendJson(res, 200, { yardPieces: yardPieces.snapshot() });
|
|
433
|
+
const yardPiece = route.match(/^\/yard-pieces\/([a-z]+)\/([a-z]+)$/);
|
|
434
|
+
if (yardPiece && method === 'PUT') return sendJson(res, 200, { yardPieces: await yardPieces.replace(yardPiece[1], yardPiece[2], (await readJsonBody(req)).path) });
|
|
435
|
+
if (yardPiece && method === 'DELETE') return sendJson(res, 200, { yardPieces: await yardPieces.restore(yardPiece[1], yardPiece[2]) });
|
|
411
436
|
if (route === '/yard/fireworks' && method === 'POST') {
|
|
412
437
|
broadcast({ type: 'yard.fireworks' });
|
|
413
438
|
return sendJson(res, 200, { ok: true, pages: [...eventClients].filter((ws) => !ws.revoked && ws.readyState === ws.OPEN).length });
|
|
@@ -435,7 +460,7 @@ export function createManagerServer({
|
|
|
435
460
|
return sendJson(res, 200, { ok: true });
|
|
436
461
|
}
|
|
437
462
|
if (route === '/folders' && method === 'GET') {
|
|
438
|
-
return sendJson(res, 200, await folderBrowser.list(url.searchParams.get('path') ?? undefined));
|
|
463
|
+
return sendJson(res, 200, await folderBrowser.list(url.searchParams.get('path') ?? undefined, { extension: url.searchParams.get('files') === 'agworld' ? '.agworld' : null, pieces: ['character', 'companion'].includes(url.searchParams.get('pieces')) ? url.searchParams.get('pieces') : null }));
|
|
439
464
|
}
|
|
440
465
|
if (route === '/folders' && method === 'POST') {
|
|
441
466
|
const { path: parent, name } = await readJsonBody(req);
|
|
@@ -771,10 +796,12 @@ export function createManagerServer({
|
|
|
771
796
|
remoteAccess?.on('updated', () => broadcast({ type: 'remote-access.updated' }));
|
|
772
797
|
deviceSignIn.on('updated', () => broadcast({ type: 'sign-in.updated' }));
|
|
773
798
|
yardArt.on('updated', (status) => broadcast({ type: 'yard-art.updated', yardArt: status }));
|
|
799
|
+
yardWorlds.on('updated', (worlds) => broadcast({ type: 'yard-worlds.updated', yardWorlds: worlds }));
|
|
800
|
+
yardPieces.on('updated', (pieces) => broadcast({ type: 'yard-pieces.updated', yardPieces: pieces }));
|
|
774
801
|
|
|
775
802
|
function handleEvents(ws, req) {
|
|
776
803
|
eventClients.add(ws);
|
|
777
|
-
const hello = { type: 'hello', version, pid: process.pid, platform: process.platform, startedAt, launcher, folderOpener: folderOpenerFor(req), remoteAccess: remoteAccess ? { available: true } : null, upgrade: upgradeInfo(), yardArt: yardArt.snapshot(), sessions: manager.list(), heartbeat: true };
|
|
804
|
+
const hello = { type: 'hello', version, pid: process.pid, platform: process.platform, startedAt, launcher, folderOpener: folderOpenerFor(req), remoteAccess: remoteAccess ? { available: true } : null, upgrade: upgradeInfo(), yardArt: yardArt.snapshot(), yardWorlds: yardWorlds.snapshot(), yardPieces: yardPieces.snapshot(), sessions: manager.list(), heartbeat: true };
|
|
778
805
|
const notesHello = notes.helloRevision();
|
|
779
806
|
if (notesHello.known) hello.notesRevision = notesHello.revision;
|
|
780
807
|
else hello.notesUnreadable = true;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
|
|
6
|
+
export const WORLD_APP = '5462970';
|
|
7
|
+
|
|
8
|
+
export function parseVdf(text) {
|
|
9
|
+
const tokens = text.match(/"(?:[^"\\]|\\.)*"|[{}]/g) ?? [];
|
|
10
|
+
const root = {}, stack = [root];
|
|
11
|
+
let key = null;
|
|
12
|
+
for (const token of tokens) {
|
|
13
|
+
const top = stack[stack.length - 1];
|
|
14
|
+
if (token === '{') {
|
|
15
|
+
if (key === null) return root;
|
|
16
|
+
stack.push(top[key.toLowerCase()] = {});
|
|
17
|
+
key = null;
|
|
18
|
+
} else if (token === '}') {
|
|
19
|
+
if (stack.length > 1) stack.pop();
|
|
20
|
+
key = null;
|
|
21
|
+
} else {
|
|
22
|
+
const value = token.slice(1, -1).replace(/\\(.)/g, '$1');
|
|
23
|
+
if (key === null) key = value;
|
|
24
|
+
else { top[key.toLowerCase()] = value; key = null; }
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return root;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function windowsSteam() {
|
|
31
|
+
return new Promise((resolve) => {
|
|
32
|
+
execFile('reg', ['query', 'HKCU\\Software\\Valve\\Steam', '/v', 'SteamPath'], { timeout: 5000, windowsHide: true }, (err, stdout) => {
|
|
33
|
+
resolve(err ? null : String(stdout).match(/SteamPath\s+REG_\w+\s+(.+)/)?.[1]?.trim() || null);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function steamRoots({ platform = process.platform, home = os.homedir(), registry = windowsSteam } = {}) {
|
|
39
|
+
if (platform === 'win32') return registry().then((root) => (root ? [root] : []));
|
|
40
|
+
if (platform === 'darwin') return Promise.resolve([path.join(home, 'Library', 'Application Support', 'Steam')]);
|
|
41
|
+
return Promise.resolve([path.join(home, '.local', 'share', 'Steam'), path.join(home, '.steam', 'steam'), path.join(home, '.var', 'app', 'com.valvesoftware.Steam', '.local', 'share', 'Steam')]);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function worldLibrary({ roots = steamRoots(), fsp = fs.promises } = {}) {
|
|
45
|
+
for (const root of await roots) {
|
|
46
|
+
let folders;
|
|
47
|
+
try { folders = parseVdf(await fsp.readFile(path.join(root, 'steamapps', 'libraryfolders.vdf'), 'utf8')).libraryfolders; } catch { continue; }
|
|
48
|
+
for (const entry of Object.values(folders ?? {})) {
|
|
49
|
+
if (typeof entry === 'object' && typeof entry.path === 'string' && entry.apps && Object.hasOwn(entry.apps, WORLD_APP)) return path.normalize(entry.path);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function createWorkshopFinder({ library = () => worldLibrary(), fsp = fs.promises } = {}) {
|
|
56
|
+
let found = null;
|
|
57
|
+
return async () => {
|
|
58
|
+
found ??= library().catch(() => null);
|
|
59
|
+
const root = await found;
|
|
60
|
+
if (!root) { found = null; return null; }
|
|
61
|
+
const folder = path.join(root, 'steamapps', 'workshop', 'content', WORLD_APP);
|
|
62
|
+
return (await fsp.stat(folder).then((stat) => stat.isDirectory(), () => false)) ? folder : null;
|
|
63
|
+
};
|
|
64
|
+
}
|
package/src/manager/yard-art.mjs
CHANGED
|
@@ -283,7 +283,7 @@ export class YardArt extends EventEmitter {
|
|
|
283
283
|
}
|
|
284
284
|
|
|
285
285
|
// Windows can hold a just-written folder briefly (indexing, antivirus).
|
|
286
|
-
async function renameRetrying(from, to) {
|
|
286
|
+
export async function renameRetrying(from, to) {
|
|
287
287
|
for (let attempt = 0; ; attempt++) {
|
|
288
288
|
try { return await fsp.rename(from, to); } catch (err) {
|
|
289
289
|
if (attempt >= 20 || !['EPERM', 'EBUSY', 'EACCES'].includes(err.code)) throw err;
|