@moly-mcp/lido 1.0.5 → 1.0.7

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,16 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ configureAlertChannels,
4
+ listAlerts,
5
+ removeAlertById,
6
+ setAlert
7
+ } from "./chunk-6UIRFWG4.js";
8
+ import "./chunk-6F64RPQQ.js";
9
+ import "./chunk-P6VFMSPM.js";
10
+ import "./chunk-PDX44BCA.js";
11
+ export {
12
+ configureAlertChannels,
13
+ listAlerts,
14
+ removeAlertById,
15
+ setAlert
16
+ };
package/dist/bin.js CHANGED
@@ -6,7 +6,8 @@ import {
6
6
  loadConfig,
7
7
  redactedConfig,
8
8
  saveConfig
9
- } from "./chunk-PIFEXJ56.js";
9
+ } from "./chunk-P6VFMSPM.js";
10
+ import "./chunk-PDX44BCA.js";
10
11
 
11
12
  // src/setup/wizard.ts
12
13
  import {
@@ -15,7 +16,6 @@ import {
15
16
  select,
16
17
  text,
17
18
  password,
18
- confirm,
19
19
  note,
20
20
  cancel,
21
21
  isCancel
@@ -29,7 +29,7 @@ function check(value) {
29
29
  return value;
30
30
  }
31
31
  function clientSnippet(client) {
32
- const entry = `"moly": { "command": "npx", "args": ["@moly/lido", "--server"] }`;
32
+ const entry = `"moly": { "command": "npx", "args": ["@moly-mcp/lido", "--server"] }`;
33
33
  switch (client) {
34
34
  case "claude-desktop":
35
35
  return `Add to ~/Library/Application Support/Claude/claude_desktop_config.json
@@ -73,6 +73,7 @@ var GEMINI_MODELS = [
73
73
  { value: "__custom__", label: "Enter custom model ID" }
74
74
  ];
75
75
  var OPENROUTER_MODELS = [
76
+ { value: "nvidia/nemotron-3-super-120b-a12b:free", label: "nvidia/nemotron-3-super-120b-a12b:free (default, free)" },
76
77
  { value: "anthropic/claude-sonnet-4-6", label: "anthropic/claude-sonnet-4-6" },
77
78
  { value: "anthropic/claude-opus-4-6", label: "anthropic/claude-opus-4-6" },
78
79
  { value: "google/gemini-2.0-flash", label: "google/gemini-2.0-flash" },
@@ -95,7 +96,7 @@ async function pickModel(provider) {
95
96
  return picked;
96
97
  }
97
98
  async function runWizard() {
98
- intro(" Moly \u2014 Lido MCP Server \u2B21");
99
+ intro(" Moly - Lido MCP Server");
99
100
  const network = check(
100
101
  await select({
101
102
  message: "Which network?",
@@ -105,6 +106,18 @@ async function runWizard() {
105
106
  ]
106
107
  })
107
108
  );
109
+ let chainScope = "ethereum";
110
+ if (network === "mainnet") {
111
+ chainScope = check(
112
+ await select({
113
+ message: "Chain scope?",
114
+ options: [
115
+ { value: "ethereum", label: "Ethereum only (L1 staking, governance, wrapping)" },
116
+ { value: "all", label: "All chains (L1 + Base/Arbitrum bridging via LI.FI)" }
117
+ ]
118
+ })
119
+ );
120
+ }
108
121
  const rpcInput = check(
109
122
  await text({
110
123
  message: "Custom RPC URL? (optional \u2014 leave blank to use public RPC)",
@@ -122,13 +135,46 @@ async function runWizard() {
122
135
  })
123
136
  );
124
137
  let privateKey = null;
125
- const wantsKey = check(
126
- await confirm({
127
- message: "Add a private key? (needed for Live mode \u2014 stored locally with chmod 600)",
128
- initialValue: mode === "live"
138
+ let ows = null;
139
+ const keySource = check(
140
+ await select({
141
+ message: "Key source? (needed for Live mode)",
142
+ options: [
143
+ { value: "ows", label: "OWS Wallet (encrypted via Open Wallet Standard)" },
144
+ { value: "raw", label: "Raw private key (stored in ~/.moly/config.json, chmod 600)" },
145
+ { value: "none", label: "None / Skip" }
146
+ ]
129
147
  })
130
148
  );
131
- if (wantsKey) {
149
+ if (keySource === "ows") {
150
+ let wallets = [];
151
+ try {
152
+ const owsSdk = await import("@open-wallet-standard/core");
153
+ wallets = owsSdk.listWallets();
154
+ } catch {
155
+ note("Could not load OWS. Install it first:\ncurl -fsSL https://openwallet.sh/install.sh | bash\nnpm install @open-wallet-standard/core", "OWS not found");
156
+ }
157
+ if (wallets.length > 0) {
158
+ const walletName = check(
159
+ await select({
160
+ message: "Which OWS wallet?",
161
+ options: wallets.map((w) => ({
162
+ value: w.name,
163
+ label: `${w.name} (${w.address.slice(0, 8)}...)`
164
+ }))
165
+ })
166
+ );
167
+ const passphrase = check(
168
+ await password({
169
+ message: "OWS passphrase:",
170
+ mask: "*"
171
+ })
172
+ );
173
+ ows = { walletName, passphrase: passphrase.trim() };
174
+ } else if (wallets.length === 0) {
175
+ note('No OWS wallets found. Create one first:\nows wallet create --name "moly"', "Empty vault");
176
+ }
177
+ } else if (keySource === "raw") {
132
178
  const pk = check(
133
179
  await password({
134
180
  message: "Private key (0x...):",
@@ -141,10 +187,10 @@ async function runWizard() {
141
187
  await select({
142
188
  message: "AI Provider? (optional \u2014 used for built-in chat + config snippet)",
143
189
  options: [
144
- { value: "none", label: "None / Skip" },
190
+ { value: "openrouter", label: "OpenRouter (access any model with one key)" },
145
191
  { value: "anthropic", label: "Anthropic (Claude)" },
146
192
  { value: "google", label: "Google (Gemini)" },
147
- { value: "openrouter", label: "OpenRouter (access any model with one key)" }
193
+ { value: "none", label: "None / Skip" }
148
194
  ]
149
195
  })
150
196
  );
@@ -164,10 +210,10 @@ async function runWizard() {
164
210
  await select({
165
211
  message: "Which AI client are you using? (for config snippet)",
166
212
  options: [
213
+ { value: "Integrated Terminal", label: "Moly Environment" },
167
214
  { value: "claude-desktop", label: "Claude Desktop" },
168
215
  { value: "cursor", label: "Cursor" },
169
- { value: "windsurf", label: "Windsurf / Codeium" },
170
- { value: "none", label: "Skip" }
216
+ { value: "windsurf", label: "Windsurf / Codeium" }
171
217
  ]
172
218
  })
173
219
  );
@@ -176,7 +222,9 @@ async function runWizard() {
176
222
  mode,
177
223
  rpc,
178
224
  privateKey,
225
+ ows,
179
226
  ai,
227
+ chainScope,
180
228
  setupComplete: true
181
229
  };
182
230
  saveConfig(cfg);
@@ -185,7 +233,7 @@ async function runWizard() {
185
233
  if (snippet) {
186
234
  note(snippet, "Add to your AI client config");
187
235
  }
188
- const terminalMode = ai !== null && clientChoice === "none";
236
+ const terminalMode = ai !== null && clientChoice === "Integrated Terminal";
189
237
  if (terminalMode) {
190
238
  outro(`Launching Moly Terminal... ${mode} \xB7 ${network} \xB7 ready`);
191
239
  } else {
@@ -206,7 +254,7 @@ async function main() {
206
254
  case "setup": {
207
255
  const { cfg, terminalMode } = await runWizard();
208
256
  if (terminalMode) {
209
- const { startChatSession } = await import("./session-RFQTJ6WZ.js");
257
+ const { startChatSession } = await import("./session-2BM4AYLG.js");
210
258
  await startChatSession(cfg);
211
259
  } else {
212
260
  await startServer();
@@ -216,7 +264,7 @@ async function main() {
216
264
  // ── moly config ───────────────────────────────────────────────────
217
265
  case "config": {
218
266
  if (!configExists()) {
219
- console.log("No config found. Run: npx @moly/lido");
267
+ console.log("No config found. Run: npx @moly-mcp/lido");
220
268
  process.exit(1);
221
269
  }
222
270
  const cfg = loadConfig();
@@ -232,14 +280,229 @@ async function main() {
232
280
  process.exit(0);
233
281
  }
234
282
  deleteConfig();
235
- console.log("Config deleted. Run: npx @moly/lido to set up again.");
283
+ console.log("Config deleted. Run: npx @moly-mcp/lido to set up again.");
284
+ break;
285
+ }
286
+ // ── moly alert ─────────────────────────────────────────────────────
287
+ case "alert": {
288
+ const sub = args[1];
289
+ const getFlag = (flag) => {
290
+ const i = args.indexOf(flag);
291
+ return i !== -1 ? args[i + 1] : void 0;
292
+ };
293
+ switch (sub) {
294
+ case "add": {
295
+ const condition = args[2];
296
+ if (!condition) {
297
+ console.log("Usage: moly alert add <condition> [threshold] [--channel webhook]");
298
+ break;
299
+ }
300
+ const threshold = args[3] && !args[3].startsWith("-") ? parseFloat(args[3]) : void 0;
301
+ const channel = getFlag("--channel") ?? "telegram";
302
+ const { setAlert } = await import("./alerts-ARQAPRIT.js");
303
+ const alert = setAlert({ condition, threshold, channel });
304
+ console.log("Alert created:", JSON.stringify(alert, null, 2));
305
+ break;
306
+ }
307
+ case "list": {
308
+ const { listAlerts } = await import("./alerts-ARQAPRIT.js");
309
+ console.log(JSON.stringify(listAlerts(), null, 2));
310
+ break;
311
+ }
312
+ case "remove": {
313
+ const id = args[2];
314
+ if (!id) {
315
+ console.log("Usage: moly alert remove <id>");
316
+ break;
317
+ }
318
+ const { removeAlertById } = await import("./alerts-ARQAPRIT.js");
319
+ console.log(JSON.stringify(removeAlertById(id), null, 2));
320
+ break;
321
+ }
322
+ case "channels": {
323
+ const { configureAlertChannels } = await import("./alerts-ARQAPRIT.js");
324
+ const result = configureAlertChannels({
325
+ telegram_token: getFlag("--telegram-token"),
326
+ telegram_chat_id: getFlag("--telegram-chat"),
327
+ webhook_url: getFlag("--webhook-url")
328
+ });
329
+ console.log("Channels configured:", JSON.stringify(result, null, 2));
330
+ break;
331
+ }
332
+ case "daemon": {
333
+ if (!configExists()) {
334
+ console.log("No config. Run: moly setup");
335
+ process.exit(1);
336
+ }
337
+ const { runDaemon } = await import("./daemon-GU45VVNU.js");
338
+ await runDaemon();
339
+ break;
340
+ }
341
+ case "start": {
342
+ const { spawn } = await import("child_process");
343
+ const child = spawn(process.argv[0], [process.argv[1], "alert", "daemon"], {
344
+ detached: true,
345
+ stdio: "ignore"
346
+ });
347
+ child.unref();
348
+ console.log(`Alert daemon started (PID: ${child.pid})`);
349
+ break;
350
+ }
351
+ default:
352
+ console.log("Usage: moly alert <add|list|remove|channels|daemon|start>");
353
+ }
354
+ break;
355
+ }
356
+ // ── moly monitor ──────────────────────────────────────────────────
357
+ case "monitor": {
358
+ const sub = args[1];
359
+ switch (sub) {
360
+ case "start": {
361
+ const { spawn } = await import("child_process");
362
+ const child = spawn(process.argv[0], [process.argv[1], "alert", "daemon"], {
363
+ detached: true,
364
+ stdio: "ignore"
365
+ });
366
+ child.unref();
367
+ console.log(`Monitor daemon started (PID: ${child.pid})`);
368
+ break;
369
+ }
370
+ case "status": {
371
+ const { loadAlerts } = await import("./store-SKFUVSK4.js");
372
+ const data = loadAlerts();
373
+ if (!data.daemonPid) {
374
+ console.log("No daemon running (or never started).");
375
+ } else {
376
+ let alive = false;
377
+ try {
378
+ process.kill(data.daemonPid, 0);
379
+ alive = true;
380
+ } catch {
381
+ }
382
+ console.log(`PID: ${data.daemonPid} alive: ${alive}`);
383
+ if (data.daemonStartedAt) console.log(`Started: ${data.daemonStartedAt}`);
384
+ if (data.lastCheckAt) console.log(`Last check: ${data.lastCheckAt}`);
385
+ console.log(`Active alerts: ${data.alerts.filter((a) => a.enabled).length}`);
386
+ }
387
+ break;
388
+ }
389
+ case "stop": {
390
+ const { loadAlerts, saveAlerts } = await import("./store-SKFUVSK4.js");
391
+ const data = loadAlerts();
392
+ if (!data.daemonPid) {
393
+ console.log("No daemon PID recorded.");
394
+ break;
395
+ }
396
+ try {
397
+ process.kill(data.daemonPid, "SIGTERM");
398
+ console.log(`Sent SIGTERM to PID ${data.daemonPid}`);
399
+ } catch {
400
+ console.log(`PID ${data.daemonPid} not running.`);
401
+ }
402
+ data.daemonPid = void 0;
403
+ saveAlerts(data);
404
+ break;
405
+ }
406
+ default:
407
+ console.log("Usage: moly monitor <start|status|stop>");
408
+ }
409
+ break;
410
+ }
411
+ // ── moly bounds ──────────────────────────────────────────────────
412
+ case "bounds": {
413
+ const sub = args[1];
414
+ const getFlag = (flag) => {
415
+ const i = args.indexOf(flag);
416
+ return i !== -1 ? args[i + 1] : void 0;
417
+ };
418
+ switch (sub) {
419
+ case "show":
420
+ case void 0: {
421
+ const { loadBounds } = await import("./store-5CEITPDY.js");
422
+ console.log(JSON.stringify(loadBounds(), null, 2));
423
+ break;
424
+ }
425
+ case "set": {
426
+ const { loadBounds, saveBounds } = await import("./store-5CEITPDY.js");
427
+ const b = loadBounds();
428
+ const mst = getFlag("--max-stake-per-tx");
429
+ const mds = getFlag("--max-daily-stake");
430
+ const mer = getFlag("--min-eth-reserve");
431
+ const art = getFlag("--auto-restake-threshold");
432
+ const gov = getFlag("--governance-auto-vote");
433
+ if (mst) b.maxStakePerTx = parseFloat(mst);
434
+ if (mds) b.maxDailyStake = parseFloat(mds);
435
+ if (mer) b.minEthReserve = parseFloat(mer);
436
+ if (art) b.autoRestakeThreshold = parseFloat(art);
437
+ if (gov) b.governanceAutoVote = gov === "true";
438
+ saveBounds(b);
439
+ console.log("Bounds updated:", JSON.stringify(b, null, 2));
440
+ break;
441
+ }
442
+ case "reset": {
443
+ const { resetBounds } = await import("./store-5CEITPDY.js");
444
+ resetBounds();
445
+ console.log("Bounds reset to defaults.");
446
+ break;
447
+ }
448
+ default:
449
+ console.log("Usage: moly bounds [show|set|reset]");
450
+ console.log(" set flags: --max-stake-per-tx, --max-daily-stake, --min-eth-reserve, --auto-restake-threshold, --governance-auto-vote");
451
+ }
452
+ break;
453
+ }
454
+ // ── moly ledger ──────────────────────────────────────────────────
455
+ case "ledger": {
456
+ const sub = args[1];
457
+ const getFlag = (flag) => {
458
+ const i = args.indexOf(flag);
459
+ return i !== -1 ? args[i + 1] : void 0;
460
+ };
461
+ const { initLedger, queryLedger, ledgerStats, exportLedger } = await import("./store-WRLUM7OW.js");
462
+ initLedger();
463
+ switch (sub) {
464
+ case "list": {
465
+ const tool = getFlag("--tool");
466
+ const since = getFlag("--since");
467
+ const limit = getFlag("--limit");
468
+ const rows = queryLedger({ tool: tool ?? void 0, since: since ?? void 0, limit: limit ? parseInt(limit) : 50 });
469
+ console.log(JSON.stringify(rows, null, 2));
470
+ break;
471
+ }
472
+ case "stats": {
473
+ const since = getFlag("--since");
474
+ console.log(JSON.stringify(ledgerStats(since ?? void 0), null, 2));
475
+ break;
476
+ }
477
+ case "export": {
478
+ const format = getFlag("--format") ?? "json";
479
+ console.log(exportLedger(format));
480
+ break;
481
+ }
482
+ default:
483
+ console.log("Usage: moly ledger <list|stats|export>");
484
+ console.log(" list: --tool <name> --since <ISO date> --limit <n>");
485
+ console.log(" export: --format <json|csv>");
486
+ }
487
+ break;
488
+ }
489
+ // ── moly position ────────────────────────────────────────────────
490
+ case "position": {
491
+ if (!configExists()) {
492
+ console.log("No config. Run: moly setup");
493
+ process.exit(1);
494
+ }
495
+ const { getTotalPosition } = await import("./position-LKVHTEKX.js");
496
+ const address = args[1];
497
+ const pos = await getTotalPosition(address);
498
+ console.log(JSON.stringify(pos, null, 2));
236
499
  break;
237
500
  }
238
501
  // ── moly --server (force-start, used in AI client configs) ────────
239
502
  case "--server": {
240
503
  if (!configExists()) {
241
504
  process.stderr.write(
242
- "ERROR: No config found. Run: npx @moly/lido to set up first.\n"
505
+ "ERROR: No config found. Run: npx @moly-mcp/lido to set up first.\n"
243
506
  );
244
507
  process.exit(1);
245
508
  }
@@ -251,7 +514,7 @@ async function main() {
251
514
  if (!configExists()) {
252
515
  const { cfg, terminalMode } = await runWizard();
253
516
  if (terminalMode) {
254
- const { startChatSession } = await import("./session-RFQTJ6WZ.js");
517
+ const { startChatSession } = await import("./session-2BM4AYLG.js");
255
518
  await startChatSession(cfg);
256
519
  } else {
257
520
  await startServer();
@@ -259,7 +522,7 @@ async function main() {
259
522
  } else {
260
523
  const cfg = loadConfig();
261
524
  process.stderr.write(
262
- `@moly/lido \u2014 starting MCP server (${cfg.mode} \xB7 ${cfg.network})
525
+ `@moly-mcp/lido \u2014 starting MCP server (${cfg.mode} \xB7 ${cfg.network})
263
526
  `
264
527
  );
265
528
  await startServer();
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ loadConfig,
4
+ updateConfig
5
+ } from "./chunk-P6VFMSPM.js";
6
+
7
+ // src/alerts/store.ts
8
+ import { existsSync, readFileSync, writeFileSync, chmodSync } from "fs";
9
+ import { homedir } from "os";
10
+ import { join } from "path";
11
+ import { randomUUID } from "crypto";
12
+ var ALERTS_PATH = join(homedir(), ".moly", "alerts.json");
13
+ function loadAlerts() {
14
+ if (!existsSync(ALERTS_PATH)) return { alerts: [] };
15
+ try {
16
+ return JSON.parse(readFileSync(ALERTS_PATH, "utf-8"));
17
+ } catch {
18
+ return { alerts: [] };
19
+ }
20
+ }
21
+ function saveAlerts(data) {
22
+ writeFileSync(ALERTS_PATH, JSON.stringify(data, null, 2), "utf-8");
23
+ try {
24
+ chmodSync(ALERTS_PATH, 384);
25
+ } catch {
26
+ }
27
+ }
28
+ function addAlert(params) {
29
+ const data = loadAlerts();
30
+ const alert = {
31
+ id: randomUUID(),
32
+ condition: params.condition,
33
+ threshold: params.threshold,
34
+ channel: params.channel ?? "telegram",
35
+ enabled: true,
36
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
37
+ };
38
+ data.alerts.push(alert);
39
+ saveAlerts(data);
40
+ return alert;
41
+ }
42
+ function removeAlert(id) {
43
+ const data = loadAlerts();
44
+ const before = data.alerts.length;
45
+ data.alerts = data.alerts.filter((a) => a.id !== id);
46
+ if (data.alerts.length === before) return false;
47
+ saveAlerts(data);
48
+ return true;
49
+ }
50
+ function loadChannelConfig() {
51
+ const cfg = loadConfig();
52
+ return cfg.alertChannels ?? {};
53
+ }
54
+ function saveChannelConfig(channels) {
55
+ updateConfig({ alertChannels: channels });
56
+ }
57
+
58
+ export {
59
+ loadAlerts,
60
+ saveAlerts,
61
+ addAlert,
62
+ removeAlert,
63
+ loadChannelConfig,
64
+ saveChannelConfig
65
+ };
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ addAlert,
4
+ loadAlerts,
5
+ loadChannelConfig,
6
+ removeAlert,
7
+ saveChannelConfig
8
+ } from "./chunk-6F64RPQQ.js";
9
+
10
+ // src/tools/alerts.ts
11
+ var VALID_CONDITIONS = [
12
+ "balance_below",
13
+ "balance_above",
14
+ "reward_rate_below",
15
+ "reward_rate_above",
16
+ "withdrawal_ready",
17
+ "proposal_new",
18
+ "conversion_rate_above",
19
+ "conversion_rate_below",
20
+ "reward_delta",
21
+ "governance_expiring"
22
+ ];
23
+ var NEEDS_THRESHOLD = /* @__PURE__ */ new Set([
24
+ "balance_below",
25
+ "balance_above",
26
+ "reward_rate_below",
27
+ "reward_rate_above",
28
+ "conversion_rate_above",
29
+ "conversion_rate_below",
30
+ "reward_delta"
31
+ ]);
32
+ function setAlert(params) {
33
+ const condition = params.condition;
34
+ if (!VALID_CONDITIONS.includes(condition)) {
35
+ throw new Error(`Invalid condition: ${params.condition}. Valid: ${VALID_CONDITIONS.join(", ")}`);
36
+ }
37
+ if (NEEDS_THRESHOLD.has(condition) && params.threshold === void 0) {
38
+ throw new Error(`Condition "${condition}" requires a threshold value`);
39
+ }
40
+ const channel = params.channel ?? "telegram";
41
+ if (channel !== "telegram" && channel !== "webhook") {
42
+ throw new Error(`Invalid channel: ${params.channel}. Valid: telegram, webhook`);
43
+ }
44
+ return addAlert({ condition, threshold: params.threshold, channel });
45
+ }
46
+ function listAlerts() {
47
+ return loadAlerts();
48
+ }
49
+ function removeAlertById(id) {
50
+ const removed = removeAlert(id);
51
+ return { removed, id };
52
+ }
53
+ function configureAlertChannels(params) {
54
+ const current = loadChannelConfig();
55
+ if (params.telegram_token && params.telegram_chat_id) {
56
+ current.telegram = { token: params.telegram_token, chatId: params.telegram_chat_id };
57
+ }
58
+ if (params.webhook_url) {
59
+ current.webhook = { url: params.webhook_url };
60
+ }
61
+ saveChannelConfig(current);
62
+ return {
63
+ telegram: current.telegram ? { chatId: current.telegram.chatId, token: "*** configured" } : void 0,
64
+ webhook: current.webhook ? { url: current.webhook.url } : void 0
65
+ };
66
+ }
67
+
68
+ export {
69
+ setAlert,
70
+ listAlerts,
71
+ removeAlertById,
72
+ configureAlertChannels
73
+ };
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/bounds/store.ts
4
+ import { existsSync, readFileSync, writeFileSync, unlinkSync, chmodSync } from "fs";
5
+ import { homedir } from "os";
6
+ import { join } from "path";
7
+ var BOUNDS_PATH = join(homedir(), ".moly", "bounds.json");
8
+ var DEFAULT_BOUNDS = {
9
+ maxStakePerTx: 10,
10
+ maxDailyStake: 50,
11
+ minEthReserve: 0.5,
12
+ autoRestakeThreshold: 0.05,
13
+ governanceAutoVote: false,
14
+ dailyStaked: 0,
15
+ lastResetDate: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)
16
+ };
17
+ function loadBounds() {
18
+ if (!existsSync(BOUNDS_PATH)) return { ...DEFAULT_BOUNDS };
19
+ try {
20
+ return { ...DEFAULT_BOUNDS, ...JSON.parse(readFileSync(BOUNDS_PATH, "utf-8")) };
21
+ } catch {
22
+ return { ...DEFAULT_BOUNDS };
23
+ }
24
+ }
25
+ function saveBounds(b) {
26
+ writeFileSync(BOUNDS_PATH, JSON.stringify(b, null, 2), "utf-8");
27
+ try {
28
+ chmodSync(BOUNDS_PATH, 384);
29
+ } catch {
30
+ }
31
+ }
32
+ function resetBounds() {
33
+ if (existsSync(BOUNDS_PATH)) unlinkSync(BOUNDS_PATH);
34
+ }
35
+
36
+ export {
37
+ DEFAULT_BOUNDS,
38
+ loadBounds,
39
+ saveBounds,
40
+ resetBounds
41
+ };