unity-open-mcp 1.2.3 → 1.4.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 (174) hide show
  1. package/README.md +34 -2
  2. package/dist/agent-identity.d.ts +12 -0
  3. package/dist/agent-identity.js +2 -0
  4. package/dist/agent-identity.js.map +1 -1
  5. package/dist/batch-spawn.d.ts +39 -0
  6. package/dist/batch-spawn.js +287 -69
  7. package/dist/batch-spawn.js.map +1 -1
  8. package/dist/capabilities/build-capabilities.js +2 -0
  9. package/dist/capabilities/build-capabilities.js.map +1 -1
  10. package/dist/capabilities/discovery.d.ts +51 -0
  11. package/dist/capabilities/discovery.js +87 -0
  12. package/dist/capabilities/discovery.js.map +1 -0
  13. package/dist/capabilities/lifecycle.d.ts +6 -0
  14. package/dist/capabilities/lifecycle.js +17 -1
  15. package/dist/capabilities/lifecycle.js.map +1 -1
  16. package/dist/capabilities/tool-groups.js +1 -0
  17. package/dist/capabilities/tool-groups.js.map +1 -1
  18. package/dist/child-supervision.d.ts +6 -0
  19. package/dist/child-supervision.js +12 -0
  20. package/dist/child-supervision.js.map +1 -1
  21. package/dist/cli/args.d.ts +24 -2
  22. package/dist/cli/args.js +97 -2
  23. package/dist/cli/args.js.map +1 -1
  24. package/dist/cli/cli.js +42 -3
  25. package/dist/cli/cli.js.map +1 -1
  26. package/dist/cli/help-text.d.ts +1 -0
  27. package/dist/cli/help-text.js +83 -3
  28. package/dist/cli/help-text.js.map +1 -1
  29. package/dist/cli/json-guards.d.ts +2 -0
  30. package/dist/cli/json-guards.js +5 -0
  31. package/dist/cli/json-guards.js.map +1 -0
  32. package/dist/cli/setup-command.d.ts +116 -0
  33. package/dist/cli/setup-command.js +696 -0
  34. package/dist/cli/setup-command.js.map +1 -0
  35. package/dist/cli/update-command.d.ts +30 -0
  36. package/dist/cli/update-command.js +271 -0
  37. package/dist/cli/update-command.js.map +1 -0
  38. package/dist/compile-verify.d.ts +3 -0
  39. package/dist/compile-verify.js +4 -0
  40. package/dist/compile-verify.js.map +1 -1
  41. package/dist/constants.d.ts +10 -2
  42. package/dist/constants.js +10 -2
  43. package/dist/constants.js.map +1 -1
  44. package/dist/dialog-dismiss.d.ts +108 -12
  45. package/dist/dialog-dismiss.js +294 -109
  46. package/dist/dialog-dismiss.js.map +1 -1
  47. package/dist/dialog-policy.d.ts +55 -9
  48. package/dist/dialog-policy.js +85 -15
  49. package/dist/dialog-policy.js.map +1 -1
  50. package/dist/editor-search.d.ts +2 -0
  51. package/dist/editor-search.js +8 -0
  52. package/dist/editor-search.js.map +1 -0
  53. package/dist/gate-error.d.ts +3 -0
  54. package/dist/gate-error.js.map +1 -1
  55. package/dist/index.js +56 -1
  56. package/dist/index.js.map +1 -1
  57. package/dist/instance-discovery.d.ts +16 -0
  58. package/dist/instance-discovery.js +45 -0
  59. package/dist/instance-discovery.js.map +1 -1
  60. package/dist/jobs/adapters.d.ts +15 -0
  61. package/dist/jobs/adapters.js +147 -0
  62. package/dist/jobs/adapters.js.map +1 -0
  63. package/dist/jobs/handler.d.ts +3 -0
  64. package/dist/jobs/handler.js +29 -0
  65. package/dist/jobs/handler.js.map +1 -0
  66. package/dist/jobs/job-manager.d.ts +123 -0
  67. package/dist/jobs/job-manager.js +290 -0
  68. package/dist/jobs/job-manager.js.map +1 -0
  69. package/dist/live-client.d.ts +50 -2
  70. package/dist/live-client.js +221 -26
  71. package/dist/live-client.js.map +1 -1
  72. package/dist/local-tools.d.ts +17 -0
  73. package/dist/local-tools.js +47 -0
  74. package/dist/local-tools.js.map +1 -0
  75. package/dist/project-path.d.ts +99 -0
  76. package/dist/project-path.js +171 -0
  77. package/dist/project-path.js.map +1 -0
  78. package/dist/release-lookup.d.ts +32 -0
  79. package/dist/release-lookup.js +147 -0
  80. package/dist/release-lookup.js.map +1 -0
  81. package/dist/routers.d.ts +17 -3
  82. package/dist/routers.js +29 -8
  83. package/dist/routers.js.map +1 -1
  84. package/dist/running-unity.d.ts +21 -0
  85. package/dist/running-unity.js +50 -0
  86. package/dist/running-unity.js.map +1 -1
  87. package/dist/schema-defaults.d.ts +4 -3
  88. package/dist/schema-defaults.js +20 -6
  89. package/dist/schema-defaults.js.map +1 -1
  90. package/dist/server-instructions.js +7 -1
  91. package/dist/server-instructions.js.map +1 -1
  92. package/dist/server.d.ts +10 -3
  93. package/dist/server.js +24 -25
  94. package/dist/server.js.map +1 -1
  95. package/dist/setup/codex-toml.d.ts +23 -0
  96. package/dist/setup/codex-toml.js +514 -0
  97. package/dist/setup/codex-toml.js.map +1 -0
  98. package/dist/setup/portable-config.d.ts +59 -0
  99. package/dist/setup/portable-config.js +241 -0
  100. package/dist/setup/portable-config.js.map +1 -0
  101. package/dist/skill/SKILL.md +68 -0
  102. package/dist/skill/client-paths.js +2 -4
  103. package/dist/skill/client-paths.js.map +1 -1
  104. package/dist/skill/copy-references.d.ts +2 -0
  105. package/dist/skill/copy-references.js +16 -0
  106. package/dist/skill/copy-references.js.map +1 -0
  107. package/dist/skill/generate-skill.js +5 -3
  108. package/dist/skill/generate-skill.js.map +1 -1
  109. package/dist/skill/references/batch-and-gates.md +142 -0
  110. package/dist/skill/references/compile-and-safe-mode.md +163 -0
  111. package/dist/skill/references/discovery-and-groups.md +30 -0
  112. package/dist/skill/references/routing-and-lifecycle.md +62 -0
  113. package/dist/skill/references/senses-and-tests.md +41 -0
  114. package/dist/skill/references/yaml-and-offline-work.md +33 -0
  115. package/dist/templates/mcp-wrapper.sh +35 -0
  116. package/dist/tool-contract.d.ts +8 -0
  117. package/dist/tool-contract.js +253 -0
  118. package/dist/tool-contract.js.map +1 -0
  119. package/dist/tool-router.d.ts +62 -7
  120. package/dist/tool-router.js +455 -17
  121. package/dist/tool-router.js.map +1 -1
  122. package/dist/tool-session-state.d.ts +6 -0
  123. package/dist/tool-session-state.js +14 -0
  124. package/dist/tool-session-state.js.map +1 -1
  125. package/dist/tools/agent-capabilities.js +9 -0
  126. package/dist/tools/agent-capabilities.js.map +1 -1
  127. package/dist/tools/batch-execute.js +17 -14
  128. package/dist/tools/batch-execute.js.map +1 -1
  129. package/dist/tools/bridge-status.js +4 -5
  130. package/dist/tools/bridge-status.js.map +1 -1
  131. package/dist/tools/compile-check.js +6 -4
  132. package/dist/tools/compile-check.js.map +1 -1
  133. package/dist/tools/component-get.js +2 -2
  134. package/dist/tools/component-get.js.map +1 -1
  135. package/dist/tools/component-modify.js +4 -4
  136. package/dist/tools/component-modify.js.map +1 -1
  137. package/dist/tools/execute-csharp.js +8 -4
  138. package/dist/tools/execute-csharp.js.map +1 -1
  139. package/dist/tools/gameobject-modify.js +2 -1
  140. package/dist/tools/gameobject-modify.js.map +1 -1
  141. package/dist/tools/index.d.ts +2 -0
  142. package/dist/tools/index.js +10 -2
  143. package/dist/tools/index.js.map +1 -1
  144. package/dist/tools/inputsim-key.js +12 -14
  145. package/dist/tools/inputsim-key.js.map +1 -1
  146. package/dist/tools/inputsim-pointer.js +3 -3
  147. package/dist/tools/inputsim-pointer.js.map +1 -1
  148. package/dist/tools/inputsim-step.js +7 -8
  149. package/dist/tools/inputsim-step.js.map +1 -1
  150. package/dist/tools/inputsim-touch.js +9 -10
  151. package/dist/tools/inputsim-touch.js.map +1 -1
  152. package/dist/tools/jobs.d.ts +40 -0
  153. package/dist/tools/jobs.js +18 -0
  154. package/dist/tools/jobs.js.map +1 -0
  155. package/dist/tools/manage-tools.js +10 -5
  156. package/dist/tools/manage-tools.js.map +1 -1
  157. package/dist/tools/project-commands.d.ts +40 -0
  158. package/dist/tools/project-commands.js +23 -0
  159. package/dist/tools/project-commands.js.map +1 -0
  160. package/dist/tools/read-compile-errors.js +14 -93
  161. package/dist/tools/read-compile-errors.js.map +1 -1
  162. package/dist/tools/scan-paths.js +4 -4
  163. package/dist/tools/scan-paths.js.map +1 -1
  164. package/dist/tools/schema-fragments.d.ts +15 -3
  165. package/dist/tools/schema-fragments.js +23 -13
  166. package/dist/tools/schema-fragments.js.map +1 -1
  167. package/dist/tools/upgrade.d.ts +40 -0
  168. package/dist/tools/upgrade.js +55 -0
  169. package/dist/tools/upgrade.js.map +1 -0
  170. package/dist/unity-install-discovery.d.ts +24 -0
  171. package/dist/unity-install-discovery.js +113 -4
  172. package/dist/unity-install-discovery.js.map +1 -1
  173. package/package.json +4 -3
  174. package/templates/mcp-wrapper.sh +35 -0
package/dist/server.js CHANGED
@@ -38,18 +38,24 @@ const PACKAGE_VERSION = readPackageVersion();
38
38
  /** Name → tool lookup, built once for default-injection in the CallTool handler. */
39
39
  const TOOL_BY_NAME = new Map(ALL_TOOLS.map((t) => [t.name, t]));
40
40
  /**
41
- * Resolve env for the MCP server. UNITY_PROJECT_PATH is mandatory. The port
42
- * uses M13 T4.3 instance discovery:
41
+ * Resolve env for the MCP server. The port uses M13 T4.3 instance discovery:
43
42
  * 1. UNITY_OPEN_MCP_BRIDGE_PORT env var (override wins; users who pin a
44
43
  * port keep working as before)
45
44
  * 2. ~/.unity-open-mcp/instances/<hash>.json lock file (when its pid is alive)
46
45
  * 3. deterministic hash of the project path (20000 + sha256 % 10000)
47
46
  * The resolved port is logged so users can see which bridge was picked.
47
+ *
48
+ * The project path is normally resolved by the caller (`index.ts`, via
49
+ * `resolveProjectPath`) and passed in already absolute, so a portable spawn
50
+ * (`--project-from-cwd`) never has to round-trip through the environment.
51
+ * When no path is supplied this falls back to raw `UNITY_PROJECT_PATH` for
52
+ * embedders that call `getEnv()` directly.
48
53
  */
49
- export function getEnv() {
50
- const projectPath = process.env[PROJECT_PATH_ENV_VAR];
54
+ export function getEnv(options = {}) {
55
+ const projectPath = options.projectPath ?? process.env[PROJECT_PATH_ENV_VAR];
51
56
  if (!projectPath) {
52
- console.error(`unity-open-mcp: ${PROJECT_PATH_ENV_VAR} environment variable is required.`);
57
+ console.error(`unity-open-mcp: ${PROJECT_PATH_ENV_VAR} environment variable is required ` +
58
+ "(or pass --project-from-cwd).");
53
59
  process.exit(1);
54
60
  }
55
61
  const rawEnvPort = process.env[PORT_ENV_VAR];
@@ -115,6 +121,7 @@ export function createServer(projectPath, port, authToken, envPort) {
115
121
  }
116
122
  };
117
123
  const router = new ToolRouter(liveClient, batchSpawn, projectPath, eventStream, sessionState, notifyToolListChanged);
124
+ server.onclose = () => router.closeJobs();
118
125
  const resourceRouter = new ResourceRouter({
119
126
  live: liveClient,
120
127
  pingCache,
@@ -165,29 +172,21 @@ export function createServer(projectPath, port, authToken, envPort) {
165
172
  });
166
173
  }
167
174
  }
175
+ // Job ownership is per request, even when the bridge port is unchanged:
176
+ // the identity travels with the call through every route, including a
177
+ // tool reached via manage_tools(action: invoke).
178
+ const identity = { agent: routing.agentId, port: routing.portOverride };
168
179
  // No port override → default router (the common single-bridge case).
169
180
  if (routing.portOverride === undefined) {
170
- return router.route(name, routedArgs);
181
+ return router.route(name, routedArgs, identity);
171
182
  }
172
- // Port override → build a transient LiveClient aimed at the override
173
- // port. The override bypasses shared session state: it is a fresh client
174
- // that resolves its own auth token from the override port's instance
175
- // lock (when one exists). The agent id travels as X-Agent-Id so the
176
- // target bridge's fair queue can schedule it.
177
- const overrideAuth = resolveAuthToken(projectPath, routing.portOverride);
178
- // M23 Plan 3 review (M2) — the override client targets a different bridge
179
- // port than the default client, so it must NOT share the default
180
- // PingCache: a fresh snapshot from the default bridge would let a call
181
- // to bridge B short-circuit its own /ping and bypass
182
- // bridge_not_connected / compile-wait checks. A private per-client cache
183
- // (matching the client's per-request, session-bypassing nature) keeps
184
- // each port's ready-state isolated.
185
- const overrideLive = new LiveClient(routing.portOverride, new PingCache(), overrideAuth, projectPath, routing.agentId,
186
- // A per-request _meta.port override is authoritative exactly like an
187
- // env-port override: pass it as envPort so refreshEndpointFromLock is
188
- // a no-op for this transient client (no lock re-resolution).
189
- routing.portOverride);
190
- return router.routeOverride(name, routedArgs, overrideLive);
183
+ // Port override → a transient LiveClient bound to the Editor whose
184
+ // instance lock claims the override port (see forPortOverride). It
185
+ // bypasses shared session state, including the default PingCache. The
186
+ // agent id travels as X-Agent-Id so the target bridge's fair queue can
187
+ // schedule it.
188
+ const overrideLive = LiveClient.forPortOverride(routing.portOverride, projectPath, routing.agentId);
189
+ return router.routeOverride(name, routedArgs, overrideLive, identity);
191
190
  });
192
191
  server.setRequestHandler(ListResourcesRequestSchema, async () => ({
193
192
  resources: ALL_RESOURCES,
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,EAAE;AACF,wEAAwE;AACxE,4EAA4E;AAC5E,yEAAyE;AACzE,2EAA2E;AAC3E,0EAA0E;AAC1E,EAAE;AACF,+EAA+E;AAC/E,2EAA2E;AAC3E,2EAA2E;AAC3E,8EAA8E;AAC9E,0BAA0B;AAE1B,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EACL,sBAAsB,EACtB,qBAAqB,EACrB,0BAA0B,EAC1B,yBAAyB,GAC1B,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,4BAA4B,EAAE,MAAM,wBAAwB,CAAC;AACtE,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC/E,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AACxE,OAAO,EACL,YAAY,EACZ,oBAAoB,EACpB,aAAa,GACd,MAAM,gBAAgB,CAAC;AACxB,qEAAqE;AACrE,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAEtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EACL,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,yBAAyB,CAAC;AAEjC,0EAA0E;AAC1E,2EAA2E;AAC3E,oDAAoD;AACpD,MAAM,eAAe,GAAG,kBAAkB,EAAE,CAAC;AAE7C,oFAAoF;AACpF,MAAM,YAAY,GAAG,IAAI,GAAG,CAC1B,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAClC,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,UAAU,MAAM;IACpB,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;IACtD,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,OAAO,CAAC,KAAK,CACX,mBAAmB,oBAAoB,oCAAoC,CAC5E,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;IAC7C,2EAA2E;IAC3E,0EAA0E;IAC1E,0EAA0E;IAC1E,6EAA6E;IAC7E,2EAA2E;IAC3E,MAAM,OAAO,GAAG,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAClE,MAAM,eAAe,GAAG,OAAO,OAAO,KAAK,QAAQ,IAAI,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,OAAO,IAAI,CAAC,CAAC;IACjG,MAAM,eAAe,GAAG,UAAU,IAAI,eAAe,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;IAC5E,MAAM,IAAI,GAAG,WAAW,CAAC,WAAW,EAAE,eAAe,CAAC,CAAC;IACvD,MAAM,MAAM,GACV,UAAU,IAAI,eAAe;QAC3B,CAAC,CAAC,cAAc;QAChB,CAAC,CAAC,oBAAoB,CAAC;IAC3B,OAAO,CAAC,KAAK,CACX,4CAA4C,IAAI,KAAK,MAAM,iBAAiB,WAAW,EAAE,CAC1F,CAAC;IACF,0EAA0E;IAC1E,0EAA0E;IAC1E,uEAAuE;IACvE,kEAAkE;IAClE,MAAM,SAAS,GAAG,gBAAgB,CAChC,WAAW,EACX,eAAe,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CACtC,CAAC;IACF,IAAI,SAAS,EAAE,CAAC;QACd,OAAO,CAAC,KAAK,CAAC,mEAAmE,CAAC,CAAC;IACrF,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,KAAK,CACX,+EAA+E,CAChF,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,eAAe,EAAE,CAAC;AACpE,CAAC;AAED,MAAM,UAAU,YAAY,CAC1B,WAAmB,EACnB,IAAY,EACZ,SAAkB,EAClB,OAAgB;IAEhB,MAAM,MAAM,GAAG,IAAI,MAAM,CACvB,EAAE,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,eAAe,EAAE,EACpD;QACE,YAAY,EAAE,EAAE,KAAK,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE;QAC7D,uEAAuE;QACvE,yEAAyE;QACzE,sEAAsE;QACtE,+DAA+D;QAC/D,kDAAkD;QAClD,YAAY,EAAE,qBAAqB,EAAE;KACtC,CACF,CAAC;IAEF,MAAM,SAAS,GAAG,IAAI,SAAS,EAAE,CAAC;IAClC,MAAM,UAAU,GAAG,IAAI,UAAU,CAAC,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;IAC/F,MAAM,UAAU,GAAG,IAAI,UAAU,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC;IACnD,2EAA2E;IAC3E,yDAAyD;IACzD,4BAA4B,EAAE,CAAC;IAC/B,4EAA4E;IAC5E,2EAA2E;IAC3E,0EAA0E;IAC1E,6EAA6E;IAC7E,6EAA6E;IAC7E,qEAAqE;IACrE,MAAM,WAAW,GAAG,IAAI,iBAAiB,CACvC,aAAa,CAAC,IAAI,CAAC,EACnB,SAAS,EACT,SAAS,EACT,WAAW,EACX,OAAO,CACR,CAAC;IACF,uEAAuE;IACvE,4EAA4E;IAC5E,kEAAkE;IAClE,MAAM,YAAY,GAAG,IAAI,gBAAgB,EAAE,CAAC;IAC5C,MAAM,qBAAqB,GAAG,KAAK,IAAmB,EAAE;QACtD,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,YAAY,CAAC,EAAE,MAAM,EAAE,kCAAkC,EAAE,CAAC,CAAC;QAC5E,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CACX,kEAAkE,EAClE,GAAG,CACJ,CAAC;QACJ,CAAC;IACH,CAAC,CAAC;IACF,MAAM,MAAM,GAAG,IAAI,UAAU,CAC3B,UAAU,EACV,UAAU,EACV,WAAW,EACX,WAAW,EACX,YAAY,EACZ,qBAAqB,CACtB,CAAC;IACF,MAAM,cAAc,GAAG,IAAI,cAAc,CAAC;QACxC,IAAI,EAAE,UAAU;QAChB,SAAS;QACT,WAAW;QACX,IAAI;KACL,CAAC,CAAC;IAEH,0EAA0E;IAC1E,yEAAyE;IACzE,oEAAoE;IACpE,kCAAkC;IAClC,MAAM,CAAC,iBAAiB,CAAC,sBAAsB,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;QAC5D,KAAK,EAAE,kBAAkB,CAAC,SAAS,EAAE,YAAY,CAAC;KACnD,CAAC,CAAC,CAAC;IAEJ,MAAM,CAAC,iBAAiB,CACtB,qBAAqB,EACrB,KAAK,EAAE,OAAO,EAA2B,EAAE;QACzC,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;QACjD,MAAM,QAAQ,GAAG,CAAC,IAAI,IAAI,EAAE,CAA4B,CAAC;QACzD,sEAAsE;QACtE,wEAAwE;QACxE,uEAAuE;QACvE,yEAAyE;QACzE,MAAM,OAAO,GAAG,cAAc,CAAC,QAAQ,CAAC,CAAC;QACzC,oEAAoE;QACpE,qEAAqE;QACrE,wEAAwE;QACxE,wEAAwE;QACxE,MAAM,IAAI,GAAG,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACpC,MAAM,UAAU,GAAG,IAAI;YACrB,CAAC,CAAC,kBAAkB,CAAC,IAAI,EAAE,OAAO,CAAC,YAAY,CAAC;YAChD,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC;QACzB,2EAA2E;QAC3E,wEAAwE;QACxE,iEAAiE;QACjE,uEAAuE;QACvE,IAAI,IAAI,EAAE,CAAC;YACT,MAAM,OAAO,GAAG,mBAAmB,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;YACtD,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACvB,OAAO,eAAe,CAAC;oBACrB,IAAI,EAAE,2BAA2B;oBACjC,OAAO,EACL,iCAAiC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;wBACvD,6CAA6C;oBAC/C,MAAM,EAAE;wBACN,KAAK,EAAE;4BACL,IAAI,EAAE,2BAA2B;4BACjC,OAAO,EAAE,iCAAiC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;yBAChE;wBACD,OAAO;qBACR;iBACF,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QACD,qEAAqE;QACrE,IAAI,OAAO,CAAC,YAAY,KAAK,SAAS,EAAE,CAAC;YACvC,OAAO,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;QACxC,CAAC;QACD,qEAAqE;QACrE,yEAAyE;QACzE,qEAAqE;QACrE,oEAAoE;QACpE,8CAA8C;QAC9C,MAAM,YAAY,GAAG,gBAAgB,CAAC,WAAW,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;QACzE,0EAA0E;QAC1E,iEAAiE;QACjE,uEAAuE;QACvE,qDAAqD;QACrD,yEAAyE;QACzE,sEAAsE;QACtE,oCAAoC;QACpC,MAAM,YAAY,GAAG,IAAI,UAAU,CACjC,OAAO,CAAC,YAAY,EACpB,IAAI,SAAS,EAAE,EACf,YAAY,EACZ,WAAW,EACX,OAAO,CAAC,OAAO;QACf,qEAAqE;QACrE,sEAAsE;QACtE,6DAA6D;QAC7D,OAAO,CAAC,YAAY,CACrB,CAAC;QACF,OAAO,MAAM,CAAC,aAAa,CAAC,IAAI,EAAE,UAAU,EAAE,YAAY,CAAC,CAAC;IAC9D,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,iBAAiB,CAAC,0BAA0B,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;QAChE,SAAS,EAAE,aAAa;KACzB,CAAC,CAAC,CAAC;IAEJ,MAAM,CAAC,iBAAiB,CAAC,yBAAyB,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;QACpE,MAAM,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;QAC/B,OAAO,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAClC,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AAChB,CAAC"}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ export declare class CodexTomlError extends Error {
2
+ readonly code: string;
3
+ constructor(code: string, message: string);
4
+ }
5
+ export interface CodexServerEntry {
6
+ command: string;
7
+ args: string[];
8
+ /** Env keys this run sets. */
9
+ env: Record<string, string>;
10
+ /** Env keys this run removes when present (a stale machine path). */
11
+ removeEnv: string[];
12
+ }
13
+ /**
14
+ * Merge `entry` into `body` under `[mcp_servers.<serverKey>]`. Keys the entry
15
+ * does not own (`enabled`, timeouts, extra env vars, `.tools.*` tables) are
16
+ * left exactly as they are. Idempotent: merging the result again returns it
17
+ * unchanged.
18
+ */
19
+ export declare function mergeCodexServer(body: string, serverKey: string, entry: CodexServerEntry): string;
20
+ /** A fresh `[mcp_servers.<key>]` block (plus `.env` when there is any). */
21
+ export declare function renderCodexServer(serverKey: string, entry: CodexServerEntry, eol?: string): string;
22
+ /** TOML basic string. */
23
+ export declare function renderString(value: string): string;
@@ -0,0 +1,514 @@
1
+ // Surgical edits to a Codex `config.toml`.
2
+ //
3
+ // `setup --client codex` owns exactly one server entry: the
4
+ // `[mcp_servers.<key>]` table and its `.env` sub-table (or an inline
5
+ // `env = { … }`). Everything else in the file — other servers, their
6
+ // comments, API tokens, formatting — must survive byte for byte, so this is a
7
+ // line-level editor rather than a parse-and-reserialize: the package ships
8
+ // with one runtime dependency and a TOML serializer would reformat the whole
9
+ // file anyway.
10
+ //
11
+ // The editor recognizes the shapes Codex documents and the Hub / bridge
12
+ // window write: a `[mcp_servers.<key>]` header (bare or quoted segments), plain
13
+ // `key = value` lines (multi-line arrays and strings included), an `.env`
14
+ // sub-table or a one-line inline `env` table, comments, and CRLF files. Any
15
+ // other way of spelling the same entry (dotted keys from a parent table, an
16
+ // inline server table, arrays of tables) is refused with `CodexTomlError`
17
+ // instead of guessed at — a wrong guess would corrupt a file that usually
18
+ // holds credentials.
19
+ export class CodexTomlError extends Error {
20
+ code;
21
+ constructor(code, message) {
22
+ super(message);
23
+ this.code = code;
24
+ this.name = "CodexTomlError";
25
+ }
26
+ }
27
+ /**
28
+ * Merge `entry` into `body` under `[mcp_servers.<serverKey>]`. Keys the entry
29
+ * does not own (`enabled`, timeouts, extra env vars, `.tools.*` tables) are
30
+ * left exactly as they are. Idempotent: merging the result again returns it
31
+ * unchanged.
32
+ */
33
+ export function mergeCodexServer(body, serverKey, entry) {
34
+ const eol = body.includes("\r\n") ? "\r\n" : "\n";
35
+ const lines = body.split(/\r?\n/);
36
+ const items = scan(lines);
37
+ const main = ["mcp_servers", serverKey];
38
+ const envPath = [...main, "env"];
39
+ const label = `[${renderKeyPath(main)}]`;
40
+ for (const item of items) {
41
+ if (item.kind === "array-header" && startsWith(item.path, main)) {
42
+ throw refuse(label, "is declared as an array of tables");
43
+ }
44
+ if (item.kind === "kv") {
45
+ const full = [...item.table, ...item.key];
46
+ if (startsWith(full, main) && item.table.length < main.length) {
47
+ throw refuse(label, "is spelled with dotted keys or an inline table");
48
+ }
49
+ if (samePath(item.table, main) && item.key.length > 1 && item.key[0] === "env") {
50
+ throw refuse(label, "sets env through dotted keys");
51
+ }
52
+ }
53
+ }
54
+ const headers = items.filter((i) => i.kind === "header");
55
+ const mainHeaders = headers.filter((h) => samePath(h.path, main));
56
+ const envHeaders = headers.filter((h) => samePath(h.path, envPath));
57
+ if (mainHeaders.length > 1 || envHeaders.length > 1) {
58
+ throw refuse(label, "is declared more than once");
59
+ }
60
+ const mainHeader = mainHeaders[0];
61
+ const envHeader = envHeaders[0];
62
+ if (!mainHeader) {
63
+ if (envHeader)
64
+ throw refuse(label, "has an env table but no server table");
65
+ return appendBlock(body, eol, renderCodexServer(serverKey, entry, eol));
66
+ }
67
+ const edits = [];
68
+ const mainKvs = kvsIn(items, main);
69
+ const find = (key) => mainKvs.find((kv) => kv.key.length === 1 && kv.key[0] === key);
70
+ const missing = [];
71
+ for (const [key, rendered] of [
72
+ ["command", renderString(entry.command)],
73
+ ["args", renderStringArray(entry.args)],
74
+ ]) {
75
+ const kv = find(key);
76
+ if (!kv)
77
+ missing.push(`${key} = ${rendered}`);
78
+ else
79
+ replaceValue(edits, kv, rendered);
80
+ }
81
+ if (missing.length > 0)
82
+ edits.push({ start: mainHeader.line + 1, end: mainHeader.line, lines: missing });
83
+ const inlineEnv = find("env");
84
+ if (inlineEnv && envHeader)
85
+ throw refuse(label, "defines env both inline and as a table");
86
+ if (inlineEnv) {
87
+ if (inlineEnv.endLine !== inlineEnv.line)
88
+ throw refuse(label, "has a multi-line inline env table");
89
+ replaceValue(edits, inlineEnv, mergeInlineTable(inlineEnv.value, entry, label));
90
+ }
91
+ else if (envHeader) {
92
+ const envKvs = kvsIn(items, envPath);
93
+ const inserts = [];
94
+ for (const [key, value] of Object.entries(entry.env)) {
95
+ const kv = envKvs.find((e) => e.key.length === 1 && e.key[0] === key);
96
+ if (kv)
97
+ replaceValue(edits, kv, renderString(value));
98
+ else
99
+ inserts.push(`${renderKey(key)} = ${renderString(value)}`);
100
+ }
101
+ for (const key of entry.removeEnv) {
102
+ if (key in entry.env)
103
+ continue;
104
+ const kv = envKvs.find((e) => e.key.length === 1 && e.key[0] === key);
105
+ if (kv)
106
+ edits.push({ start: kv.line, end: kv.endLine, lines: [] });
107
+ }
108
+ if (inserts.length > 0)
109
+ edits.push({ start: envHeader.line + 1, end: envHeader.line, lines: inserts });
110
+ }
111
+ else if (Object.keys(entry.env).length > 0) {
112
+ // After the last line of the server table, before any `.tools.*` table.
113
+ const last = Math.max(mainHeader.line, ...mainKvs.map((kv) => kv.endLine));
114
+ edits.push({
115
+ start: last + 1,
116
+ end: last,
117
+ lines: ["", `[${renderKeyPath(envPath)}]`, ...envLines(entry.env)],
118
+ });
119
+ }
120
+ return applyEdits(lines, edits).join(eol);
121
+ }
122
+ /** A fresh `[mcp_servers.<key>]` block (plus `.env` when there is any). */
123
+ export function renderCodexServer(serverKey, entry, eol = "\n") {
124
+ const main = ["mcp_servers", serverKey];
125
+ const out = [
126
+ `[${renderKeyPath(main)}]`,
127
+ "enabled = true",
128
+ `command = ${renderString(entry.command)}`,
129
+ `args = ${renderStringArray(entry.args)}`,
130
+ ];
131
+ if (Object.keys(entry.env).length > 0) {
132
+ out.push("", `[${renderKeyPath([...main, "env"])}]`, ...envLines(entry.env));
133
+ }
134
+ return out.join(eol) + eol;
135
+ }
136
+ function scan(lines) {
137
+ const items = [];
138
+ let table = [];
139
+ for (let i = 0; i < lines.length; i++) {
140
+ const line = lines[i];
141
+ const trimmed = line.trimStart();
142
+ if (trimmed === "" || trimmed.startsWith("#"))
143
+ continue;
144
+ if (trimmed.startsWith("[")) {
145
+ const header = parseHeader(line);
146
+ if (header) {
147
+ items.push({ kind: header.array ? "array-header" : "header", line: i, path: header.path });
148
+ table = header.path;
149
+ continue;
150
+ }
151
+ }
152
+ const key = parseKeyPath(line, 0);
153
+ if (!key)
154
+ continue;
155
+ let pos = skipSpaces(line, key.end);
156
+ if (line[pos] !== "=")
157
+ continue;
158
+ pos = skipSpaces(line, pos + 1);
159
+ const end = scanValue(lines, i, pos);
160
+ const value = end.line === i
161
+ ? line.slice(pos, end.col)
162
+ : [line.slice(pos), ...lines.slice(i + 1, end.line), lines[end.line].slice(0, end.col)].join("\n");
163
+ items.push({
164
+ kind: "kv",
165
+ line: i,
166
+ endLine: end.line,
167
+ table,
168
+ key: key.path,
169
+ prefix: line.slice(0, pos),
170
+ value,
171
+ trailing: lines[end.line].slice(end.col),
172
+ });
173
+ i = end.line;
174
+ }
175
+ return items;
176
+ }
177
+ function parseHeader(line) {
178
+ let pos = skipSpaces(line, 0);
179
+ const array = line.startsWith("[[", pos);
180
+ pos += array ? 2 : 1;
181
+ const key = parseKeyPath(line, pos);
182
+ if (!key)
183
+ return null;
184
+ pos = skipSpaces(line, key.end);
185
+ if (!line.startsWith(array ? "]]" : "]", pos))
186
+ return null;
187
+ pos = skipSpaces(line, pos + (array ? 2 : 1));
188
+ if (pos < line.length && line[pos] !== "#")
189
+ return null;
190
+ return { path: key.path, array };
191
+ }
192
+ /** Dotted key (`a.b`, `"a b".c`, `'lit'`) starting at `start` (after spaces). */
193
+ function parseKeyPath(line, start) {
194
+ const path = [];
195
+ let pos = skipSpaces(line, start);
196
+ for (;;) {
197
+ const ch = line[pos];
198
+ let segment;
199
+ if (ch === '"') {
200
+ let out = "";
201
+ pos++;
202
+ while (pos < line.length && line[pos] !== '"') {
203
+ if (line[pos] === "\\" && pos + 1 < line.length) {
204
+ out += line[pos + 1];
205
+ pos += 2;
206
+ }
207
+ else {
208
+ out += line[pos++];
209
+ }
210
+ }
211
+ if (line[pos] !== '"')
212
+ return null;
213
+ pos++;
214
+ segment = out;
215
+ }
216
+ else if (ch === "'") {
217
+ const close = line.indexOf("'", pos + 1);
218
+ if (close < 0)
219
+ return null;
220
+ segment = line.slice(pos + 1, close);
221
+ pos = close + 1;
222
+ }
223
+ else {
224
+ const match = /^[A-Za-z0-9_-]+/.exec(line.slice(pos));
225
+ if (!match)
226
+ return null;
227
+ segment = match[0];
228
+ pos += segment.length;
229
+ }
230
+ path.push(segment);
231
+ const next = skipSpaces(line, pos);
232
+ if (line[next] !== ".")
233
+ return { path, end: pos };
234
+ pos = skipSpaces(line, next + 1);
235
+ }
236
+ }
237
+ /**
238
+ * Where the value that begins at (`line`, `col`) ends: strings (single and
239
+ * multi-line, basic and literal) and bracket nesting may carry it across
240
+ * lines. Returns the position just past the value on its last line, before
241
+ * any trailing spaces or comment.
242
+ */
243
+ function scanValue(lines, line, col) {
244
+ let state = "normal";
245
+ let depth = 0;
246
+ for (let l = line; l < lines.length; l++) {
247
+ const text = lines[l];
248
+ let lastValueEnd = l === line ? col : 0;
249
+ for (let c = l === line ? col : 0; c < text.length; c++) {
250
+ const ch = text[c];
251
+ if (state === "normal") {
252
+ if (ch === "#")
253
+ break;
254
+ if (text.startsWith('"""', c)) {
255
+ state = "ml-basic";
256
+ c += 2;
257
+ }
258
+ else if (text.startsWith("'''", c)) {
259
+ state = "ml-literal";
260
+ c += 2;
261
+ }
262
+ else if (ch === '"')
263
+ state = "basic";
264
+ else if (ch === "'")
265
+ state = "literal";
266
+ else if (ch === "[" || ch === "{")
267
+ depth++;
268
+ else if (ch === "]" || ch === "}")
269
+ depth--;
270
+ if (ch !== " " && ch !== "\t")
271
+ lastValueEnd = c + 1;
272
+ }
273
+ else if (state === "basic" || state === "ml-basic") {
274
+ if (ch === "\\") {
275
+ c++;
276
+ }
277
+ else if (state === "basic" && ch === '"') {
278
+ state = "normal";
279
+ }
280
+ else if (state === "ml-basic" && text.startsWith('"""', c)) {
281
+ state = "normal";
282
+ c += 2;
283
+ }
284
+ lastValueEnd = c + 1;
285
+ }
286
+ else {
287
+ if (state === "literal" && ch === "'")
288
+ state = "normal";
289
+ else if (state === "ml-literal" && text.startsWith("'''", c)) {
290
+ state = "normal";
291
+ c += 2;
292
+ }
293
+ lastValueEnd = c + 1;
294
+ }
295
+ }
296
+ // A single-line string cannot run past its line; treat it as closed.
297
+ if (state === "basic" || state === "literal")
298
+ state = "normal";
299
+ if (state === "normal" && depth <= 0)
300
+ return { line: l, col: lastValueEnd };
301
+ }
302
+ return { line: lines.length - 1, col: lines[lines.length - 1].length };
303
+ }
304
+ function replaceValue(edits, kv, rendered) {
305
+ if (kv.value === rendered)
306
+ return;
307
+ // Same strings spelled differently (a multi-line array, literal quotes):
308
+ // nothing to change, so the user's formatting stays.
309
+ const current = parseStrings(kv.value);
310
+ const next = parseStrings(rendered);
311
+ if (current && next && current.kind === next.kind && sameStrings(current.values, next.values))
312
+ return;
313
+ edits.push({ start: kv.line, end: kv.endLine, lines: [`${kv.prefix}${rendered}${kv.trailing}`] });
314
+ }
315
+ function applyEdits(lines, edits) {
316
+ const out = [...lines];
317
+ // Bottom-up so earlier line numbers stay valid; an insert at the same line
318
+ // as a replacement lands after it.
319
+ const ordered = [...edits].sort((a, b) => b.start - a.start || b.end - a.end);
320
+ for (const edit of ordered) {
321
+ out.splice(edit.start, Math.max(0, edit.end - edit.start + 1), ...edit.lines);
322
+ }
323
+ return out;
324
+ }
325
+ function appendBlock(body, eol, block) {
326
+ if (body.trim() === "")
327
+ return block;
328
+ let out = body.endsWith("\n") ? body : body + eol;
329
+ const lines = out.split(/\r?\n/);
330
+ const lastContent = lines.length >= 2 ? lines[lines.length - 2] : "";
331
+ if (lastContent.trim() !== "")
332
+ out += eol;
333
+ return out + block;
334
+ }
335
+ /** `{ A = "1", B = "2" }` with this run's keys set in place and removals dropped. */
336
+ function mergeInlineTable(raw, entry, label) {
337
+ const inner = raw.trim();
338
+ if (!inner.startsWith("{") || !inner.endsWith("}")) {
339
+ throw refuse(label, "has an env value that is not a table");
340
+ }
341
+ const pairs = splitTopLevel(inner.slice(1, -1));
342
+ const kept = [];
343
+ const seen = new Set();
344
+ for (const pair of pairs) {
345
+ const key = parseKeyPath(pair, 0);
346
+ if (!key)
347
+ throw refuse(label, "has an env table this editor cannot read");
348
+ const name = key.path.length === 1 ? key.path[0] : undefined;
349
+ if (name !== undefined && name in entry.env) {
350
+ kept.push(`${pair.slice(0, skipSpaces(pair, skipSpaces(pair, key.end) + 1))}${renderString(entry.env[name])}`);
351
+ seen.add(name);
352
+ }
353
+ else if (name !== undefined && entry.removeEnv.includes(name)) {
354
+ continue;
355
+ }
356
+ else {
357
+ kept.push(pair);
358
+ }
359
+ }
360
+ for (const [key, value] of Object.entries(entry.env)) {
361
+ if (!seen.has(key))
362
+ kept.push(`${renderKey(key)} = ${renderString(value)}`);
363
+ }
364
+ return kept.length === 0 ? "{}" : `{ ${kept.join(", ")} }`;
365
+ }
366
+ /** Split `a = 1, b = [2, 3]` on commas outside strings and brackets; trimmed, empties dropped. */
367
+ function splitTopLevel(text) {
368
+ const parts = [];
369
+ let depth = 0;
370
+ let quote = null;
371
+ let start = 0;
372
+ for (let i = 0; i < text.length; i++) {
373
+ const ch = text[i];
374
+ if (quote) {
375
+ if (quote === '"' && ch === "\\")
376
+ i++;
377
+ else if (ch === quote)
378
+ quote = null;
379
+ }
380
+ else if (ch === '"' || ch === "'")
381
+ quote = ch;
382
+ else if (ch === "[" || ch === "{")
383
+ depth++;
384
+ else if (ch === "]" || ch === "}")
385
+ depth--;
386
+ else if (ch === "," && depth === 0) {
387
+ parts.push(text.slice(start, i));
388
+ start = i + 1;
389
+ }
390
+ }
391
+ parts.push(text.slice(start));
392
+ return parts.map((p) => p.trim()).filter((p) => p.length > 0);
393
+ }
394
+ /**
395
+ * A string or an array of strings, read back from raw TOML; `null` for any
396
+ * other value. Only used to decide that a value is already what we would write.
397
+ */
398
+ function parseStrings(raw) {
399
+ const text = raw.trim();
400
+ const readString = (pos) => {
401
+ const quote = text[pos];
402
+ if (quote === "'") {
403
+ const close = text.indexOf("'", pos + 1);
404
+ return close < 0 ? null : { value: text.slice(pos + 1, close), end: close + 1 };
405
+ }
406
+ if (quote !== '"')
407
+ return null;
408
+ let value = "";
409
+ for (let i = pos + 1; i < text.length; i++) {
410
+ const ch = text[i];
411
+ if (ch === '"')
412
+ return { value, end: i + 1 };
413
+ if (ch !== "\\") {
414
+ value += ch;
415
+ continue;
416
+ }
417
+ const esc = text[++i];
418
+ const simple = { n: "\n", t: "\t", r: "\r", '"': '"', "\\": "\\" };
419
+ if (esc in simple)
420
+ value += simple[esc];
421
+ else if (esc === "u" || esc === "U") {
422
+ const len = esc === "u" ? 4 : 8;
423
+ value += String.fromCodePoint(parseInt(text.slice(i + 1, i + 1 + len), 16));
424
+ i += len;
425
+ }
426
+ else
427
+ return null;
428
+ }
429
+ return null;
430
+ };
431
+ if (!text.startsWith("[")) {
432
+ const single = readString(0);
433
+ return single && single.end === text.length ? { kind: "string", values: [single.value] } : null;
434
+ }
435
+ const values = [];
436
+ let pos = 1;
437
+ for (;;) {
438
+ while (pos < text.length && /[\s,]/.test(text[pos]))
439
+ pos++;
440
+ if (text[pos] === "#") {
441
+ while (pos < text.length && text[pos] !== "\n")
442
+ pos++;
443
+ continue;
444
+ }
445
+ if (text[pos] === "]")
446
+ return pos === text.length - 1 ? { kind: "array", values } : null;
447
+ const item = readString(pos);
448
+ if (!item)
449
+ return null;
450
+ values.push(item.value);
451
+ pos = item.end;
452
+ }
453
+ }
454
+ function sameStrings(a, b) {
455
+ return a.length === b.length && a.every((value, i) => value === b[i]);
456
+ }
457
+ // ---------------------------------------------------------------------------
458
+ // Rendering
459
+ // ---------------------------------------------------------------------------
460
+ function envLines(env) {
461
+ return Object.entries(env).map(([key, value]) => `${renderKey(key)} = ${renderString(value)}`);
462
+ }
463
+ function renderKey(key) {
464
+ return /^[A-Za-z0-9_-]+$/.test(key) ? key : renderString(key);
465
+ }
466
+ function renderKeyPath(path) {
467
+ return path.map(renderKey).join(".");
468
+ }
469
+ function renderStringArray(values) {
470
+ return `[${values.map(renderString).join(", ")}]`;
471
+ }
472
+ /** TOML basic string. */
473
+ export function renderString(value) {
474
+ let out = '"';
475
+ for (const ch of value) {
476
+ const code = ch.codePointAt(0);
477
+ if (ch === "\\")
478
+ out += "\\\\";
479
+ else if (ch === '"')
480
+ out += '\\"';
481
+ else if (ch === "\n")
482
+ out += "\\n";
483
+ else if (ch === "\r")
484
+ out += "\\r";
485
+ else if (ch === "\t")
486
+ out += "\\t";
487
+ else if (code < 0x20 || code === 0x7f)
488
+ out += `\\u${code.toString(16).padStart(4, "0")}`;
489
+ else
490
+ out += ch;
491
+ }
492
+ return out + '"';
493
+ }
494
+ // ---------------------------------------------------------------------------
495
+ // Helpers
496
+ // ---------------------------------------------------------------------------
497
+ function kvsIn(items, table) {
498
+ return items.filter((i) => i.kind === "kv" && samePath(i.table, table));
499
+ }
500
+ function samePath(a, b) {
501
+ return a.length === b.length && a.every((segment, i) => segment === b[i]);
502
+ }
503
+ function startsWith(path, prefix) {
504
+ return path.length >= prefix.length && prefix.every((segment, i) => path[i] === segment);
505
+ }
506
+ function skipSpaces(text, pos) {
507
+ while (pos < text.length && (text[pos] === " " || text[pos] === "\t"))
508
+ pos++;
509
+ return pos;
510
+ }
511
+ function refuse(label, why) {
512
+ return new CodexTomlError("unsupported_codex_config", `${label} ${why}; setup only edits the plain table form and leaves this file unchanged.`);
513
+ }
514
+ //# sourceMappingURL=codex-toml.js.map