ever-terminal 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,318 @@
1
+ import { networkInterfaces } from "node:os";
2
+ import { createServer } from "node:net";
3
+ import { readFileSync } from "node:fs";
4
+ import { resolve, dirname } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { execSync } from "node:child_process";
7
+ import qrcodeTerminal from "qrcode-terminal";
8
+ import { getDefaultProvider } from "../session.js";
9
+ import { spawnShim } from "../util/spawn-shim.js";
10
+ export const CODEX_APP_SERVER_PORT = parseInt(process.env.CODEX_APP_SERVER_PORT || "8765", 10);
11
+ const __dirname = dirname(fileURLToPath(import.meta.url));
12
+ const pkg = JSON.parse(readFileSync(resolve(__dirname, "../../package.json"), "utf8"));
13
+ export function getLanAddress() {
14
+ const nets = networkInterfaces();
15
+ for (const ifaces of Object.values(nets)) {
16
+ for (const iface of ifaces ?? []) {
17
+ if (iface.family === "IPv4" && !iface.internal)
18
+ return iface.address;
19
+ }
20
+ }
21
+ }
22
+ function getTailscaleIp() {
23
+ try {
24
+ const out = execSync("tailscale ip -4", {
25
+ stdio: ["ignore", "pipe", "ignore"],
26
+ timeout: 3000,
27
+ })
28
+ .toString()
29
+ .trim();
30
+ const first = out.split("\n")[0]?.trim();
31
+ return first || undefined;
32
+ }
33
+ catch {
34
+ return undefined;
35
+ }
36
+ }
37
+ function getInterfaceIp(name) {
38
+ const ifaces = networkInterfaces()[name];
39
+ if (!ifaces)
40
+ return undefined;
41
+ for (const iface of ifaces) {
42
+ if (iface.family === "IPv4")
43
+ return iface.address;
44
+ }
45
+ return undefined;
46
+ }
47
+ /** Resolve host based on EVER_HOST_MODE / EVER_HOST_INTERFACE; exits on failure. */
48
+ export function resolveHost() {
49
+ const mode = process.env.EVER_HOST_MODE;
50
+ if (mode === "tailscale") {
51
+ const ip = getTailscaleIp();
52
+ if (!ip) {
53
+ console.error("error: failed to get Tailscale IPv4 address (is `tailscale` installed and running?)");
54
+ process.exit(1);
55
+ }
56
+ return { label: "Tailscale", address: ip };
57
+ }
58
+ if (mode === "interface") {
59
+ const name = process.env.EVER_HOST_INTERFACE ?? "";
60
+ if (!name) {
61
+ console.error("error: --interface requires a name");
62
+ process.exit(1);
63
+ }
64
+ const ip = getInterfaceIp(name);
65
+ if (!ip) {
66
+ console.error(`error: failed to get IPv4 address for interface "${name}"`);
67
+ process.exit(1);
68
+ }
69
+ return { label: name, address: ip };
70
+ }
71
+ return { label: "LAN", address: getLanAddress() ?? "" };
72
+ }
73
+ export function truncPath(p, max) {
74
+ if (p.length <= max)
75
+ return p;
76
+ return "..." + p.slice(-(max - 3));
77
+ }
78
+ function detectColorLevel() {
79
+ const { TERM, COLORTERM } = process.env;
80
+ if (!process.stdout.isTTY)
81
+ return "none";
82
+ if (TERM === "dumb")
83
+ return "none";
84
+ if (COLORTERM === "truecolor" || COLORTERM === "24bit")
85
+ return "truecolor";
86
+ if (TERM && /-256(color)?$/i.test(TERM))
87
+ return "ansi256";
88
+ if (TERM && /color|xterm|screen|vt100|ansi|cygwin|linux/i.test(TERM))
89
+ return "basic";
90
+ return "none";
91
+ }
92
+ function wrapQrColors(code, level) {
93
+ let bg;
94
+ let fg;
95
+ switch (level) {
96
+ case "truecolor":
97
+ bg = "\x1b[48;2;0;0;0m";
98
+ fg = "\x1b[38;2;255;255;255m";
99
+ break;
100
+ case "ansi256":
101
+ bg = "\x1b[48;5;16m";
102
+ fg = "\x1b[38;5;231m";
103
+ break;
104
+ case "basic":
105
+ bg = "\x1b[40m";
106
+ fg = "\x1b[37m";
107
+ break;
108
+ case "none":
109
+ return code;
110
+ }
111
+ const reset = "\x1b[0m";
112
+ const lines = code.split("\n");
113
+ while (lines.length && lines[0].trim() === "")
114
+ lines.shift();
115
+ while (lines.length && lines[lines.length - 1].trim() === "")
116
+ lines.pop();
117
+ return lines
118
+ .map((line, i) => {
119
+ const prefix = i === 0 && /^▄+$/.test(line) ? fg : `${bg}${fg}`;
120
+ return `${prefix}${line}${reset}`;
121
+ })
122
+ .join("\n");
123
+ }
124
+ /** Write directly to stdout, bypassing the timestamp-patched console.log.
125
+ * Use this for visual output (banners, QR codes) that must not be prefixed. */
126
+ export function rawLog(msg = "") {
127
+ process.stdout.write(msg + "\n");
128
+ }
129
+ export function printQRCode(str, afterCb) {
130
+ const level = detectColorLevel();
131
+ qrcodeTerminal.generate(str, { small: true }, (code) => {
132
+ rawLog(wrapQrColors(code, level));
133
+ if (afterCb)
134
+ afterCb();
135
+ });
136
+ }
137
+ let codexAppServerProcess = null;
138
+ function isPortTakenError(text) {
139
+ return /\bEADDRINUSE\b|address already in use|addrinuse/i.test(text);
140
+ }
141
+ function canBindLocalPort(port) {
142
+ return new Promise((resolve) => {
143
+ const server = createServer();
144
+ let settled = false;
145
+ const done = (available) => {
146
+ if (settled)
147
+ return;
148
+ settled = true;
149
+ server.close(() => resolve(available));
150
+ };
151
+ server.once("error", (err) => {
152
+ if (err.code === "EADDRINUSE") {
153
+ resolve(false);
154
+ return;
155
+ }
156
+ console.error(`[codex] WARN: Failed to bind-check port ${port}: ${err.message}`);
157
+ resolve(false);
158
+ });
159
+ server.once("listening", () => done(true));
160
+ server.listen(port, "127.0.0.1");
161
+ });
162
+ }
163
+ export async function startCodexAppServer() {
164
+ const listenUrl = `ws://127.0.0.1:${CODEX_APP_SERVER_PORT}`;
165
+ if (!(await canBindLocalPort(CODEX_APP_SERVER_PORT))) {
166
+ console.error(`[codex] ERROR: Port ${CODEX_APP_SERVER_PORT} appears to be in use. Set CODEX_APP_SERVER_PORT to another port and restart.`);
167
+ console.error(`[codex] ERROR: Codex app-server was not started.`);
168
+ console.error(`[codex] NOTE: This is harmless if you only intend to use Claude (or providers other than Codex).`);
169
+ return false;
170
+ }
171
+ return new Promise((resolve) => {
172
+ let resolved = false;
173
+ let started = false;
174
+ const done = () => {
175
+ if (!resolved) {
176
+ resolved = true;
177
+ resolve(started);
178
+ }
179
+ };
180
+ let stderrText = "";
181
+ let printedPortHint = false;
182
+ const printPortHint = (text) => {
183
+ if (printedPortHint || !isPortTakenError(text))
184
+ return;
185
+ printedPortHint = true;
186
+ console.error(`[codex] ERROR: Port ${CODEX_APP_SERVER_PORT} appears to be in use. Set CODEX_APP_SERVER_PORT to another port and restart.`);
187
+ };
188
+ let child;
189
+ try {
190
+ child = spawnShim("codex", ["app-server", "--listen", listenUrl], {
191
+ env: process.env,
192
+ stdio: ["ignore", "pipe", "pipe"],
193
+ });
194
+ }
195
+ catch (err) {
196
+ console.error(`[codex] ERROR: Failed to spawn codex app-server: ${err.message}`);
197
+ console.error(`[codex] ERROR: Codex provider will not work in this environment.`);
198
+ console.error(`[codex] NOTE: This is harmless if you only intend to use Claude (or providers other than Codex).`);
199
+ done();
200
+ return;
201
+ }
202
+ child.on("error", (err) => {
203
+ console.error(`[codex] ERROR: Failed to start codex app-server: ${err.message}`);
204
+ printPortHint(err.message);
205
+ console.error(`[codex] ERROR: Codex provider will not work in this environment.`);
206
+ console.error(`[codex] NOTE: This is harmless if you only intend to use Claude (or providers other than Codex).`);
207
+ codexAppServerProcess = null;
208
+ done();
209
+ });
210
+ child.on("close", (code) => {
211
+ if (code !== null && code !== 0) {
212
+ console.error(`[codex] ERROR: codex app-server exited with code ${code}`);
213
+ printPortHint(stderrText);
214
+ console.error(`[codex] NOTE: This is harmless if you only intend to use Claude (or providers other than Codex).`);
215
+ }
216
+ codexAppServerProcess = null;
217
+ done();
218
+ });
219
+ child.stderr?.on("data", (data) => {
220
+ const text = data.toString().trim();
221
+ if (text) {
222
+ stderrText += `${text}\n`;
223
+ console.log(`[codex-app-server] ${text}`);
224
+ printPortHint(text);
225
+ if (text.includes("listening on:")) {
226
+ started = true;
227
+ done();
228
+ }
229
+ }
230
+ });
231
+ child.stdout?.on("data", (data) => {
232
+ const text = data.toString().trim();
233
+ if (text)
234
+ console.log(`[codex-app-server] ${text}`);
235
+ });
236
+ codexAppServerProcess = child;
237
+ console.log(`[codex] app-server starting on ${listenUrl}`);
238
+ // Fallback timeout in case we miss the ready signal
239
+ setTimeout(done, 5000);
240
+ });
241
+ }
242
+ export function stopCodexAppServer() {
243
+ if (codexAppServerProcess) {
244
+ codexAppServerProcess.kill();
245
+ codexAppServerProcess = null;
246
+ }
247
+ }
248
+ /**
249
+ * Spawn the codex app-server on first call (and re-spawn if it died). Concurrent
250
+ * callers share the same in-flight promise; on any settlement the cached promise
251
+ * is cleared so the next API call can retry.
252
+ */
253
+ let codexAppServerStartPromise = null;
254
+ export function ensureCodexAppServerStarted() {
255
+ if (codexAppServerProcess &&
256
+ codexAppServerProcess.exitCode === null &&
257
+ codexAppServerProcess.signalCode === null) {
258
+ return Promise.resolve(true);
259
+ }
260
+ if (codexAppServerStartPromise)
261
+ return codexAppServerStartPromise;
262
+ codexAppServerStartPromise = startCodexAppServer().finally(() => {
263
+ codexAppServerStartPromise = null;
264
+ });
265
+ return codexAppServerStartPromise;
266
+ }
267
+ export function buildClientQuery(token) {
268
+ const defaultProvider = getDefaultProvider();
269
+ const name = process.env.EVER_TERMINAL_NAME ?? "";
270
+ const params = new URLSearchParams({ token, defaultProvider });
271
+ if (name)
272
+ params.set("name", name);
273
+ return params;
274
+ }
275
+ export function printServerBanner(port, token, cwd, printQr) {
276
+ const host = resolveHost();
277
+ const name = process.env.EVER_TERMINAL_NAME ?? "";
278
+ const labelWidth = Math.max("Local".length, "Token".length, "Name".length, "CWD".length, host.label.length);
279
+ const pad = (s) => s.padEnd(labelWidth);
280
+ const logo = [
281
+ "██ ████████",
282
+ "████ ",
283
+ "██ ████████",
284
+ "████ ",
285
+ "██ ████████",
286
+ ];
287
+ const info = [
288
+ `Ever Terminal v${pkg.version}`,
289
+ name ? `${pad("Name")}: ${name}` : "",
290
+ `${pad("Local")}: http://localhost:${port}`,
291
+ host.address
292
+ ? `${pad(host.label)}: http://${host.address}:${port}`
293
+ : "",
294
+ `${pad("Token")}: ${token.slice(0, 8)}...${token.slice(-4)}`,
295
+ `${pad("CWD")}: ${truncPath(cwd, 40)}`,
296
+ "",
297
+ "",
298
+ ];
299
+ const gap = " ";
300
+ rawLog("");
301
+ for (let i = 0; i < Math.max(logo.length, info.length); i++) {
302
+ const logoLine = (logo[i] ?? "").padEnd(12);
303
+ rawLog(` ${logoLine}${gap}${info[i] ?? ""}`);
304
+ }
305
+ rawLog("");
306
+ rawLog(" Made by Even Realities · Connect your terminal to G2 glasses");
307
+ rawLog(" " + "─".repeat(61));
308
+ rawLog("");
309
+ rawLog(` Full token: ${token}`);
310
+ rawLog("");
311
+ const params = buildClientQuery(token);
312
+ const address = host.address || "localhost";
313
+ const url = `http://${address}:${port}?${params.toString()}`;
314
+ if (printQr && host.address) {
315
+ rawLog(url);
316
+ printQRCode(url, () => rawLog(""));
317
+ }
318
+ }
@@ -0,0 +1,89 @@
1
+ import { homedir } from "node:os";
2
+ import { mkdirSync, readdirSync, readFileSync, unlinkSync, writeFileSync, } from "node:fs";
3
+ import { join } from "node:path";
4
+ export const INSTANCE_DIR = join(homedir(), ".ever-terminal", "instances");
5
+ function instanceFilePath(pid) {
6
+ return join(INSTANCE_DIR, `${pid}.json`);
7
+ }
8
+ export function writeInstancePidfile(info) {
9
+ mkdirSync(INSTANCE_DIR, { recursive: true });
10
+ const full = {
11
+ pid: process.pid,
12
+ platform: process.platform,
13
+ startedAt: Date.now(),
14
+ ...info,
15
+ };
16
+ const path = instanceFilePath(process.pid);
17
+ writeFileSync(path, JSON.stringify(full, null, 2), { mode: 0o600 });
18
+ return path;
19
+ }
20
+ export function removeInstancePidfile() {
21
+ try {
22
+ unlinkSync(instanceFilePath(process.pid));
23
+ }
24
+ catch {
25
+ // already gone — fine
26
+ }
27
+ }
28
+ function isPidAlive(pid) {
29
+ try {
30
+ process.kill(pid, 0);
31
+ return true;
32
+ }
33
+ catch (err) {
34
+ return err?.code === "EPERM";
35
+ }
36
+ }
37
+ /** List live instances; silently prunes stale pidfiles. */
38
+ export function listLiveInstances() {
39
+ let entries;
40
+ try {
41
+ entries = readdirSync(INSTANCE_DIR);
42
+ }
43
+ catch (err) {
44
+ if (err?.code === "ENOENT")
45
+ return [];
46
+ throw err;
47
+ }
48
+ const live = [];
49
+ for (const name of entries) {
50
+ if (!name.endsWith(".json"))
51
+ continue;
52
+ const path = join(INSTANCE_DIR, name);
53
+ let info;
54
+ try {
55
+ info = JSON.parse(readFileSync(path, "utf8"));
56
+ }
57
+ catch {
58
+ try {
59
+ unlinkSync(path);
60
+ }
61
+ catch { }
62
+ continue;
63
+ }
64
+ if (typeof info.pid !== "number" || !isPidAlive(info.pid)) {
65
+ try {
66
+ unlinkSync(path);
67
+ }
68
+ catch { }
69
+ continue;
70
+ }
71
+ live.push(info);
72
+ }
73
+ // Newest first — handy for "most recent" fallbacks and stable display.
74
+ live.sort((a, b) => (b.startedAt ?? 0) - (a.startedAt ?? 0));
75
+ return live;
76
+ }
77
+ /** Best-effort age string (e.g. "3m", "2h"). */
78
+ export function formatInstanceAge(startedAt) {
79
+ const secs = Math.max(0, Math.floor((Date.now() - startedAt) / 1000));
80
+ if (secs < 60)
81
+ return `${secs}s`;
82
+ const mins = Math.floor(secs / 60);
83
+ if (mins < 60)
84
+ return `${mins}m`;
85
+ const hours = Math.floor(mins / 60);
86
+ if (hours < 24)
87
+ return `${hours}h`;
88
+ return `${Math.floor(hours / 24)}d`;
89
+ }
@@ -0,0 +1,17 @@
1
+ export function fileName(filePath) {
2
+ if (!filePath)
3
+ return "file";
4
+ const parts = filePath.split(/[\\/]/);
5
+ return parts[parts.length - 1] || filePath;
6
+ }
7
+ export function truncate(value, max) {
8
+ const text = oneLine(value);
9
+ if (!text)
10
+ return "";
11
+ return text.length > max ? text.slice(0, max) + "..." : text;
12
+ }
13
+ export function oneLine(value) {
14
+ if (value === null || value === undefined)
15
+ return "";
16
+ return String(value).replace(/\s+/g, " ").trim();
17
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/dist/update.js ADDED
@@ -0,0 +1,56 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { resolve, dirname } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ const __dirname = dirname(fileURLToPath(import.meta.url));
5
+ const pkg = JSON.parse(readFileSync(resolve(__dirname, "../package.json"), "utf8"));
6
+ const NPM_REGISTRY_URL = "https://registry.npmjs.org";
7
+ const FETCH_TIMEOUT_MS = 5000;
8
+ function isNewerVersion(candidate, current) {
9
+ const candidateParts = candidate.split(".").map(Number);
10
+ const currentParts = current.split(".").map(Number);
11
+ for (let i = 0; i < 3; i++) {
12
+ if (candidateParts[i] !== currentParts[i])
13
+ return candidateParts[i] > currentParts[i];
14
+ }
15
+ return false;
16
+ }
17
+ async function fetchNewestVersion(packageName) {
18
+ const controller = new AbortController();
19
+ const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
20
+ try {
21
+ const url = `${NPM_REGISTRY_URL}/${encodeURIComponent(packageName)}`;
22
+ const response = await fetch(url, {
23
+ headers: { Accept: "application/vnd.npm.install-v1+json" },
24
+ signal: controller.signal,
25
+ });
26
+ if (!response.ok) {
27
+ throw new Error(`npm registry returned ${response.status}`);
28
+ }
29
+ const body = (await response.json());
30
+ const latest = body["dist-tags"]?.latest;
31
+ if (typeof latest !== "string" || latest.length === 0) {
32
+ throw new Error("npm registry response did not include dist-tags.latest");
33
+ }
34
+ return latest;
35
+ }
36
+ finally {
37
+ clearTimeout(timer);
38
+ }
39
+ }
40
+ export async function checkForUpdate() {
41
+ const newestVersion = await fetchNewestVersion(pkg.name);
42
+ return {
43
+ packageName: pkg.name,
44
+ currentVersion: pkg.version,
45
+ newestVersion,
46
+ updateAvailable: isNewerVersion(newestVersion, pkg.version),
47
+ checkedAt: new Date().toISOString(),
48
+ };
49
+ }
50
+ export function getCurrentAppVersion() {
51
+ return {
52
+ packageName: pkg.name,
53
+ currentVersion: pkg.version,
54
+ checkedAt: new Date().toISOString(),
55
+ };
56
+ }
@@ -0,0 +1,25 @@
1
+ import { spawn, spawnSync, } from "node:child_process";
2
+ // Cross-platform spawn that resolves PATHEXT-style shims (`foo.cmd`, `foo.bat`,
3
+ // extensionless npm shims, etc.) on Windows by routing through `cmd.exe /c`.
4
+ // POSIX uses `execvp`, which already searches PATH — no shell wrapper needed.
5
+ //
6
+ // Avoids `shell: true` + array args (DEP0190 in Node 22+, removed in Node 24),
7
+ // while still letting Windows resolve PATHEXT correctly. Args here are passed
8
+ // through Node's per-arg quoting (safer than shell-string concatenation).
9
+ function windowsShimArgs(file, args) {
10
+ return { file: process.env.ComSpec || "cmd.exe", args: ["/c", file, ...args] };
11
+ }
12
+ export function spawnShim(file, args = [], opts = {}) {
13
+ if (process.platform === "win32") {
14
+ const w = windowsShimArgs(file, args);
15
+ return spawn(w.file, w.args, opts);
16
+ }
17
+ return spawn(file, args, opts);
18
+ }
19
+ export function spawnSyncShim(file, args = [], opts = {}) {
20
+ if (process.platform === "win32") {
21
+ const w = windowsShimArgs(file, args);
22
+ return spawnSync(w.file, w.args, opts);
23
+ }
24
+ return spawnSync(file, args, opts);
25
+ }
package/package.json ADDED
@@ -0,0 +1,79 @@
1
+ {
2
+ "name": "ever-terminal",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "description": "Ever Terminal — AI Coding CLI on Smart Glasses & Flutter App",
6
+ "license": "MIT",
7
+ "packageManager": "pnpm@10.33.3",
8
+ "keywords": [
9
+ "cli",
10
+ "claude-code",
11
+ "codex",
12
+ "ai-coding",
13
+ "smart-glasses",
14
+ "even-realities",
15
+ "terminal"
16
+ ],
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
20
+ "bin": {
21
+ "ever-terminal": "dist/cli.js"
22
+ },
23
+ "files": [
24
+ "dist/",
25
+ "!dist/index-evenhub.js",
26
+ "!dist/routes/audio.js",
27
+ "!dist/asr.js",
28
+ "!dist/at-file.js",
29
+ "!dist/startup/evenhub.js"
30
+ ],
31
+ "scripts": {
32
+ "start": "tsx src/index.ts",
33
+ "dev": "tsx watch src/index.ts",
34
+ "build": "rm -rf dist && tsc -p tsconfig.build.json",
35
+ "build:evenhub": "rm -rf dist public && tsc -p tsconfig.build.json && pnpm run copy-frontend",
36
+ "build:bundle": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.mjs --packages=external",
37
+ "start:prod": "node dist/index.mjs",
38
+ "copy-frontend": "node -e \"const fs=require('fs');fs.cpSync('../frontend/dist','public',{recursive:true})\"",
39
+ "prepack": "pnpm run build",
40
+ "pack:evenhub": "V=$(node -p \"require('./package.json').version\") && pnpm run build:evenhub && node -e \"const fs=require('fs');const p=JSON.parse(fs.readFileSync('package.json','utf8'));p.files=['dist/','public/'];fs.writeFileSync('package.json',JSON.stringify(p,null,2)+'\\n');\" && pnpm pack --ignore-scripts && git checkout package.json && mv ever-terminal-$V.tgz ever-terminal-evenhub-$V.tgz",
41
+ "lint": "oxlint src",
42
+ "test": "node --import tsx --test \"src/**/*.test.ts\"",
43
+ "typecheck": "tsc --noEmit"
44
+ },
45
+ "engines": {
46
+ "node": ">=18"
47
+ },
48
+ "dependencies": {
49
+ "@anthropic-ai/claude-agent-sdk": "^0.3.195",
50
+ "@bomb.sh/tab": "^0.0.17",
51
+ "cors": "^2.8.6",
52
+ "express": "^5.2.1",
53
+ "qrcode-terminal": "^0.12.0",
54
+ "ws": "^8.21.0",
55
+ "yargs": "^18.0.0"
56
+ },
57
+ "devDependencies": {
58
+ "@semantic-release/commit-analyzer": "^13.0.1",
59
+ "@semantic-release/github": "^11.0.1",
60
+ "@semantic-release/npm": "^12.0.1",
61
+ "@semantic-release/release-notes-generator": "^14.0.3",
62
+ "@types/cors": "^2.8.19",
63
+ "@types/express": "^5.0.6",
64
+ "@types/node": "^26.0.1",
65
+ "@types/qrcode-terminal": "^0.12.2",
66
+ "@types/ws": "^8.18.1",
67
+ "@types/yargs": "^17.0.35",
68
+ "esbuild": "^0.28.1",
69
+ "oxlint": "^1.71.0",
70
+ "semantic-release": "^24.2.3",
71
+ "tsx": "^4.21.0",
72
+ "typescript": "^5.9.3"
73
+ },
74
+ "pnpm": {
75
+ "onlyBuiltDependencies": [
76
+ "esbuild"
77
+ ]
78
+ }
79
+ }