ocuclaw 0.1.0 → 1.3.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.
Files changed (59) hide show
  1. package/README.md +63 -8
  2. package/dist/config/runtime-config.js +81 -3
  3. package/dist/domain/activity-status-adapter.js +138 -605
  4. package/dist/domain/activity-status-arbiter.js +109 -0
  5. package/dist/domain/activity-status-labels.js +906 -0
  6. package/dist/domain/code-span-regions.js +103 -0
  7. package/dist/domain/conversation-state.js +14 -1
  8. package/dist/domain/debug-store.js +41 -184
  9. package/dist/domain/glasses-ui-content-summary.js +62 -0
  10. package/dist/domain/glasses-ui-system-prompt.js +28 -0
  11. package/dist/domain/message-emoji-allowlist.js +16 -0
  12. package/dist/domain/message-emoji-filter.js +33 -55
  13. package/dist/domain/neural-emoji-reactor-system-prompt.js +43 -0
  14. package/dist/domain/neural-emoji-reactor-tag-config.js +56 -0
  15. package/dist/domain/neural-pace-modulator-system-prompt.js +32 -0
  16. package/dist/domain/neural-pace-modulator-tag-config.js +51 -0
  17. package/dist/domain/tagged-span-parser.js +121 -0
  18. package/dist/domain/tagged-span-strip.js +38 -0
  19. package/dist/even-ai/even-ai-endpoint.js +91 -0
  20. package/dist/even-ai/even-ai-run-waiter.js +14 -0
  21. package/dist/even-ai/even-ai-settings-store.js +14 -0
  22. package/dist/gateway/gateway-bridge.js +14 -2
  23. package/dist/gateway/gateway-timing-ledger.js +457 -0
  24. package/dist/gateway/openclaw-client.js +462 -38
  25. package/dist/index.js +28 -1
  26. package/dist/runtime/downstream-handler.js +909 -68
  27. package/dist/runtime/downstream-server.js +1004 -512
  28. package/dist/runtime/ocuclaw-settings-store.js +74 -31
  29. package/dist/runtime/plugin-update-service.js +216 -0
  30. package/dist/runtime/protocol-adapter.js +9 -0
  31. package/dist/runtime/provider-usage-select.js +168 -0
  32. package/dist/runtime/relay-client-nudge-controller.js +553 -0
  33. package/dist/runtime/relay-core.js +1357 -210
  34. package/dist/runtime/relay-health-monitor.js +172 -0
  35. package/dist/runtime/relay-operation-registry.js +263 -0
  36. package/dist/runtime/relay-service.js +201 -1
  37. package/dist/runtime/relay-worker-approval-replay-cache.js +68 -0
  38. package/dist/runtime/relay-worker-entry.js +32 -0
  39. package/dist/runtime/relay-worker-health.js +272 -0
  40. package/dist/runtime/relay-worker-protocol.js +285 -0
  41. package/dist/runtime/relay-worker-queue.js +202 -0
  42. package/dist/runtime/relay-worker-supervisor.js +1081 -0
  43. package/dist/runtime/relay-worker-transport.js +1051 -0
  44. package/dist/runtime/session-context-service.js +189 -0
  45. package/dist/runtime/session-service.js +656 -38
  46. package/dist/runtime/upstream-runtime.js +1167 -60
  47. package/dist/tools/device-info-tool.js +242 -0
  48. package/dist/tools/glasses-ui-cron.js +427 -0
  49. package/dist/tools/glasses-ui-descriptors.js +261 -0
  50. package/dist/tools/glasses-ui-limits.js +21 -0
  51. package/dist/tools/glasses-ui-paint-floor.js +99 -0
  52. package/dist/tools/glasses-ui-recipes.js +746 -0
  53. package/dist/tools/glasses-ui-surfaces.js +278 -0
  54. package/dist/tools/glasses-ui-template.js +182 -0
  55. package/dist/tools/glasses-ui-tool.js +1147 -0
  56. package/dist/tools/session-title-tool.js +209 -0
  57. package/dist/version.js +2 -0
  58. package/openclaw.plugin.json +163 -15
  59. package/package.json +12 -4
@@ -0,0 +1,209 @@
1
+ export const SESSION_TITLE_LIMITS = {
2
+ titleMax: 55, // 64 SDK list-item cap minus ~9 chars headroom for "<time> - " prefix
3
+ };
4
+
5
+ export const sessionTitleParametersSchema = {
6
+ type: "object",
7
+ required: ["title"],
8
+ properties: {
9
+ title: {
10
+ type: "string",
11
+ minLength: 1,
12
+ maxLength: SESSION_TITLE_LIMITS.titleMax,
13
+ },
14
+ },
15
+ additionalProperties: false,
16
+ };
17
+
18
+ export function validateSessionTitleInput(input) {
19
+ if (!input || typeof input !== "object") {
20
+ return { ok: false, code: "missing_field", message: "input must be an object with a title field" };
21
+ }
22
+ if (!("title" in input)) {
23
+ return { ok: false, code: "missing_field", message: "title field is required" };
24
+ }
25
+ if (typeof input.title !== "string") {
26
+ return { ok: false, code: "invalid_type", message: "title must be a string" };
27
+ }
28
+ const trimmed = input.title.trim();
29
+ if (trimmed.length === 0) {
30
+ return { ok: false, code: "title_empty", message: "title cannot be empty or whitespace-only" };
31
+ }
32
+ if (trimmed.length > SESSION_TITLE_LIMITS.titleMax) {
33
+ return {
34
+ ok: false,
35
+ code: "title_too_long",
36
+ message: `title is ${trimmed.length} chars; max ${SESSION_TITLE_LIMITS.titleMax}`,
37
+ };
38
+ }
39
+ return { ok: true, spec: { title: trimmed } };
40
+ }
41
+
42
+ const EVEN_AI_DEDICATED_KEY_PREFIX = "ocuclaw:even-ai";
43
+
44
+ function isEvenAiDedicatedKey(sessionKey) {
45
+ if (typeof sessionKey !== "string") return false;
46
+ const normalized = sessionKey.trim().toLowerCase();
47
+ return (
48
+ normalized === EVEN_AI_DEDICATED_KEY_PREFIX ||
49
+ normalized.startsWith(`${EVEN_AI_DEDICATED_KEY_PREFIX}:`)
50
+ );
51
+ }
52
+
53
+ function gateReason(sessionKey, deps) {
54
+ if (typeof deps.isSessionUserLocked === "function" && deps.isSessionUserLocked(sessionKey)) {
55
+ return "session_user_locked";
56
+ }
57
+ if (
58
+ typeof deps.isNeuralSessionNamesEnabled === "function" &&
59
+ !deps.isNeuralSessionNamesEnabled(sessionKey)
60
+ ) {
61
+ return "feature_disabled";
62
+ }
63
+ // Hard guard against the agent titling a session before the user has sent
64
+ // their first real message. The synthetic session-starter prompt the agent
65
+ // sees on /new + the proactive prompt-hook nudge can otherwise tempt the
66
+ // model into titling from a non-user input (observed: titles like "New
67
+ // session"). Real user sends are recorded via dispatchOcuClawUserSend ->
68
+ // sessionService.recordFirstSentUserMessage; the synthetic starter never is.
69
+ if (
70
+ typeof deps.hasRecordedUserMessage === "function" &&
71
+ !deps.hasRecordedUserMessage(sessionKey)
72
+ ) {
73
+ return "no_user_message_yet";
74
+ }
75
+ return null;
76
+ }
77
+
78
+ export function createSessionTitleToolHandler(deps) {
79
+ async function setSessionTitle(params) {
80
+ const validation = validateSessionTitleInput(params);
81
+ if (!validation.ok) {
82
+ const err = new Error(`${validation.code}: ${validation.message}`);
83
+ err.code = validation.code;
84
+ throw err;
85
+ }
86
+ const sessionKey = deps.peekSessionKey();
87
+ if (typeof sessionKey !== "string" || !sessionKey.trim()) {
88
+ const err = new Error(
89
+ "no_active_session: no OcuClaw session is currently active",
90
+ );
91
+ err.code = "no_active_session";
92
+ throw err;
93
+ }
94
+ if (isEvenAiDedicatedKey(sessionKey)) {
95
+ const err = new Error(
96
+ "session_not_renamable: the persistent EvenAI session cannot be retitled",
97
+ );
98
+ err.code = "session_not_renamable";
99
+ throw err;
100
+ }
101
+ const blockedReason = gateReason(sessionKey, deps);
102
+ if (blockedReason) {
103
+ const err = new Error(`${blockedReason}: tool unavailable for this session`);
104
+ err.code = blockedReason;
105
+ throw err;
106
+ }
107
+ const result = await deps.setSessionTitle(sessionKey, validation.spec.title);
108
+ if (result && result.ok === false) {
109
+ const err = new Error(`${result.code}: ${result.message || "set rejected"}`);
110
+ err.code = result.code;
111
+ throw err;
112
+ }
113
+ return result || { ok: true };
114
+ }
115
+ return { setSessionTitle };
116
+ }
117
+
118
+ const TOOL_DESCRIPTION = [
119
+ "Set a short title for the current chat session (shown in the user's glasses session list).",
120
+ "",
121
+ "When: first user turn that names any concrete topic — call eagerly. Later, only when the topic has clearly shifted. Skip greetings, acknowledgments, and no-topic messages.",
122
+ "",
123
+ "Title: 2-5 word noun phrase, ≤55 chars, no trailing punctuation or surrounding quotes.",
124
+ "",
125
+ "Do not announce the rename unless the user explicitly asked to retitle.",
126
+ ].join("\n");
127
+
128
+ export function createSessionTitlePromptHook(deps) {
129
+ return function sessionTitleBeforePromptBuild(_event, ctx) {
130
+ const sessionKey = ctx && typeof ctx.sessionKey === "string" ? ctx.sessionKey : null;
131
+ if (!sessionKey) return undefined;
132
+ const title = typeof deps.getSessionTitle === "function" ? deps.getSessionTitle(sessionKey) : null;
133
+ const userLocked =
134
+ typeof deps.isSessionUserLocked === "function" && deps.isSessionUserLocked(sessionKey);
135
+ const featureEnabled =
136
+ typeof deps.isNeuralSessionNamesEnabled === "function"
137
+ ? deps.isNeuralSessionNamesEnabled(sessionKey)
138
+ : true;
139
+
140
+ const fragments = [];
141
+ if (title) {
142
+ fragments.push(`Current session title: "${title}".`);
143
+ }
144
+ if (userLocked) {
145
+ fragments.push(
146
+ "The user has set a custom title; do not call set_session_title.",
147
+ );
148
+ } else if (!featureEnabled) {
149
+ fragments.push(
150
+ "Neural Topic Distiller is disabled; do not call set_session_title.",
151
+ );
152
+ } else if (title) {
153
+ fragments.push(
154
+ "Call set_session_title only if the topic has clearly shifted.",
155
+ );
156
+ } else {
157
+ const hasUserMessage =
158
+ typeof deps.hasRecordedUserMessage !== "function" ||
159
+ deps.hasRecordedUserMessage(sessionKey);
160
+ if (hasUserMessage) {
161
+ fragments.push(
162
+ "No session title is set yet. Call set_session_title now if the user's latest message names any concrete topic.",
163
+ );
164
+ }
165
+ }
166
+
167
+ if (fragments.length === 0) return undefined;
168
+ return { appendSystemContext: fragments.join(" ") };
169
+ };
170
+ }
171
+
172
+ export function registerSessionTitleTool(api, service) {
173
+ if (!api || typeof api.registerTool !== "function") {
174
+ throw new Error("registerSessionTitleTool requires api.registerTool");
175
+ }
176
+ if (!service) {
177
+ throw new Error("registerSessionTitleTool requires the OcuClaw relay service");
178
+ }
179
+
180
+ const handler = createSessionTitleToolHandler({
181
+ peekSessionKey: () => service.peekSessionKey(),
182
+ setSessionTitle: (sessionKey, title, opts) => service.setSessionTitle(sessionKey, title, opts),
183
+ isSessionUserLocked: (sessionKey) => service.isSessionUserLocked(sessionKey),
184
+ isNeuralSessionNamesEnabled: (sessionKey) => service.isNeuralSessionNamesEnabled(sessionKey),
185
+ hasRecordedUserMessage: (sessionKey) => service.hasRecordedUserMessage(sessionKey),
186
+ });
187
+
188
+ api.registerTool({
189
+ name: "set_session_title",
190
+ description: TOOL_DESCRIPTION,
191
+ parameters: sessionTitleParametersSchema,
192
+ async execute(_toolCallId, params) {
193
+ await handler.setSessionTitle(params);
194
+ return {
195
+ content: [{ type: "text", text: JSON.stringify({ status: "accepted" }) }],
196
+ };
197
+ },
198
+ });
199
+
200
+ if (typeof api.on === "function") {
201
+ const hook = createSessionTitlePromptHook({
202
+ getSessionTitle: (sessionKey) => service.getSessionTitle(sessionKey),
203
+ isSessionUserLocked: (sessionKey) => service.isSessionUserLocked(sessionKey),
204
+ isNeuralSessionNamesEnabled: (sessionKey) => service.isNeuralSessionNamesEnabled(sessionKey),
205
+ hasRecordedUserMessage: (sessionKey) => service.hasRecordedUserMessage(sessionKey),
206
+ });
207
+ api.on("before_prompt_build", hook);
208
+ }
209
+ }
@@ -0,0 +1,2 @@
1
+ export const PLUGIN_VERSION = "1.3.0";
2
+ export const REQUIRES_CLIENT_VERSION = "1.3.0";
@@ -1,34 +1,144 @@
1
1
  {
2
2
  "id": "ocuclaw",
3
+ "activation": {
4
+ "onStartup": true
5
+ },
3
6
  "name": "OcuClaw",
4
- "description": "OcuClaw for Even G2 smart glasses, powered by OpenClaw.",
7
+ "description": "OcuClaw for Even Realities G2 smart glasses.",
8
+ "contracts": {
9
+ "tools": [
10
+ "render_glasses_ui",
11
+ "set_session_title",
12
+ "get_evenrealities_device_info"
13
+ ]
14
+ },
15
+ "skills": ["skills/glasses-ui"],
16
+ "hooks": {
17
+ "allowConversationAccess": true
18
+ },
19
+ "uiHints": {
20
+ "relayToken": {
21
+ "label": "Relay token",
22
+ "help": "Shared secret. Must match the relay server token set in the OcuClaw app inside Even Hub.",
23
+ "placeholder": "user-defined password",
24
+ "sensitive": true
25
+ },
26
+ "sonioxApiKey": {
27
+ "label": "Soniox API key",
28
+ "help": "Optional. Enables Soniox speech-to-text for voice input.",
29
+ "sensitive": true
30
+ },
31
+ "evenAiEnabled": {
32
+ "label": "Enable Even AI",
33
+ "help": "Routes Even AI agent requests through OcuClaw. Requires evenAiToken when enabled."
34
+ },
35
+ "evenAiToken": {
36
+ "label": "Even AI token",
37
+ "help": "Shared secret. Must match the password set in the Even AI Agent Configure section of the Even Realities app. Required when evenAiEnabled is true.",
38
+ "placeholder": "user-defined password",
39
+ "sensitive": true
40
+ },
41
+ "evenAiSystemPrompt": {
42
+ "label": "Even AI system prompt",
43
+ "help": "Optional extra system prompt appended to Even AI runs only.",
44
+ "advanced": true
45
+ },
46
+ "evenAiRoutingMode": {
47
+ "label": "Even AI routing mode",
48
+ "help": "active = current session; background = dedicated background session; background_new = fresh background session per request.",
49
+ "advanced": true
50
+ },
51
+ "wsBind": {
52
+ "label": "WebSocket bind address",
53
+ "help": "Local interface the OcuClaw relay listens on.",
54
+ "advanced": true
55
+ },
56
+ "wsPort": {
57
+ "label": "WebSocket port",
58
+ "help": "Local port the OcuClaw relay listens on.",
59
+ "advanced": true
60
+ },
61
+ "sessionLimit": {
62
+ "label": "Concurrent session limit",
63
+ "help": "Maximum simultaneous client sessions accepted by the relay.",
64
+ "advanced": true
65
+ },
66
+ "debugPayloadMaxBytes": {
67
+ "label": "Debug payload size limit",
68
+ "help": "Maximum byte size for debug payloads recorded for debugctl.",
69
+ "advanced": true
70
+ },
71
+ "debugNoisyPolicies": {
72
+ "label": "Debug noise policies",
73
+ "help": "Per-channel filters that suppress or sample noisy debug events.",
74
+ "advanced": true
75
+ },
76
+ "externalDebugToolsEnabled": {
77
+ "label": "External debug tools",
78
+ "help": "Allow debugctl-style external tools to call debug-set, debug-dump, and remote-control.",
79
+ "advanced": true
80
+ },
81
+ "evenAiRequestTimeoutMs": {
82
+ "label": "Even AI request timeout (ms) (deprecated)",
83
+ "advanced": true
84
+ },
85
+ "evenAiMaxBodyBytes": {
86
+ "label": "Even AI max body size (bytes) (deprecated)",
87
+ "advanced": true
88
+ },
89
+ "evenAiDedupWindowMs": {
90
+ "label": "Even AI dedup window (ms) (deprecated)",
91
+ "advanced": true
92
+ },
93
+ "evenAiDedicatedSessionKey": {
94
+ "label": "Even AI dedicated session key (deprecated)",
95
+ "help": "Deprecated — scheduled for removal in a future release. Internal session key prefix for background routing. Must start with 'ocuclaw:'.",
96
+ "advanced": true
97
+ },
98
+ "renderGlassesUiTimeoutMs": {
99
+ "label": "render_glasses_ui timeout (ms)",
100
+ "help": "How long a render_glasses_ui call waits for a user pick before resolving with { result: \"timeout\" }. Bounds the orphan tool_use corruption window if the gateway dies mid-render. Default 1800000 (30 minutes). Set 0 to disable (infinite wait — pre-2026-05-23 behaviour).",
101
+ "advanced": true
102
+ },
103
+ "freshnessWindowMs": {
104
+ "label": "Activity summary freshness window",
105
+ "help": "Milliseconds an agent summary outranks a tool label (3000-8000).",
106
+ "advanced": true
107
+ }
108
+ },
5
109
  "configSchema": {
6
110
  "type": "object",
7
111
  "additionalProperties": false,
112
+ "required": ["relayToken"],
8
113
  "properties": {
9
114
  "relayToken": {
10
115
  "type": "string",
11
- "minLength": 1
116
+ "minLength": 1,
117
+ "description": "Shared secret matching the OcuClaw relay token in the Even Hub app. Required."
12
118
  },
13
119
  "wsBind": {
14
120
  "type": "string",
15
- "default": "127.0.0.1"
121
+ "default": "127.0.0.1",
122
+ "description": "Local interface the relay WebSocket listens on."
16
123
  },
17
124
  "wsPort": {
18
125
  "type": "integer",
19
126
  "minimum": 1,
20
127
  "maximum": 65535,
21
- "default": 9000
128
+ "default": 9000,
129
+ "description": "Local port the relay WebSocket listens on."
22
130
  },
23
131
  "sessionLimit": {
24
132
  "type": "integer",
25
133
  "minimum": 1,
26
- "default": 10
134
+ "default": 10,
135
+ "description": "Maximum concurrent client sessions accepted by the relay."
27
136
  },
28
137
  "debugPayloadMaxBytes": {
29
138
  "type": "integer",
30
139
  "minimum": 1,
31
- "default": 2048
140
+ "default": 2048,
141
+ "description": "Maximum byte size for individual debug payloads recorded for debugctl."
32
142
  },
33
143
  "debugNoisyPolicies": {
34
144
  "anyOf": [
@@ -41,7 +151,8 @@
41
151
  {
42
152
  "type": "null"
43
153
  }
44
- ]
154
+ ],
155
+ "description": "Per-channel filters that suppress or sample noisy debug events."
45
156
  },
46
157
  "externalDebugToolsEnabled": {
47
158
  "type": "boolean",
@@ -49,14 +160,18 @@
49
160
  "description": "Allow debugctl-style external debug tools to use debug-set, debug-dump, and remote-control."
50
161
  },
51
162
  "sonioxApiKey": {
52
- "type": "string"
163
+ "type": "string",
164
+ "description": "Optional Soniox API key. Enables Soniox speech-to-text for voice input."
53
165
  },
54
166
  "evenAiEnabled": {
55
167
  "type": "boolean",
56
- "default": false
168
+ "default": false,
169
+ "description": "Route Even AI agent requests through OcuClaw. When true, evenAiToken is required."
57
170
  },
58
171
  "evenAiToken": {
59
- "type": "string"
172
+ "type": "string",
173
+ "minLength": 1,
174
+ "description": "Shared secret matching the password set in the Even AI Agent Configure section of the Even Realities app. Required when evenAiEnabled is true."
60
175
  },
61
176
  "evenAiSystemPrompt": {
62
177
  "type": "string",
@@ -65,17 +180,23 @@
65
180
  "evenAiRequestTimeoutMs": {
66
181
  "type": "integer",
67
182
  "minimum": 1,
68
- "default": 60000
183
+ "default": 60000,
184
+ "deprecated": true,
185
+ "description": "Deprecated — scheduled for removal in a future release. The built-in default is used when this key is absent. Timeout (ms) for Even AI upstream requests."
69
186
  },
70
187
  "evenAiMaxBodyBytes": {
71
188
  "type": "integer",
72
189
  "minimum": 1,
73
- "default": 65536
190
+ "default": 65536,
191
+ "deprecated": true,
192
+ "description": "Deprecated — scheduled for removal in a future release. The built-in default is used when this key is absent. Maximum response body size (bytes) accepted from the Even AI upstream."
74
193
  },
75
194
  "evenAiDedupWindowMs": {
76
195
  "type": "integer",
77
196
  "minimum": 0,
78
- "default": 500
197
+ "default": 500,
198
+ "deprecated": true,
199
+ "description": "Deprecated — scheduled for removal in a future release. The built-in default is used when this key is absent. Dedup window (ms) for collapsing repeated Even AI requests."
79
200
  },
80
201
  "evenAiRoutingMode": {
81
202
  "type": "string",
@@ -84,12 +205,39 @@
84
205
  "background",
85
206
  "background_new"
86
207
  ],
87
- "default": "active"
208
+ "default": "active",
209
+ "description": "Even AI session routing: active uses the current session, background reuses a dedicated session, background_new starts a fresh session per request."
88
210
  },
89
211
  "evenAiDedicatedSessionKey": {
90
212
  "type": "string",
91
- "default": "ocuclaw:even-ai"
213
+ "default": "ocuclaw:even-ai",
214
+ "deprecated": true,
215
+ "description": "Deprecated — scheduled for removal in a future release. The built-in default is used when this key is absent. Internal session key prefix for background Even AI routing. Must start with 'ocuclaw:'."
216
+ },
217
+ "renderGlassesUiTimeoutMs": {
218
+ "type": "integer",
219
+ "minimum": 0,
220
+ "default": 1800000,
221
+ "description": "How long render_glasses_ui waits for user interaction before resolving as { result: \"timeout\" }. Bounds the orphan tool_use corruption window if the gateway dies during a pending render. Default 1800000 (30 minutes). 0 disables the timeout (infinite wait)."
222
+ },
223
+ "freshnessWindowMs": {
224
+ "type": "integer",
225
+ "minimum": 3000,
226
+ "maximum": 8000,
227
+ "default": 5000,
228
+ "description": "How long (ms) a fresh agent summary stays preferred over a tool label in the glasses activity status. Clamped 3000-8000."
92
229
  }
230
+ },
231
+ "if": {
232
+ "properties": {
233
+ "evenAiEnabled": {
234
+ "const": true
235
+ }
236
+ },
237
+ "required": ["evenAiEnabled"]
238
+ },
239
+ "then": {
240
+ "required": ["evenAiToken"]
93
241
  }
94
242
  }
95
243
  }
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "ocuclaw",
3
- "version": "0.1.0",
4
- "description": "OcuClaw for Even G2 smart glasses, powered by OpenClaw.",
3
+ "version": "1.3.0",
4
+ "requiresClientVersion": "1.3.0",
5
+ "description": "OcuClaw for Even Realities G2 smart glasses.",
5
6
  "type": "module",
6
7
  "main": "./dist/index.js",
7
8
  "files": [
@@ -24,13 +25,20 @@
24
25
  "openclaw": {
25
26
  "extensions": [
26
27
  "./dist/index.js"
27
- ]
28
+ ],
29
+ "install": {
30
+ "npmSpec": "ocuclaw",
31
+ "defaultChoice": "npm",
32
+ "minHostVersion": ">=2026.5.20"
33
+ }
28
34
  },
29
35
  "dependencies": {
30
36
  "marked": "^17.0.2",
37
+ "undici": "^6.26.0",
31
38
  "ws": "^8.19.0"
32
39
  },
33
40
  "scripts": {
34
- "build": "node ./scripts/build.mjs"
41
+ "build": "node ./scripts/build.mjs",
42
+ "prepare": "node ./scripts/build.mjs"
35
43
  }
36
44
  }