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.
- package/README.md +34 -2
- package/dist/agent-identity.d.ts +12 -0
- package/dist/agent-identity.js +2 -0
- package/dist/agent-identity.js.map +1 -1
- package/dist/batch-spawn.d.ts +39 -0
- package/dist/batch-spawn.js +287 -69
- package/dist/batch-spawn.js.map +1 -1
- package/dist/capabilities/build-capabilities.js +2 -0
- package/dist/capabilities/build-capabilities.js.map +1 -1
- package/dist/capabilities/discovery.d.ts +51 -0
- package/dist/capabilities/discovery.js +87 -0
- package/dist/capabilities/discovery.js.map +1 -0
- package/dist/capabilities/lifecycle.d.ts +6 -0
- package/dist/capabilities/lifecycle.js +17 -1
- package/dist/capabilities/lifecycle.js.map +1 -1
- package/dist/capabilities/tool-groups.js +1 -0
- package/dist/capabilities/tool-groups.js.map +1 -1
- package/dist/child-supervision.d.ts +6 -0
- package/dist/child-supervision.js +12 -0
- package/dist/child-supervision.js.map +1 -1
- package/dist/cli/args.d.ts +24 -2
- package/dist/cli/args.js +97 -2
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/cli.js +42 -3
- package/dist/cli/cli.js.map +1 -1
- package/dist/cli/help-text.d.ts +1 -0
- package/dist/cli/help-text.js +83 -3
- package/dist/cli/help-text.js.map +1 -1
- package/dist/cli/json-guards.d.ts +2 -0
- package/dist/cli/json-guards.js +5 -0
- package/dist/cli/json-guards.js.map +1 -0
- package/dist/cli/setup-command.d.ts +116 -0
- package/dist/cli/setup-command.js +696 -0
- package/dist/cli/setup-command.js.map +1 -0
- package/dist/cli/update-command.d.ts +30 -0
- package/dist/cli/update-command.js +271 -0
- package/dist/cli/update-command.js.map +1 -0
- package/dist/compile-verify.d.ts +3 -0
- package/dist/compile-verify.js +4 -0
- package/dist/compile-verify.js.map +1 -1
- package/dist/constants.d.ts +10 -2
- package/dist/constants.js +10 -2
- package/dist/constants.js.map +1 -1
- package/dist/dialog-dismiss.d.ts +108 -12
- package/dist/dialog-dismiss.js +294 -109
- package/dist/dialog-dismiss.js.map +1 -1
- package/dist/dialog-policy.d.ts +55 -9
- package/dist/dialog-policy.js +85 -15
- package/dist/dialog-policy.js.map +1 -1
- package/dist/editor-search.d.ts +2 -0
- package/dist/editor-search.js +8 -0
- package/dist/editor-search.js.map +1 -0
- package/dist/gate-error.d.ts +3 -0
- package/dist/gate-error.js.map +1 -1
- package/dist/index.js +56 -1
- package/dist/index.js.map +1 -1
- package/dist/instance-discovery.d.ts +16 -0
- package/dist/instance-discovery.js +45 -0
- package/dist/instance-discovery.js.map +1 -1
- package/dist/jobs/adapters.d.ts +15 -0
- package/dist/jobs/adapters.js +147 -0
- package/dist/jobs/adapters.js.map +1 -0
- package/dist/jobs/handler.d.ts +3 -0
- package/dist/jobs/handler.js +29 -0
- package/dist/jobs/handler.js.map +1 -0
- package/dist/jobs/job-manager.d.ts +123 -0
- package/dist/jobs/job-manager.js +290 -0
- package/dist/jobs/job-manager.js.map +1 -0
- package/dist/live-client.d.ts +50 -2
- package/dist/live-client.js +221 -26
- package/dist/live-client.js.map +1 -1
- package/dist/local-tools.d.ts +17 -0
- package/dist/local-tools.js +47 -0
- package/dist/local-tools.js.map +1 -0
- package/dist/project-path.d.ts +99 -0
- package/dist/project-path.js +171 -0
- package/dist/project-path.js.map +1 -0
- package/dist/release-lookup.d.ts +32 -0
- package/dist/release-lookup.js +147 -0
- package/dist/release-lookup.js.map +1 -0
- package/dist/routers.d.ts +17 -3
- package/dist/routers.js +29 -8
- package/dist/routers.js.map +1 -1
- package/dist/running-unity.d.ts +21 -0
- package/dist/running-unity.js +50 -0
- package/dist/running-unity.js.map +1 -1
- package/dist/schema-defaults.d.ts +4 -3
- package/dist/schema-defaults.js +20 -6
- package/dist/schema-defaults.js.map +1 -1
- package/dist/server-instructions.js +7 -1
- package/dist/server-instructions.js.map +1 -1
- package/dist/server.d.ts +10 -3
- package/dist/server.js +24 -25
- package/dist/server.js.map +1 -1
- package/dist/setup/codex-toml.d.ts +23 -0
- package/dist/setup/codex-toml.js +514 -0
- package/dist/setup/codex-toml.js.map +1 -0
- package/dist/setup/portable-config.d.ts +59 -0
- package/dist/setup/portable-config.js +241 -0
- package/dist/setup/portable-config.js.map +1 -0
- package/dist/skill/SKILL.md +68 -0
- package/dist/skill/client-paths.js +2 -4
- package/dist/skill/client-paths.js.map +1 -1
- package/dist/skill/copy-references.d.ts +2 -0
- package/dist/skill/copy-references.js +16 -0
- package/dist/skill/copy-references.js.map +1 -0
- package/dist/skill/generate-skill.js +5 -3
- package/dist/skill/generate-skill.js.map +1 -1
- package/dist/skill/references/batch-and-gates.md +142 -0
- package/dist/skill/references/compile-and-safe-mode.md +163 -0
- package/dist/skill/references/discovery-and-groups.md +30 -0
- package/dist/skill/references/routing-and-lifecycle.md +62 -0
- package/dist/skill/references/senses-and-tests.md +41 -0
- package/dist/skill/references/yaml-and-offline-work.md +33 -0
- package/dist/templates/mcp-wrapper.sh +35 -0
- package/dist/tool-contract.d.ts +8 -0
- package/dist/tool-contract.js +253 -0
- package/dist/tool-contract.js.map +1 -0
- package/dist/tool-router.d.ts +62 -7
- package/dist/tool-router.js +455 -17
- package/dist/tool-router.js.map +1 -1
- package/dist/tool-session-state.d.ts +6 -0
- package/dist/tool-session-state.js +14 -0
- package/dist/tool-session-state.js.map +1 -1
- package/dist/tools/agent-capabilities.js +9 -0
- package/dist/tools/agent-capabilities.js.map +1 -1
- package/dist/tools/batch-execute.js +17 -14
- package/dist/tools/batch-execute.js.map +1 -1
- package/dist/tools/bridge-status.js +4 -5
- package/dist/tools/bridge-status.js.map +1 -1
- package/dist/tools/compile-check.js +6 -4
- package/dist/tools/compile-check.js.map +1 -1
- package/dist/tools/component-get.js +2 -2
- package/dist/tools/component-get.js.map +1 -1
- package/dist/tools/component-modify.js +4 -4
- package/dist/tools/component-modify.js.map +1 -1
- package/dist/tools/execute-csharp.js +8 -4
- package/dist/tools/execute-csharp.js.map +1 -1
- package/dist/tools/gameobject-modify.js +2 -1
- package/dist/tools/gameobject-modify.js.map +1 -1
- package/dist/tools/index.d.ts +2 -0
- package/dist/tools/index.js +10 -2
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/inputsim-key.js +12 -14
- package/dist/tools/inputsim-key.js.map +1 -1
- package/dist/tools/inputsim-pointer.js +3 -3
- package/dist/tools/inputsim-pointer.js.map +1 -1
- package/dist/tools/inputsim-step.js +7 -8
- package/dist/tools/inputsim-step.js.map +1 -1
- package/dist/tools/inputsim-touch.js +9 -10
- package/dist/tools/inputsim-touch.js.map +1 -1
- package/dist/tools/jobs.d.ts +40 -0
- package/dist/tools/jobs.js +18 -0
- package/dist/tools/jobs.js.map +1 -0
- package/dist/tools/manage-tools.js +10 -5
- package/dist/tools/manage-tools.js.map +1 -1
- package/dist/tools/project-commands.d.ts +40 -0
- package/dist/tools/project-commands.js +23 -0
- package/dist/tools/project-commands.js.map +1 -0
- package/dist/tools/read-compile-errors.js +14 -93
- package/dist/tools/read-compile-errors.js.map +1 -1
- package/dist/tools/scan-paths.js +4 -4
- package/dist/tools/scan-paths.js.map +1 -1
- package/dist/tools/schema-fragments.d.ts +15 -3
- package/dist/tools/schema-fragments.js +23 -13
- package/dist/tools/schema-fragments.js.map +1 -1
- package/dist/tools/upgrade.d.ts +40 -0
- package/dist/tools/upgrade.js +55 -0
- package/dist/tools/upgrade.js.map +1 -0
- package/dist/unity-install-discovery.d.ts +24 -0
- package/dist/unity-install-discovery.js +113 -4
- package/dist/unity-install-discovery.js.map +1 -1
- package/package.json +4 -3
- 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.
|
|
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 →
|
|
173
|
-
//
|
|
174
|
-
//
|
|
175
|
-
//
|
|
176
|
-
//
|
|
177
|
-
const
|
|
178
|
-
|
|
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,
|
package/dist/server.js.map
CHANGED
|
@@ -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,
|
|
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
|