openmausbot 0.1.91 → 0.1.93

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 (164) hide show
  1. package/dist/assets/DesktopViewer-CKNR3SEW.js +5 -0
  2. package/dist/assets/{abnfDiagram-O67JEVCF-yP-w8pRq.js → abnfDiagram-O67JEVCF-Ckrm1I1X.js} +1 -1
  3. package/dist/assets/{arc-DsivfZEh.js → arc-BBdBuCgB.js} +1 -1
  4. package/dist/assets/{architectureDiagram-NJMV4G6O-CILw7lzT.js → architectureDiagram-NJMV4G6O-CDdA3Hjs.js} +1 -1
  5. package/dist/assets/{blockDiagram-BEXU5L5S-DEQUV3HF.js → blockDiagram-BEXU5L5S-AUrsx1YR.js} +1 -1
  6. package/dist/assets/{c4Diagram-YGBWAQC7-lSXCZoKA.js → c4Diagram-YGBWAQC7-BEqQ0Unh.js} +1 -1
  7. package/dist/assets/channel-BU94VpxE.js +1 -0
  8. package/dist/assets/{chunk-2Q5K7J3B-QCD5bgWP.js → chunk-2Q5K7J3B-CEEuvd4C.js} +1 -1
  9. package/dist/assets/{chunk-3FUC2YCW-C5-iZJuL.js → chunk-3FUC2YCW-DyANCiHl.js} +2 -2
  10. package/dist/assets/{chunk-5VM5RSS4-cxsawMpY.js → chunk-5VM5RSS4-Dx_Slpw9.js} +1 -1
  11. package/dist/assets/{chunk-GTNCS2PH-DV8tV8H7.js → chunk-GTNCS2PH-Dl7h7iiD.js} +1 -1
  12. package/dist/assets/{chunk-GWA4HPMP-CJFZyfbW.js → chunk-GWA4HPMP-BLx07SPb.js} +1 -1
  13. package/dist/assets/{chunk-JWPE2WC7-BigbMXco.js → chunk-JWPE2WC7-BMxDj3n7.js} +1 -1
  14. package/dist/assets/{chunk-NETBCI7D-oVxTfpDy.js → chunk-NETBCI7D-CN66PT4H.js} +1 -1
  15. package/dist/assets/{chunk-WEXAMYUT-CH4BiroW.js → chunk-WEXAMYUT-6HwhsDKw.js} +1 -1
  16. package/dist/assets/{chunk-XXDRQBXY-KEDa6gax.js → chunk-XXDRQBXY-gWnLt7SN.js} +1 -1
  17. package/dist/assets/{classDiagram-v2-NBCMYWYE-BiPjfqVv.js → classDiagram-v2-NBCMYWYE-CMS5l2ea.js} +1 -1
  18. package/dist/assets/{cose-bilkent-JH36ORCC-Caxew3OY.js → cose-bilkent-JH36ORCC-CV7sQwIG.js} +1 -1
  19. package/dist/assets/{cynefin-OW5HDTMX-B_Pui369.js → cynefin-OW5HDTMX-CSyl-7vl.js} +1 -1
  20. package/dist/assets/{cynefinDiagram-VND7K2PF-X64ydIhP.js → cynefinDiagram-VND7K2PF-BCLKL6az.js} +1 -1
  21. package/dist/assets/{dagre-6A5THRUB-TqYCI1E1.js → dagre-6A5THRUB-DiaZs6S1.js} +1 -1
  22. package/dist/assets/{diagram-22UHCM2B-TeOt3rAQ.js → diagram-22UHCM2B-BeTtmt3I.js} +1 -1
  23. package/dist/assets/{diagram-3UASUU5V-D70BLhDA.js → diagram-3UASUU5V-CsdBG-QQ.js} +1 -1
  24. package/dist/assets/{diagram-ATOU4E4O-BbQDLwUC.js → diagram-ATOU4E4O-BczvdbR1.js} +1 -1
  25. package/dist/assets/{diagram-CDSNMT55-ChNJZ1u-.js → diagram-CDSNMT55-DHTTTMhi.js} +1 -1
  26. package/dist/assets/{diagram-MLGK6HIB-DMBl2APa.js → diagram-MLGK6HIB-RTjHyd8z.js} +1 -1
  27. package/dist/assets/{diagram-MPIPVDR6-CEqDchOp.js → diagram-MPIPVDR6-BDenHEH_.js} +1 -1
  28. package/dist/assets/{ebnfDiagram-ZINNZB2B-BVDKRp_-.js → ebnfDiagram-ZINNZB2B-BSiRSbtL.js} +1 -1
  29. package/dist/assets/{elk-276RUBZZ-CRkofO9B.js → elk-276RUBZZ-BCAElss-.js} +1 -1
  30. package/dist/assets/{erDiagram-OPXOYQCR-DruOMh5e.js → erDiagram-OPXOYQCR-C4_1ZPJe.js} +1 -1
  31. package/dist/assets/{flowDiagram-KWPJA3E3-8GMQeB7t.js → flowDiagram-KWPJA3E3-Bcb4Z75S.js} +1 -1
  32. package/dist/assets/{ganttDiagram-FUAMR5RP-ORE9jcsM.js → ganttDiagram-FUAMR5RP-BiPOzxzb.js} +1 -1
  33. package/dist/assets/{gitGraphDiagram-X574FWY7-BmsedsWh.js → gitGraphDiagram-X574FWY7-B1wtF3ZS.js} +1 -1
  34. package/dist/assets/index-Biji-bfL.css +1 -0
  35. package/dist/assets/{index-DKNEM8Q6.js → index-BkYwilkG.js} +1 -1
  36. package/dist/assets/index-DZlhoES0.js +597 -0
  37. package/dist/assets/{infoDiagram-VRGFBTTK-E_p7AooJ.js → infoDiagram-VRGFBTTK-A19No71U.js} +1 -1
  38. package/dist/assets/{ishikawaDiagram-OU5B5YK6-Y-B2mKda.js → ishikawaDiagram-OU5B5YK6-BQsqJ_cV.js} +1 -1
  39. package/dist/assets/{journeyDiagram-ZHPQQLJL-eA8RELcK.js → journeyDiagram-ZHPQQLJL-DAO0pVan.js} +1 -1
  40. package/dist/assets/{kanban-definition-PNTS6WVX-DqIL2e8e.js → kanban-definition-PNTS6WVX-NepmKEe2.js} +1 -1
  41. package/dist/assets/{linear-yC_29R6e.js → linear-bq1PTDTW.js} +1 -1
  42. package/dist/assets/{mermaid.core-VhEMkSND.js → mermaid.core-s4LTbQle.js} +5 -5
  43. package/dist/assets/{mindmap-definition-NLK3R4M7-oJ7kyQZy.js → mindmap-definition-NLK3R4M7-LtdbgHFH.js} +1 -1
  44. package/dist/assets/{pegDiagram-GJSIUBJH-BmfYcY0O.js → pegDiagram-GJSIUBJH-tdbaCxiK.js} +1 -1
  45. package/dist/assets/{pieDiagram-5QR66LMP-DKvgP7w9.js → pieDiagram-5QR66LMP-DvkbpW8l.js} +1 -1
  46. package/dist/assets/{quadrantDiagram-O4NWA36T-BEKL3W0L.js → quadrantDiagram-O4NWA36T-Do4KaH-E.js} +1 -1
  47. package/dist/assets/{railroadDiagram-XR7U4H2S-BEsiqR5s.js → railroadDiagram-XR7U4H2S-BQWULOAs.js} +1 -1
  48. package/dist/assets/{requirementDiagram-PLB6GJNP-DWBcWvAP.js → requirementDiagram-PLB6GJNP-w9kei1FU.js} +1 -1
  49. package/dist/assets/{sankeyDiagram-IPEJSGJF-rpZ2_O4s.js → sankeyDiagram-IPEJSGJF-D2gRy0e8.js} +1 -1
  50. package/dist/assets/{sequenceDiagram-PO4LG4MO-B99HQdJR.js → sequenceDiagram-PO4LG4MO-a2sO5_Eo.js} +1 -1
  51. package/dist/assets/{sizeCapture-INFHLROL-BjN0qT5T.js → sizeCapture-INFHLROL-CiPJmFEB.js} +1 -1
  52. package/dist/assets/{stateDiagram-v2-GCMORJYK-BbsVgGsC.js → stateDiagram-v2-GCMORJYK-DAkUUlL7.js} +1 -1
  53. package/dist/assets/{swimlanes-2SLR337P-DG3gKkeR.js → swimlanes-2SLR337P-DANf2R8Y.js} +1 -1
  54. package/dist/assets/swimlanesDiagram-TC7HE7FX-CdHxG8aM.js +8 -0
  55. package/dist/assets/{timeline-definition-EJHVYXUP-BTHOdtF_.js → timeline-definition-EJHVYXUP-BKpQmzrT.js} +1 -1
  56. package/dist/assets/{usecaseDiagram-POWQR4AR-acLSLFwc.js → usecaseDiagram-POWQR4AR-BtI7qLN-.js} +1 -1
  57. package/dist/assets/{vennDiagram-UO4OBE2U-C5JzNX9G.js → vennDiagram-UO4OBE2U-gXJXrAG_.js} +1 -1
  58. package/dist/assets/{wardleyDiagram-VNRHLVJA-DL1-PrR_.js → wardleyDiagram-VNRHLVJA-GU8Po5-n.js} +1 -1
  59. package/dist/assets/{xychartDiagram-PMCCYNJV-Bvr1477p.js → xychartDiagram-PMCCYNJV-BF4J30g6.js} +1 -1
  60. package/dist/index.html +2 -2
  61. package/dist/novnc-NOTICE.txt +118 -0
  62. package/dist-server/cloud-home-start.js +112 -14
  63. package/dist-server/companion/src/routes.js +24 -1
  64. package/dist-server/container-mcp.js +201 -16
  65. package/dist-server/drivers/agents-proxy.js +7 -4
  66. package/dist-server/enterprise/server/index.js +5 -1
  67. package/dist-server/index.js +10698 -4737
  68. package/dist-server/local-computer-proxy.js +133 -8
  69. package/dist-server/mcp-gate.js +16 -6
  70. package/dist-server/model-catalog/models-dev.snapshot.json +7299 -0
  71. package/dist-server/openmausbot.js +4656 -1258
  72. package/dist-server/pair-cli.js +4656 -1258
  73. package/dist-server/server/boat.js +5 -5
  74. package/dist-server/server/browser-engine.js +10 -1
  75. package/dist-server/server/browser-live.js +46 -2
  76. package/dist-server/server/browser-runtime.js +74 -8
  77. package/dist-server/server/browser-tool-shape.js +6 -4
  78. package/dist-server/server/chief-of-staff.js +1 -1
  79. package/dist-server/server/cloud-home-start.js +118 -26
  80. package/dist-server/server/cloud-home.js +39 -3
  81. package/dist-server/server/cloud-lending.js +74 -10
  82. package/dist-server/server/cloud-owner.js +196 -0
  83. package/dist-server/server/cloud-secrets-boot.js +6 -0
  84. package/dist-server/server/cloud-secrets.js +65 -0
  85. package/dist-server/server/computer-control.js +6 -0
  86. package/dist-server/server/config.js +187 -14
  87. package/dist-server/server/container-computer.js +46 -34
  88. package/dist-server/server/desktop-viewer-targets.js +26 -0
  89. package/dist-server/server/digest.js +12 -2
  90. package/dist-server/server/drivers/acp/core.js +339 -33
  91. package/dist-server/server/drivers/acp/cursor.js +13 -1
  92. package/dist-server/server/drivers/acp/opencode-go.js +614 -85
  93. package/dist-server/server/drivers/acp/quiet-status.js +257 -0
  94. package/dist-server/server/drivers/acp/qwen.js +18 -0
  95. package/dist-server/server/drivers/agents-catalog.js +6 -6
  96. package/dist-server/server/drivers/antigravity-acp.js +69 -11
  97. package/dist-server/server/drivers/antigravity-temp.js +360 -0
  98. package/dist-server/server/drivers/antigravity.js +21 -0
  99. package/dist-server/server/drivers/builtIn.js +2 -0
  100. package/dist-server/server/drivers/cerebras.js +108 -0
  101. package/dist-server/server/drivers/chat-mcp-tools.js +20 -3
  102. package/dist-server/server/drivers/chatgpt-plan-auth.js +481 -0
  103. package/dist-server/server/drivers/claude.js +329 -37
  104. package/dist-server/server/drivers/codex.js +228 -18
  105. package/dist-server/server/drivers/grok.js +2 -2
  106. package/dist-server/server/drivers/mistral.js +4 -3
  107. package/dist-server/server/drivers/openai-chat-protocol.js +4 -3
  108. package/dist-server/server/drivers/openai-chat.js +66 -4
  109. package/dist-server/server/drivers/openai-compat.js +73 -23
  110. package/dist-server/server/drivers/prompt-split.js +2 -0
  111. package/dist-server/server/engine-install.js +46 -12
  112. package/dist-server/server/env-path.js +21 -5
  113. package/dist-server/server/harness/registry.js +2 -2
  114. package/dist-server/server/index.js +1545 -238
  115. package/dist-server/server/lending-memory.js +479 -0
  116. package/dist-server/server/local-vm-idle.js +24 -6
  117. package/dist-server/server/local-vm-stop-reason.js +26 -0
  118. package/dist-server/server/managed-desktop.js +3 -0
  119. package/dist-server/server/mcp-bridge.js +5 -3
  120. package/dist-server/server/mcp-http.js +6 -3
  121. package/dist-server/server/mcp-oauth-discovery.js +133 -0
  122. package/dist-server/server/mcp-oauth-store.js +70 -0
  123. package/dist-server/server/mcp-oauth.js +550 -0
  124. package/dist-server/server/mcp-probe.js +14 -1
  125. package/dist-server/server/mcp-registry.js +43 -0
  126. package/dist-server/server/memory-upkeep.js +94 -84
  127. package/dist-server/server/model-catalog/catalog.js +374 -0
  128. package/dist-server/server/model-catalog/presets.js +176 -0
  129. package/dist-server/server/model-catalog/trim.js +219 -0
  130. package/dist-server/server/provider-key-check.js +5 -2
  131. package/dist-server/server/recent-work.js +1 -1
  132. package/dist-server/server/request-auth.js +23 -0
  133. package/dist-server/server/routes/antigravity-leftovers.js +45 -0
  134. package/dist-server/server/routes/bot-memory.js +31 -6
  135. package/dist-server/server/routes/desktop-viewer.js +238 -0
  136. package/dist-server/server/routine-requests.js +1 -1
  137. package/dist-server/server/routines.js +6 -3
  138. package/dist-server/server/sessions.js +56 -9
  139. package/dist-server/server/skills.js +25 -10
  140. package/dist-server/server/store.js +116 -122
  141. package/dist-server/server/thread-events.js +2 -0
  142. package/dist-server/server/thread-starters.js +33 -0
  143. package/dist-server/server/thread-title.js +46 -0
  144. package/dist-server/server/tts/fish.js +4 -5
  145. package/dist-server/server/tts/index.js +5 -3
  146. package/dist-server/server/vps-computer.js +80 -13
  147. package/dist-server/server/webhooks.js +18 -1
  148. package/dist-server/server/workspace-backup-policy.js +3 -0
  149. package/dist-server/server/workspace-backup.js +25 -0
  150. package/dist-server/server/workspace.js +66 -16
  151. package/dist-server/shared/approval-mode.js +1 -1
  152. package/dist-server/shared/redact.js +1 -0
  153. package/dist-server/shared/tool-name.js +22 -0
  154. package/dist-server/shared/workspace-backup-client.js +1 -0
  155. package/dist-server/src/lib/citations.js +177 -0
  156. package/dist-server/src/lib/composer-attachments.js +7 -1
  157. package/dist-server/vps-container-mcp.js +202 -16
  158. package/dist-server/workspace-backup.worker.js +87 -9
  159. package/enterprise/server/index.js +5 -1
  160. package/package.json +1 -1
  161. package/dist/assets/channel-DrfGMu7X.js +0 -1
  162. package/dist/assets/index-BlnCEcU6.js +0 -588
  163. package/dist/assets/index-DVxjOvID.css +0 -1
  164. package/dist/assets/swimlanesDiagram-TC7HE7FX-BCZhxl7e.js +0 -8
@@ -0,0 +1,133 @@
1
+ // Find where a URL MCP server wants its users to sign in. MCP servers that
2
+ // require OAuth answer 401 and point at their protected-resource metadata
3
+ // (RFC 9728), which names an authorization server whose own metadata
4
+ // (RFC 8414, or OpenID discovery) lists the endpoints a client needs.
5
+ // Nothing here follows a redirect or accepts a plain-http endpoint off
6
+ // this machine: a token endpoint is where a sign-in code is spent.
7
+ const FETCH_TIMEOUT_MS = 5_000;
8
+ const MAX_METADATA_BYTES = 65_536;
9
+ const LOOPBACK_HOSTS = new Set(["127.0.0.1", "localhost", "[::1]"]);
10
+ /** https anywhere; http only to this machine (local test servers). */
11
+ export function isAllowedAuthUrl(value) {
12
+ let url;
13
+ try {
14
+ url = new URL(value);
15
+ }
16
+ catch {
17
+ return false;
18
+ }
19
+ if (url.username || url.password)
20
+ return false;
21
+ if (url.protocol === "https:")
22
+ return true;
23
+ return url.protocol === "http:" && LOOPBACK_HOSTS.has(url.hostname);
24
+ }
25
+ /** The `resource_metadata` parameter of a `WWW-Authenticate: Bearer …` challenge. */
26
+ export function resourceMetadataUrl(wwwAuthenticate) {
27
+ if (!wwwAuthenticate)
28
+ return null;
29
+ const match = /resource_metadata\s*=\s*(?:"([^"]*)"|([^\s,]+))/i.exec(wwwAuthenticate);
30
+ const value = match?.[1] ?? match?.[2];
31
+ return value || null;
32
+ }
33
+ async function getJson(url, options) {
34
+ if (!isAllowedAuthUrl(url))
35
+ return null;
36
+ const timeout = AbortSignal.timeout(FETCH_TIMEOUT_MS);
37
+ const signal = options.signal ? AbortSignal.any([options.signal, timeout]) : timeout;
38
+ try {
39
+ const response = await (options.fetch ?? fetch)(url, { headers: { accept: "application/json" }, redirect: "error", signal });
40
+ if (!response.ok) {
41
+ void response.body?.cancel().catch(() => { });
42
+ return null;
43
+ }
44
+ const text = await response.text();
45
+ if (text.length > MAX_METADATA_BYTES)
46
+ return null;
47
+ const parsed = JSON.parse(text);
48
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
49
+ }
50
+ catch {
51
+ return null;
52
+ }
53
+ }
54
+ function stringList(value) {
55
+ return Array.isArray(value) && value.every((entry) => typeof entry === "string") ? value : undefined;
56
+ }
57
+ async function protectedResource(mcpUrl, hint, options) {
58
+ const path = mcpUrl.pathname === "/" ? "" : mcpUrl.pathname;
59
+ const candidates = [
60
+ ...(hint ? [hint] : []),
61
+ `${mcpUrl.origin}/.well-known/oauth-protected-resource${path}`,
62
+ `${mcpUrl.origin}/.well-known/oauth-protected-resource`,
63
+ ];
64
+ for (const candidate of new Set(candidates)) {
65
+ const document = await getJson(candidate, options);
66
+ if (document)
67
+ return document;
68
+ }
69
+ return null;
70
+ }
71
+ async function authorizationServer(issuer, options) {
72
+ const base = issuer.replace(/\/+$/, "");
73
+ const url = new URL(base);
74
+ const suffix = url.pathname === "/" ? "" : url.pathname;
75
+ const candidates = [
76
+ `${url.origin}/.well-known/oauth-authorization-server${suffix}`,
77
+ `${url.origin}/.well-known/openid-configuration${suffix}`,
78
+ ...(suffix ? [`${base}/.well-known/openid-configuration`] : []),
79
+ ];
80
+ for (const candidate of candidates) {
81
+ const document = await getJson(candidate, options);
82
+ if (document)
83
+ return document;
84
+ }
85
+ return null;
86
+ }
87
+ /** The sign-in endpoints for an MCP server, or null when it does not
88
+ * describe a usable OAuth sign-in (no metadata, no PKCE S256, or an
89
+ * endpoint that is not https). */
90
+ export async function discoverMcpAuth(mcpUrl, wwwAuthenticate, options = {}) {
91
+ let target;
92
+ try {
93
+ target = new URL(mcpUrl);
94
+ }
95
+ catch {
96
+ return null;
97
+ }
98
+ const resource = await protectedResource(target, resourceMetadataUrl(wwwAuthenticate), options);
99
+ const servers = stringList(resource?.authorization_servers);
100
+ const issuer = servers?.[0] ?? target.origin;
101
+ if (!isAllowedAuthUrl(issuer))
102
+ return null;
103
+ const metadata = await authorizationServer(issuer, options);
104
+ if (!metadata)
105
+ return null;
106
+ const authorizationEndpoint = metadata.authorization_endpoint;
107
+ const tokenEndpoint = metadata.token_endpoint;
108
+ if (typeof authorizationEndpoint !== "string" || !isAllowedAuthUrl(authorizationEndpoint))
109
+ return null;
110
+ if (typeof tokenEndpoint !== "string" || !isAllowedAuthUrl(tokenEndpoint))
111
+ return null;
112
+ if (!stringList(metadata.code_challenge_methods_supported)?.includes("S256"))
113
+ return null;
114
+ // RFC 8414 §3.3: the metadata must be for the issuer it was fetched for.
115
+ if (typeof metadata.issuer === "string" && metadata.issuer.replace(/\/+$/, "") !== issuer.replace(/\/+$/, ""))
116
+ return null;
117
+ const optionalEndpoint = (value) => typeof value === "string" && isAllowedAuthUrl(value) ? value : undefined;
118
+ const registrationEndpoint = optionalEndpoint(metadata.registration_endpoint);
119
+ const revocationEndpoint = optionalEndpoint(metadata.revocation_endpoint);
120
+ const scopes = stringList(resource?.scopes_supported);
121
+ const tokenEndpointAuthMethods = stringList(metadata.token_endpoint_auth_methods_supported);
122
+ target.hash = "";
123
+ return {
124
+ issuer,
125
+ authorizationEndpoint,
126
+ tokenEndpoint,
127
+ ...(registrationEndpoint ? { registrationEndpoint } : {}),
128
+ ...(revocationEndpoint ? { revocationEndpoint } : {}),
129
+ ...(scopes?.length ? { scopes } : {}),
130
+ ...(tokenEndpointAuthMethods?.length ? { tokenEndpointAuthMethods } : {}),
131
+ resource: target.toString(),
132
+ };
133
+ }
@@ -0,0 +1,70 @@
1
+ // Sign-in state for URL MCP servers, kept apart from config.json so a
2
+ // pasted or exported config never carries a token. One owner-only file:
3
+ // per server, the URL it was signed in for (a token never follows the
4
+ // entry to another address), the client it signed in as, and its tokens.
5
+ import { mkdirSync, readFileSync } from "node:fs";
6
+ import { dirname } from "node:path";
7
+ import { z } from "zod";
8
+ import { writeFileAtomic } from "./atomic.js";
9
+ const tokensSchema = z.object({
10
+ access: z.string().min(1),
11
+ refresh: z.string().min(1).optional(),
12
+ expiresAt: z.number().optional(),
13
+ scope: z.string().optional(),
14
+ });
15
+ const recordSchema = z.object({
16
+ url: z.string(),
17
+ state: z.enum(["needs-sign-in", "signed-in"]),
18
+ issuer: z.string().optional(),
19
+ clientId: z.string().optional(),
20
+ /** How a pre-registered app with a secret proves itself at the token
21
+ * endpoint. The secret stays in config.json; it is never kept here. */
22
+ tokenAuth: z.enum(["client_secret_post", "client_secret_basic"]).optional(),
23
+ redirectUri: z.string().optional(),
24
+ tokenEndpoint: z.string().optional(),
25
+ revocationEndpoint: z.string().optional(),
26
+ tokens: tokensSchema.optional(),
27
+ });
28
+ const fileSchema = z.object({
29
+ servers: z.record(z.string(), recordSchema).default({}),
30
+ });
31
+ export class McpOAuthStore {
32
+ file;
33
+ constructor(file) {
34
+ this.file = file;
35
+ }
36
+ /** The record for this server, only while it is still for this URL. */
37
+ get(name, url) {
38
+ const record = this.read().servers[name];
39
+ return record && record.url === url ? record : undefined;
40
+ }
41
+ put(name, record) {
42
+ const data = this.read();
43
+ data.servers[name] = recordSchema.parse(record);
44
+ this.write(data);
45
+ }
46
+ delete(name) {
47
+ const data = this.read();
48
+ if (!Object.hasOwn(data.servers, name))
49
+ return;
50
+ delete data.servers[name];
51
+ this.write(data);
52
+ }
53
+ // Read through on every call: the file is small, and another process
54
+ // (the config CLI, a second window's server) may have changed it.
55
+ read() {
56
+ try {
57
+ const parsed = fileSchema.safeParse(JSON.parse(readFileSync(this.file, "utf8")));
58
+ if (parsed.success)
59
+ return parsed.data;
60
+ }
61
+ catch {
62
+ // missing or unreadable: empty until the next write
63
+ }
64
+ return { servers: {} };
65
+ }
66
+ write(data) {
67
+ mkdirSync(dirname(this.file), { recursive: true });
68
+ writeFileAtomic(this.file, JSON.stringify(data, null, 2), { mode: 0o600 });
69
+ }
70
+ }