@ainecto/mcp 0.2.0 → 0.5.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.
@@ -5,6 +5,37 @@ import { createHash } from "crypto";
5
5
  import { mkdir, readFile, rename, rm, stat, writeFile, chmod } from "fs/promises";
6
6
  import { homedir } from "os";
7
7
  import { dirname, join } from "path";
8
+
9
+ // src/core/harness/role.ts
10
+ var HARNESS_ROLES = ["design", "development", "test", "validation"];
11
+ var ROLE_SCOPE_PREFIX = "ai-erd:role:";
12
+ var ROLE_HEADER = "X-AI-ERD-Role";
13
+ var ROLE_ENV_VAR = "AINECTO_ROLE";
14
+ var LEGACY_ROLE_ENV_VAR = "AI_ERD_ROLE";
15
+ function resolveRole(input = {}) {
16
+ if (input.role !== void 0) {
17
+ return { role: assertRole(input.role), source: "flag" };
18
+ }
19
+ const envVars = input.envVars ?? process.env;
20
+ for (const envVar of [ROLE_ENV_VAR, LEGACY_ROLE_ENV_VAR]) {
21
+ const fromEnv = envVars[envVar];
22
+ if (fromEnv && fromEnv.trim()) {
23
+ return { role: assertRole(fromEnv), source: "env", envVar };
24
+ }
25
+ }
26
+ return { source: "none" };
27
+ }
28
+ function assertRole(value) {
29
+ const normalized = value.trim().toLowerCase();
30
+ if (HARNESS_ROLES.includes(normalized)) {
31
+ return normalized;
32
+ }
33
+ throw new Error(
34
+ `Invalid role "${value}". Expected one of: ${HARNESS_ROLES.join(", ")}.`
35
+ );
36
+ }
37
+
38
+ // src/core/auth/tokenStore.ts
8
39
  var FileTokenStore = class {
9
40
  filePath;
10
41
  constructor(rootDir = join(homedir(), ".ainecto")) {
@@ -15,6 +46,7 @@ var FileTokenStore = class {
15
46
  return file[tokenKey(endpoint)];
16
47
  }
17
48
  async save(endpoint, token) {
49
+ assertNoRoleScope(token);
18
50
  const file = await this.readFile();
19
51
  file[tokenKey(endpoint)] = { ...token, endpoint, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
20
52
  await this.writeFile(file);
@@ -59,6 +91,13 @@ var FileTokenStore = class {
59
91
  function tokenKey(endpoint) {
60
92
  return createHash("sha256").update(endpoint).digest("hex");
61
93
  }
94
+ function assertNoRoleScope(token) {
95
+ if (token.scope?.split(/\s+/).some((scope) => scope.startsWith(ROLE_SCOPE_PREFIX))) {
96
+ throw new Error(
97
+ "Refusing to store a role-scoped access token: sign-in is one per machine and the role comes from the repository. Run `ainecto auth login` again."
98
+ );
99
+ }
100
+ }
62
101
  function isNodeError(error, code) {
63
102
  return error instanceof Error && "code" in error && error.code === code;
64
103
  }
@@ -84,13 +123,20 @@ import { URLSearchParams } from "url";
84
123
 
85
124
  // src/core/config/endpoints.ts
86
125
  var DEFAULT_ENDPOINTS = {
87
- prod: "https://ai-erd.com/mcp",
126
+ prod: "https://ainecto.com/mcp",
88
127
  dev: "https://dev.ai-erd.com/mcp"
89
128
  };
129
+ var LEGACY_PROD_ENDPOINT = "https://ai-erd.com/mcp";
90
130
  var LEGACY_DEFAULT_ENDPOINTS = [
91
- "https://ainecto.com/mcp",
131
+ LEGACY_PROD_ENDPOINT,
92
132
  "https://dev.ainecto.com/mcp"
93
133
  ];
134
+ var TOKEN_SLOT_FALLBACKS = {
135
+ [DEFAULT_ENDPOINTS.prod]: [LEGACY_PROD_ENDPOINT]
136
+ };
137
+ function fallbackTokenEndpointsFor(endpoint) {
138
+ return TOKEN_SLOT_FALLBACKS[normalizeEndpoint(endpoint)] ?? [];
139
+ }
94
140
  function resolveEndpoint(input = {}) {
95
141
  const envVars = input.envVars ?? process.env;
96
142
  const env = input.env ?? "prod";
@@ -108,6 +154,9 @@ function normalizeEndpoint(endpoint) {
108
154
  url.hash = "";
109
155
  return url.toString().replace(/\/$/, "");
110
156
  }
157
+ function isDefaultEndpointFor(env, endpoint) {
158
+ return normalizeEndpoint(endpoint) === DEFAULT_ENDPOINTS[env];
159
+ }
111
160
  function isDefaultEndpoint(endpoint) {
112
161
  const normalized = normalizeEndpoint(endpoint);
113
162
  return Object.values(DEFAULT_ENDPOINTS).includes(normalized) || LEGACY_DEFAULT_ENDPOINTS.includes(normalized);
@@ -150,7 +199,7 @@ async function registerPublicClient(registrationEndpoint, redirectUri, fetchImpl
150
199
  method: "POST",
151
200
  headers: { "content-type": "application/json", accept: "application/json" },
152
201
  body: JSON.stringify({
153
- client_name: "Ainecto CLI",
202
+ client_name: "AiNecto CLI",
154
203
  redirect_uris: [redirectUri],
155
204
  token_endpoint_auth_method: "none",
156
205
  grant_types: ["authorization_code", "refresh_token"],
@@ -174,6 +223,15 @@ function isRecord(value) {
174
223
  }
175
224
 
176
225
  // src/core/auth/oauth.ts
226
+ var DEFAULT_LOGIN_TIMEOUT_MS = 3e5;
227
+ var OAuthLoginError = class extends Error {
228
+ constructor(code, message) {
229
+ super(message);
230
+ this.code = code;
231
+ this.name = "OAuthLoginError";
232
+ }
233
+ code;
234
+ };
177
235
  var OAuthClient = class {
178
236
  constructor(options) {
179
237
  this.options = options;
@@ -191,21 +249,27 @@ var OAuthClient = class {
191
249
  assertEnvTokenEndpointAllowed(this.options.endpoint, this.envVars);
192
250
  return envToken;
193
251
  }
194
- const stored = await this.options.tokenStore.load(this.options.endpoint);
252
+ const stored = await this.loadStored();
195
253
  if (!stored) {
196
254
  return void 0;
197
255
  }
198
256
  if (!shouldRefresh(stored)) {
199
257
  return stored.accessToken;
200
258
  }
201
- return this.refreshStoredToken(stored);
259
+ if (this.options.readOnly) {
260
+ return isExpired(stored) ? void 0 : stored.accessToken;
261
+ }
262
+ if (!stored.refreshToken) {
263
+ return isExpired(stored) ? void 0 : stored.accessToken;
264
+ }
265
+ return this.refreshStoredToken({ ...stored, refreshToken: stored.refreshToken });
202
266
  }
203
267
  async refreshAfterUnauthorized() {
204
- const stored = await this.options.tokenStore.load(this.options.endpoint);
205
- if (!stored?.refreshToken) {
268
+ const stored = await this.loadStored();
269
+ if (this.options.readOnly || !stored?.refreshToken) {
206
270
  return void 0;
207
271
  }
208
- return this.refreshStoredToken(stored);
272
+ return this.refreshStoredToken({ ...stored, refreshToken: stored.refreshToken });
209
273
  }
210
274
  async login() {
211
275
  const pkce = generatePkcePair();
@@ -223,8 +287,13 @@ var OAuthClient = class {
223
287
  authorizeUrl.searchParams.set("resource", this.options.endpoint);
224
288
  authorizeUrl.searchParams.set("state", state);
225
289
  assertTrustedEndpointUrl(authorizeUrl, "OAuth authorization URL");
226
- await this.openBrowserImpl(authorizeUrl.toString());
227
- const code = await loopback.waitForCode();
290
+ const url = authorizeUrl.toString();
291
+ this.options.onAuthorizeUrl?.(url);
292
+ const browserFailure = this.openBrowserImpl(url).then(() => new Promise(() => void 0));
293
+ const code = await withTimeout(
294
+ Promise.race([loopback.waitForCode(), browserFailure]),
295
+ this.options.loginTimeoutMs ?? DEFAULT_LOGIN_TIMEOUT_MS
296
+ );
228
297
  const token = await exchangeToken(metadata.tokenEndpoint, {
229
298
  grant_type: "authorization_code",
230
299
  code,
@@ -241,13 +310,32 @@ var OAuthClient = class {
241
310
  await loopback.close();
242
311
  }
243
312
  }
313
+ /**
314
+ * ★대체로 읽는 옛 칸도 함께 지운다. 새 칸만 지우면 다음 읽기가 곧바로 옛 칸으로 대체돼
315
+ * «로그아웃했는데 여전히 로그인됨»이 된다. 로그인은 기계당 한 번이므로 옛 CLI 의 칸도 같이 끝난다.
316
+ */
244
317
  async logout() {
245
318
  await this.options.tokenStore.delete(this.options.endpoint);
319
+ for (const fallback of fallbackTokenEndpointsFor(this.options.endpoint)) {
320
+ await this.options.tokenStore.delete(fallback);
321
+ }
246
322
  }
247
- async refreshStoredToken(stored) {
248
- if (!stored.refreshToken) {
249
- return stored.accessToken;
323
+ /** 이 주소의 칸, 비었으면 옛 기본 주소의 칸({@link fallbackTokenEndpointsFor}). 쓰기는 늘 이 주소의 칸이다. */
324
+ async loadStored() {
325
+ const own = await this.options.tokenStore.load(this.options.endpoint);
326
+ if (own) {
327
+ return own;
328
+ }
329
+ for (const fallback of fallbackTokenEndpointsFor(this.options.endpoint)) {
330
+ const stored = await this.options.tokenStore.load(fallback);
331
+ if (stored) {
332
+ return stored;
333
+ }
250
334
  }
335
+ return void 0;
336
+ }
337
+ /** 부르는 쪽이 refresh token 이 있음을 이미 확인했다 — 타입이 그것을 요구한다. */
338
+ async refreshStoredToken(stored) {
251
339
  const metadata = await discoverOAuthMetadata(this.options.endpoint, this.fetchImpl);
252
340
  const token = await exchangeToken(metadata.tokenEndpoint, {
253
341
  grant_type: "refresh_token",
@@ -269,6 +357,27 @@ function generatePkcePair() {
269
357
  function generateOAuthState() {
270
358
  return randomBytes(32).toString("base64url");
271
359
  }
360
+ function isExpired(token, now = Date.now()) {
361
+ return token.expiresAt !== void 0 && token.expiresAt <= now;
362
+ }
363
+ async function withTimeout(work, timeoutMs) {
364
+ let timer;
365
+ const timeout = new Promise((_, reject) => {
366
+ timer = setTimeout(() => {
367
+ const minutes = timeoutMs / 6e4;
368
+ const span = Number.isInteger(minutes) ? `${minutes} minute${minutes === 1 ? "" : "s"}` : `${timeoutMs} ms`;
369
+ reject(new OAuthLoginError(
370
+ "LOGIN_TIMEOUT",
371
+ `Sign-in was not completed within ${span}. Nothing was saved \u2014 run the same command again.`
372
+ ));
373
+ }, timeoutMs);
374
+ });
375
+ try {
376
+ return await Promise.race([work, timeout]);
377
+ } finally {
378
+ clearTimeout(timer);
379
+ }
380
+ }
272
381
  function shouldRefresh(token, now = Date.now()) {
273
382
  if (!token.expiresAt) {
274
383
  return false;
@@ -361,11 +470,11 @@ function toStoredToken(endpoint, token, metadata, previous) {
361
470
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
362
471
  };
363
472
  }
364
- async function createLoopbackReceiver(expectedState) {
473
+ async function createLoopbackReceiver(expectedState, host = "127.0.0.1") {
365
474
  let resolveCode;
366
475
  let rejectCode;
367
- const codePromise = new Promise((resolve, reject) => {
368
- resolveCode = resolve;
476
+ const codePromise = new Promise((resolve3, reject) => {
477
+ resolveCode = resolve3;
369
478
  rejectCode = reject;
370
479
  });
371
480
  codePromise.catch(() => void 0);
@@ -376,13 +485,13 @@ async function createLoopbackReceiver(expectedState) {
376
485
  const state = url.searchParams.get("state");
377
486
  if (error) {
378
487
  res.writeHead(400, { "content-type": "text/plain" });
379
- res.end("Ainecto CLI authorization failed. You can close this tab.");
488
+ res.end("AiNecto CLI authorization failed. You can close this tab.");
380
489
  rejectCode(new Error(`OAuth authorization failed: ${error}`));
381
490
  return;
382
491
  }
383
492
  if (!state || state !== expectedState) {
384
493
  res.writeHead(400, { "content-type": "text/plain" });
385
- res.end("Ainecto CLI authorization state mismatch. You can close this tab.");
494
+ res.end("AiNecto CLI authorization state mismatch. You can close this tab.");
386
495
  rejectCode(new Error("OAuth state mismatch."));
387
496
  return;
388
497
  }
@@ -393,10 +502,16 @@ async function createLoopbackReceiver(expectedState) {
393
502
  return;
394
503
  }
395
504
  res.writeHead(200, { "content-type": "text/plain" });
396
- res.end("Ainecto CLI authorization complete. You can close this tab.");
505
+ res.end("AiNecto CLI authorization complete. You can close this tab.");
397
506
  resolveCode(code);
398
507
  });
399
- await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
508
+ await new Promise((resolve3, reject) => {
509
+ server.once("error", (error) => reject(new OAuthLoginError(
510
+ "CALLBACK_UNAVAILABLE",
511
+ `Could not start the local sign-in callback on ${host} (${error.message}). If this command runs in a sandbox, allow local network binding or run it outside the sandbox.`
512
+ )));
513
+ server.listen(0, host, () => resolve3());
514
+ });
400
515
  const address = server.address();
401
516
  if (!address || typeof address === "string") {
402
517
  throw new Error("Loopback OAuth server did not bind to a TCP port.");
@@ -404,7 +519,10 @@ async function createLoopbackReceiver(expectedState) {
404
519
  return {
405
520
  redirectUri: `http://127.0.0.1:${address.port}/callback`,
406
521
  waitForCode: () => codePromise,
407
- close: () => new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve()))
522
+ close: () => new Promise((resolve3, reject) => {
523
+ server.closeAllConnections?.();
524
+ server.close((error) => error ? reject(error) : resolve3());
525
+ })
408
526
  };
409
527
  }
410
528
  function parseStringArray(value) {
@@ -428,10 +546,39 @@ function getBrowserOpenCommand(url, platform = process.platform) {
428
546
  }
429
547
  return { command: "xdg-open", args: [url] };
430
548
  }
431
- async function openBrowser(url) {
432
- const { command, args } = getBrowserOpenCommand(url);
433
- const child = spawn(command, args, { stdio: "ignore", detached: true });
434
- child.unref();
549
+ function launchBrowser(url, deps = {}) {
550
+ const platform = deps.platform ?? process.platform;
551
+ const { command, args } = getBrowserOpenCommand(url, platform);
552
+ const spawnImpl = deps.spawnImpl ?? spawn;
553
+ return new Promise((resolve3, reject) => {
554
+ const unavailable = (reason) => new OAuthLoginError(
555
+ "BROWSER_UNAVAILABLE",
556
+ `Could not open a browser on this machine (${command}: ${reason}). Sign-in needs a browser here.`
557
+ );
558
+ let child;
559
+ try {
560
+ child = spawnImpl(command, args, { stdio: "ignore", detached: true });
561
+ } catch (error) {
562
+ reject(unavailable(error instanceof Error ? error.message : String(error)));
563
+ return;
564
+ }
565
+ child.once("error", (error) => reject(unavailable(error.message)));
566
+ if (platform === "win32") {
567
+ child.once("spawn", () => resolve3());
568
+ } else {
569
+ child.once("exit", (code, signal) => {
570
+ if (code === 0) {
571
+ resolve3();
572
+ } else {
573
+ reject(unavailable(code === null ? `stopped by ${signal ?? "a signal"}` : `exited with code ${code}`));
574
+ }
575
+ });
576
+ }
577
+ child.unref();
578
+ });
579
+ }
580
+ function openBrowser(url) {
581
+ return launchBrowser(url);
435
582
  }
436
583
  function assertEnvTokenEndpointAllowed(endpoint, envVars) {
437
584
  if (envVars.AINECTO_ALLOW_CUSTOM_ENDPOINT_TOKEN === "1") {
@@ -502,6 +649,12 @@ async function readAllStdin(stdin) {
502
649
  return text;
503
650
  }
504
651
 
652
+ // src/core/version.ts
653
+ var CLI_VERSION = true ? "0.5.0" : "dev";
654
+ var PACKAGE_NAME = "@ainecto/mcp";
655
+ var LEGACY_PACKAGE_NAME = "@ai-erd/mcp";
656
+ var CLI_USER_AGENT = `${PACKAGE_NAME} ${CLI_VERSION}`;
657
+
505
658
  // src/core/mcp/rpcClient.ts
506
659
  var McpRpcError = class extends Error {
507
660
  constructor(message, code, details) {
@@ -524,7 +677,7 @@ var McpRpcClient = class {
524
677
  return this.request("initialize", {
525
678
  protocolVersion: "2024-11-05",
526
679
  capabilities: {},
527
- clientInfo: { name: "@ainecto/mcp", version: "0.1.0" }
680
+ clientInfo: { name: PACKAGE_NAME, version: CLI_VERSION }
528
681
  });
529
682
  }
530
683
  async toolsList() {
@@ -589,6 +742,9 @@ var McpRpcClient = class {
589
742
  if (token) {
590
743
  headers.authorization = `Bearer ${token}`;
591
744
  }
745
+ if (this.options.role) {
746
+ headers[ROLE_HEADER.toLowerCase()] = this.options.role;
747
+ }
592
748
  return this.fetchImpl(this.options.endpoint, {
593
749
  method: "POST",
594
750
  headers,
@@ -596,6 +752,9 @@ var McpRpcClient = class {
596
752
  });
597
753
  }
598
754
  };
755
+ function isToolError(result) {
756
+ return isRecord2(result) && result.isError === true;
757
+ }
599
758
  function isJsonRpcResponse(value) {
600
759
  return isRecord2(value) && value.jsonrpc === "2.0" && ("result" in value || "error" in value);
601
760
  }
@@ -693,55 +852,67 @@ function redactSecretString(value) {
693
852
 
694
853
  // src/core/catalog/generated.prod.ts
695
854
  var generatedTools = [
696
- { "mcpName": "mcp__ainecto__create_documents", "commandPath": ["documents", "create"], "group": "document", "description": "Create documents. If a document body is tens of KB or more, prefer request_upload_token (purpose=document.create) and upload the body with curl.", "inputSchema": { "properties": { "items": { "description": "Documents", "items": { "properties": { "body": { "description": "Deprecated compatibility alias for typePayload.body when document type is markdown. \uBCF8\uBB38\uC774 \uC218\uC2ED KB \uC774\uC0C1\uC73C\uB85C \uD06C\uBA74 LLM \uD1A0\uD070 \uC808\uC57D\uC744 \uC704\uD574 `request_upload_token (purpose=document.create)` \uB97C \uD638\uCD9C\uD574 uploadUrl \uC744 \uBC1B\uACE0 curl \uB85C \uC9C1\uC811 PUT \uC73C\uB85C \uBCF4\uB0BC \uAC83.", "type": "string" }, "folderUuid": { "description": "Optional folder UUID", "type": "string" }, "projectUuid": { "description": "Project UUID", "type": "string" }, "title": { "description": "Document title", "type": "string" }, "type": { "description": "Document type. Enabled values: erd, markdown.", "enum": ["erd", "markdown"], "type": "string" }, "typePayload": { "description": "Type-specific payload for enabled document types. markdown: {body}.", "type": "object" } }, "required": ["projectUuid", "type", "title"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "4aa976b9bb3b0eb6" },
697
- { "mcpName": "mcp__ainecto__create_folders", "commandPath": ["folders", "create"], "group": "generic", "description": "", "inputSchema": { "properties": { "items": { "description": "Folders", "items": { "properties": { "name": { "description": "Folder name", "type": "string" }, "parentFolderUuid": { "description": "Parent folder UUID", "type": "string" }, "projectUuid": { "description": "Project UUID", "type": "string" } }, "required": ["projectUuid", "name"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "d423d3c265631664" },
698
- { "mcpName": "mcp__ainecto__create_projects", "commandPath": ["projects", "create"], "group": "generic", "description": "", "inputSchema": { "properties": { "items": { "description": "Projects", "items": { "properties": { "description": { "description": "Description", "type": "string" }, "name": { "description": "Project name", "type": "string" }, "projectKey": { "description": "Unique project key", "type": "string" }, "workspaceUuid": { "description": "Workspace UUID \u2014 null uses user's default workspace", "type": "string" } }, "required": ["name"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "396c80c6cc2ba41a" },
699
- { "mcpName": "mcp__ainecto__create_workspaces", "commandPath": ["workspaces", "create"], "group": "workspace", "description": "", "inputSchema": { "properties": { "items": { "description": "Workspaces", "items": { "properties": { "description": { "description": "Description", "type": "string" }, "name": { "description": "Workspace name", "type": "string" } }, "required": ["name"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "d663d461f9c9da65" },
700
- { "mcpName": "mcp__ainecto__delete_attachments", "commandPath": ["attachments", "delete"], "group": "attachment", "description": "", "inputSchema": { "properties": { "uuids": { "description": "Attachment UUIDs", "items": { "type": "string" }, "type": "array" } }, "required": ["uuids"], "type": "object" }, "required": ["uuids"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "prod", "schemaHash": "6e55e6511d732199" },
701
- { "mcpName": "mcp__ainecto__delete_documents", "commandPath": ["documents", "delete"], "group": "document", "description": "", "inputSchema": { "properties": { "uuids": { "description": "Document UUIDs", "items": { "type": "string" }, "type": "array" } }, "required": ["uuids"], "type": "object" }, "required": ["uuids"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "prod", "schemaHash": "5fbc7c142bb4a49d" },
702
- { "mcpName": "mcp__ainecto__delete_folders", "commandPath": ["folders", "delete"], "group": "generic", "description": "", "inputSchema": { "properties": { "uuids": { "description": "Folder UUIDs", "items": { "type": "string" }, "type": "array" } }, "required": ["uuids"], "type": "object" }, "required": ["uuids"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "prod", "schemaHash": "49fa7c0f823871b2" },
703
- { "mcpName": "mcp__ainecto__delete_projects", "commandPath": ["projects", "delete"], "group": "generic", "description": "", "inputSchema": { "properties": { "uuids": { "description": "Project UUIDs", "items": { "type": "string" }, "type": "array" } }, "required": ["uuids"], "type": "object" }, "required": ["uuids"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "prod", "schemaHash": "e1a86ddaa9638298" },
704
- { "mcpName": "mcp__ainecto__delete_workspaces", "commandPath": ["workspaces", "delete"], "group": "workspace", "description": "", "inputSchema": { "properties": { "uuids": { "description": "Workspace UUIDs", "items": { "type": "string" }, "type": "array" } }, "required": ["uuids"], "type": "object" }, "required": ["uuids"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "prod", "schemaHash": "2aca644f7a105b06" },
705
- { "mcpName": "mcp__ainecto__disable_shares", "commandPath": ["tools", "run", "disable-shares"], "group": "generic", "description": "", "inputSchema": { "properties": { "targetType": { "description": "Share target type", "enum": ["project", "folder", "document"], "type": "string" }, "targetUuids": { "description": "Target UUIDs", "items": { "type": "string" }, "type": "array" } }, "required": ["targetType", "targetUuids"], "type": "object" }, "required": ["targetType", "targetUuids"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "a4b4358eff0faa97" },
706
- { "mcpName": "mcp__ainecto__enable_shares", "commandPath": ["tools", "run", "enable-shares"], "group": "generic", "description": "", "inputSchema": { "properties": { "targetType": { "description": "Share target type", "enum": ["project", "folder", "document"], "type": "string" }, "targetUuids": { "description": "Target UUIDs", "items": { "type": "string" }, "type": "array" } }, "required": ["targetType", "targetUuids"], "type": "object" }, "required": ["targetType", "targetUuids"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "ebace643de07e826" },
707
- { "mcpName": "mcp__ainecto__erd_apply_changes", "commandPath": ["erd", "apply-changes"], "group": "erd", "description": 'Required single-call mutation path for ERD create/update/delete changes. Put tables, columns, indexes, refs, enums, enum_values, and table groups in one operations array and call this once.\nRules: (1) all data field names are snake_case; (2) for create, generate your own UUIDs and put them in data.uuid, then later operations in the same batch may reference those UUIDs without read-back calls; (3) enum values are separate enum_value operations, not inline on enum; (4) ref.relationship uses short dbdiagram-style symbols: >, <, or -; (5) ref_column operations map FK columns explicitly, never automatically.\nDo not look for type-specific ERD CUD tools; this tool is the only ERD CUD entry point. If operations is very large, use mcp__ainecto__request_upload_token with purpose=mcp.call and send the tools/call payload with curl to save LLM tokens.\nOne-call worked example:\n{\n "documentUuid": "erd-doc-uuid",\n "description": "create users/posts ERD",\n "operations": [\n {"op":"create","type":"table","data":{"uuid":"tbl_users","name":"users","display_order":1}},\n {"op":"create","type":"column","data":{"uuid":"col_users_id","table_uuid":"tbl_users","name":"id","type":"bigint","is_pk":true,"is_not_null":true,"display_order":1}},\n {"op":"create","type":"column","data":{"uuid":"col_users_email","table_uuid":"tbl_users","name":"email","type":"varchar(255)","is_unique":true,"is_not_null":true,"display_order":2}},\n {"op":"create","type":"table","data":{"uuid":"tbl_posts","name":"posts","display_order":2}},\n {"op":"create","type":"column","data":{"uuid":"col_posts_id","table_uuid":"tbl_posts","name":"id","type":"bigint","is_pk":true,"is_not_null":true,"display_order":1}},\n {"op":"create","type":"column","data":{"uuid":"col_posts_author_id","table_uuid":"tbl_posts","name":"author_id","type":"bigint","is_not_null":true,"display_order":2}},\n {"op":"create","type":"column","data":{"uuid":"col_posts_status","table_uuid":"tbl_posts","name":"status","type":"post_status","is_not_null":true,"display_order":3}},\n {"op":"create","type":"enum","data":{"uuid":"enum_post_status","name":"post_status"}},\n {"op":"create","type":"enum_value","data":{"uuid":"enum_post_status_draft","enum_uuid":"enum_post_status","value":"draft","display_order":1}},\n {"op":"create","type":"enum_value","data":{"uuid":"enum_post_status_published","enum_uuid":"enum_post_status","value":"published","display_order":2}},\n {"op":"create","type":"ref","data":{"uuid":"ref_posts_author","name":"fk_posts_author","from_table_uuid":"tbl_posts","to_table_uuid":"tbl_users","relationship":">","on_delete":"cascade"}},\n {"op":"create","type":"ref_column","data":{"uuid":"refcol_posts_author","ref_uuid":"ref_posts_author","from_column_uuid":"col_posts_author_id","to_column_uuid":"col_users_id","display_order":1}}\n ]\n}', "inputSchema": { "properties": { "arrangeNewTables": { "description": "Whether to auto-place newly-created tables after the batch; defaults to true", "type": "boolean" }, "description": { "description": "Reason for batch (audit)", "type": "string" }, "documentUuid": { "description": "ERD document UUID", "type": "string" }, "operations": { "description": "Atomic create/update/delete operations", "items": { "description": "One ERD mutation. Use a single operations array with client-generated data.uuid values to create cross-referenced tables, columns, enums, refs, and ref_columns without read-back calls.", "discriminator": { "propertyName": "type" }, "oneOf": [{ "allOf": [{ "if": { "properties": { "op": { "const": "create", "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": { "data": { "description": "Payload for operation.type='table'.", "properties": { "alias": { "description": "Display alias.", "type": "string" }, "display_order": { "description": "Display order.", "type": "integer" }, "header_color": { "description": "Header color.", "type": "string" }, "name": { "description": "Table name. Required for create.", "type": "string" }, "note": { "description": "Table note.", "type": "string" }, "schema_name": { "description": "Schema name.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "required": ["name"], "title": "table data", "type": "object" } }, "required": ["data"], "type": "object" } }, { "if": { "properties": { "op": { "enum": ["update", "delete"], "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": {}, "required": ["uuid"], "type": "object" } }], "properties": { "data": { "description": "Payload for operation.type='table'.", "properties": { "alias": { "description": "Display alias.", "type": "string" }, "display_order": { "description": "Display order.", "type": "integer" }, "header_color": { "description": "Header color.", "type": "string" }, "name": { "description": "Table name. Required for create.", "type": "string" }, "note": { "description": "Table note.", "type": "string" }, "schema_name": { "description": "Schema name.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "title": "table data", "type": "object" }, "op": { "description": "Operation", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "const": "table", "description": "Must be 'table' for this payload.", "type": "string" }, "uuid": { "description": "Target UUID for update/delete.", "type": "string" } }, "required": ["op", "type"], "title": "table operation", "type": "object" }, { "allOf": [{ "if": { "properties": { "op": { "const": "create", "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": { "data": { "description": "Payload for operation.type='column'.", "properties": { "default_value": { "description": "Default value.", "type": "string" }, "display_order": { "description": "Display order.", "type": "integer" }, "is_increment": { "description": "Auto-increment flag.", "type": "boolean" }, "is_not_null": { "description": "Not-null flag.", "type": "boolean" }, "is_pk": { "description": "Primary key flag.", "type": "boolean" }, "is_unique": { "description": "Unique flag.", "type": "boolean" }, "name": { "description": "Column name.", "type": "string" }, "note": { "description": "Column note.", "type": "string" }, "table_uuid": { "description": "Parent table UUID. Required for create; may be a data.uuid from an earlier table operation in this batch.", "type": "string" }, "type": { "description": "SQL data type. Use this exact field name; do not send data_type.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "required": ["table_uuid"], "title": "column data", "type": "object" } }, "required": ["data"], "type": "object" } }, { "if": { "properties": { "op": { "enum": ["update", "delete"], "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": {}, "required": ["uuid"], "type": "object" } }], "properties": { "data": { "description": "Payload for operation.type='column'.", "properties": { "default_value": { "description": "Default value.", "type": "string" }, "display_order": { "description": "Display order.", "type": "integer" }, "is_increment": { "description": "Auto-increment flag.", "type": "boolean" }, "is_not_null": { "description": "Not-null flag.", "type": "boolean" }, "is_pk": { "description": "Primary key flag.", "type": "boolean" }, "is_unique": { "description": "Unique flag.", "type": "boolean" }, "name": { "description": "Column name.", "type": "string" }, "note": { "description": "Column note.", "type": "string" }, "table_uuid": { "description": "Parent table UUID. Required for create; may be a data.uuid from an earlier table operation in this batch.", "type": "string" }, "type": { "description": "SQL data type. Use this exact field name; do not send data_type.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "title": "column data", "type": "object" }, "op": { "description": "Operation", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "const": "column", "description": "Must be 'column' for this payload.", "type": "string" }, "uuid": { "description": "Target UUID for update/delete.", "type": "string" } }, "required": ["op", "type"], "title": "column operation", "type": "object" }, { "allOf": [{ "if": { "properties": { "op": { "const": "create", "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": { "data": { "description": "Payload for operation.type='index'.", "properties": { "is_pk": { "description": "Primary key index flag.", "type": "boolean" }, "is_unique": { "description": "Unique index flag.", "type": "boolean" }, "name": { "description": "Index name.", "type": "string" }, "note": { "description": "Index note.", "type": "string" }, "table_uuid": { "description": "Parent table UUID. Required for create.", "type": "string" }, "type": { "description": "Index type.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "required": ["table_uuid"], "title": "index data", "type": "object" } }, "required": ["data"], "type": "object" } }, { "if": { "properties": { "op": { "enum": ["update", "delete"], "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": {}, "required": ["uuid"], "type": "object" } }], "properties": { "data": { "description": "Payload for operation.type='index'.", "properties": { "is_pk": { "description": "Primary key index flag.", "type": "boolean" }, "is_unique": { "description": "Unique index flag.", "type": "boolean" }, "name": { "description": "Index name.", "type": "string" }, "note": { "description": "Index note.", "type": "string" }, "table_uuid": { "description": "Parent table UUID. Required for create.", "type": "string" }, "type": { "description": "Index type.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "title": "index data", "type": "object" }, "op": { "description": "Operation", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "const": "index", "description": "Must be 'index' for this payload.", "type": "string" }, "uuid": { "description": "Target UUID for update/delete.", "type": "string" } }, "required": ["op", "type"], "title": "index operation", "type": "object" }, { "allOf": [{ "if": { "properties": { "op": { "const": "create", "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": { "data": { "description": "Payload for operation.type='index_column'.", "properties": { "column_uuid": { "description": "Column UUID. Required for create.", "type": "string" }, "display_order": { "description": "Display order.", "type": "integer" }, "index_uuid": { "description": "Index UUID. Required for create.", "type": "string" }, "sort_order": { "description": "Sort order, for example ASC or DESC.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "required": ["index_uuid", "column_uuid"], "title": "index_column data", "type": "object" } }, "required": ["data"], "type": "object" } }, { "if": { "properties": { "op": { "enum": ["update", "delete"], "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": {}, "required": ["uuid"], "type": "object" } }], "properties": { "data": { "description": "Payload for operation.type='index_column'.", "properties": { "column_uuid": { "description": "Column UUID. Required for create.", "type": "string" }, "display_order": { "description": "Display order.", "type": "integer" }, "index_uuid": { "description": "Index UUID. Required for create.", "type": "string" }, "sort_order": { "description": "Sort order, for example ASC or DESC.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "title": "index_column data", "type": "object" }, "op": { "description": "Operation", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "const": "index_column", "description": "Must be 'index_column' for this payload.", "type": "string" }, "uuid": { "description": "Target UUID for update/delete.", "type": "string" } }, "required": ["op", "type"], "title": "index_column operation", "type": "object" }, { "allOf": [{ "if": { "properties": { "op": { "const": "create", "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": { "data": { "description": "Payload for operation.type='ref'.", "properties": { "from_table_uuid": { "description": "Source table UUID. Required for create.", "type": "string" }, "name": { "description": "Reference name.", "type": "string" }, "on_delete": { "description": "ON DELETE action.", "type": "string" }, "on_update": { "description": "ON UPDATE action.", "type": "string" }, "relationship": { "description": "Required for create. Use short dbdiagram-style relationship symbols.", "enum": [">", "<", "-"], "type": "string" }, "to_table_uuid": { "description": "Target table UUID. Required for create.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "required": ["from_table_uuid", "to_table_uuid", "relationship"], "title": "ref data", "type": "object" } }, "required": ["data"], "type": "object" } }, { "if": { "properties": { "op": { "enum": ["update", "delete"], "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": {}, "required": ["uuid"], "type": "object" } }], "properties": { "data": { "description": "Payload for operation.type='ref'.", "properties": { "from_table_uuid": { "description": "Source table UUID. Required for create.", "type": "string" }, "name": { "description": "Reference name.", "type": "string" }, "on_delete": { "description": "ON DELETE action.", "type": "string" }, "on_update": { "description": "ON UPDATE action.", "type": "string" }, "relationship": { "description": "Required for create. Use short dbdiagram-style relationship symbols.", "enum": [">", "<", "-"], "type": "string" }, "to_table_uuid": { "description": "Target table UUID. Required for create.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "title": "ref data", "type": "object" }, "op": { "description": "Operation", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "const": "ref", "description": "Must be 'ref' for this payload.", "type": "string" }, "uuid": { "description": "Target UUID for update/delete.", "type": "string" } }, "required": ["op", "type"], "title": "ref operation", "type": "object" }, { "allOf": [{ "if": { "properties": { "op": { "const": "create", "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": { "data": { "description": "Payload for operation.type='ref_column'.", "properties": { "display_order": { "description": "Display order.", "type": "integer" }, "from_column_uuid": { "description": "Source column UUID. Required for create.", "type": "string" }, "ref_uuid": { "description": "Reference UUID. Required for create; may be a data.uuid from an earlier ref operation.", "type": "string" }, "to_column_uuid": { "description": "Target column UUID. Required for create.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "required": ["ref_uuid", "from_column_uuid", "to_column_uuid"], "title": "ref_column data", "type": "object" } }, "required": ["data"], "type": "object" } }, { "if": { "properties": { "op": { "enum": ["update", "delete"], "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": {}, "required": ["uuid"], "type": "object" } }], "properties": { "data": { "description": "Payload for operation.type='ref_column'.", "properties": { "display_order": { "description": "Display order.", "type": "integer" }, "from_column_uuid": { "description": "Source column UUID. Required for create.", "type": "string" }, "ref_uuid": { "description": "Reference UUID. Required for create; may be a data.uuid from an earlier ref operation.", "type": "string" }, "to_column_uuid": { "description": "Target column UUID. Required for create.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "title": "ref_column data", "type": "object" }, "op": { "description": "Operation", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "const": "ref_column", "description": "Must be 'ref_column' for this payload.", "type": "string" }, "uuid": { "description": "Target UUID for update/delete.", "type": "string" } }, "required": ["op", "type"], "title": "ref_column operation", "type": "object" }, { "allOf": [{ "if": { "properties": { "op": { "const": "create", "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": { "data": { "description": "Payload for operation.type='enum'.", "properties": { "name": { "description": "Enum name.", "type": "string" }, "note": { "description": "Enum note.", "type": "string" }, "schema_name": { "description": "Schema name.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "title": "enum data", "type": "object" } }, "required": ["data"], "type": "object" } }, { "if": { "properties": { "op": { "enum": ["update", "delete"], "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": {}, "required": ["uuid"], "type": "object" } }], "properties": { "data": { "description": "Payload for operation.type='enum'.", "properties": { "name": { "description": "Enum name.", "type": "string" }, "note": { "description": "Enum note.", "type": "string" }, "schema_name": { "description": "Schema name.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "title": "enum data", "type": "object" }, "op": { "description": "Operation", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "const": "enum", "description": "Must be 'enum' for this payload.", "type": "string" }, "uuid": { "description": "Target UUID for update/delete.", "type": "string" } }, "required": ["op", "type"], "title": "enum operation", "type": "object" }, { "allOf": [{ "if": { "properties": { "op": { "const": "create", "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": { "data": { "description": "Payload for operation.type='enum_value'.", "properties": { "display_order": { "description": "Display order.", "type": "integer" }, "enum_uuid": { "description": "Parent enum UUID. Required for create; may be a data.uuid from an earlier enum operation.", "type": "string" }, "note": { "description": "Enum value note.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" }, "value": { "description": "Enum value. Inline enum values on enum are not persisted; create enum_value operations.", "type": "string" } }, "required": ["enum_uuid"], "title": "enum_value data", "type": "object" } }, "required": ["data"], "type": "object" } }, { "if": { "properties": { "op": { "enum": ["update", "delete"], "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": {}, "required": ["uuid"], "type": "object" } }], "properties": { "data": { "description": "Payload for operation.type='enum_value'.", "properties": { "display_order": { "description": "Display order.", "type": "integer" }, "enum_uuid": { "description": "Parent enum UUID. Required for create; may be a data.uuid from an earlier enum operation.", "type": "string" }, "note": { "description": "Enum value note.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" }, "value": { "description": "Enum value. Inline enum values on enum are not persisted; create enum_value operations.", "type": "string" } }, "title": "enum_value data", "type": "object" }, "op": { "description": "Operation", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "const": "enum_value", "description": "Must be 'enum_value' for this payload.", "type": "string" }, "uuid": { "description": "Target UUID for update/delete.", "type": "string" } }, "required": ["op", "type"], "title": "enum_value operation", "type": "object" }, { "allOf": [{ "if": { "properties": { "op": { "const": "create", "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": { "data": { "description": "Payload for operation.type='table_group'.", "properties": { "color": { "description": "Group color.", "type": "string" }, "name": { "description": "Table group name.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "title": "table_group data", "type": "object" } }, "required": ["data"], "type": "object" } }, { "if": { "properties": { "op": { "enum": ["update", "delete"], "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": {}, "required": ["uuid"], "type": "object" } }], "properties": { "data": { "description": "Payload for operation.type='table_group'.", "properties": { "color": { "description": "Group color.", "type": "string" }, "name": { "description": "Table group name.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "title": "table_group data", "type": "object" }, "op": { "description": "Operation", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "const": "table_group", "description": "Must be 'table_group' for this payload.", "type": "string" }, "uuid": { "description": "Target UUID for update/delete.", "type": "string" } }, "required": ["op", "type"], "title": "table_group operation", "type": "object" }, { "allOf": [{ "if": { "properties": { "op": { "const": "create", "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": { "data": { "description": "Payload for operation.type='table_group_table'.", "properties": { "display_order": { "description": "Display order.", "type": "integer" }, "group_uuid": { "description": "Table group UUID. Required for create.", "type": "string" }, "schema_name": { "description": "Table schema name.", "type": "string" }, "table_name": { "description": "Table name. Required for create.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "required": ["group_uuid", "table_name"], "title": "table_group_table data", "type": "object" } }, "required": ["data"], "type": "object" } }, { "if": { "properties": { "op": { "enum": ["update", "delete"], "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": {}, "required": ["uuid"], "type": "object" } }], "properties": { "data": { "description": "Payload for operation.type='table_group_table'.", "properties": { "display_order": { "description": "Display order.", "type": "integer" }, "group_uuid": { "description": "Table group UUID. Required for create.", "type": "string" }, "schema_name": { "description": "Table schema name.", "type": "string" }, "table_name": { "description": "Table name. Required for create.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "title": "table_group_table data", "type": "object" }, "op": { "description": "Operation", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "const": "table_group_table", "description": "Must be 'table_group_table' for this payload.", "type": "string" }, "uuid": { "description": "Target UUID for update/delete.", "type": "string" } }, "required": ["op", "type"], "title": "table_group_table operation", "type": "object" }, { "allOf": [{ "if": { "properties": { "op": { "const": "create", "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": { "data": { "description": "Payload for operation.type='table_group_note'.", "properties": { "display_order": { "description": "Display order.", "type": "integer" }, "group_uuid": { "description": "Table group UUID. Required for create.", "type": "string" }, "note_uuid": { "description": "Note UUID. Required for create.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "required": ["group_uuid", "note_uuid"], "title": "table_group_note data", "type": "object" } }, "required": ["data"], "type": "object" } }, { "if": { "properties": { "op": { "enum": ["update", "delete"], "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": {}, "required": ["uuid"], "type": "object" } }], "properties": { "data": { "description": "Payload for operation.type='table_group_note'.", "properties": { "display_order": { "description": "Display order.", "type": "integer" }, "group_uuid": { "description": "Table group UUID. Required for create.", "type": "string" }, "note_uuid": { "description": "Note UUID. Required for create.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "title": "table_group_note data", "type": "object" }, "op": { "description": "Operation", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "const": "table_group_note", "description": "Must be 'table_group_note' for this payload.", "type": "string" }, "uuid": { "description": "Target UUID for update/delete.", "type": "string" } }, "required": ["op", "type"], "title": "table_group_note operation", "type": "object" }, { "allOf": [{ "if": { "properties": { "op": { "const": "create", "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": { "data": { "description": "Payload for operation.type='filter'.", "properties": { "included_tables": { "description": "JSON string/list of included tables.", "type": "string" }, "is_default": { "description": "Default filter flag.", "type": "boolean" }, "name": { "description": "Filter name.", "type": "string" }, "table_groups": { "description": "JSON string/object of table groups.", "type": "string" }, "table_positions": { "description": "JSON string/object of table positions.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "title": "filter data", "type": "object" } }, "required": ["data"], "type": "object" } }, { "if": { "properties": { "op": { "enum": ["update", "delete"], "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": {}, "required": ["uuid"], "type": "object" } }], "properties": { "data": { "description": "Payload for operation.type='filter'.", "properties": { "included_tables": { "description": "JSON string/list of included tables.", "type": "string" }, "is_default": { "description": "Default filter flag.", "type": "boolean" }, "name": { "description": "Filter name.", "type": "string" }, "table_groups": { "description": "JSON string/object of table groups.", "type": "string" }, "table_positions": { "description": "JSON string/object of table positions.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "title": "filter data", "type": "object" }, "op": { "description": "Operation", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "const": "filter", "description": "Must be 'filter' for this payload.", "type": "string" }, "uuid": { "description": "Target UUID for update/delete.", "type": "string" } }, "required": ["op", "type"], "title": "filter operation", "type": "object" }, { "allOf": [{ "if": { "properties": { "op": { "const": "create", "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": { "data": { "description": "Payload for operation.type='note'.", "properties": { "color": { "description": "Note color.", "type": "string" }, "content": { "description": "Note content.", "type": "string" }, "height": { "description": "Height.", "type": "integer" }, "icon_name": { "description": "Icon name.", "type": "string" }, "position_x": { "description": "X position.", "type": "integer" }, "position_y": { "description": "Y position.", "type": "integer" }, "title": { "description": "Note title.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" }, "width": { "description": "Width.", "type": "integer" } }, "title": "note data", "type": "object" } }, "required": ["data"], "type": "object" } }, { "if": { "properties": { "op": { "enum": ["update", "delete"], "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": {}, "required": ["uuid"], "type": "object" } }], "properties": { "data": { "description": "Payload for operation.type='note'.", "properties": { "color": { "description": "Note color.", "type": "string" }, "content": { "description": "Note content.", "type": "string" }, "height": { "description": "Height.", "type": "integer" }, "icon_name": { "description": "Icon name.", "type": "string" }, "position_x": { "description": "X position.", "type": "integer" }, "position_y": { "description": "Y position.", "type": "integer" }, "title": { "description": "Note title.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" }, "width": { "description": "Width.", "type": "integer" } }, "title": "note data", "type": "object" }, "op": { "description": "Operation", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "const": "note", "description": "Must be 'note' for this payload.", "type": "string" }, "uuid": { "description": "Target UUID for update/delete.", "type": "string" } }, "required": ["op", "type"], "title": "note operation", "type": "object" }], "properties": { "data": { "description": "Type-specific payload. All field names are snake_case. For create, data.uuid may be client-supplied and referenced by later operations in the same batch.", "oneOf": [{ "description": "Payload for operation.type='table'.", "properties": { "alias": { "description": "Display alias.", "type": "string" }, "display_order": { "description": "Display order.", "type": "integer" }, "header_color": { "description": "Header color.", "type": "string" }, "name": { "description": "Table name. Required for create.", "type": "string" }, "note": { "description": "Table note.", "type": "string" }, "schema_name": { "description": "Schema name.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "title": "table data", "type": "object" }, { "description": "Payload for operation.type='column'.", "properties": { "default_value": { "description": "Default value.", "type": "string" }, "display_order": { "description": "Display order.", "type": "integer" }, "is_increment": { "description": "Auto-increment flag.", "type": "boolean" }, "is_not_null": { "description": "Not-null flag.", "type": "boolean" }, "is_pk": { "description": "Primary key flag.", "type": "boolean" }, "is_unique": { "description": "Unique flag.", "type": "boolean" }, "name": { "description": "Column name.", "type": "string" }, "note": { "description": "Column note.", "type": "string" }, "table_uuid": { "description": "Parent table UUID. Required for create; may be a data.uuid from an earlier table operation in this batch.", "type": "string" }, "type": { "description": "SQL data type. Use this exact field name; do not send data_type.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "title": "column data", "type": "object" }, { "description": "Payload for operation.type='index'.", "properties": { "is_pk": { "description": "Primary key index flag.", "type": "boolean" }, "is_unique": { "description": "Unique index flag.", "type": "boolean" }, "name": { "description": "Index name.", "type": "string" }, "note": { "description": "Index note.", "type": "string" }, "table_uuid": { "description": "Parent table UUID. Required for create.", "type": "string" }, "type": { "description": "Index type.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "title": "index data", "type": "object" }, { "description": "Payload for operation.type='index_column'.", "properties": { "column_uuid": { "description": "Column UUID. Required for create.", "type": "string" }, "display_order": { "description": "Display order.", "type": "integer" }, "index_uuid": { "description": "Index UUID. Required for create.", "type": "string" }, "sort_order": { "description": "Sort order, for example ASC or DESC.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "title": "index_column data", "type": "object" }, { "description": "Payload for operation.type='ref'.", "properties": { "from_table_uuid": { "description": "Source table UUID. Required for create.", "type": "string" }, "name": { "description": "Reference name.", "type": "string" }, "on_delete": { "description": "ON DELETE action.", "type": "string" }, "on_update": { "description": "ON UPDATE action.", "type": "string" }, "relationship": { "description": "Required for create. Use short dbdiagram-style relationship symbols.", "enum": [">", "<", "-"], "type": "string" }, "to_table_uuid": { "description": "Target table UUID. Required for create.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "title": "ref data", "type": "object" }, { "description": "Payload for operation.type='ref_column'.", "properties": { "display_order": { "description": "Display order.", "type": "integer" }, "from_column_uuid": { "description": "Source column UUID. Required for create.", "type": "string" }, "ref_uuid": { "description": "Reference UUID. Required for create; may be a data.uuid from an earlier ref operation.", "type": "string" }, "to_column_uuid": { "description": "Target column UUID. Required for create.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "title": "ref_column data", "type": "object" }, { "description": "Payload for operation.type='enum'.", "properties": { "name": { "description": "Enum name.", "type": "string" }, "note": { "description": "Enum note.", "type": "string" }, "schema_name": { "description": "Schema name.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "title": "enum data", "type": "object" }, { "description": "Payload for operation.type='enum_value'.", "properties": { "display_order": { "description": "Display order.", "type": "integer" }, "enum_uuid": { "description": "Parent enum UUID. Required for create; may be a data.uuid from an earlier enum operation.", "type": "string" }, "note": { "description": "Enum value note.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" }, "value": { "description": "Enum value. Inline enum values on enum are not persisted; create enum_value operations.", "type": "string" } }, "title": "enum_value data", "type": "object" }, { "description": "Payload for operation.type='table_group'.", "properties": { "color": { "description": "Group color.", "type": "string" }, "name": { "description": "Table group name.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "title": "table_group data", "type": "object" }, { "description": "Payload for operation.type='table_group_table'.", "properties": { "display_order": { "description": "Display order.", "type": "integer" }, "group_uuid": { "description": "Table group UUID. Required for create.", "type": "string" }, "schema_name": { "description": "Table schema name.", "type": "string" }, "table_name": { "description": "Table name. Required for create.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "title": "table_group_table data", "type": "object" }, { "description": "Payload for operation.type='table_group_note'.", "properties": { "display_order": { "description": "Display order.", "type": "integer" }, "group_uuid": { "description": "Table group UUID. Required for create.", "type": "string" }, "note_uuid": { "description": "Note UUID. Required for create.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "title": "table_group_note data", "type": "object" }, { "description": "Payload for operation.type='filter'.", "properties": { "included_tables": { "description": "JSON string/list of included tables.", "type": "string" }, "is_default": { "description": "Default filter flag.", "type": "boolean" }, "name": { "description": "Filter name.", "type": "string" }, "table_groups": { "description": "JSON string/object of table groups.", "type": "string" }, "table_positions": { "description": "JSON string/object of table positions.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "title": "filter data", "type": "object" }, { "description": "Payload for operation.type='note'.", "properties": { "color": { "description": "Note color.", "type": "string" }, "content": { "description": "Note content.", "type": "string" }, "height": { "description": "Height.", "type": "integer" }, "icon_name": { "description": "Icon name.", "type": "string" }, "position_x": { "description": "X position.", "type": "integer" }, "position_y": { "description": "Y position.", "type": "integer" }, "title": { "description": "Note title.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" }, "width": { "description": "Width.", "type": "integer" } }, "title": "note data", "type": "object" }], "type": "object" }, "op": { "description": "Operation. For create, put any new UUID in data.uuid; for update/delete, put target UUID in operation.uuid.", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "description": "ERD entity type. This selects the operation.data shape.", "enum": ["table", "column", "index", "index_column", "ref", "ref_column", "enum", "enum_value", "table_group", "table_group_table", "table_group_note", "filter", "note"], "type": "string" }, "uuid": { "description": "Target UUID for update/delete. Do not use this for create; use data.uuid instead.", "type": "string" } }, "required": ["op", "type"], "type": "object" }, "type": "array" } }, "required": ["documentUuid", "description", "operations"], "type": "object" }, "required": ["description", "documentUuid", "operations"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "dd64d1f996060ab1" },
708
- { "mcpName": "mcp__ainecto__erd_arrange_tables", "commandPath": ["erd", "arrange-tables"], "group": "erd", "description": "Arrange ERD tables after edits. Default incremental mode places newly-created tables while preserving existing positions; full=true re-layouts the entire diagram. Use after several separate table edits to tidy the canvas.", "inputSchema": { "properties": { "documentUuid": { "description": "ERD document UUID", "type": "string" }, "full": { "description": "Re-layout the ENTIRE diagram (overwrites existing positions). Default false = arrange only newly-added (uncoordinated) tables to the top-right while preserving existing table positions. Call this after creating several tables to tidy them up.", "type": "boolean" } }, "required": ["documentUuid"], "type": "object" }, "required": ["documentUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "d2e66548e3d60d30" },
709
- { "mcpName": "mcp__ainecto__erd_get_enum", "commandPath": ["erd", "get-enum"], "group": "erd", "description": "", "inputSchema": { "properties": { "documentUuid": { "description": "ERD document UUID", "type": "string" }, "enumUuid": { "description": "Enum UUID", "type": "string" } }, "required": ["documentUuid", "enumUuid"], "type": "object" }, "required": ["documentUuid", "enumUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "1e477b3fadb50c6b" },
710
- { "mcpName": "mcp__ainecto__erd_get_ref", "commandPath": ["erd", "get-ref"], "group": "erd", "description": "", "inputSchema": { "properties": { "documentUuid": { "description": "ERD document UUID", "type": "string" }, "refUuid": { "description": "Relationship UUID", "type": "string" } }, "required": ["documentUuid", "refUuid"], "type": "object" }, "required": ["documentUuid", "refUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "40bf1eb02e7a9730" },
711
- { "mcpName": "mcp__ainecto__erd_get_table", "commandPath": ["erd", "get-table"], "group": "erd", "description": "Get one ERD table with nested columns and indexes. Returns persistent table/column/index UUIDs usable directly as mcp__ainecto__erd_apply_changes update/delete targets and ref_column column references. Column fields use apply_changes snake_case names: uuid, table_uuid, name, type, is_pk, is_unique, is_not_null, is_increment, default_value, note, display_order. Index fields include uuid, name, columns (column UUIDs), is_unique, is_pk, type.", "inputSchema": { "description": "Get one ERD table with nested columns and indexes. Returns persistent table/column/index UUIDs usable directly as mcp__ainecto__erd_apply_changes update/delete targets and ref_column column references. Column fields use apply_changes snake_case names: uuid, table_uuid, name, type, is_pk, is_unique, is_not_null, is_increment, default_value, note, display_order. Index fields include uuid, name, columns (column UUIDs), is_unique, is_pk, type.", "properties": { "documentUuid": { "description": "ERD document UUID", "type": "string" }, "tableUuid": { "description": "Table UUID. Response includes columns[] and indexes[] with persistent UUIDs for apply_changes.", "type": "string" } }, "required": ["documentUuid", "tableUuid"], "type": "object" }, "required": ["documentUuid", "tableUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "8edd40794f39f47a" },
712
- { "mcpName": "mcp__ainecto__erd_get_table_group", "commandPath": ["erd", "get-table-group"], "group": "erd", "description": "", "inputSchema": { "properties": { "documentUuid": { "description": "ERD document UUID", "type": "string" }, "tableGroupUuid": { "description": "Table group UUID", "type": "string" } }, "required": ["documentUuid", "tableGroupUuid"], "type": "object" }, "required": ["documentUuid", "tableGroupUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "bcd417344639c0c9" },
713
- { "mcpName": "mcp__ainecto__erd_list_enums", "commandPath": ["erd", "list-enums"], "group": "erd", "description": "", "inputSchema": { "properties": { "documentUuid": { "description": "ERD document UUID", "type": "string" } }, "required": ["documentUuid"], "type": "object" }, "required": ["documentUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "e0c2207905dbb068" },
714
- { "mcpName": "mcp__ainecto__erd_list_refs", "commandPath": ["erd", "list-refs"], "group": "erd", "description": "", "inputSchema": { "properties": { "documentUuid": { "description": "ERD document UUID", "type": "string" } }, "required": ["documentUuid"], "type": "object" }, "required": ["documentUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "9ae2e04c310b57f2" },
715
- { "mcpName": "mcp__ainecto__erd_list_table_groups", "commandPath": ["erd", "list-table-groups"], "group": "erd", "description": "", "inputSchema": { "properties": { "documentUuid": { "description": "ERD document UUID", "type": "string" } }, "required": ["documentUuid"], "type": "object" }, "required": ["documentUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "dc8811071df04ac2" },
716
- { "mcpName": "mcp__ainecto__erd_list_tables", "commandPath": ["erd", "list-tables"], "group": "erd", "description": "", "inputSchema": { "properties": { "documentUuid": { "description": "ERD document UUID", "type": "string" } }, "required": ["documentUuid"], "type": "object" }, "required": ["documentUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "ca05b35b174fc209" },
717
- { "mcpName": "mcp__ainecto__get_attachment", "commandPath": ["attachment", "get"], "group": "attachment", "description": "", "inputSchema": { "properties": { "uuid": { "description": "Attachment UUID", "type": "string" } }, "required": ["uuid"], "type": "object" }, "required": ["uuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "12bee70f6d254a5d" },
718
- { "mcpName": "mcp__ainecto__get_document", "commandPath": ["document", "get"], "group": "document", "description": "", "inputSchema": { "properties": { "uuid": { "description": "Document UUID", "type": "string" } }, "required": ["uuid"], "type": "object" }, "required": ["uuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "4949c8a5bb425491" },
719
- { "mcpName": "mcp__ainecto__get_project", "commandPath": ["project", "get"], "group": "generic", "description": "", "inputSchema": { "properties": { "uuid": { "description": "Project UUID", "type": "string" } }, "required": ["uuid"], "type": "object" }, "required": ["uuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "b4ee9097f4b80df4" },
720
- { "mcpName": "mcp__ainecto__get_version", "commandPath": ["version", "get"], "group": "generic", "description": "", "inputSchema": { "properties": { "documentUuid": { "description": "Document UUID", "type": "string" }, "versionNumber": { "description": "Version number", "type": "integer" } }, "required": ["documentUuid", "versionNumber"], "type": "object" }, "required": ["documentUuid", "versionNumber"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "ad81d04200d3d7f5" },
721
- { "mcpName": "mcp__ainecto__get_workspace", "commandPath": ["workspace", "get"], "group": "workspace", "description": "", "inputSchema": { "properties": { "uuid": { "description": "Workspace UUID", "type": "string" } }, "required": ["uuid"], "type": "object" }, "required": ["uuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "ef7bae555463116e" },
722
- { "mcpName": "mcp__ainecto__list_attachments", "commandPath": ["attachments", "list"], "group": "attachment", "description": "", "inputSchema": { "properties": { "projectUuid": { "description": "Project UUID", "type": "string" } }, "required": ["projectUuid"], "type": "object" }, "required": ["projectUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "a75657bb2e5eb383" },
723
- { "mcpName": "mcp__ainecto__list_documents", "commandPath": ["documents", "list"], "group": "document", "description": "", "inputSchema": { "properties": { "folderUuid": { "description": "Optional folder UUID", "type": "string" }, "limit": { "description": "Result cap when q is set (default 50)", "type": "integer" }, "projectUuid": { "description": "Project UUID", "type": "string" }, "q": { "description": "Optional full-text query. When provided, results come from the search index instead of folder listing.", "type": "string" }, "type": { "description": "Optional document type filter. Enabled values: erd, markdown.", "enum": ["erd", "markdown"], "type": "string" } }, "type": "object" }, "required": [], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "8dca77c1db9372c4" },
724
- { "mcpName": "mcp__ainecto__list_folders", "commandPath": ["folders", "list"], "group": "generic", "description": "", "inputSchema": { "properties": { "projectUuid": { "description": "Project UUID", "type": "string" } }, "required": ["projectUuid"], "type": "object" }, "required": ["projectUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "f3cde57fe87d375c" },
725
- { "mcpName": "mcp__ainecto__list_projects", "commandPath": ["projects", "list"], "group": "generic", "description": "", "inputSchema": { "properties": { "limit": { "description": "Result cap when q is set (default 50)", "type": "integer" }, "q": { "description": "Optional full-text query", "type": "string" }, "workspaceUuid": { "description": "Optional Workspace UUID", "type": "string" } }, "type": "object" }, "required": [], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "9174d6965d7e53f5" },
726
- { "mcpName": "mcp__ainecto__list_trash", "commandPath": ["trash", "list"], "group": "generic", "description": "", "inputSchema": { "properties": {}, "type": "object" }, "required": [], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "82f8586d68bfcd7d" },
727
- { "mcpName": "mcp__ainecto__list_versions", "commandPath": ["versions", "list"], "group": "generic", "description": "", "inputSchema": { "properties": { "documentUuid": { "description": "Document UUID", "type": "string" } }, "required": ["documentUuid"], "type": "object" }, "required": ["documentUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "370e4b8b8ec18e18" },
728
- { "mcpName": "mcp__ainecto__list_workspaces", "commandPath": ["workspaces", "list"], "group": "workspace", "description": "", "inputSchema": { "properties": {}, "type": "object" }, "required": [], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "7c32c74000696b2f" },
729
- { "mcpName": "mcp__ainecto__markdown_append_document", "commandPath": ["markdown", "append-document"], "group": "markdown", "description": "Append to Markdown documents. If the appended body is tens of KB or more, prefer request_upload_token (purpose=document.append) and upload the body with curl.", "inputSchema": { "properties": { "items": { "description": "Appends", "items": { "properties": { "addition": { "description": "Body addition. \uCD94\uAC00\uD560 \uB0B4\uC6A9\uC774 \uD06C\uBA74 (\uC218\uC2ED KB \uC774\uC0C1) LLM \uD1A0\uD070 \uC808\uC57D\uC744 \uC704\uD574 `request_upload_token (purpose=document.append, scopeJson={documentUuid, separator})` \uB85C uploadUrl \uC744 \uBC1B\uACE0 curl \uB85C PUT \uC73C\uB85C \uBCF4\uB0BC \uAC83.", "type": "string" }, "separator": { "description": "Separator between existing body and addition", "type": "string" }, "uuid": { "description": "Document UUID", "type": "string" } }, "required": ["uuid", "addition"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "f189514b3cccd1cc" },
730
- { "mcpName": "mcp__ainecto__markdown_generate_document", "commandPath": ["markdown", "generate-document"], "group": "markdown", "description": "", "inputSchema": { "properties": { "items": { "description": "Generation requests", "items": { "properties": { "context": { "description": "Optional JSON context", "type": "string" }, "documentUuid": { "description": "Target document UUID", "type": "string" }, "prompt": { "description": "Generation prompt", "type": "string" } }, "required": ["documentUuid", "prompt"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "72114a2d2642ed92" },
731
- { "mcpName": "mcp__ainecto__move_documents", "commandPath": ["documents", "move"], "group": "document", "description": "", "inputSchema": { "properties": { "clientMutationId": { "description": "Optional mutation identifier for client retry tracing", "maxLength": 128, "minLength": 1, "pattern": "^[\\x21-\\x7E]{1,128}$", "type": "string" }, "items": { "description": "Moves", "items": { "properties": { "newFolderUuid": { "description": "New folder UUID \u2014 null/empty for root", "type": "string" }, "targetProjectUuid": { "description": "Target project UUID \u2014 omit to stay in current project", "type": "string" }, "targetWorkspaceUuid": { "description": "Guard only: must match targetProjectUuid workspace", "type": "string" }, "uuid": { "description": "Document UUID", "type": "string" } }, "required": ["uuid"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "prod", "schemaHash": "9758d6a262913a3f" },
732
- { "mcpName": "mcp__ainecto__move_folders", "commandPath": ["folders", "move"], "group": "generic", "description": "", "inputSchema": { "properties": { "items": { "description": "Moves", "items": { "properties": { "newParentFolderUuid": { "description": "New parent UUID \u2014 null/empty for root", "type": "string" }, "uuid": { "description": "Folder UUID", "type": "string" } }, "required": ["uuid"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "prod", "schemaHash": "612a779b1fd176bb" },
733
- { "mcpName": "mcp__ainecto__request_upload_token", "commandPath": ["tools", "run", "request-upload-token"], "group": "attachment", "description": "Use this for large payloads of tens of KB or more to save LLM tokens. purpose=mcp.call returns a short-lived bearer token that can call any MCP tool, including mcp__ainecto__erd_apply_changes, via curl. purpose=document.create/update/append uploads document bodies directly to the upload endpoint with PUT/curl. Tokens are reusable for 5 minutes by default.", "inputSchema": { "properties": { "maxBytes": { "description": "\uCD5C\uB300 \uC5C5\uB85C\uB4DC byte (\uC11C\uBC84 \uCE21 \uD55C\uB3C4)", "type": "integer" }, "purpose": { "description": "Token \uC6A9\uB3C4. `mcp.call` \uC740 curl \uB85C /mcp \uD638\uCD9C, `document.create/update/append` \uB294 PUT body \uB85C \uBCF8\uBB38 \uC5C5\uB85C\uB4DC, `attachment.upload` \uB294 \uD30C\uC77C bytes \uC5C5\uB85C\uB4DC. \uCF58\uD150\uCE20\uAC00 \uD06C\uBA74 (\uC218\uC2ED KB \uC774\uC0C1) LLM \uD1A0\uD070 \uC808\uC57D\uC744 \uC704\uD574 \uC774 \uB3C4\uAD6C\uB85C 5\uBD84 \uC7AC\uC0AC\uC6A9 \uD1A0\uD070\uC744 \uBC1B\uACE0 curl \uB85C \uBCF4\uB0B4\uB294 \uAC83\uC744 \uAC15\uB825 \uAD8C\uC7A5.", "enum": ["mcp.call", "document.create", "document.update", "document.append", "attachment.upload"], "type": "string" }, "scopeJson": { "description": "\uC120\uD0DD \u2014 JSON scope. document.create \uB294 projectUuid, type, title \uC744 \uD3EC\uD568\uD558\uACE0 enabled type values \uB294 erd, markdown. document.update/document.append \uB294 documentUuid \uB97C \uD3EC\uD568. attachment.upload: {documentUuid, filename, contentType?, sizeBytes?}; storageKey \uB294 \uC11C\uBC84\uAC00 \uBC1C\uAE09 scope \uC5D0 \uCD94\uAC00.", "type": "string" }, "ttlSeconds": { "description": "TTL (\uCD08). \uAE30\uBCF8 300 (5\uBD84)", "type": "integer" } }, "required": ["purpose"], "type": "object" }, "required": ["purpose"], "payloadMode": "binary-upload", "destructive": false, "sourceCatalog": "prod", "schemaHash": "f1eb94523f43e72b" },
734
- { "mcpName": "mcp__ainecto__restore_versions", "commandPath": ["versions", "restore"], "group": "generic", "description": "", "inputSchema": { "properties": { "items": { "description": "Restores", "items": { "properties": { "documentUuid": { "description": "Document UUID", "type": "string" }, "versionNumber": { "description": "Version number to restore", "type": "integer" } }, "required": ["documentUuid", "versionNumber"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "prod", "schemaHash": "c4f448cc3b96c7a6" },
735
- { "mcpName": "mcp__ainecto__search_documents", "commandPath": ["documents", "search"], "group": "document", "description": "", "inputSchema": { "properties": { "limit": { "description": "\uACB0\uACFC \uAC1C\uC218 (\uAE30\uBCF8 20, \uCD5C\uB300 50)", "type": "integer" }, "offset": { "description": "\uD398\uC774\uC9C0\uB124\uC774\uC158 offset (\uAE30\uBCF8 0)", "type": "integer" }, "projectUuid": { "description": "\uD2B9\uC815 \uD504\uB85C\uC81D\uD2B8\uB85C \uD55C\uC815 (\uC120\uD0DD)", "type": "string" }, "q": { "description": "\uAC80\uC0C9 \uD0A4\uC6CC\uB4DC", "type": "string" }, "type": { "description": "Optional document type filter. Enabled values: erd, markdown.", "enum": ["erd", "markdown"], "type": "string" } }, "required": ["q"], "type": "object" }, "required": ["q"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "b88c3537086d0c7a" },
736
- { "mcpName": "mcp__ainecto__unarchive_documents", "commandPath": ["documents", "unarchive"], "group": "document", "description": "", "inputSchema": { "properties": { "uuids": { "description": "Document UUIDs", "items": { "type": "string" }, "type": "array" } }, "required": ["uuids"], "type": "object" }, "required": ["uuids"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "prod", "schemaHash": "4aae7df04fba8779" },
737
- { "mcpName": "mcp__ainecto__unarchive_folders", "commandPath": ["folders", "unarchive"], "group": "generic", "description": "", "inputSchema": { "properties": { "uuids": { "description": "Folder UUIDs", "items": { "type": "string" }, "type": "array" } }, "required": ["uuids"], "type": "object" }, "required": ["uuids"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "prod", "schemaHash": "e307cab0ebafd179" },
738
- { "mcpName": "mcp__ainecto__unarchive_projects", "commandPath": ["projects", "unarchive"], "group": "generic", "description": "", "inputSchema": { "properties": { "uuids": { "description": "Project UUIDs", "items": { "type": "string" }, "type": "array" } }, "required": ["uuids"], "type": "object" }, "required": ["uuids"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "prod", "schemaHash": "15ea4a409d1549de" },
739
- { "mcpName": "mcp__ainecto__unarchive_workspaces", "commandPath": ["workspaces", "unarchive"], "group": "workspace", "description": "", "inputSchema": { "properties": { "uuids": { "description": "Workspace UUIDs", "items": { "type": "string" }, "type": "array" } }, "required": ["uuids"], "type": "object" }, "required": ["uuids"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "prod", "schemaHash": "641de5b64ec75ab2" },
740
- { "mcpName": "mcp__ainecto__update_documents", "commandPath": ["documents", "update"], "group": "document", "description": "Update documents. If a replacement body is tens of KB or more, prefer request_upload_token (purpose=document.update) and upload the body with curl.", "inputSchema": { "properties": { "items": { "description": "Updates", "items": { "properties": { "body": { "description": "Deprecated compatibility alias for typePayload.body when document type is markdown. \uBCF8\uBB38\uC774 \uC218\uC2ED KB \uC774\uC0C1\uC73C\uB85C \uD06C\uBA74 LLM \uD1A0\uD070 \uC808\uC57D\uC744 \uC704\uD574 `request_upload_token (purpose=document.update)` \uB97C \uD638\uCD9C\uD574 uploadUrl \uC744 \uBC1B\uACE0 curl \uB85C \uC9C1\uC811 PUT \uC73C\uB85C \uBCF4\uB0BC \uAC83.", "type": "string" }, "title": { "description": "New title", "type": "string" }, "typePayload": { "description": "Type-specific payload for enabled document types. markdown: {body}.", "type": "object" }, "uuid": { "description": "Document UUID", "type": "string" } }, "required": ["uuid"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "5bedb6e5f5ba4f8d" },
741
- { "mcpName": "mcp__ainecto__update_folders", "commandPath": ["folders", "update"], "group": "generic", "description": "", "inputSchema": { "properties": { "items": { "description": "Updates", "items": { "properties": { "name": { "description": "New name", "type": "string" }, "uuid": { "description": "Folder UUID", "type": "string" } }, "required": ["uuid", "name"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "7bef3b4702df1f00" },
742
- { "mcpName": "mcp__ainecto__update_projects", "commandPath": ["projects", "update"], "group": "generic", "description": "", "inputSchema": { "properties": { "items": { "description": "Updates", "items": { "properties": { "description": { "description": "New description", "type": "string" }, "name": { "description": "New name", "type": "string" }, "uuid": { "description": "Project UUID", "type": "string" } }, "required": ["uuid"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "10cc0d84aeb2192b" },
743
- { "mcpName": "mcp__ainecto__update_workspaces", "commandPath": ["workspaces", "update"], "group": "workspace", "description": "", "inputSchema": { "properties": { "items": { "description": "Updates", "items": { "properties": { "description": { "description": "New description", "type": "string" }, "name": { "description": "New name", "type": "string" }, "uuid": { "description": "Workspace UUID", "type": "string" } }, "required": ["uuid"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "723e4f6d36ee9d02" },
744
- { "mcpName": "mcp__ainecto__upload_attachments", "commandPath": ["tools", "run", "upload-attachments"], "group": "attachment", "description": "", "inputSchema": { "properties": { "items": { "description": "Attachments to register", "items": { "properties": { "contentType": { "description": "MIME type", "type": "string" }, "documentUuid": { "description": "Parent document UUID", "type": "string" }, "filename": { "description": "Original file name", "type": "string" }, "sizeBytes": { "description": "Size in bytes", "type": "integer" }, "storageKey": { "description": "Storage key returned by request_upload_token purpose=attachment.upload", "type": "string" } }, "required": ["documentUuid", "filename", "storageKey"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "binary-upload", "destructive": false, "sourceCatalog": "prod", "schemaHash": "0622e590de3404dd" }
855
+ { "mcpName": "create_documents", "commandPath": ["documents", "create"], "group": "document", "description": "Create documents. If a document body is tens of KB or more, prefer request_upload_token (purpose=document.create) and upload the body with curl.", "inputSchema": { "properties": { "items": { "description": "Documents", "items": { "properties": { "body": { "description": "Deprecated compatibility alias for typePayload.body when document type is markdown. \uBCF8\uBB38\uC774 \uC218\uC2ED KB \uC774\uC0C1\uC73C\uB85C \uD06C\uBA74 LLM \uD1A0\uD070 \uC808\uC57D\uC744 \uC704\uD574 `request_upload_token (purpose=document.create)` \uB97C \uD638\uCD9C\uD574 uploadUrl \uC744 \uBC1B\uACE0 curl \uB85C \uC9C1\uC811 PUT \uC73C\uB85C \uBCF4\uB0BC \uAC83.", "type": "string" }, "folderUuid": { "description": "Optional folder UUID", "type": "string" }, "projectUuid": { "description": "Project UUID", "type": "string" }, "title": { "description": "Document title", "type": "string" }, "type": { "description": "Document type. Creatable values: erd, markdown, diagram, openapi, html.", "enum": ["erd", "markdown", "diagram", "openapi", "html"], "type": "string" }, "typePayload": { "description": "Type-specific payload for enabled document types. markdown: {body}; diagram: {diagramSource,sourceLanguage,edgeStyle,isModule,metadata}; openapi: {content}; html: {content} (a full HTML page).", "type": "object" } }, "required": ["projectUuid", "type", "title"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "105d051791cf595b" },
856
+ { "mcpName": "create_folders", "commandPath": ["folders", "create"], "group": "generic", "description": "", "inputSchema": { "properties": { "items": { "description": "Folders", "items": { "properties": { "name": { "description": "Folder name", "type": "string" }, "parentFolderUuid": { "description": "Parent folder UUID", "type": "string" }, "projectUuid": { "description": "Project UUID", "type": "string" } }, "required": ["projectUuid", "name"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "30f45b887259609f" },
857
+ { "mcpName": "create_projects", "commandPath": ["projects", "create"], "group": "generic", "description": "", "inputSchema": { "properties": { "items": { "description": "Projects", "items": { "properties": { "description": { "description": "Description", "type": "string" }, "name": { "description": "Project name", "type": "string" }, "projectKey": { "description": "Unique project key", "type": "string" }, "workspaceUuid": { "description": "Workspace UUID \u2014 null uses user's default workspace", "type": "string" } }, "required": ["name"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "edf5cf688d7c039a" },
858
+ { "mcpName": "create_workspaces", "commandPath": ["workspaces", "create"], "group": "workspace", "description": "", "inputSchema": { "properties": { "items": { "description": "Workspaces", "items": { "properties": { "description": { "description": "Description", "type": "string" }, "name": { "description": "Workspace name", "type": "string" } }, "required": ["name"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "d9d1b0d561828b8e" },
859
+ { "mcpName": "delete_attachments", "commandPath": ["attachments", "delete"], "group": "attachment", "description": "", "inputSchema": { "properties": { "uuids": { "description": "Attachment UUIDs", "items": { "type": "string" }, "type": "array" } }, "required": ["uuids"], "type": "object" }, "required": ["uuids"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "prod", "schemaHash": "b22c24bffdfe3d7d" },
860
+ { "mcpName": "delete_documents", "commandPath": ["documents", "delete"], "group": "document", "description": "", "inputSchema": { "properties": { "uuids": { "description": "Document UUIDs", "items": { "type": "string" }, "type": "array" } }, "required": ["uuids"], "type": "object" }, "required": ["uuids"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "prod", "schemaHash": "3a725ae70956c270" },
861
+ { "mcpName": "delete_folders", "commandPath": ["folders", "delete"], "group": "generic", "description": "", "inputSchema": { "properties": { "uuids": { "description": "Folder UUIDs", "items": { "type": "string" }, "type": "array" } }, "required": ["uuids"], "type": "object" }, "required": ["uuids"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "prod", "schemaHash": "2254cde89c9e8f06" },
862
+ { "mcpName": "delete_projects", "commandPath": ["projects", "delete"], "group": "generic", "description": "", "inputSchema": { "properties": { "uuids": { "description": "Project UUIDs", "items": { "type": "string" }, "type": "array" } }, "required": ["uuids"], "type": "object" }, "required": ["uuids"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "prod", "schemaHash": "6521c33a28cb5465" },
863
+ { "mcpName": "delete_workspaces", "commandPath": ["workspaces", "delete"], "group": "workspace", "description": "", "inputSchema": { "properties": { "uuids": { "description": "Workspace UUIDs", "items": { "type": "string" }, "type": "array" } }, "required": ["uuids"], "type": "object" }, "required": ["uuids"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "prod", "schemaHash": "3177868221137066" },
864
+ { "mcpName": "diagram_apply_changes", "commandPath": ["tools", "run", "diagram-apply-changes"], "group": "generic", "description": "Required single-call mutation path for diagram node/edge/group create/update/delete changes. Put nodes, edges, and groups in one request and call this once. Do not look for type-specific diagram CUD tools; this tool is the only diagram CUD entry point.", "inputSchema": { "properties": { "description": { "description": "Reason for batch (audit)", "type": "string" }, "diagramUuid": { "description": "Diagram UUID", "type": "string" }, "edges": { "description": "Edge operations", "items": { "properties": { "action": { "description": "Operation kind", "enum": ["create", "update", "delete"], "type": "string" }, "bridgeType": { "description": "Bridge type", "type": "string" }, "fromUuid": { "description": "Source", "type": "string" }, "label": { "description": "Edge label", "type": "string" }, "lineStyle": { "description": "Line style", "type": "string" }, "strokeColor": { "description": "Stroke color", "type": "string" }, "strokeWidth": { "description": "Stroke width", "type": "integer" }, "toUuid": { "description": "Target", "type": "string" }, "uuid": { "description": "Required for update/delete", "type": "string" } }, "required": ["action"], "type": "object" }, "type": "array" }, "groups": { "description": "Group operations", "items": { "properties": { "action": { "description": "Operation kind", "enum": ["create", "update", "delete"], "type": "string" }, "collapsed": { "description": "Collapsed state", "type": "boolean" }, "color": { "description": "Group color", "type": "string" }, "height": { "description": "Explicit height; omit to keep it derived from member nodes", "type": "number" }, "linkMode": { "description": "Link mode", "type": "string" }, "linkedDiagramUuid": { "description": "Linked diagram UUID", "type": "string" }, "name": { "description": "Group name", "type": "string" }, "parentGroupUuid": { "description": "Parent group UUID", "type": "string" }, "sortOrder": { "description": "Sort order", "type": "integer" }, "uuid": { "description": "Required for update/delete", "type": "string" }, "width": { "description": "Explicit width; omit to keep it derived from member nodes", "type": "number" } }, "required": ["action"], "type": "object" }, "type": "array" }, "nodes": { "description": "Node operations", "items": { "properties": { "action": { "description": "Operation kind", "enum": ["create", "update", "delete"], "type": "string" }, "fillColor": { "description": "Fill color", "type": "string" }, "groupUuid": { "description": "Group UUID; empty string clears membership", "type": "string" }, "height": { "description": "Height", "type": "number" }, "shape": { "description": "Node shape (start/end/diamond/cylinder/parallelogram/stadium/hexagon/rect/roundRect/...)", "enum": ["rect", "roundRect", "stadium", "diamond", "decision", "circle", "start", "doubleCircle", "end", "hexagon", "cylinder", "parallelogram"], "type": "string" }, "strokeColor": { "description": "Stroke color", "type": "string" }, "text": { "description": "Node text/label. Supports newlines (\\n) for multi-line display.", "maxLength": 4e3, "type": "string" }, "textColor": { "description": "Text color", "type": "string" }, "type": { "description": "Node type", "type": "string" }, "uuid": { "description": "Required for update/delete; optional for create", "type": "string" }, "width": { "description": "Width", "type": "number" }, "x": { "description": "X", "type": "number" }, "y": { "description": "Y", "type": "number" } }, "required": ["action"], "type": "object" }, "type": "array" } }, "required": ["diagramUuid", "description"], "type": "object" }, "required": ["description", "diagramUuid"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "add78d8edce88c86" },
865
+ { "mcpName": "diagram_get_edge", "commandPath": ["tools", "run", "diagram-get-edge"], "group": "generic", "description": "", "inputSchema": { "properties": { "edgeUuid": { "description": "Edge UUID", "type": "string" } }, "required": ["edgeUuid"], "type": "object" }, "required": ["edgeUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "2c03c4f7daa69898" },
866
+ { "mcpName": "diagram_get_group", "commandPath": ["tools", "run", "diagram-get-group"], "group": "generic", "description": "", "inputSchema": { "properties": { "groupUuid": { "description": "Group UUID", "type": "string" } }, "required": ["groupUuid"], "type": "object" }, "required": ["groupUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "55c5aa27b8cf489d" },
867
+ { "mcpName": "diagram_get_node", "commandPath": ["tools", "run", "diagram-get-node"], "group": "generic", "description": "", "inputSchema": { "properties": { "nodeUuid": { "description": "Node UUID", "type": "string" } }, "required": ["nodeUuid"], "type": "object" }, "required": ["nodeUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "646d553238328a9e" },
868
+ { "mcpName": "diagram_get_pending_llm_requests", "commandPath": ["tools", "run", "diagram-get-pending-llm-requests"], "group": "generic", "description": "", "inputSchema": { "properties": {}, "type": "object" }, "required": [], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "2143d616cf7822bf" },
869
+ { "mcpName": "diagram_list_edges", "commandPath": ["tools", "run", "diagram-list-edges"], "group": "generic", "description": "", "inputSchema": { "properties": { "diagramUuid": { "description": "Diagram document UUID", "type": "string" } }, "required": ["diagramUuid"], "type": "object" }, "required": ["diagramUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "4645085bde616381" },
870
+ { "mcpName": "diagram_list_groups", "commandPath": ["tools", "run", "diagram-list-groups"], "group": "generic", "description": "", "inputSchema": { "properties": { "diagramUuid": { "description": "Diagram document UUID", "type": "string" } }, "required": ["diagramUuid"], "type": "object" }, "required": ["diagramUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "8a7ca45dd34d1aef" },
871
+ { "mcpName": "diagram_list_nodes", "commandPath": ["tools", "run", "diagram-list-nodes"], "group": "generic", "description": "", "inputSchema": { "properties": { "diagramUuid": { "description": "Diagram document UUID", "type": "string" } }, "required": ["diagramUuid"], "type": "object" }, "required": ["diagramUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "d444066b5ca6eeb4" },
872
+ { "mcpName": "diagram_submit_llm_result", "commandPath": ["tools", "run", "diagram-submit-llm-result"], "group": "generic", "description": "", "inputSchema": { "properties": { "cachedInputTokens": { "description": "Cached subset of provider total input tokens", "type": "integer" }, "creditsCharged": { "description": "Deprecated; ignored. Credits are calculated server-side from tokens.", "type": "integer" }, "inputTokens": { "description": "Provider total input tokens; Java persists uncached input after one normalization", "type": "integer" }, "outputTokens": { "description": "Output token count", "type": "integer" }, "result": { "description": "LLM result text", "type": "string" }, "uuid": { "description": "LLM request UUID", "type": "string" } }, "required": ["uuid", "result"], "type": "object" }, "required": ["result", "uuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "870ace32642a770a" },
873
+ { "mcpName": "diagram_update_llm_request_status", "commandPath": ["tools", "run", "diagram-update-llm-request-status"], "group": "generic", "description": "", "inputSchema": { "properties": { "errorMessage": { "description": "Error message (when status=FAILED)", "type": "string" }, "status": { "description": "Target status \u2014 currently only FAILED is mutable from caller side", "enum": ["FAILED"], "type": "string" }, "uuid": { "description": "LLM request UUID", "type": "string" } }, "required": ["uuid", "status"], "type": "object" }, "required": ["status", "uuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "9ad96e3dc5fd2a8f" },
874
+ { "mcpName": "disable_shares", "commandPath": ["tools", "run", "disable-shares"], "group": "generic", "description": "", "inputSchema": { "properties": { "targetType": { "description": "Share target type", "enum": ["project", "folder", "document"], "type": "string" }, "targetUuids": { "description": "Target UUIDs", "items": { "type": "string" }, "type": "array" } }, "required": ["targetType", "targetUuids"], "type": "object" }, "required": ["targetType", "targetUuids"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "bd1b310200b78420" },
875
+ { "mcpName": "enable_shares", "commandPath": ["tools", "run", "enable-shares"], "group": "generic", "description": "", "inputSchema": { "properties": { "targetType": { "description": "Share target type. A document gets a shareUrl anyone can open; a project gets a public API path only (there is no public project page); a folder token is stored but nothing public reads it yet.", "enum": ["project", "folder", "document"], "type": "string" }, "targetUuids": { "description": "Target UUIDs", "items": { "type": "string" }, "type": "array" } }, "required": ["targetType", "targetUuids"], "type": "object" }, "required": ["targetType", "targetUuids"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "18559d7c0af09d2c" },
876
+ { "mcpName": "erd_apply_changes", "commandPath": ["erd", "apply-changes"], "group": "erd", "description": 'Required single-call mutation path for ERD create/update/delete changes. Put tables, columns, indexes, refs, enums, enum_values, and table groups in one operations array and call this once.\nRules: (1) all data field names are snake_case; (2) for create, generate your own UUIDs and put them in data.uuid, then later operations in the same batch may reference those UUIDs without read-back calls; (3) enum values are separate enum_value operations, not inline on enum; (4) ref.relationship uses short dbdiagram-style symbols: >, <, or -; (5) ref_column operations map FK columns explicitly, never automatically.\nDo not look for type-specific ERD CUD tools; this tool is the only ERD CUD entry point. If operations is very large, use request_upload_token with purpose=mcp.call and send the tools/call payload with curl to save LLM tokens.\nOne-call worked example:\n{\n "documentUuid": "erd-doc-uuid",\n "description": "create users/posts ERD",\n "operations": [\n {"op":"create","type":"table","data":{"uuid":"tbl_users","name":"users","display_order":1}},\n {"op":"create","type":"column","data":{"uuid":"col_users_id","table_uuid":"tbl_users","name":"id","type":"bigint","is_pk":true,"is_not_null":true,"display_order":1}},\n {"op":"create","type":"column","data":{"uuid":"col_users_email","table_uuid":"tbl_users","name":"email","type":"varchar(255)","is_unique":true,"is_not_null":true,"display_order":2}},\n {"op":"create","type":"table","data":{"uuid":"tbl_posts","name":"posts","display_order":2}},\n {"op":"create","type":"column","data":{"uuid":"col_posts_id","table_uuid":"tbl_posts","name":"id","type":"bigint","is_pk":true,"is_not_null":true,"display_order":1}},\n {"op":"create","type":"column","data":{"uuid":"col_posts_author_id","table_uuid":"tbl_posts","name":"author_id","type":"bigint","is_not_null":true,"display_order":2}},\n {"op":"create","type":"column","data":{"uuid":"col_posts_status","table_uuid":"tbl_posts","name":"status","type":"post_status","is_not_null":true,"display_order":3}},\n {"op":"create","type":"enum","data":{"uuid":"enum_post_status","name":"post_status"}},\n {"op":"create","type":"enum_value","data":{"uuid":"enum_post_status_draft","enum_uuid":"enum_post_status","value":"draft","display_order":1}},\n {"op":"create","type":"enum_value","data":{"uuid":"enum_post_status_published","enum_uuid":"enum_post_status","value":"published","display_order":2}},\n {"op":"create","type":"ref","data":{"uuid":"ref_posts_author","name":"fk_posts_author","from_table_uuid":"tbl_posts","to_table_uuid":"tbl_users","relationship":">","on_delete":"cascade"}},\n {"op":"create","type":"ref_column","data":{"uuid":"refcol_posts_author","ref_uuid":"ref_posts_author","from_column_uuid":"col_posts_author_id","to_column_uuid":"col_users_id","display_order":1}}\n ]\n}', "inputSchema": { "properties": { "arrangeNewTables": { "description": "Whether to auto-place newly-created tables after the batch; defaults to true", "type": "boolean" }, "description": { "description": "Reason for batch (audit)", "type": "string" }, "documentUuid": { "description": "ERD document UUID", "type": "string" }, "operations": { "description": "Atomic create/update/delete operations", "items": { "description": "One ERD mutation. Use a single operations array with client-generated data.uuid values to create cross-referenced tables, columns, enums, refs, and ref_columns without read-back calls.", "discriminator": { "propertyName": "type" }, "oneOf": [{ "allOf": [{ "if": { "properties": { "op": { "const": "create", "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": { "data": { "description": "Payload for operation.type='table'.", "properties": { "alias": { "description": "Display alias.", "type": "string" }, "display_order": { "description": "Display order.", "type": "integer" }, "header_color": { "description": "Header color.", "type": "string" }, "name": { "description": "Table name. Required for create.", "type": "string" }, "note": { "description": "Table note.", "type": "string" }, "schema_name": { "description": "Schema name.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "required": ["name"], "title": "table data", "type": "object" } }, "required": ["data"], "type": "object" } }, { "if": { "properties": { "op": { "enum": ["update", "delete"], "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": {}, "required": ["uuid"], "type": "object" } }], "properties": { "data": { "description": "Payload for operation.type='table'.", "properties": { "alias": { "description": "Display alias.", "type": "string" }, "display_order": { "description": "Display order.", "type": "integer" }, "header_color": { "description": "Header color.", "type": "string" }, "name": { "description": "Table name. Required for create.", "type": "string" }, "note": { "description": "Table note.", "type": "string" }, "schema_name": { "description": "Schema name.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "title": "table data", "type": "object" }, "op": { "description": "Operation", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "const": "table", "description": "Must be 'table' for this payload.", "type": "string" }, "uuid": { "description": "Target UUID for update/delete.", "type": "string" } }, "required": ["op", "type"], "title": "table operation", "type": "object" }, { "allOf": [{ "if": { "properties": { "op": { "const": "create", "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": { "data": { "description": "Payload for operation.type='column'.", "properties": { "default_value": { "description": "Default value.", "type": "string" }, "display_order": { "description": "Display order.", "type": "integer" }, "is_increment": { "description": "Auto-increment flag.", "type": "boolean" }, "is_not_null": { "description": "Not-null flag.", "type": "boolean" }, "is_pk": { "description": "Primary key flag.", "type": "boolean" }, "is_unique": { "description": "Unique flag.", "type": "boolean" }, "name": { "description": "Column name.", "type": "string" }, "note": { "description": "Column note.", "type": "string" }, "table_uuid": { "description": "Parent table UUID. Required for create; may be a data.uuid from an earlier table operation in this batch.", "type": "string" }, "type": { "description": "SQL data type. Use this exact field name; do not send data_type.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "required": ["table_uuid"], "title": "column data", "type": "object" } }, "required": ["data"], "type": "object" } }, { "if": { "properties": { "op": { "enum": ["update", "delete"], "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": {}, "required": ["uuid"], "type": "object" } }], "properties": { "data": { "description": "Payload for operation.type='column'.", "properties": { "default_value": { "description": "Default value.", "type": "string" }, "display_order": { "description": "Display order.", "type": "integer" }, "is_increment": { "description": "Auto-increment flag.", "type": "boolean" }, "is_not_null": { "description": "Not-null flag.", "type": "boolean" }, "is_pk": { "description": "Primary key flag.", "type": "boolean" }, "is_unique": { "description": "Unique flag.", "type": "boolean" }, "name": { "description": "Column name.", "type": "string" }, "note": { "description": "Column note.", "type": "string" }, "table_uuid": { "description": "Parent table UUID. Required for create; may be a data.uuid from an earlier table operation in this batch.", "type": "string" }, "type": { "description": "SQL data type. Use this exact field name; do not send data_type.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "title": "column data", "type": "object" }, "op": { "description": "Operation", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "const": "column", "description": "Must be 'column' for this payload.", "type": "string" }, "uuid": { "description": "Target UUID for update/delete.", "type": "string" } }, "required": ["op", "type"], "title": "column operation", "type": "object" }, { "allOf": [{ "if": { "properties": { "op": { "const": "create", "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": { "data": { "description": "Payload for operation.type='index'.", "properties": { "is_pk": { "description": "Primary key index flag.", "type": "boolean" }, "is_unique": { "description": "Unique index flag.", "type": "boolean" }, "name": { "description": "Index name.", "type": "string" }, "note": { "description": "Index note.", "type": "string" }, "table_uuid": { "description": "Parent table UUID. Required for create.", "type": "string" }, "type": { "description": "Index type.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "required": ["table_uuid"], "title": "index data", "type": "object" } }, "required": ["data"], "type": "object" } }, { "if": { "properties": { "op": { "enum": ["update", "delete"], "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": {}, "required": ["uuid"], "type": "object" } }], "properties": { "data": { "description": "Payload for operation.type='index'.", "properties": { "is_pk": { "description": "Primary key index flag.", "type": "boolean" }, "is_unique": { "description": "Unique index flag.", "type": "boolean" }, "name": { "description": "Index name.", "type": "string" }, "note": { "description": "Index note.", "type": "string" }, "table_uuid": { "description": "Parent table UUID. Required for create.", "type": "string" }, "type": { "description": "Index type.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "title": "index data", "type": "object" }, "op": { "description": "Operation", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "const": "index", "description": "Must be 'index' for this payload.", "type": "string" }, "uuid": { "description": "Target UUID for update/delete.", "type": "string" } }, "required": ["op", "type"], "title": "index operation", "type": "object" }, { "allOf": [{ "if": { "properties": { "op": { "const": "create", "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": { "data": { "description": "Payload for operation.type='index_column'.", "properties": { "column_uuid": { "description": "Column UUID. Required for create.", "type": "string" }, "display_order": { "description": "Display order.", "type": "integer" }, "index_uuid": { "description": "Index UUID. Required for create.", "type": "string" }, "sort_order": { "description": "Sort order, for example ASC or DESC.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "required": ["index_uuid", "column_uuid"], "title": "index_column data", "type": "object" } }, "required": ["data"], "type": "object" } }, { "if": { "properties": { "op": { "enum": ["update", "delete"], "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": {}, "required": ["uuid"], "type": "object" } }], "properties": { "data": { "description": "Payload for operation.type='index_column'.", "properties": { "column_uuid": { "description": "Column UUID. Required for create.", "type": "string" }, "display_order": { "description": "Display order.", "type": "integer" }, "index_uuid": { "description": "Index UUID. Required for create.", "type": "string" }, "sort_order": { "description": "Sort order, for example ASC or DESC.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "title": "index_column data", "type": "object" }, "op": { "description": "Operation", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "const": "index_column", "description": "Must be 'index_column' for this payload.", "type": "string" }, "uuid": { "description": "Target UUID for update/delete.", "type": "string" } }, "required": ["op", "type"], "title": "index_column operation", "type": "object" }, { "allOf": [{ "if": { "properties": { "op": { "const": "create", "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": { "data": { "description": "Payload for operation.type='ref'.", "properties": { "from_table_uuid": { "description": "Source table UUID. Required for create.", "type": "string" }, "name": { "description": "Reference name.", "type": "string" }, "on_delete": { "description": "ON DELETE action.", "type": "string" }, "on_update": { "description": "ON UPDATE action.", "type": "string" }, "relationship": { "description": "Required for create. Use short dbdiagram-style relationship symbols.", "enum": [">", "<", "-"], "type": "string" }, "to_table_uuid": { "description": "Target table UUID. Required for create.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "required": ["from_table_uuid", "to_table_uuid", "relationship"], "title": "ref data", "type": "object" } }, "required": ["data"], "type": "object" } }, { "if": { "properties": { "op": { "enum": ["update", "delete"], "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": {}, "required": ["uuid"], "type": "object" } }], "properties": { "data": { "description": "Payload for operation.type='ref'.", "properties": { "from_table_uuid": { "description": "Source table UUID. Required for create.", "type": "string" }, "name": { "description": "Reference name.", "type": "string" }, "on_delete": { "description": "ON DELETE action.", "type": "string" }, "on_update": { "description": "ON UPDATE action.", "type": "string" }, "relationship": { "description": "Required for create. Use short dbdiagram-style relationship symbols.", "enum": [">", "<", "-"], "type": "string" }, "to_table_uuid": { "description": "Target table UUID. Required for create.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "title": "ref data", "type": "object" }, "op": { "description": "Operation", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "const": "ref", "description": "Must be 'ref' for this payload.", "type": "string" }, "uuid": { "description": "Target UUID for update/delete.", "type": "string" } }, "required": ["op", "type"], "title": "ref operation", "type": "object" }, { "allOf": [{ "if": { "properties": { "op": { "const": "create", "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": { "data": { "description": "Payload for operation.type='ref_column'.", "properties": { "display_order": { "description": "Display order.", "type": "integer" }, "from_column_uuid": { "description": "Source column UUID. Required for create.", "type": "string" }, "ref_uuid": { "description": "Reference UUID. Required for create; may be a data.uuid from an earlier ref operation.", "type": "string" }, "to_column_uuid": { "description": "Target column UUID. Required for create.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "required": ["ref_uuid", "from_column_uuid", "to_column_uuid"], "title": "ref_column data", "type": "object" } }, "required": ["data"], "type": "object" } }, { "if": { "properties": { "op": { "enum": ["update", "delete"], "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": {}, "required": ["uuid"], "type": "object" } }], "properties": { "data": { "description": "Payload for operation.type='ref_column'.", "properties": { "display_order": { "description": "Display order.", "type": "integer" }, "from_column_uuid": { "description": "Source column UUID. Required for create.", "type": "string" }, "ref_uuid": { "description": "Reference UUID. Required for create; may be a data.uuid from an earlier ref operation.", "type": "string" }, "to_column_uuid": { "description": "Target column UUID. Required for create.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "title": "ref_column data", "type": "object" }, "op": { "description": "Operation", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "const": "ref_column", "description": "Must be 'ref_column' for this payload.", "type": "string" }, "uuid": { "description": "Target UUID for update/delete.", "type": "string" } }, "required": ["op", "type"], "title": "ref_column operation", "type": "object" }, { "allOf": [{ "if": { "properties": { "op": { "const": "create", "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": { "data": { "description": "Payload for operation.type='enum'.", "properties": { "name": { "description": "Enum name.", "type": "string" }, "note": { "description": "Enum note.", "type": "string" }, "schema_name": { "description": "Schema name.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "title": "enum data", "type": "object" } }, "required": ["data"], "type": "object" } }, { "if": { "properties": { "op": { "enum": ["update", "delete"], "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": {}, "required": ["uuid"], "type": "object" } }], "properties": { "data": { "description": "Payload for operation.type='enum'.", "properties": { "name": { "description": "Enum name.", "type": "string" }, "note": { "description": "Enum note.", "type": "string" }, "schema_name": { "description": "Schema name.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "title": "enum data", "type": "object" }, "op": { "description": "Operation", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "const": "enum", "description": "Must be 'enum' for this payload.", "type": "string" }, "uuid": { "description": "Target UUID for update/delete.", "type": "string" } }, "required": ["op", "type"], "title": "enum operation", "type": "object" }, { "allOf": [{ "if": { "properties": { "op": { "const": "create", "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": { "data": { "description": "Payload for operation.type='enum_value'.", "properties": { "display_order": { "description": "Display order.", "type": "integer" }, "enum_uuid": { "description": "Parent enum UUID. Required for create; may be a data.uuid from an earlier enum operation.", "type": "string" }, "note": { "description": "Enum value note.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" }, "value": { "description": "Enum value. Inline enum values on enum are not persisted; create enum_value operations.", "type": "string" } }, "required": ["enum_uuid"], "title": "enum_value data", "type": "object" } }, "required": ["data"], "type": "object" } }, { "if": { "properties": { "op": { "enum": ["update", "delete"], "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": {}, "required": ["uuid"], "type": "object" } }], "properties": { "data": { "description": "Payload for operation.type='enum_value'.", "properties": { "display_order": { "description": "Display order.", "type": "integer" }, "enum_uuid": { "description": "Parent enum UUID. Required for create; may be a data.uuid from an earlier enum operation.", "type": "string" }, "note": { "description": "Enum value note.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" }, "value": { "description": "Enum value. Inline enum values on enum are not persisted; create enum_value operations.", "type": "string" } }, "title": "enum_value data", "type": "object" }, "op": { "description": "Operation", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "const": "enum_value", "description": "Must be 'enum_value' for this payload.", "type": "string" }, "uuid": { "description": "Target UUID for update/delete.", "type": "string" } }, "required": ["op", "type"], "title": "enum_value operation", "type": "object" }, { "allOf": [{ "if": { "properties": { "op": { "const": "create", "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": { "data": { "description": "Payload for operation.type='table_group'.", "properties": { "color": { "description": "Group color.", "type": "string" }, "name": { "description": "Table group name.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "title": "table_group data", "type": "object" } }, "required": ["data"], "type": "object" } }, { "if": { "properties": { "op": { "enum": ["update", "delete"], "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": {}, "required": ["uuid"], "type": "object" } }], "properties": { "data": { "description": "Payload for operation.type='table_group'.", "properties": { "color": { "description": "Group color.", "type": "string" }, "name": { "description": "Table group name.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "title": "table_group data", "type": "object" }, "op": { "description": "Operation", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "const": "table_group", "description": "Must be 'table_group' for this payload.", "type": "string" }, "uuid": { "description": "Target UUID for update/delete.", "type": "string" } }, "required": ["op", "type"], "title": "table_group operation", "type": "object" }, { "allOf": [{ "if": { "properties": { "op": { "const": "create", "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": { "data": { "description": "Payload for operation.type='table_group_table'.", "properties": { "display_order": { "description": "Display order.", "type": "integer" }, "group_uuid": { "description": "Table group UUID. Required for create.", "type": "string" }, "schema_name": { "description": "Table schema name.", "type": "string" }, "table_name": { "description": "Table name. Required for create.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "required": ["group_uuid", "table_name"], "title": "table_group_table data", "type": "object" } }, "required": ["data"], "type": "object" } }, { "if": { "properties": { "op": { "enum": ["update", "delete"], "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": {}, "required": ["uuid"], "type": "object" } }], "properties": { "data": { "description": "Payload for operation.type='table_group_table'.", "properties": { "display_order": { "description": "Display order.", "type": "integer" }, "group_uuid": { "description": "Table group UUID. Required for create.", "type": "string" }, "schema_name": { "description": "Table schema name.", "type": "string" }, "table_name": { "description": "Table name. Required for create.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "title": "table_group_table data", "type": "object" }, "op": { "description": "Operation", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "const": "table_group_table", "description": "Must be 'table_group_table' for this payload.", "type": "string" }, "uuid": { "description": "Target UUID for update/delete.", "type": "string" } }, "required": ["op", "type"], "title": "table_group_table operation", "type": "object" }, { "allOf": [{ "if": { "properties": { "op": { "const": "create", "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": { "data": { "description": "Payload for operation.type='table_group_note'.", "properties": { "display_order": { "description": "Display order.", "type": "integer" }, "group_uuid": { "description": "Table group UUID. Required for create.", "type": "string" }, "note_uuid": { "description": "Note UUID. Required for create.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "required": ["group_uuid", "note_uuid"], "title": "table_group_note data", "type": "object" } }, "required": ["data"], "type": "object" } }, { "if": { "properties": { "op": { "enum": ["update", "delete"], "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": {}, "required": ["uuid"], "type": "object" } }], "properties": { "data": { "description": "Payload for operation.type='table_group_note'.", "properties": { "display_order": { "description": "Display order.", "type": "integer" }, "group_uuid": { "description": "Table group UUID. Required for create.", "type": "string" }, "note_uuid": { "description": "Note UUID. Required for create.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "title": "table_group_note data", "type": "object" }, "op": { "description": "Operation", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "const": "table_group_note", "description": "Must be 'table_group_note' for this payload.", "type": "string" }, "uuid": { "description": "Target UUID for update/delete.", "type": "string" } }, "required": ["op", "type"], "title": "table_group_note operation", "type": "object" }, { "allOf": [{ "if": { "properties": { "op": { "const": "create", "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": { "data": { "description": "Payload for operation.type='filter'.", "properties": { "included_tables": { "description": "JSON string/list of included tables.", "type": "string" }, "is_default": { "description": "Default filter flag.", "type": "boolean" }, "name": { "description": "Filter name.", "type": "string" }, "table_groups": { "description": "JSON string/object of table groups.", "type": "string" }, "table_positions": { "description": "JSON string/object of table positions.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "title": "filter data", "type": "object" } }, "required": ["data"], "type": "object" } }, { "if": { "properties": { "op": { "enum": ["update", "delete"], "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": {}, "required": ["uuid"], "type": "object" } }], "properties": { "data": { "description": "Payload for operation.type='filter'.", "properties": { "included_tables": { "description": "JSON string/list of included tables.", "type": "string" }, "is_default": { "description": "Default filter flag.", "type": "boolean" }, "name": { "description": "Filter name.", "type": "string" }, "table_groups": { "description": "JSON string/object of table groups.", "type": "string" }, "table_positions": { "description": "JSON string/object of table positions.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "title": "filter data", "type": "object" }, "op": { "description": "Operation", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "const": "filter", "description": "Must be 'filter' for this payload.", "type": "string" }, "uuid": { "description": "Target UUID for update/delete.", "type": "string" } }, "required": ["op", "type"], "title": "filter operation", "type": "object" }, { "allOf": [{ "if": { "properties": { "op": { "const": "create", "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": { "data": { "description": "Payload for operation.type='note'.", "properties": { "color": { "description": "Note color.", "type": "string" }, "content": { "description": "Note content.", "type": "string" }, "height": { "description": "Height.", "type": "integer" }, "icon_name": { "description": "Icon name.", "type": "string" }, "position_x": { "description": "X position.", "type": "integer" }, "position_y": { "description": "Y position.", "type": "integer" }, "title": { "description": "Note title.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" }, "width": { "description": "Width.", "type": "integer" } }, "title": "note data", "type": "object" } }, "required": ["data"], "type": "object" } }, { "if": { "properties": { "op": { "enum": ["update", "delete"], "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": {}, "required": ["uuid"], "type": "object" } }], "properties": { "data": { "description": "Payload for operation.type='note'.", "properties": { "color": { "description": "Note color.", "type": "string" }, "content": { "description": "Note content.", "type": "string" }, "height": { "description": "Height.", "type": "integer" }, "icon_name": { "description": "Icon name.", "type": "string" }, "position_x": { "description": "X position.", "type": "integer" }, "position_y": { "description": "Y position.", "type": "integer" }, "title": { "description": "Note title.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" }, "width": { "description": "Width.", "type": "integer" } }, "title": "note data", "type": "object" }, "op": { "description": "Operation", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "const": "note", "description": "Must be 'note' for this payload.", "type": "string" }, "uuid": { "description": "Target UUID for update/delete.", "type": "string" } }, "required": ["op", "type"], "title": "note operation", "type": "object" }], "properties": { "data": { "description": "Type-specific payload. All field names are snake_case. For create, data.uuid may be client-supplied and referenced by later operations in the same batch.", "oneOf": [{ "description": "Payload for operation.type='table'.", "properties": { "alias": { "description": "Display alias.", "type": "string" }, "display_order": { "description": "Display order.", "type": "integer" }, "header_color": { "description": "Header color.", "type": "string" }, "name": { "description": "Table name. Required for create.", "type": "string" }, "note": { "description": "Table note.", "type": "string" }, "schema_name": { "description": "Schema name.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "title": "table data", "type": "object" }, { "description": "Payload for operation.type='column'.", "properties": { "default_value": { "description": "Default value.", "type": "string" }, "display_order": { "description": "Display order.", "type": "integer" }, "is_increment": { "description": "Auto-increment flag.", "type": "boolean" }, "is_not_null": { "description": "Not-null flag.", "type": "boolean" }, "is_pk": { "description": "Primary key flag.", "type": "boolean" }, "is_unique": { "description": "Unique flag.", "type": "boolean" }, "name": { "description": "Column name.", "type": "string" }, "note": { "description": "Column note.", "type": "string" }, "table_uuid": { "description": "Parent table UUID. Required for create; may be a data.uuid from an earlier table operation in this batch.", "type": "string" }, "type": { "description": "SQL data type. Use this exact field name; do not send data_type.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "title": "column data", "type": "object" }, { "description": "Payload for operation.type='index'.", "properties": { "is_pk": { "description": "Primary key index flag.", "type": "boolean" }, "is_unique": { "description": "Unique index flag.", "type": "boolean" }, "name": { "description": "Index name.", "type": "string" }, "note": { "description": "Index note.", "type": "string" }, "table_uuid": { "description": "Parent table UUID. Required for create.", "type": "string" }, "type": { "description": "Index type.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "title": "index data", "type": "object" }, { "description": "Payload for operation.type='index_column'.", "properties": { "column_uuid": { "description": "Column UUID. Required for create.", "type": "string" }, "display_order": { "description": "Display order.", "type": "integer" }, "index_uuid": { "description": "Index UUID. Required for create.", "type": "string" }, "sort_order": { "description": "Sort order, for example ASC or DESC.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "title": "index_column data", "type": "object" }, { "description": "Payload for operation.type='ref'.", "properties": { "from_table_uuid": { "description": "Source table UUID. Required for create.", "type": "string" }, "name": { "description": "Reference name.", "type": "string" }, "on_delete": { "description": "ON DELETE action.", "type": "string" }, "on_update": { "description": "ON UPDATE action.", "type": "string" }, "relationship": { "description": "Required for create. Use short dbdiagram-style relationship symbols.", "enum": [">", "<", "-"], "type": "string" }, "to_table_uuid": { "description": "Target table UUID. Required for create.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "title": "ref data", "type": "object" }, { "description": "Payload for operation.type='ref_column'.", "properties": { "display_order": { "description": "Display order.", "type": "integer" }, "from_column_uuid": { "description": "Source column UUID. Required for create.", "type": "string" }, "ref_uuid": { "description": "Reference UUID. Required for create; may be a data.uuid from an earlier ref operation.", "type": "string" }, "to_column_uuid": { "description": "Target column UUID. Required for create.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "title": "ref_column data", "type": "object" }, { "description": "Payload for operation.type='enum'.", "properties": { "name": { "description": "Enum name.", "type": "string" }, "note": { "description": "Enum note.", "type": "string" }, "schema_name": { "description": "Schema name.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "title": "enum data", "type": "object" }, { "description": "Payload for operation.type='enum_value'.", "properties": { "display_order": { "description": "Display order.", "type": "integer" }, "enum_uuid": { "description": "Parent enum UUID. Required for create; may be a data.uuid from an earlier enum operation.", "type": "string" }, "note": { "description": "Enum value note.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" }, "value": { "description": "Enum value. Inline enum values on enum are not persisted; create enum_value operations.", "type": "string" } }, "title": "enum_value data", "type": "object" }, { "description": "Payload for operation.type='table_group'.", "properties": { "color": { "description": "Group color.", "type": "string" }, "name": { "description": "Table group name.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "title": "table_group data", "type": "object" }, { "description": "Payload for operation.type='table_group_table'.", "properties": { "display_order": { "description": "Display order.", "type": "integer" }, "group_uuid": { "description": "Table group UUID. Required for create.", "type": "string" }, "schema_name": { "description": "Table schema name.", "type": "string" }, "table_name": { "description": "Table name. Required for create.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "title": "table_group_table data", "type": "object" }, { "description": "Payload for operation.type='table_group_note'.", "properties": { "display_order": { "description": "Display order.", "type": "integer" }, "group_uuid": { "description": "Table group UUID. Required for create.", "type": "string" }, "note_uuid": { "description": "Note UUID. Required for create.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "title": "table_group_note data", "type": "object" }, { "description": "Payload for operation.type='filter'.", "properties": { "included_tables": { "description": "JSON string/list of included tables.", "type": "string" }, "is_default": { "description": "Default filter flag.", "type": "boolean" }, "name": { "description": "Filter name.", "type": "string" }, "table_groups": { "description": "JSON string/object of table groups.", "type": "string" }, "table_positions": { "description": "JSON string/object of table positions.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "title": "filter data", "type": "object" }, { "description": "Payload for operation.type='note'.", "properties": { "color": { "description": "Note color.", "type": "string" }, "content": { "description": "Note content.", "type": "string" }, "height": { "description": "Height.", "type": "integer" }, "icon_name": { "description": "Icon name.", "type": "string" }, "position_x": { "description": "X position.", "type": "integer" }, "position_y": { "description": "Y position.", "type": "integer" }, "title": { "description": "Note title.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" }, "width": { "description": "Width.", "type": "integer" } }, "title": "note data", "type": "object" }], "type": "object" }, "op": { "description": "Operation. For create, put any new UUID in data.uuid; for update/delete, put target UUID in operation.uuid.", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "description": "ERD entity type. This selects the operation.data shape.", "enum": ["table", "column", "index", "index_column", "ref", "ref_column", "enum", "enum_value", "table_group", "table_group_table", "table_group_note", "filter", "note"], "type": "string" }, "uuid": { "description": "Target UUID for update/delete. Do not use this for create; use data.uuid instead.", "type": "string" } }, "required": ["op", "type"], "type": "object" }, "type": "array" } }, "required": ["documentUuid", "description", "operations"], "type": "object" }, "required": ["description", "documentUuid", "operations"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "87f5bcf5a253815a" },
877
+ { "mcpName": "erd_arrange_tables", "commandPath": ["erd", "arrange-tables"], "group": "erd", "description": "Arrange ERD tables after edits. Default incremental mode places newly-created tables while preserving existing positions; full=true re-layouts the entire diagram. Use after several separate table edits to tidy the canvas.", "inputSchema": { "properties": { "documentUuid": { "description": "ERD document UUID", "type": "string" }, "full": { "description": "Re-layout the ENTIRE diagram (overwrites existing positions). Default false = arrange only newly-added (uncoordinated) tables to the top-right while preserving existing table positions. Call this after creating several tables to tidy them up.", "type": "boolean" } }, "required": ["documentUuid"], "type": "object" }, "required": ["documentUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "651fd0d54e3635ff" },
878
+ { "mcpName": "erd_get_enum", "commandPath": ["erd", "get-enum"], "group": "erd", "description": "", "inputSchema": { "properties": { "documentUuid": { "description": "ERD document UUID", "type": "string" }, "enumUuid": { "description": "Enum UUID", "type": "string" } }, "required": ["documentUuid", "enumUuid"], "type": "object" }, "required": ["documentUuid", "enumUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "f0d79d7a1996939c" },
879
+ { "mcpName": "erd_get_ref", "commandPath": ["erd", "get-ref"], "group": "erd", "description": "", "inputSchema": { "properties": { "documentUuid": { "description": "ERD document UUID", "type": "string" }, "refUuid": { "description": "Relationship UUID", "type": "string" } }, "required": ["documentUuid", "refUuid"], "type": "object" }, "required": ["documentUuid", "refUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "5b47fe7d99e60d4d" },
880
+ { "mcpName": "erd_get_table", "commandPath": ["erd", "get-table"], "group": "erd", "description": "Get one ERD table with nested columns and indexes. Returns persistent table/column/index UUIDs usable directly as erd_apply_changes update/delete targets and ref_column column references. Column fields use apply_changes snake_case names: uuid, table_uuid, name, type, is_pk, is_unique, is_not_null, is_increment, default_value, note, display_order. Index fields include uuid, name, columns (column UUIDs), is_unique, is_pk, type.", "inputSchema": { "description": "Get one ERD table with nested columns and indexes. Returns persistent table/column/index UUIDs usable directly as mcp__ainecto__erd_apply_changes update/delete targets and ref_column column references. Column fields use apply_changes snake_case names: uuid, table_uuid, name, type, is_pk, is_unique, is_not_null, is_increment, default_value, note, display_order. Index fields include uuid, name, columns (column UUIDs), is_unique, is_pk, type.", "properties": { "documentUuid": { "description": "ERD document UUID", "type": "string" }, "tableUuid": { "description": "Table UUID. Response includes columns[] and indexes[] with persistent UUIDs for apply_changes.", "type": "string" } }, "required": ["documentUuid", "tableUuid"], "type": "object" }, "required": ["documentUuid", "tableUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "0dc13648bf3fbf8f" },
881
+ { "mcpName": "erd_get_table_group", "commandPath": ["erd", "get-table-group"], "group": "erd", "description": "", "inputSchema": { "properties": { "documentUuid": { "description": "ERD document UUID", "type": "string" }, "tableGroupUuid": { "description": "Table group UUID", "type": "string" } }, "required": ["documentUuid", "tableGroupUuid"], "type": "object" }, "required": ["documentUuid", "tableGroupUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "3db246dfe8663370" },
882
+ { "mcpName": "erd_list_enums", "commandPath": ["erd", "list-enums"], "group": "erd", "description": "", "inputSchema": { "properties": { "documentUuid": { "description": "ERD document UUID", "type": "string" } }, "required": ["documentUuid"], "type": "object" }, "required": ["documentUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "4d1f602fa487a0e4" },
883
+ { "mcpName": "erd_list_refs", "commandPath": ["erd", "list-refs"], "group": "erd", "description": "", "inputSchema": { "properties": { "documentUuid": { "description": "ERD document UUID", "type": "string" } }, "required": ["documentUuid"], "type": "object" }, "required": ["documentUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "307d766c777bbe8c" },
884
+ { "mcpName": "erd_list_table_groups", "commandPath": ["erd", "list-table-groups"], "group": "erd", "description": "", "inputSchema": { "properties": { "documentUuid": { "description": "ERD document UUID", "type": "string" } }, "required": ["documentUuid"], "type": "object" }, "required": ["documentUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "775bff53bbc21c9e" },
885
+ { "mcpName": "erd_list_tables", "commandPath": ["erd", "list-tables"], "group": "erd", "description": "", "inputSchema": { "properties": { "documentUuid": { "description": "ERD document UUID", "type": "string" } }, "required": ["documentUuid"], "type": "object" }, "required": ["documentUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "34324dfc7ef461c8" },
886
+ { "mcpName": "get_attachment", "commandPath": ["attachment", "get"], "group": "attachment", "description": "", "inputSchema": { "properties": { "uuid": { "description": "Attachment UUID", "type": "string" } }, "required": ["uuid"], "type": "object" }, "required": ["uuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "1e0e7f1bf33b669c" },
887
+ { "mcpName": "get_document", "commandPath": ["document", "get"], "group": "document", "description": "Read a document. The body comes back for the types whose create/update take one: markdown \u2192 content; openapi \u2192 content (the spec text) + format/specVersion/pathCount/operationCount; html \u2192 content (the HTML text); diagram \u2192 diagramSource/sourceLanguage/edgeStyle/isModule/metadata; erd \u2192 metadata only (read tables/refs via erd_list_*).", "inputSchema": { "properties": { "uuid": { "description": "Document UUID", "type": "string" } }, "required": ["uuid"], "type": "object" }, "required": ["uuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "5de278aad6b11bc5" },
888
+ { "mcpName": "get_project", "commandPath": ["project", "get"], "group": "generic", "description": "", "inputSchema": { "properties": { "uuid": { "description": "Project UUID", "type": "string" } }, "required": ["uuid"], "type": "object" }, "required": ["uuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "192403c3484ac774" },
889
+ { "mcpName": "get_version", "commandPath": ["version", "get"], "group": "generic", "description": "", "inputSchema": { "properties": { "documentUuid": { "description": "Document UUID", "type": "string" }, "versionNumber": { "description": "Version number", "type": "integer" } }, "required": ["documentUuid", "versionNumber"], "type": "object" }, "required": ["documentUuid", "versionNumber"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "4e308c1a8c7203d1" },
890
+ { "mcpName": "get_workspace", "commandPath": ["workspace", "get"], "group": "workspace", "description": "", "inputSchema": { "properties": { "uuid": { "description": "Workspace UUID", "type": "string" } }, "required": ["uuid"], "type": "object" }, "required": ["uuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "db6d1b4879d57c56" },
891
+ { "mcpName": "identity_get_self", "commandPath": ["tools", "run", "identity-get-self"], "group": "generic", "description": "Return the authenticated MCP caller identity summary; never returns email or token claims.", "inputSchema": { "additionalProperties": false, "properties": {}, "type": "object" }, "required": [], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "f29c86804eb91700" },
892
+ { "mcpName": "list_attachments", "commandPath": ["attachments", "list"], "group": "attachment", "description": "", "inputSchema": { "properties": { "projectUuid": { "description": "Project UUID", "type": "string" } }, "required": ["projectUuid"], "type": "object" }, "required": ["projectUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "fae7152d1e4a930b" },
893
+ { "mcpName": "list_documents", "commandPath": ["documents", "list"], "group": "document", "description": "", "inputSchema": { "properties": { "folderUuid": { "description": "Optional folder UUID", "type": "string" }, "limit": { "description": "Result cap when q is set (default 50)", "type": "integer" }, "projectUuid": { "description": "Project UUID", "type": "string" }, "q": { "description": "Optional full-text query. When provided, results come from the search index instead of folder listing.", "type": "string" }, "type": { "description": "Optional document type filter. Readable values: erd, markdown, diagram, openapi, html.", "enum": ["erd", "markdown", "diagram", "openapi", "html"], "type": "string" } }, "type": "object" }, "required": [], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "4086c52be4dd0c15" },
894
+ { "mcpName": "list_folders", "commandPath": ["folders", "list"], "group": "generic", "description": "", "inputSchema": { "properties": { "projectUuid": { "description": "Project UUID", "type": "string" } }, "required": ["projectUuid"], "type": "object" }, "required": ["projectUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "461dc6735ac92c3b" },
895
+ { "mcpName": "list_projects", "commandPath": ["projects", "list"], "group": "generic", "description": "", "inputSchema": { "properties": { "limit": { "description": "Result cap when q is set (default 50)", "type": "integer" }, "q": { "description": "Optional full-text query", "type": "string" }, "workspaceUuid": { "description": "Optional Workspace UUID", "type": "string" } }, "type": "object" }, "required": [], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "939bfcc088425771" },
896
+ { "mcpName": "list_trash", "commandPath": ["trash", "list"], "group": "generic", "description": "", "inputSchema": { "properties": {}, "type": "object" }, "required": [], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "329250aecc06a298" },
897
+ { "mcpName": "list_versions", "commandPath": ["versions", "list"], "group": "generic", "description": "", "inputSchema": { "properties": { "documentUuid": { "description": "Document UUID", "type": "string" } }, "required": ["documentUuid"], "type": "object" }, "required": ["documentUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "f08f9faee3ba9107" },
898
+ { "mcpName": "list_workspaces", "commandPath": ["workspaces", "list"], "group": "workspace", "description": "", "inputSchema": { "properties": {}, "type": "object" }, "required": [], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "39dba0089fdbdf6a" },
899
+ { "mcpName": "markdown_append_document", "commandPath": ["markdown", "append-document"], "group": "markdown", "description": "Append to Markdown documents. If the appended body is tens of KB or more, prefer request_upload_token (purpose=document.append) and upload the body with curl.", "inputSchema": { "properties": { "items": { "description": "Appends", "items": { "properties": { "addition": { "description": "Body addition. \uCD94\uAC00\uD560 \uB0B4\uC6A9\uC774 \uD06C\uBA74 (\uC218\uC2ED KB \uC774\uC0C1) LLM \uD1A0\uD070 \uC808\uC57D\uC744 \uC704\uD574 `request_upload_token (purpose=document.append, scopeJson={documentUuid, separator})` \uB85C uploadUrl \uC744 \uBC1B\uACE0 curl \uB85C PUT \uC73C\uB85C \uBCF4\uB0BC \uAC83.", "type": "string" }, "separator": { "description": "Separator between existing body and addition", "type": "string" }, "uuid": { "description": "Document UUID", "type": "string" } }, "required": ["uuid", "addition"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "bf82401f055f29b0" },
900
+ { "mcpName": "markdown_generate_document", "commandPath": ["markdown", "generate-document"], "group": "markdown", "description": "", "inputSchema": { "properties": { "items": { "description": "Generation requests", "items": { "properties": { "context": { "description": "Optional JSON context", "type": "string" }, "documentUuid": { "description": "Target document UUID", "type": "string" }, "prompt": { "description": "Generation prompt", "type": "string" } }, "required": ["documentUuid", "prompt"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "1e4724ea42f25743" },
901
+ { "mcpName": "move_documents", "commandPath": ["documents", "move"], "group": "document", "description": "", "inputSchema": { "properties": { "clientMutationId": { "description": "Optional mutation identifier for client retry tracing", "maxLength": 128, "minLength": 1, "pattern": "^[\\x21-\\x7E]{1,128}$", "type": "string" }, "items": { "description": "Moves", "items": { "properties": { "newFolderUuid": { "description": "New folder UUID \u2014 null/empty for root", "type": "string" }, "targetProjectUuid": { "description": "Target project UUID \u2014 omit to stay in current project", "type": "string" }, "targetWorkspaceUuid": { "description": "Guard only: must match targetProjectUuid workspace", "type": "string" }, "uuid": { "description": "Document UUID", "type": "string" } }, "required": ["uuid"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "prod", "schemaHash": "45899053743a0c1e" },
902
+ { "mcpName": "move_folders", "commandPath": ["folders", "move"], "group": "generic", "description": "", "inputSchema": { "properties": { "items": { "description": "Moves", "items": { "properties": { "newParentFolderUuid": { "description": "New parent UUID \u2014 null/empty for root", "type": "string" }, "uuid": { "description": "Folder UUID", "type": "string" } }, "required": ["uuid"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "prod", "schemaHash": "eff4458d2f35587a" },
903
+ { "mcpName": "move_projects", "commandPath": ["projects", "move"], "group": "generic", "description": "", "inputSchema": { "properties": { "items": { "description": "Moves", "items": { "properties": { "targetWorkspaceUuid": { "description": "Destination workspace UUID (you need write access there)", "type": "string" }, "uuid": { "description": "Project UUID", "type": "string" } }, "required": ["uuid", "targetWorkspaceUuid"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "prod", "schemaHash": "abe0196da3c48ad5" },
904
+ { "mcpName": "request_upload_token", "commandPath": ["tools", "run", "request-upload-token"], "group": "attachment", "description": "Use this for large payloads of tens of KB or more to save LLM tokens. purpose=mcp.call returns a short-lived bearer token that can call any MCP tool, including erd_apply_changes, via curl. purpose=document.create/update/append uploads document bodies directly to the upload endpoint with PUT/curl. Tokens are reusable for 5 minutes by default.", "inputSchema": { "properties": { "maxBytes": { "description": "\uCD5C\uB300 \uC5C5\uB85C\uB4DC byte (\uC11C\uBC84 \uCE21 \uD55C\uB3C4)", "type": "integer" }, "purpose": { "description": "Token \uC6A9\uB3C4. `mcp.call` \uC740 curl \uB85C /mcp \uD638\uCD9C, `document.create/update/append` \uB294 PUT body \uB85C \uBCF8\uBB38 \uC5C5\uB85C\uB4DC, `attachment.upload` \uB294 \uD30C\uC77C bytes \uC5C5\uB85C\uB4DC. \uCF58\uD150\uCE20\uAC00 \uD06C\uBA74 (\uC218\uC2ED KB \uC774\uC0C1) LLM \uD1A0\uD070 \uC808\uC57D\uC744 \uC704\uD574 \uC774 \uB3C4\uAD6C\uB85C 5\uBD84 \uC7AC\uC0AC\uC6A9 \uD1A0\uD070\uC744 \uBC1B\uACE0 curl \uB85C \uBCF4\uB0B4\uB294 \uAC83\uC744 \uAC15\uB825 \uAD8C\uC7A5.", "enum": ["mcp.call", "document.create", "document.update", "document.append", "attachment.upload"], "type": "string" }, "scopeJson": { "description": "\uC120\uD0DD \u2014 JSON scope. document.create \uB294 projectUuid, type, title \uC744 \uD3EC\uD568\uD558\uACE0 creatable type values \uB294 erd, markdown, diagram, openapi, html. document.update/document.append \uB294 documentUuid \uB97C \uD3EC\uD568. attachment.upload typed: {parentType:TEST_CASE|TEST_EXECUTION,parentUuid,filename,contentType,sizeBytes}.", "type": "string" }, "ttlSeconds": { "description": "TTL (\uCD08). \uAE30\uBCF8 300 (5\uBD84)", "type": "integer" } }, "required": ["purpose"], "type": "object" }, "required": ["purpose"], "payloadMode": "binary-upload", "destructive": false, "sourceCatalog": "prod", "schemaHash": "85867a646f6c652f" },
905
+ { "mcpName": "restore_versions", "commandPath": ["versions", "restore"], "group": "generic", "description": "", "inputSchema": { "properties": { "items": { "description": "Restores", "items": { "properties": { "documentUuid": { "description": "Document UUID", "type": "string" }, "versionNumber": { "description": "Version number to restore", "type": "integer" } }, "required": ["documentUuid", "versionNumber"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "prod", "schemaHash": "005b5b08a19fc605" },
906
+ { "mcpName": "search_documents", "commandPath": ["documents", "search"], "group": "document", "description": "", "inputSchema": { "properties": { "limit": { "description": "\uACB0\uACFC \uAC1C\uC218 (\uAE30\uBCF8 20, \uCD5C\uB300 50)", "type": "integer" }, "offset": { "description": "\uD398\uC774\uC9C0\uB124\uC774\uC158 offset (\uAE30\uBCF8 0)", "type": "integer" }, "projectUuid": { "description": "\uD2B9\uC815 \uD504\uB85C\uC81D\uD2B8\uB85C \uD55C\uC815 (\uC120\uD0DD)", "type": "string" }, "q": { "description": "\uAC80\uC0C9 \uD0A4\uC6CC\uB4DC", "type": "string" }, "type": { "description": "Optional document type filter. Readable values: erd, markdown, diagram, openapi, html.", "enum": ["erd", "markdown", "diagram", "openapi", "html"], "type": "string" } }, "required": ["q"], "type": "object" }, "required": ["q"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "d24e109383700433" },
907
+ { "mcpName": "unarchive_documents", "commandPath": ["documents", "unarchive"], "group": "document", "description": "", "inputSchema": { "properties": { "uuids": { "description": "Document UUIDs", "items": { "type": "string" }, "type": "array" } }, "required": ["uuids"], "type": "object" }, "required": ["uuids"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "prod", "schemaHash": "f2810e71583a320f" },
908
+ { "mcpName": "unarchive_folders", "commandPath": ["folders", "unarchive"], "group": "generic", "description": "", "inputSchema": { "properties": { "uuids": { "description": "Folder UUIDs", "items": { "type": "string" }, "type": "array" } }, "required": ["uuids"], "type": "object" }, "required": ["uuids"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "prod", "schemaHash": "2a24cc44696b7906" },
909
+ { "mcpName": "unarchive_projects", "commandPath": ["projects", "unarchive"], "group": "generic", "description": "", "inputSchema": { "properties": { "uuids": { "description": "Project UUIDs", "items": { "type": "string" }, "type": "array" } }, "required": ["uuids"], "type": "object" }, "required": ["uuids"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "prod", "schemaHash": "6925e444671afe19" },
910
+ { "mcpName": "unarchive_workspaces", "commandPath": ["workspaces", "unarchive"], "group": "workspace", "description": "", "inputSchema": { "properties": { "uuids": { "description": "Workspace UUIDs", "items": { "type": "string" }, "type": "array" } }, "required": ["uuids"], "type": "object" }, "required": ["uuids"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "prod", "schemaHash": "8dafe9bc6378c3f2" },
911
+ { "mcpName": "update_documents", "commandPath": ["documents", "update"], "group": "document", "description": "Update documents. If a replacement body is tens of KB or more, prefer request_upload_token (purpose=document.update) and upload the body with curl.", "inputSchema": { "properties": { "items": { "description": "Updates", "items": { "properties": { "body": { "description": "Deprecated compatibility alias for typePayload.body when document type is markdown. \uBCF8\uBB38\uC774 \uC218\uC2ED KB \uC774\uC0C1\uC73C\uB85C \uD06C\uBA74 LLM \uD1A0\uD070 \uC808\uC57D\uC744 \uC704\uD574 `request_upload_token (purpose=document.update)` \uB97C \uD638\uCD9C\uD574 uploadUrl \uC744 \uBC1B\uACE0 curl \uB85C \uC9C1\uC811 PUT \uC73C\uB85C \uBCF4\uB0BC \uAC83.", "type": "string" }, "title": { "description": "New title", "type": "string" }, "typePayload": { "description": "Type-specific payload for enabled document types. markdown: {body}; diagram: {diagramSource,sourceLanguage,edgeStyle,isModule,metadata}; openapi: {content}; html: {content} (a full HTML page).", "type": "object" }, "uuid": { "description": "Document UUID", "type": "string" } }, "required": ["uuid"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "4c3cfe4d25def672" },
912
+ { "mcpName": "update_folders", "commandPath": ["folders", "update"], "group": "generic", "description": "", "inputSchema": { "properties": { "items": { "description": "Updates", "items": { "properties": { "name": { "description": "New name", "type": "string" }, "uuid": { "description": "Folder UUID", "type": "string" } }, "required": ["uuid", "name"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "43c58a96ee632b5f" },
913
+ { "mcpName": "update_projects", "commandPath": ["projects", "update"], "group": "generic", "description": "", "inputSchema": { "properties": { "items": { "description": "Updates", "items": { "properties": { "description": { "description": "New description", "type": "string" }, "name": { "description": "New name", "type": "string" }, "uuid": { "description": "Project UUID", "type": "string" } }, "required": ["uuid"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "7d849ef0a830aa8c" },
914
+ { "mcpName": "update_workspaces", "commandPath": ["workspaces", "update"], "group": "workspace", "description": "", "inputSchema": { "properties": { "items": { "description": "Updates", "items": { "properties": { "description": { "description": "New description", "type": "string" }, "name": { "description": "New name", "type": "string" }, "uuid": { "description": "Workspace UUID", "type": "string" } }, "required": ["uuid"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "553275e6da799c5a" },
915
+ { "mcpName": "upload_attachments", "commandPath": ["tools", "run", "upload-attachments"], "group": "attachment", "description": "", "inputSchema": { "properties": { "items": { "description": "Attachments to register", "items": { "properties": { "contentType": { "description": "MIME type", "type": "string" }, "documentUuid": { "description": "Parent document UUID", "type": "string" }, "filename": { "description": "Original file name", "type": "string" }, "sizeBytes": { "description": "Size in bytes", "type": "integer" }, "storageKey": { "description": "Storage key returned by request_upload_token purpose=attachment.upload", "type": "string" } }, "required": ["documentUuid", "filename", "storageKey"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "binary-upload", "destructive": false, "sourceCatalog": "prod", "schemaHash": "deda4a56fbbe064f" }
745
916
  ];
746
917
 
747
918
  // src/core/catalog/generated.dev.ts
@@ -834,10 +1005,147 @@ var generatedTools2 = [
834
1005
  { "mcpName": "mcp__ainecto__upload_attachments", "commandPath": ["tools", "run", "upload-attachments"], "group": "attachment", "description": "", "inputSchema": { "properties": { "items": { "description": "Attachments to register", "items": { "properties": { "contentType": { "description": "MIME type", "type": "string" }, "documentUuid": { "description": "Parent document UUID", "type": "string" }, "filename": { "description": "Original file name", "type": "string" }, "sizeBytes": { "description": "Size in bytes", "type": "integer" }, "storageKey": { "description": "Storage key returned by request_upload_token purpose=attachment.upload", "type": "string" } }, "required": ["documentUuid", "filename", "storageKey"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "binary-upload", "destructive": false, "sourceCatalog": "dev", "schemaHash": "0622e590de3404dd" }
835
1006
  ];
836
1007
 
1008
+ // src/core/catalog/generator.ts
1009
+ import { createHash as createHash3 } from "crypto";
1010
+ var LEGACY_PREFIX = "mcp__ainecto__";
1011
+ function bareToolName(name) {
1012
+ return name.startsWith(LEGACY_PREFIX) ? name.slice(LEGACY_PREFIX.length) : name;
1013
+ }
1014
+ var DESTRUCTIVE_PREFIXES = [
1015
+ "delete_",
1016
+ "remove_",
1017
+ "archive_",
1018
+ "unarchive_",
1019
+ "restore_",
1020
+ "move_",
1021
+ "erd_delete_",
1022
+ "erd_remove_",
1023
+ "task_delete_",
1024
+ "testcase_delete_"
1025
+ ];
1026
+ function generateCatalog(tools, sourceCatalog) {
1027
+ return tools.map((tool) => normalizeTool(tool, sourceCatalog)).sort((a, b) => a.mcpName.localeCompare(b.mcpName));
1028
+ }
1029
+ function normalizeTool(tool, sourceCatalog) {
1030
+ if (!tool.name) {
1031
+ throw new Error("Unsupported tool name: empty.");
1032
+ }
1033
+ const stripped = bareToolName(tool.name);
1034
+ const group = inferGroup(stripped);
1035
+ const inputSchema = normalizeSchema(tool.inputSchema ?? { type: "object", properties: {} });
1036
+ const required = Array.isArray(inputSchema.required) ? inputSchema.required.filter((value) => typeof value === "string").sort() : [];
1037
+ const definition = {
1038
+ mcpName: tool.name,
1039
+ commandPath: generateCommandPath(stripped, group),
1040
+ group,
1041
+ description: tool.description ?? "",
1042
+ inputSchema,
1043
+ required,
1044
+ payloadMode: inferPayloadMode(stripped, inputSchema),
1045
+ destructive: inferDestructive(stripped, tool),
1046
+ sourceCatalog,
1047
+ schemaHash: ""
1048
+ };
1049
+ return { ...definition, schemaHash: computeSchemaHash(definition) };
1050
+ }
1051
+ function inferGroup(strippedName) {
1052
+ if (strippedName.startsWith("erd_")) return "erd";
1053
+ if (strippedName.startsWith("flow_")) return "flow";
1054
+ if (strippedName.startsWith("testcase_")) return "testcase";
1055
+ if (strippedName.startsWith("markdown_")) return "markdown";
1056
+ if (strippedName.includes("attachment") || strippedName.includes("upload")) return "attachment";
1057
+ if (strippedName.startsWith("task_")) return "task";
1058
+ if (strippedName.includes("workspace")) return "workspace";
1059
+ if (strippedName.includes("document")) return "document";
1060
+ return "generic";
1061
+ }
1062
+ function generateCommandPath(strippedName, group) {
1063
+ if (group !== "generic" && strippedName.startsWith(`${group}_`)) {
1064
+ return [group, snakeToKebab(strippedName.slice(group.length + 1))];
1065
+ }
1066
+ const [verb, ...rest] = strippedName.split("_");
1067
+ if (verb && rest.length > 0 && isVerb(verb)) {
1068
+ return [rest.join("-"), verb];
1069
+ }
1070
+ return ["tools", "run", snakeToKebab(strippedName)];
1071
+ }
1072
+ function inferPayloadMode(strippedName, inputSchema) {
1073
+ if (strippedName.includes("upload") || strippedName.includes("attachment_upload")) {
1074
+ return "binary-upload";
1075
+ }
1076
+ const properties = inputSchema.properties;
1077
+ if (!isRecord3(properties)) {
1078
+ return "flags";
1079
+ }
1080
+ const values = Object.values(properties);
1081
+ if (values.length === 0) {
1082
+ return "flags";
1083
+ }
1084
+ if (values.every(isScalarSchema)) {
1085
+ return "flags";
1086
+ }
1087
+ return "file-json";
1088
+ }
1089
+ function inferDestructive(strippedName, tool) {
1090
+ if (tool.annotations?.destructiveHint === true) {
1091
+ return true;
1092
+ }
1093
+ return DESTRUCTIVE_PREFIXES.some((prefix) => strippedName.startsWith(prefix));
1094
+ }
1095
+ function computeSchemaHash(tool) {
1096
+ return createHash3("sha256").update(stableStringify({
1097
+ name: tool.mcpName,
1098
+ description: tool.description,
1099
+ inputSchema: tool.inputSchema
1100
+ })).digest("hex").slice(0, 16);
1101
+ }
1102
+ function stableStringify(value) {
1103
+ if (Array.isArray(value)) {
1104
+ return `[${value.map(stableStringify).join(",")}]`;
1105
+ }
1106
+ if (isRecord3(value)) {
1107
+ return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`).join(",")}}`;
1108
+ }
1109
+ return JSON.stringify(value);
1110
+ }
1111
+ function normalizeSchema(schema) {
1112
+ return JSON.parse(stableStringify(schema));
1113
+ }
1114
+ function isScalarSchema(value) {
1115
+ if (!isRecord3(value)) {
1116
+ return false;
1117
+ }
1118
+ const type = value.type;
1119
+ return type === "string" || type === "number" || type === "integer" || type === "boolean";
1120
+ }
1121
+ function isVerb(value) {
1122
+ return [
1123
+ "list",
1124
+ "get",
1125
+ "create",
1126
+ "update",
1127
+ "delete",
1128
+ "move",
1129
+ "restore",
1130
+ "archive",
1131
+ "unarchive",
1132
+ "search"
1133
+ ].includes(value);
1134
+ }
1135
+ function snakeToKebab(value) {
1136
+ return value.replaceAll("_", "-");
1137
+ }
1138
+ function isRecord3(value) {
1139
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1140
+ }
1141
+
837
1142
  // src/core/catalog/index.ts
838
1143
  function getGeneratedTools(env) {
839
1144
  return env === "dev" ? generatedTools2 : generatedTools;
840
1145
  }
1146
+ async function loadLiveTools(env, client) {
1147
+ return generateCatalog(await client.toolsList(), env);
1148
+ }
841
1149
 
842
1150
  // src/core/mcp/stdioBridge.ts
843
1151
  import { createInterface } from "readline";
@@ -867,7 +1175,7 @@ async function runStdioBridge(options) {
867
1175
  }
868
1176
  function parseJsonRpcRequest(line) {
869
1177
  const parsed = JSON.parse(line);
870
- if (!isRecord3(parsed) || parsed.jsonrpc !== "2.0" || typeof parsed.method !== "string") {
1178
+ if (!isRecord4(parsed) || parsed.jsonrpc !== "2.0" || typeof parsed.method !== "string") {
871
1179
  throw new Error("Invalid JSON-RPC request.");
872
1180
  }
873
1181
  return {
@@ -877,15 +1185,18 @@ function parseJsonRpcRequest(line) {
877
1185
  params: parsed.params
878
1186
  };
879
1187
  }
880
- function isRecord3(value) {
1188
+ function isRecord4(value) {
881
1189
  return typeof value === "object" && value !== null && !Array.isArray(value);
882
1190
  }
883
1191
 
884
1192
  // src/adapters/mcp/connector.ts
885
1193
  async function runConnector(options) {
886
- const tokenStore = new FileTokenStore();
887
- const auth = new OAuthClient({ endpoint: options.endpoint, tokenStore });
888
- const client = new McpRpcClient({ endpoint: options.endpoint, tokenProvider: auth });
1194
+ const auth = new OAuthClient({ endpoint: options.endpoint, tokenStore: new FileTokenStore() });
1195
+ const client = new McpRpcClient({
1196
+ endpoint: options.endpoint,
1197
+ tokenProvider: auth,
1198
+ role: options.role
1199
+ });
889
1200
  await runStdioBridge({
890
1201
  input: options.input,
891
1202
  output: options.output,
@@ -897,85 +1208,85 @@ async function runConnector(options) {
897
1208
  // src/core/catalog/enrichments.ts
898
1209
  var enrichments = [
899
1210
  {
900
- mcpName: "mcp__ainecto__list_projects",
1211
+ mcpName: "list_projects",
901
1212
  outputHint: "table",
902
1213
  tableColumns: ["uuid", "name", "projectKey", "workspaceUuid", "updatedAt"],
903
1214
  displayName: "List projects",
904
1215
  examples: ["ainecto projects list --env dev"]
905
1216
  },
906
1217
  {
907
- mcpName: "mcp__ainecto__list_workspaces",
1218
+ mcpName: "list_workspaces",
908
1219
  outputHint: "table",
909
1220
  tableColumns: ["uuid", "name", "updatedAt"],
910
1221
  displayName: "List workspaces"
911
1222
  },
912
1223
  {
913
- mcpName: "mcp__ainecto__list_folders",
1224
+ mcpName: "list_folders",
914
1225
  outputHint: "table",
915
1226
  tableColumns: ["uuid", "name", "projectUuid", "parentFolderUuid", "updatedAt"],
916
1227
  displayName: "List folders"
917
1228
  },
918
1229
  {
919
- mcpName: "mcp__ainecto__list_documents",
1230
+ mcpName: "list_documents",
920
1231
  outputHint: "table",
921
1232
  tableColumns: ["uuid", "title", "type", "projectUuid", "folderUuid", "updatedAt"],
922
1233
  displayName: "List documents"
923
1234
  },
924
1235
  {
925
- mcpName: "mcp__ainecto__search_documents",
1236
+ mcpName: "search_documents",
926
1237
  outputHint: "table",
927
1238
  tableColumns: ["uuid", "title", "type", "projectUuid", "updatedAt"],
928
1239
  displayName: "Search documents"
929
1240
  },
930
1241
  {
931
- mcpName: "mcp__ainecto__list_attachments",
1242
+ mcpName: "list_attachments",
932
1243
  outputHint: "table",
933
1244
  tableColumns: ["uuid", "filename", "contentType", "sizeBytes", "documentUuid"],
934
1245
  displayName: "List attachments"
935
1246
  },
936
1247
  {
937
- mcpName: "mcp__ainecto__erd_list_tables",
1248
+ mcpName: "erd_list_tables",
938
1249
  outputHint: "table",
939
1250
  tableColumns: ["uuid", "name", "schema_name", "display_order"],
940
1251
  displayName: "List ERD tables"
941
1252
  },
942
1253
  {
943
- mcpName: "mcp__ainecto__erd_list_refs",
1254
+ mcpName: "erd_list_refs",
944
1255
  outputHint: "table",
945
1256
  tableColumns: ["uuid", "name", "from_table_uuid", "to_table_uuid", "relationship"],
946
1257
  displayName: "List ERD refs"
947
1258
  },
948
1259
  {
949
- mcpName: "mcp__ainecto__erd_list_enums",
1260
+ mcpName: "erd_list_enums",
950
1261
  outputHint: "table",
951
1262
  tableColumns: ["uuid", "name", "display_order"],
952
1263
  displayName: "List ERD enums"
953
1264
  },
954
1265
  {
955
- mcpName: "mcp__ainecto__erd_list_table_groups",
1266
+ mcpName: "erd_list_table_groups",
956
1267
  outputHint: "table",
957
1268
  tableColumns: ["uuid", "name", "display_order"],
958
1269
  displayName: "List ERD table groups"
959
1270
  },
960
1271
  {
961
- mcpName: "mcp__ainecto__task_list_tasks",
1272
+ mcpName: "task_list_tasks",
962
1273
  outputHint: "table",
963
1274
  tableColumns: ["uuid", "title", "statusUuid", "assigneeId", "updatedAt"],
964
1275
  displayName: "List tasks"
965
1276
  },
966
1277
  {
967
- mcpName: "mcp__ainecto__task_list_statuses",
1278
+ mcpName: "task_list_statuses",
968
1279
  outputHint: "table",
969
1280
  tableColumns: ["uuid", "name", "displayOrder"],
970
1281
  displayName: "List task statuses"
971
1282
  },
972
1283
  {
973
- mcpName: "mcp__ainecto__enable_shares",
1284
+ mcpName: "enable_shares",
974
1285
  aliases: ["shares enable"],
975
1286
  displayName: "Enable shares"
976
1287
  },
977
1288
  {
978
- mcpName: "mcp__ainecto__disable_shares",
1289
+ mcpName: "disable_shares",
979
1290
  aliases: ["shares disable"],
980
1291
  displayName: "Disable shares"
981
1292
  }
@@ -984,7 +1295,7 @@ var enrichments = [
984
1295
  // src/core/output/table.ts
985
1296
  function renderTable(data, options = {}) {
986
1297
  const rows = extractRows(data);
987
- if (!rows.length || !rows.every(isRecord4)) {
1298
+ if (!rows.length || !rows.every(isRecord5)) {
988
1299
  return void 0;
989
1300
  }
990
1301
  const columns = options.columns?.length ? options.columns : inferColumns(rows);
@@ -1001,7 +1312,7 @@ function extractRows(data) {
1001
1312
  if (Array.isArray(data)) {
1002
1313
  return data;
1003
1314
  }
1004
- if (isRecord4(data)) {
1315
+ if (isRecord5(data)) {
1005
1316
  for (const key of ["items", "projects", "workspaces", "folders", "documents", "tasks", "attachments", "results", "data"]) {
1006
1317
  const value = data[key];
1007
1318
  if (Array.isArray(value)) {
@@ -1029,7 +1340,7 @@ function formatCell(value) {
1029
1340
  }
1030
1341
  return String(value);
1031
1342
  }
1032
- function isRecord4(value) {
1343
+ function isRecord5(value) {
1033
1344
  return typeof value === "object" && value !== null && !Array.isArray(value);
1034
1345
  }
1035
1346
 
@@ -1082,7 +1393,7 @@ function payloadToArgs(value) {
1082
1393
  if (value === void 0 || value === null) {
1083
1394
  return {};
1084
1395
  }
1085
- if (!isRecord5(value)) {
1396
+ if (!isRecord6(value)) {
1086
1397
  throw new CliCommandError("INVALID_PAYLOAD", "Generated command payload must be a JSON object.", { value });
1087
1398
  }
1088
1399
  return { ...value };
@@ -1186,12 +1497,12 @@ function coerceFlagValue(value, schema, key) {
1186
1497
  }
1187
1498
  function getSchemaProperties(schema) {
1188
1499
  const properties = schema.properties;
1189
- if (!isRecord5(properties)) {
1500
+ if (!isRecord6(properties)) {
1190
1501
  return {};
1191
1502
  }
1192
1503
  const result = {};
1193
1504
  for (const [key, value] of Object.entries(properties)) {
1194
- if (isRecord5(value)) {
1505
+ if (isRecord6(value)) {
1195
1506
  result[key] = value;
1196
1507
  }
1197
1508
  }
@@ -1206,7 +1517,7 @@ function requireValue(flag, value) {
1206
1517
  }
1207
1518
  return value;
1208
1519
  }
1209
- function isRecord5(value) {
1520
+ function isRecord6(value) {
1210
1521
  return typeof value === "object" && value !== null && !Array.isArray(value);
1211
1522
  }
1212
1523
 
@@ -1248,9 +1559,8 @@ async function promptConfirmation(io, message) {
1248
1559
  var ATTACHMENTS_UPLOAD_COMMAND_PATH = ["attachments", "upload"];
1249
1560
 
1250
1561
  // src/adapters/cli/generatedCommandRouter.ts
1251
- function matchGeneratedCommand(env, argv) {
1562
+ function matchGeneratedCommand(tools, argv) {
1252
1563
  const commandTokens = argv.filter((arg) => !arg.startsWith("-"));
1253
- const tools = getGeneratedTools(env);
1254
1564
  const entries = buildRouteEntries(tools, enrichments);
1255
1565
  const match = entries.filter((entry) => isPrefix(entry.path, commandTokens)).sort((a, b) => b.path.length - a.path.length)[0];
1256
1566
  if (!match) {
@@ -1266,7 +1576,7 @@ function matchGeneratedCommand(env, argv) {
1266
1576
  };
1267
1577
  }
1268
1578
  async function executeGeneratedCommand(options) {
1269
- const route = matchGeneratedCommand(options.env, options.argv);
1579
+ const route = matchGeneratedCommand(options.tools, options.argv);
1270
1580
  if (!route) {
1271
1581
  return 2;
1272
1582
  }
@@ -1284,7 +1594,7 @@ async function executeGeneratedCommand(options) {
1284
1594
  warnings,
1285
1595
  presentation: route.presentation
1286
1596
  }));
1287
- return 0;
1597
+ return isToolError(result) ? 1 : 0;
1288
1598
  }
1289
1599
  function renderGeneratedResult(result, options) {
1290
1600
  if (!options.json && options.presentation?.outputHint === "table") {
@@ -1429,7 +1739,7 @@ async function requestUploadToken(client, metadata, context) {
1429
1739
  requestArgs.ttlSeconds = context.ttlSeconds;
1430
1740
  }
1431
1741
  const response = unwrapMcpToolPayload(await client.toolsCall(context.requestToolName, requestArgs));
1432
- if (!isRecord6(response)) {
1742
+ if (!isRecord7(response)) {
1433
1743
  throw new AttachmentUploadError("UPLOAD_TOKEN_PROTOCOL_ERROR", "request_upload_token response was not an object.");
1434
1744
  }
1435
1745
  const token = response.token;
@@ -1499,7 +1809,7 @@ function parsePutResponse(text) {
1499
1809
  } catch {
1500
1810
  throw new AttachmentUploadError("ATTACHMENT_UPLOAD_PROTOCOL_ERROR", "Attachment PUT response was not valid JSON.");
1501
1811
  }
1502
- if (!isRecord6(parsed)) {
1812
+ if (!isRecord7(parsed)) {
1503
1813
  throw new AttachmentUploadError("ATTACHMENT_UPLOAD_PROTOCOL_ERROR", "Attachment PUT response was not an object.");
1504
1814
  }
1505
1815
  if (typeof parsed.storageKey !== "string") {
@@ -1532,10 +1842,10 @@ function validatePutResult(token, metadata, putResult) {
1532
1842
  }
1533
1843
  }
1534
1844
  function unwrapMcpToolPayload(value) {
1535
- if (!isRecord6(value) || !Array.isArray(value.content)) {
1845
+ if (!isRecord7(value) || !Array.isArray(value.content)) {
1536
1846
  return value;
1537
1847
  }
1538
- const text = value.content.map((item) => isRecord6(item) && typeof item.text === "string" ? item.text : "").find((itemText) => itemText.trim().startsWith("{"));
1848
+ const text = value.content.map((item) => isRecord7(item) && typeof item.text === "string" ? item.text : "").find((itemText) => itemText.trim().startsWith("{"));
1539
1849
  if (!text) {
1540
1850
  return value;
1541
1851
  }
@@ -1577,7 +1887,7 @@ var MIME_BY_EXTENSION = {
1577
1887
  ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
1578
1888
  ".zip": "application/zip"
1579
1889
  };
1580
- function isRecord6(value) {
1890
+ function isRecord7(value) {
1581
1891
  return typeof value === "object" && value !== null && !Array.isArray(value);
1582
1892
  }
1583
1893
 
@@ -1701,166 +2011,2554 @@ function renderAttachmentUploadResult(result, json) {
1701
2011
  return renderSuccess(lines.join("\n"), { json: false });
1702
2012
  }
1703
2013
 
1704
- // src/adapters/cli/ainectoCli.ts
1705
- async function runAinectoCli(argv, io) {
1706
- let parsed = { json: argv.includes("--json"), help: false, positionals: [] };
1707
- try {
1708
- parsed = parseGlobalArgs(argv);
1709
- if (parsed.help || parsed.positionals.length === 0) {
1710
- io.stdout.write(helpText());
1711
- return 0;
1712
- }
1713
- const resolved = resolveEndpoint({ env: parsed.env, endpoint: parsed.endpoint });
1714
- const tokenStore = new FileTokenStore();
1715
- const auth = new OAuthClient({ endpoint: resolved.endpoint, tokenStore });
1716
- const client = new McpRpcClient({ endpoint: resolved.endpoint, tokenProvider: auth });
1717
- const [domain, action, ...rest] = parsed.positionals;
1718
- if (domain === "mcp") {
1719
- await runConnector({
1720
- endpoint: resolved.endpoint,
1721
- input: io.stdin,
1722
- output: io.stdout,
1723
- errorOutput: io.stderr
1724
- });
1725
- return 0;
1726
- }
1727
- if (domain === "auth") {
1728
- return handleAuth(action, auth, resolved.endpoint, parsed.json, io);
1729
- }
1730
- if (isAttachmentsUploadCommand(domain, action)) {
1731
- return await executeAttachmentsUploadCommand({
1732
- argv: rest,
1733
- client,
1734
- json: parsed.json,
1735
- io
1736
- });
1737
- }
1738
- const generatedResult = await executeGeneratedCommand({
1739
- env: parsed.env ?? "prod",
1740
- argv: parsed.positionals,
1741
- client,
1742
- json: parsed.json,
1743
- io
1744
- });
1745
- if (generatedResult !== 2) {
1746
- return generatedResult;
1747
- }
1748
- if (domain === "tools") {
1749
- return handleTools(action, rest, parsed, client, io);
1750
- }
1751
- throw new Error(`Unknown command "${domain}".`);
1752
- } catch (error) {
1753
- io.stderr.write(renderError(error, { json: parsed.json }));
1754
- return 1;
2014
+ // src/adapters/cli/initCommand.ts
2015
+ import { chmod as chmod2, mkdir as mkdir2, rename as rename2, rm as rm2, rmdir, stat as stat3, writeFile as writeFile2 } from "fs/promises";
2016
+ import { dirname as dirname3, join as join3, relative, resolve as resolve2, sep as sep2 } from "path";
2017
+
2018
+ // src/core/harness/agentTargets.ts
2019
+ import { basename as basename2 } from "path";
2020
+ var SERVER_NAME = "ainecto";
2021
+ var LEGACY_SERVER_NAME = "ai-erd";
2022
+ var AGENT_TARGETS = [
2023
+ { id: "claude-code", label: "Claude Code", path: ".mcp.json" },
2024
+ { id: "cursor", label: "Cursor", path: ".cursor/mcp.json" }
2025
+ ];
2026
+ function buildServerEntry(input) {
2027
+ const args = ["-y", PACKAGE_NAME, "--role", input.role];
2028
+ if (input.env && input.env !== "prod") {
2029
+ args.push("--env", input.env);
2030
+ }
2031
+ if (input.endpoint && !isDefaultEndpointFor(input.env ?? "prod", input.endpoint)) {
2032
+ args.push("--endpoint", input.endpoint);
1755
2033
  }
2034
+ return { command: "npx", args };
1756
2035
  }
1757
- async function handleAuth(action, auth, endpoint, json, io) {
1758
- if (action === "login") {
1759
- await auth.login();
1760
- io.stdout.write(renderSuccess({ endpoint, authenticated: true }, { json }));
1761
- return 0;
2036
+ function readRoleFromEntry(entry) {
2037
+ if (!isRecord8(entry) || !Array.isArray(entry.args)) {
2038
+ return void 0;
1762
2039
  }
1763
- if (action === "status") {
1764
- const token = await auth.getAccessToken();
1765
- io.stdout.write(renderSuccess({ endpoint, authenticated: Boolean(token) }, { json }));
1766
- return 0;
2040
+ const index = entry.args.indexOf("--role");
2041
+ const value = index >= 0 ? entry.args[index + 1] : void 0;
2042
+ return typeof value === "string" && HARNESS_ROLES.includes(value) ? value : void 0;
2043
+ }
2044
+ function readServerEntry(existing) {
2045
+ if (existing === void 0) {
2046
+ return void 0;
1767
2047
  }
1768
- if (action === "logout") {
1769
- await auth.logout();
1770
- io.stdout.write(renderSuccess({ endpoint, authenticated: false }, { json }));
1771
- return 0;
2048
+ try {
2049
+ const parsed = JSON.parse(existing);
2050
+ const servers = parsed?.mcpServers;
2051
+ return isRecord8(servers) ? servers[ourKeyIn(servers) ?? SERVER_NAME] : void 0;
2052
+ } catch {
2053
+ return void 0;
1772
2054
  }
1773
- throw new Error("Expected auth login, auth status, or auth logout.");
1774
2055
  }
1775
- async function handleTools(action, rest, parsed, client, io) {
1776
- if (action === "list") {
1777
- const tools = await client.toolsList();
1778
- io.stdout.write(renderSuccess(tools, { json: parsed.json }));
1779
- return 0;
2056
+ function ourKeyIn(servers) {
2057
+ if (Object.prototype.hasOwnProperty.call(servers, SERVER_NAME)) {
2058
+ return SERVER_NAME;
1780
2059
  }
1781
- if (action === "call") {
1782
- const toolName = rest[0];
1783
- if (!toolName) {
1784
- throw new Error("Expected MCP tool name after tools call.");
1785
- }
1786
- const toolArgs = rest.slice(1);
1787
- const payloadOptions = parsePayloadArgs(toolArgs);
1788
- const payload = await parsePayloadSource({
1789
- file: payloadOptions.file,
1790
- inline: payloadOptions.inline,
1791
- stdin: io.stdin
1792
- });
1793
- const result = await client.toolsCall(toolName, payload.value);
1794
- io.stdout.write(renderSuccess(result, { json: parsed.json, warnings: payload.warnings }));
1795
- return 0;
2060
+ return isOurBridge(servers[LEGACY_SERVER_NAME]) ? LEGACY_SERVER_NAME : void 0;
2061
+ }
2062
+ function legacyBridgeIn(servers) {
2063
+ return isOurBridge(servers[LEGACY_SERVER_NAME]);
2064
+ }
2065
+ function isOurBridge(entry) {
2066
+ if (!isRecord8(entry)) {
2067
+ return false;
1796
2068
  }
1797
- if (action === "catalog") {
1798
- const catalogEnv = parsed.env ?? "prod";
1799
- io.stdout.write(renderSuccess(getGeneratedTools(parsed.env ?? "prod"), {
1800
- json: parsed.json,
1801
- warnings: [catalogEnv === "dev" ? "Local dev catalog is a checked-in live sync snapshot; rerun sync:tools --env dev to refresh it." : "Local prod catalog is seed fixture only until authenticated prod live sync updates generated catalogs."]
1802
- }));
1803
- return 0;
2069
+ const { command, args } = entry;
2070
+ const argList = Array.isArray(args) ? args.filter((arg) => typeof arg === "string") : [];
2071
+ const isOurPackage = (arg) => [PACKAGE_NAME, LEGACY_PACKAGE_NAME].some((name) => arg === name || arg.startsWith(`${name}@`));
2072
+ if (argList.some((arg) => isOurPackage(arg) || arg === "--role")) {
2073
+ return true;
1804
2074
  }
1805
- throw new Error("Expected tools list, tools call <mcpName>, or tools catalog.");
2075
+ const bin = typeof command === "string" ? basename2(command) : "";
2076
+ return OUR_BINS.includes(bin);
1806
2077
  }
1807
- function parseGlobalArgs(argv) {
1808
- const result = { json: false, help: false, positionals: [] };
1809
- for (let index = 0; index < argv.length; index += 1) {
1810
- const arg = argv[index];
1811
- if (!arg) {
1812
- continue;
1813
- }
1814
- if (arg === "--env") {
1815
- result.env = assertEnv(requireValue3(arg, argv[++index]));
1816
- } else if (arg === "--endpoint") {
1817
- result.endpoint = requireValue3(arg, argv[++index]);
1818
- } else if (arg === "--json") {
1819
- result.json = true;
1820
- } else if (arg === "--help" || arg === "-h") {
1821
- result.help = true;
1822
- } else {
1823
- result.positionals.push(arg);
1824
- }
2078
+ var OUR_BINS = ["ainecto", "ai-erd", "ai-erd-mcp"];
2079
+ function mergeServerEntry(existing, entry) {
2080
+ const root = parseConfigObject(existing);
2081
+ const servers = readServers(root);
2082
+ const ourKey = ourKeyIn(servers);
2083
+ const replaced = ourKey !== void 0;
2084
+ const replacedEntry = ourKey === void 0 ? void 0 : servers[ourKey];
2085
+ if (legacyBridgeIn(servers)) {
2086
+ delete servers[LEGACY_SERVER_NAME];
2087
+ }
2088
+ servers[SERVER_NAME] = entry;
2089
+ return { content: stringify({ ...root, mcpServers: servers }), replaced, replacedEntry };
2090
+ }
2091
+ function removeServerEntry(existing, restore) {
2092
+ if (existing === void 0) {
2093
+ return { removed: false, emptied: false };
2094
+ }
2095
+ const root = parseConfigObject(existing);
2096
+ const servers = readServers(root);
2097
+ const ourKey = ourKeyIn(servers);
2098
+ const removed = ourKey !== void 0;
2099
+ if (restore !== void 0) {
2100
+ servers[ourKey ?? SERVER_NAME] = restore.value;
2101
+ return { content: stringify({ ...root, mcpServers: servers }), removed, emptied: false };
2102
+ }
2103
+ if (ourKey !== void 0) {
2104
+ delete servers[ourKey];
2105
+ }
2106
+ const otherKeys = Object.keys(root).filter((key) => key !== "mcpServers");
2107
+ const emptied = Object.keys(servers).length === 0 && otherKeys.length === 0;
2108
+ return { content: stringify({ ...root, mcpServers: servers }), removed, emptied };
2109
+ }
2110
+ function codexAddCommand(input) {
2111
+ const entry = buildServerEntry(input);
2112
+ return `codex mcp add ${SERVER_NAME} -- ${entry.command} ${entry.args.join(" ")}`;
2113
+ }
2114
+ function codexProfileSetup(input) {
2115
+ const entry = buildServerEntry(input);
2116
+ return [
2117
+ `Per-session roles in Codex use profiles. Put this in $CODEX_HOME/${input.role}.config.toml:`,
2118
+ "",
2119
+ ` [mcp_servers.${SERVER_NAME}]`,
2120
+ ` command = "${entry.command}"`,
2121
+ ` args = ${JSON.stringify(entry.args)}`,
2122
+ "",
2123
+ `Then start that session with: codex -p ${input.role}`,
2124
+ "A profile entry with the same server name replaces the global one, so the session still sees exactly one."
2125
+ ];
2126
+ }
2127
+ function codexRemoveCommand() {
2128
+ return `codex mcp remove ${SERVER_NAME}`;
2129
+ }
2130
+ function readServers(root) {
2131
+ const servers = root.mcpServers;
2132
+ if (servers === void 0) {
2133
+ return {};
1825
2134
  }
1826
- return result;
2135
+ if (!isRecord8(servers)) {
2136
+ throw new Error(
2137
+ 'Existing MCP config has an "mcpServers" value that is not an object, so it was left untouched. Fix it by hand and run init again.'
2138
+ );
2139
+ }
2140
+ return { ...servers };
1827
2141
  }
1828
- function parsePayloadArgs(argv) {
1829
- const result = {};
1830
- const inline = [];
1831
- for (let index = 0; index < argv.length; index += 1) {
1832
- const arg = argv[index];
1833
- if (!arg) {
1834
- continue;
1835
- }
1836
- if (arg === "--file" || arg === "-f") {
1837
- result.file = requireValue3(arg, argv[++index]);
1838
- } else {
1839
- inline.push(arg);
1840
- }
2142
+ function parseConfigObject(existing) {
2143
+ if (existing === void 0 || !existing.trim()) {
2144
+ return {};
1841
2145
  }
1842
- if (inline.length > 0) {
1843
- result.inline = inline.join(" ");
2146
+ let parsed;
2147
+ try {
2148
+ parsed = JSON.parse(existing);
2149
+ } catch (error) {
2150
+ throw new Error(
2151
+ `Existing MCP config is not valid JSON, so it was left untouched. Fix it by hand and run init again. (${error instanceof Error ? error.message : String(error)})`
2152
+ );
1844
2153
  }
1845
- return result;
2154
+ if (!isRecord8(parsed)) {
2155
+ throw new Error("Existing MCP config must be a JSON object; it was left untouched.");
2156
+ }
2157
+ return parsed;
1846
2158
  }
1847
- function requireValue3(flag, value) {
1848
- if (!value || value.startsWith("--")) {
1849
- throw new Error(`${flag} requires a value.`);
2159
+ function stringify(value) {
2160
+ return `${JSON.stringify(value, null, 2)}
2161
+ `;
2162
+ }
2163
+ function isRecord8(value) {
2164
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2165
+ }
2166
+
2167
+ // src/core/harness/harnessDoc.ts
2168
+ var MARKER_BEGIN = "<!-- ainecto:begin -->";
2169
+ var MARKER_END = "<!-- ainecto:end -->";
2170
+ var LEGACY_MARKER_BEGIN = "<!-- ai-erd:begin -->";
2171
+ var LEGACY_MARKER_END = "<!-- ai-erd:end -->";
2172
+ var MARKERS = {
2173
+ begin: [MARKER_BEGIN, LEGACY_MARKER_BEGIN],
2174
+ end: [MARKER_END, LEGACY_MARKER_END]
2175
+ };
2176
+ var HARNESS_DIR = ".ainecto";
2177
+ var LEGACY_HARNESS_DIR = ".ai-erd";
2178
+ var HARNESS_DOC_PATH = `${HARNESS_DIR}/HARNESS.md`;
2179
+ var HARNESS_CONFIG_PATH = `${HARNESS_DIR}/config.json`;
2180
+ var HARNESS_RECORD_PATH = `${HARNESS_DIR}/init-record.json`;
2181
+ function renderHarnessDoc(input, template) {
2182
+ return fillTemplate(template ?? DEFAULT_HARNESS_DOC, {
2183
+ projectName: input.projectName,
2184
+ projectUuid: input.projectUuid,
2185
+ endpoint: input.endpoint
2186
+ });
2187
+ }
2188
+ function fillTemplate(template, values) {
2189
+ return template.replace(/\{\{([a-zA-Z0-9_]+)}}/g, (whole, name) => Object.prototype.hasOwnProperty.call(values, name) ? values[name] : whole);
2190
+ }
2191
+ var DOC_KEY = "HARNESS.DOC";
2192
+ var AGENT_NOTE_KEY = "HARNESS.AGENT_NOTE";
2193
+ var REQUIRED_VARIABLES = {
2194
+ [DOC_KEY]: ["projectName", "projectUuid", "endpoint"],
2195
+ [AGENT_NOTE_KEY]: ["harnessDocPath", "serverName"]
2196
+ };
2197
+ function documentContractProblem(key, body) {
2198
+ for (const name of REQUIRED_VARIABLES[key] ?? []) {
2199
+ if (!body.includes(`{{${name}}}`)) {
2200
+ return `${key} is missing the required {{${name}}} placeholder`;
2201
+ }
1850
2202
  }
1851
- return value;
2203
+ if (key === AGENT_NOTE_KEY) {
2204
+ return managedBlockProblem(key, withCurrentMarkers(body));
2205
+ }
2206
+ return void 0;
1852
2207
  }
1853
- function helpText() {
1854
- return [
1855
- "Ainecto CLI",
1856
- "",
1857
- "Usage:",
1858
- " ainecto auth login|status|logout [--env prod|dev] [--endpoint URL] [--json]",
1859
- " ainecto tools list [--env prod|dev] [--endpoint URL] [--json]",
1860
- " ainecto tools call <mcpName> [-f payload.json|@-] [inline-json] [--json]",
2208
+ var DEFAULT_HARNESS_DOC = `# AiNecto Harness
2209
+
2210
+ This repository is wired to an AiNecto project. The design source of truth lives in AiNecto,
2211
+ not in this repository's files.
2212
+
2213
+ - Project: **{{projectName}}** (\`{{projectUuid}}\`)
2214
+ - MCP endpoint: {{endpoint}}
2215
+
2216
+ ## One session, one role
2217
+
2218
+ A session picks exactly one role and keeps it for the whole session. The role comes from the
2219
+ MCP server configuration \u2014 not from this file \u2014 and it does not change during a session. The
2220
+ user picks the role; an agent applies a new one only when the user asks, and it takes effect in
2221
+ the next session. This is a working guardrail, not a security boundary. The server exposes only
2222
+ the tools that role is allowed to use, so a tool that is out of role is not merely discouraged:
2223
+ it is absent.
2224
+
2225
+ | Role | Owns | Must not |
2226
+ | --- | --- | --- |
2227
+ | Design | Requirements, ERD, domain and module boundaries, dependency direction, tasks | Write product or test code |
2228
+ | Development | Product code, following the approved design | Change requirements, ERD, architecture, or data ownership |
2229
+ | Test | Test scenarios and test code, running them and reading results | Change product code to make a test pass; change the design |
2230
+ | Validation | Judging the result against the design and the rules | Change anything; it reports PASS or FAIL with evidence |
2231
+
2232
+ Every role can **read** the whole source of truth. Reading the design is how the other roles
2233
+ do their job. A session that cannot do something reports it in the session, to you.
2234
+
2235
+ ## When you are blocked
2236
+
2237
+ If the work needs something your role cannot do, **stop and say so**. Do not look for a way
2238
+ around it: not by editing files the tools would have written, not by asking for the role to be
2239
+ changed mid-session, not by deferring the decision into the implementation.
2240
+
2241
+ The work needs a design change.
2242
+ This is a Development Session, so I cannot make it.
2243
+ Settle it in a Design Session, then continue here.
2244
+
2245
+ Validation reports \`PASS\`, or \`FAIL\` with the rule violated, where it happened, the evidence,
2246
+ and which session should pick it up next.
2247
+
2248
+ ## Not every change needs four sessions
2249
+
2250
+ A small change that fits the design already recorded in AiNecto starts at Development. The rule
2251
+ is not "always run four steps" \u2014 it is that when a step is needed, it does not happen inside
2252
+ another role's session.
2253
+
2254
+ ## Switching roles
2255
+
2256
+ Change the role in the MCP server configuration and start a new session:
2257
+
2258
+ ${AGENT_TARGETS.map((target) => ` ${target.label.padEnd(12)} ${target.path}`).join("\n")}
2259
+ Codex global config \u2014 use a profile: codex -p <role>, or set ${ROLE_ENV_VAR}
2260
+
2261
+ Or run \`ainecto init --role <design|development|test|validation>\` to rewrite them.
2262
+
2263
+ ---
2264
+ Generated by \`ainecto init\`. Remove everything it wrote with \`ainecto init --undo\`.
2265
+ `;
2266
+ function renderAgentNote(template) {
2267
+ return fillTemplate(withCurrentMarkers(template ?? DEFAULT_AGENT_NOTE), {
2268
+ harnessDocPath: HARNESS_DOC_PATH,
2269
+ serverName: SERVER_NAME
2270
+ });
2271
+ }
2272
+ function withCurrentMarkers(template) {
2273
+ return template.split(LEGACY_MARKER_BEGIN).join(MARKER_BEGIN).split(LEGACY_MARKER_END).join(MARKER_END);
2274
+ }
2275
+ var DEFAULT_AGENT_NOTE = `${MARKER_BEGIN}
2276
+ ## AiNecto Harness
2277
+
2278
+ This repository uses AiNecto as its design source of truth, and each session has one role
2279
+ (Design / Development / Test / Validation). Read \`{{harnessDocPath}}\` before doing work that
2280
+ touches requirements, the ERD, or architecture.
2281
+
2282
+ The role of this session comes from the \`{{serverName}}\` MCP server configuration that
2283
+ \`ainecto init\` wrote; the server states it in its \`initialize\` instructions and exposes only
2284
+ that role's tools. If the work needs something outside the role,
2285
+ stop and report which session is required.
2286
+ ${MARKER_END}`;
2287
+ function managedBlockProblem(key, body) {
2288
+ const trimmed = body.trim();
2289
+ if (!trimmed.startsWith(MARKER_BEGIN)) {
2290
+ return `${key} must begin with ${MARKER_BEGIN} \u2014 text outside the block is never removed by --undo`;
2291
+ }
2292
+ if (!trimmed.endsWith(MARKER_END)) {
2293
+ return `${key} must end with ${MARKER_END} \u2014 text outside the block is never removed by --undo`;
2294
+ }
2295
+ const inner = trimmed.slice(MARKER_BEGIN.length, trimmed.length - MARKER_END.length);
2296
+ if (inner.includes(MARKER_BEGIN) || inner.includes(MARKER_END)) {
2297
+ return `${key} must contain exactly one ${MARKER_BEGIN} and one ${MARKER_END}`;
2298
+ }
2299
+ if (!inner.startsWith("\n")) {
2300
+ return `${key} must put ${MARKER_BEGIN} alone on its own line`;
2301
+ }
2302
+ if (!inner.endsWith("\n")) {
2303
+ return `${key} must put ${MARKER_END} alone on its own line`;
2304
+ }
2305
+ if (hasUnclosedFence(inner)) {
2306
+ return `${key} contains an unclosed code fence \u2014 the closing marker would fall inside it`;
2307
+ }
2308
+ return void 0;
2309
+ }
2310
+ function hasUnclosedFence(content) {
2311
+ let open;
2312
+ for (const line of content.split("\n")) {
2313
+ const fence = fenceOf(line);
2314
+ if (!fence) continue;
2315
+ if (!open) open = { char: fence.char, length: fence.length };
2316
+ else if (fence.char === open.char && fence.length >= open.length && fence.info === "") open = void 0;
2317
+ }
2318
+ return open !== void 0;
2319
+ }
2320
+ function canonicalManagedBlock(block) {
2321
+ return block.trim();
2322
+ }
2323
+ function upsertMarkerBlock(existing, block) {
2324
+ const current = existing ?? "";
2325
+ assertNoDanglingMarker(current);
2326
+ const normalized = canonicalManagedBlock(block);
2327
+ const range = markerRange(current);
2328
+ const composed = range ? `${current.slice(0, range.start)}${normalized}${current.slice(range.end)}` : `${current.trim() ? `${current.replace(/\s*$/, "")}
2329
+
2330
+ ` : ""}${normalized}
2331
+ `;
2332
+ assertComposedBlockIsOurs(composed, current);
2333
+ return composed;
2334
+ }
2335
+ function assertComposedBlockIsOurs(composed, existing) {
2336
+ if (ownedMarkerCount(composed, "begin") === 1 && ownedMarkerCount(composed, "end") === 1) {
2337
+ return;
2338
+ }
2339
+ const reason = hasUnclosedFence(existing) ? "this file ends inside an unclosed code fence, so the block would sit inside it" : "the block could not be found again once merged into this file";
2340
+ throw new Error(
2341
+ `ainecto could not place its block where --undo would find it again \u2014 ${reason}. The file was left untouched; fix that and run again.`
2342
+ );
2343
+ }
2344
+ function assertNoDanglingMarker(content) {
2345
+ const begins = ownedMarkerCount(content, "begin");
2346
+ const ends = ownedMarkerCount(content, "end");
2347
+ if (begins !== ends || begins > 1) {
2348
+ throw new Error(
2349
+ `AiNecto markers are unbalanced (${begins} begin, ${ends} end). The file was left untouched \u2014 remove the stray ${MARKER_BEGIN} / ${MARKER_END} by hand and run again.`
2350
+ );
2351
+ }
2352
+ if (begins === 0) {
2353
+ return;
2354
+ }
2355
+ if (ownedMarker(content, "end", 0).at < ownedMarker(content, "begin", 0).at) {
2356
+ throw new Error(
2357
+ `AiNecto markers are out of order (${MARKER_END} appears before ${MARKER_BEGIN}). The file was left untouched \u2014 fix the markers by hand and run again.`
2358
+ );
2359
+ }
2360
+ }
2361
+ function ownedMarkerCount(content, kind) {
2362
+ let count = 0;
2363
+ for (let hit = ownedMarker(content, kind, 0); hit !== void 0; hit = ownedMarker(content, kind, hit.at + 1)) {
2364
+ count += 1;
2365
+ }
2366
+ return count;
2367
+ }
2368
+ function ownedMarker(content, kind, from) {
2369
+ let best;
2370
+ for (const marker of MARKERS[kind]) {
2371
+ const at = ownedMarkerIndex(content, marker, from);
2372
+ if (at >= 0 && (best === void 0 || at < best.at)) {
2373
+ best = { at, length: marker.length };
2374
+ }
2375
+ }
2376
+ return best;
2377
+ }
2378
+ function anyMarker(content, kind, from) {
2379
+ let best;
2380
+ for (const marker of MARKERS[kind]) {
2381
+ const at = content.indexOf(marker, from);
2382
+ if (at >= 0 && (best === void 0 || at < best.at)) {
2383
+ best = { at, length: marker.length };
2384
+ }
2385
+ }
2386
+ return best;
2387
+ }
2388
+ function isInsideFencedBlock(content, index) {
2389
+ let open;
2390
+ let offset = 0;
2391
+ for (const line of content.split("\n")) {
2392
+ const nextOffset = offset + line.length + 1;
2393
+ if (index < nextOffset) {
2394
+ return open !== void 0;
2395
+ }
2396
+ const fence = fenceOf(line);
2397
+ if (fence) {
2398
+ if (!open) {
2399
+ open = { char: fence.char, length: fence.length };
2400
+ } else if (fence.char === open.char && fence.length >= open.length && fence.info === "") {
2401
+ open = void 0;
2402
+ }
2403
+ }
2404
+ offset = nextOffset;
2405
+ }
2406
+ return open !== void 0;
2407
+ }
2408
+ function fenceOf(line) {
2409
+ const match = /^ {0,3}(`{3,}|~{3,})(.*)$/.exec(line);
2410
+ if (!match) {
2411
+ return void 0;
2412
+ }
2413
+ const run = match[1];
2414
+ const info = match[2].trim();
2415
+ if (run.startsWith("`") && info.includes("`")) {
2416
+ return void 0;
2417
+ }
2418
+ return { char: run[0], length: run.length, info };
2419
+ }
2420
+ function removeMarkerBlock(existing) {
2421
+ const current = existing ?? "";
2422
+ assertNoDanglingMarker(current);
2423
+ const range = markerRange(current);
2424
+ if (!range) {
2425
+ return { content: current, removed: false, emptied: !current.trim() };
2426
+ }
2427
+ const before = current.slice(0, range.start).replace(/\n*$/, "");
2428
+ const after = current.slice(range.end).replace(/^\n*/, "");
2429
+ const stripped = before && after ? `${before}
2430
+
2431
+ ${after}` : before ? `${before}
2432
+ ` : after;
2433
+ return { content: stripped.trim() ? stripped : "", removed: true, emptied: !stripped.trim() };
2434
+ }
2435
+ function replaceMarkerBlockWith(content, block) {
2436
+ const range = markerRange(content);
2437
+ return range ? `${content.slice(0, range.start)}${block}${content.slice(range.end)}` : content;
2438
+ }
2439
+ var UNPAIRED_BEGIN = "(unpaired ai-erd begin)";
2440
+ var UNPAIRED_END = "(unpaired ai-erd end)";
2441
+ function markerEvidence(content) {
2442
+ if (!content) {
2443
+ return [];
2444
+ }
2445
+ const owned = markerRange(content);
2446
+ const rest = owned === void 0 ? content : content.slice(0, owned.start) + content.slice(owned.end);
2447
+ const evidence = [];
2448
+ let cursor = 0;
2449
+ let begins = 0;
2450
+ let ends = 0;
2451
+ while (true) {
2452
+ const begin = anyMarker(rest, "begin", cursor);
2453
+ const close = anyMarker(rest, "end", cursor);
2454
+ if (begin === void 0 && close === void 0) {
2455
+ break;
2456
+ }
2457
+ if (begin === void 0 || close !== void 0 && close.at < begin.at) {
2458
+ ends += 1;
2459
+ cursor = close.at + close.length;
2460
+ continue;
2461
+ }
2462
+ const pairEnd = anyMarker(rest, "end", begin.at + begin.length);
2463
+ if (pairEnd === void 0) {
2464
+ begins += 1;
2465
+ cursor = begin.at + begin.length;
2466
+ continue;
2467
+ }
2468
+ evidence.push(blockShape(rest.slice(begin.at, pairEnd.at + pairEnd.length)));
2469
+ cursor = pairEnd.at + pairEnd.length;
2470
+ }
2471
+ for (let i = 0; i < begins; i += 1) evidence.push(UNPAIRED_BEGIN);
2472
+ for (let i = 0; i < ends; i += 1) evidence.push(UNPAIRED_END);
2473
+ return evidence;
2474
+ }
2475
+ function blockShape(block) {
2476
+ return block.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join("\n");
2477
+ }
2478
+ function existingMarkerBlock(content) {
2479
+ if (!content) {
2480
+ return void 0;
2481
+ }
2482
+ const range = markerRange(content);
2483
+ return range ? content.slice(range.start, range.end) : void 0;
2484
+ }
2485
+ function markerRange(content) {
2486
+ const start = ownedMarker(content, "begin", 0);
2487
+ if (start === void 0) {
2488
+ return void 0;
2489
+ }
2490
+ const endMarker = ownedMarker(content, "end", start.at);
2491
+ if (endMarker === void 0) {
2492
+ return void 0;
2493
+ }
2494
+ return { start: start.at, end: endMarker.at + endMarker.length };
2495
+ }
2496
+ function ownedMarkerIndex(content, marker, from) {
2497
+ for (let at = content.indexOf(marker, from); at >= 0; at = content.indexOf(marker, at + 1)) {
2498
+ const lineStart = content.lastIndexOf("\n", at - 1) + 1;
2499
+ if (lineStart !== at) {
2500
+ continue;
2501
+ }
2502
+ const lineEnd = content.indexOf("\n", at);
2503
+ const rest = (lineEnd < 0 ? content.slice(at) : content.slice(at, lineEnd)).slice(marker.length);
2504
+ if (rest.trim() !== "") {
2505
+ continue;
2506
+ }
2507
+ if (isInsideFencedBlock(content, at)) {
2508
+ continue;
2509
+ }
2510
+ return at;
2511
+ }
2512
+ return -1;
2513
+ }
2514
+
2515
+ // src/core/harness/initRecord.ts
2516
+ var RECORD_KIND = "ainecto/init-record";
2517
+ var LEGACY_RECORD_KIND = "ai-erd/init-record";
2518
+ var RECORD_VERSION = 2;
2519
+ var ABSENT = { at: "absent" };
2520
+ function emptyRecord() {
2521
+ return { version: RECORD_VERSION, files: {}, createdDirectories: [] };
2522
+ }
2523
+ function readRecord(content, recordPath = HARNESS_RECORD_PATH) {
2524
+ const HARNESS_RECORD_PATH2 = recordPath;
2525
+ if (content === void 0) {
2526
+ return { state: "absent" };
2527
+ }
2528
+ let parsed;
2529
+ try {
2530
+ parsed = JSON.parse(content);
2531
+ } catch {
2532
+ return { state: "corrupt", reason: `${HARNESS_RECORD_PATH2} is not valid JSON` };
2533
+ }
2534
+ if (!isObject(parsed)) {
2535
+ return { state: "corrupt", reason: `${HARNESS_RECORD_PATH2} is not a JSON object` };
2536
+ }
2537
+ if (parsed.kind !== RECORD_KIND && parsed.kind !== LEGACY_RECORD_KIND) {
2538
+ return {
2539
+ state: "corrupt",
2540
+ reason: `${HARNESS_RECORD_PATH2} exists but is not an AiNecto init record (kind=${JSON.stringify(parsed.kind)})`
2541
+ };
2542
+ }
2543
+ if (parsed.version !== RECORD_VERSION) {
2544
+ return {
2545
+ state: "legacy",
2546
+ reason: `${HARNESS_RECORD_PATH2} was written by another version of this CLI (record version ${JSON.stringify(parsed.version)}, this CLI reads ${RECORD_VERSION})`
2547
+ };
2548
+ }
2549
+ const files = parsed.files;
2550
+ if (!isObject(files)) {
2551
+ return { state: "corrupt", reason: `${HARNESS_RECORD_PATH2} has no "files" object` };
2552
+ }
2553
+ const out = {};
2554
+ for (const [path, raw] of Object.entries(files)) {
2555
+ const read = readContract(raw);
2556
+ if ("problem" in read) {
2557
+ return {
2558
+ state: "corrupt",
2559
+ reason: `${HARNESS_RECORD_PATH2} has an unusable entry for ${path} \u2014 it ${read.problem}`
2560
+ };
2561
+ }
2562
+ out[path] = read.contract;
2563
+ }
2564
+ const directories = parsed.createdDirectories;
2565
+ if (directories !== void 0 && !isStringArray(directories)) {
2566
+ return { state: "corrupt", reason: `${HARNESS_RECORD_PATH2} has a malformed createdDirectories` };
2567
+ }
2568
+ return {
2569
+ state: "valid",
2570
+ record: {
2571
+ version: RECORD_VERSION,
2572
+ files: out,
2573
+ createdDirectories: directories === void 0 ? [] : [...directories]
2574
+ }
2575
+ };
2576
+ }
2577
+ var LEGACY_MANAGED_KEYS = [
2578
+ "created",
2579
+ "originals",
2580
+ "lastWritten",
2581
+ "lastWrittenFragment",
2582
+ "lastWrittenBody",
2583
+ "replacedEntries",
2584
+ "replacedBlocks",
2585
+ "blockAdded",
2586
+ "foreignMarkers"
2587
+ ];
2588
+ function legacyManagedRecord(configContent) {
2589
+ if (configContent === void 0) {
2590
+ return false;
2591
+ }
2592
+ let parsed;
2593
+ try {
2594
+ parsed = JSON.parse(configContent);
2595
+ } catch {
2596
+ return false;
2597
+ }
2598
+ if (!isObject(parsed) || !isObject(parsed.managed)) {
2599
+ return false;
2600
+ }
2601
+ const managed = parsed.managed;
2602
+ const hasLegacyShape = LEGACY_MANAGED_KEYS.some(
2603
+ (key) => Object.prototype.hasOwnProperty.call(managed, key)
2604
+ );
2605
+ if (!hasLegacyShape) {
2606
+ return false;
2607
+ }
2608
+ if (typeof parsed.generatedBy === "string") {
2609
+ return true;
2610
+ }
2611
+ const lastWritten = managed.lastWritten;
2612
+ return isObject(lastWritten) && [HARNESS_CONFIG_PATH, `${LEGACY_HARNESS_DIR}/config.json`].some((path) => Object.prototype.hasOwnProperty.call(lastWritten, path));
2613
+ }
2614
+ function legacyManagedNotice(configPath) {
2615
+ return `${configPath} holds an init record written by an older version of this CLI (a "managed" field). This version keeps its record in ${HARNESS_RECORD_PATH} and cannot read that one, so your saved originals are still in there and nothing was changed. Undo with the version that wrote it (for example: npx -y -p @ai-erd/mcp@0.2 ai-erd init --undo), then run this version again.`;
2616
+ }
2617
+ function serializeRecord(record) {
2618
+ return `${JSON.stringify({
2619
+ kind: RECORD_KIND,
2620
+ version: RECORD_VERSION,
2621
+ files: record.files,
2622
+ createdDirectories: record.createdDirectories
2623
+ }, null, 2)}
2624
+ `;
2625
+ }
2626
+ function readContract(raw) {
2627
+ if (!isObject(raw)) {
2628
+ return { problem: "is not a JSON object" };
2629
+ }
2630
+ const scope = raw.scope;
2631
+ if (scope !== "whole" && scope !== "entry" && scope !== "block" && scope !== "fields") {
2632
+ return { problem: `has an unknown scope (${JSON.stringify(raw.scope)})` };
2633
+ }
2634
+ if (typeof raw.createdByUs !== "boolean") {
2635
+ return { problem: "does not say whether ainecto created the file" };
2636
+ }
2637
+ const original = readOwned(raw.original);
2638
+ const written = readOwned(raw.written);
2639
+ if (original === void 0) {
2640
+ return { problem: "has no readable record of what was there before" };
2641
+ }
2642
+ if (written === void 0) {
2643
+ return { problem: "has no readable record of what ainecto wrote" };
2644
+ }
2645
+ if (raw.fileFingerprint !== void 0 && typeof raw.fileFingerprint !== "string") {
2646
+ return { problem: "has a malformed fileFingerprint" };
2647
+ }
2648
+ if (raw.writtenFields !== void 0 && !isObject(raw.writtenFields)) {
2649
+ return { problem: "has a malformed writtenFields" };
2650
+ }
2651
+ let originalFields;
2652
+ if (raw.originalFields !== void 0) {
2653
+ if (!isObject(raw.originalFields)) {
2654
+ return { problem: "has a malformed originalFields" };
2655
+ }
2656
+ originalFields = {};
2657
+ for (const [field, value] of Object.entries(raw.originalFields)) {
2658
+ const owned = readOwned(value);
2659
+ if (owned === void 0) {
2660
+ return { problem: `has no readable original for the field ${field}` };
2661
+ }
2662
+ originalFields[field] = owned;
2663
+ }
2664
+ }
2665
+ if (raw.foreignBlocks !== void 0 && !isStringArray(raw.foreignBlocks)) {
2666
+ return { problem: "has a malformed foreignBlocks" };
2667
+ }
2668
+ if (scope === "block" && raw.foreignBlocks === void 0) {
2669
+ return { problem: "owns a marker block but does not say what marker blocks were already in the file" };
2670
+ }
2671
+ if (scope === "fields") {
2672
+ const problem = fieldsContractProblem({
2673
+ written,
2674
+ writtenFields: isObject(raw.writtenFields) ? raw.writtenFields : void 0,
2675
+ originalFields
2676
+ });
2677
+ if (problem !== void 0) {
2678
+ return { problem };
2679
+ }
2680
+ }
2681
+ const contract = {
2682
+ scope,
2683
+ createdByUs: raw.createdByUs,
2684
+ original,
2685
+ written
2686
+ };
2687
+ if (typeof raw.fileFingerprint === "string") contract.fileFingerprint = raw.fileFingerprint;
2688
+ if (isObject(raw.writtenFields)) contract.writtenFields = { ...raw.writtenFields };
2689
+ if (originalFields !== void 0) contract.originalFields = originalFields;
2690
+ if (isStringArray(raw.foreignBlocks)) contract.foreignBlocks = [...raw.foreignBlocks];
2691
+ return { contract };
2692
+ }
2693
+ function fieldsContractProblem(contract, ownedFields) {
2694
+ const { written, writtenFields, originalFields } = contract;
2695
+ if (writtenFields === void 0) {
2696
+ return "owns only some fields of the file but does not say which ones it wrote (writtenFields)";
2697
+ }
2698
+ if (originalFields === void 0) {
2699
+ return "owns only some fields of the file but does not say what was in them before (originalFields)";
2700
+ }
2701
+ const writtenKeys = Object.keys(writtenFields).sort();
2702
+ const originalKeys = Object.keys(originalFields).sort();
2703
+ if (writtenKeys.length !== originalKeys.length || writtenKeys.some((key, at) => key !== originalKeys[at])) {
2704
+ return `lists different fields as written (${writtenKeys.join(", ") || "none"}) and as backed up (${originalKeys.join(", ") || "none"})`;
2705
+ }
2706
+ for (const key of writtenKeys) {
2707
+ const original = originalFields[key];
2708
+ if (original.at !== "absent" && original.at !== "json") {
2709
+ return `records the original of ${key} as ${original.at}, which is not something --undo can put back into a JSON field`;
2710
+ }
2711
+ }
2712
+ if (written.at !== "text") {
2713
+ return "owns only some fields of the file but does not hold the file text it wrote";
2714
+ }
2715
+ let body;
2716
+ try {
2717
+ body = JSON.parse(written.text);
2718
+ } catch {
2719
+ return "holds a written file body that is not valid JSON";
2720
+ }
2721
+ if (!isObject(body)) {
2722
+ return "holds a written file body that is not a JSON object";
2723
+ }
2724
+ for (const key of writtenKeys) {
2725
+ if (!Object.prototype.hasOwnProperty.call(body, key)) {
2726
+ return `says it wrote ${key}, but that field is not in the file body it recorded`;
2727
+ }
2728
+ if (stableStringify2(body[key]) !== stableStringify2(writtenFields[key])) {
2729
+ return `records a different value for ${key} than the file body it wrote`;
2730
+ }
2731
+ }
2732
+ if (ownedFields !== void 0) {
2733
+ for (const key of ownedFields) {
2734
+ if (Object.prototype.hasOwnProperty.call(body, key) && !Object.prototype.hasOwnProperty.call(writtenFields, key)) {
2735
+ return `wrote ${key} into the file but does not record what it put there`;
2736
+ }
2737
+ }
2738
+ }
2739
+ return void 0;
2740
+ }
2741
+ function readOwned(raw) {
2742
+ if (!isObject(raw)) {
2743
+ return void 0;
2744
+ }
2745
+ if (raw.at === "absent") {
2746
+ return ABSENT;
2747
+ }
2748
+ if (raw.at === "json") {
2749
+ return Object.prototype.hasOwnProperty.call(raw, "value") ? { at: "json", value: raw.value } : void 0;
2750
+ }
2751
+ if (raw.at === "text") {
2752
+ return typeof raw.text === "string" ? { at: "text", text: raw.text } : void 0;
2753
+ }
2754
+ return void 0;
2755
+ }
2756
+ function stableStringify2(value) {
2757
+ if (Array.isArray(value)) {
2758
+ return `[${value.map(stableStringify2).join(",")}]`;
2759
+ }
2760
+ if (value && typeof value === "object") {
2761
+ const entries = Object.entries(value).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0);
2762
+ return `{${entries.map(([key, item]) => `${JSON.stringify(key)}:${stableStringify2(item)}`).join(",")}}`;
2763
+ }
2764
+ return JSON.stringify(value) ?? "null";
2765
+ }
2766
+ function isObject(value) {
2767
+ return !!value && typeof value === "object" && !Array.isArray(value);
2768
+ }
2769
+ function isStringArray(value) {
2770
+ return Array.isArray(value) && value.every((entry) => typeof entry === "string");
2771
+ }
2772
+
2773
+ // src/core/harness/initPlan.ts
2774
+ import { createHash as createHash4 } from "crypto";
2775
+
2776
+ // src/core/harness/repositoryRole.ts
2777
+ import { lstat, readFile as readFile4, realpath } from "fs/promises";
2778
+ import { homedir as homedir2 } from "os";
2779
+ import { dirname as dirname2, join as join2, resolve, sep } from "path";
2780
+ function readConfigRole(path, content) {
2781
+ if (content === void 0 || !content.trim()) {
2782
+ return { kind: "absent" };
2783
+ }
2784
+ let servers;
2785
+ try {
2786
+ const parsed = JSON.parse(content);
2787
+ const raw = parsed?.mcpServers;
2788
+ servers = typeof raw === "object" && raw !== null && !Array.isArray(raw) ? raw : {};
2789
+ } catch {
2790
+ return content.includes(SERVER_NAME) || content.includes(LEGACY_SERVER_NAME) ? { kind: "problem", reason: `${path} is not valid JSON` } : { kind: "absent" };
2791
+ }
2792
+ const roles = [];
2793
+ for (const key of [SERVER_NAME, LEGACY_SERVER_NAME]) {
2794
+ const entry = servers[key];
2795
+ if (!isOurBridge(entry)) {
2796
+ continue;
2797
+ }
2798
+ const role = readRoleFromEntry(entry);
2799
+ if (role === void 0) {
2800
+ return { kind: "problem", reason: `${path} has the "${key}" bridge without a valid --role` };
2801
+ }
2802
+ if (!roles.includes(role)) {
2803
+ roles.push(role);
2804
+ }
2805
+ }
2806
+ if (roles.length > 1) {
2807
+ return {
2808
+ kind: "problem",
2809
+ reason: `${path} has both the "${SERVER_NAME}" and the "${LEGACY_SERVER_NAME}" bridge with different roles (${roles.join(", ")})`
2810
+ };
2811
+ }
2812
+ return roles.length === 0 ? { kind: "absent" } : { kind: "role", role: roles[0] };
2813
+ }
2814
+ function rolesInFiles(files) {
2815
+ const roles = [];
2816
+ const problems = [];
2817
+ for (const target of AGENT_TARGETS) {
2818
+ const read = readConfigRole(target.path, files.get(target.path));
2819
+ if (read.kind === "problem") {
2820
+ problems.push(read.reason);
2821
+ } else if (read.kind === "role" && !roles.includes(read.role)) {
2822
+ roles.push(read.role);
2823
+ }
2824
+ }
2825
+ return { roles, problems, found: roles.length > 0 || problems.length > 0 };
2826
+ }
2827
+ async function findRepositoryRole(cwd, home = homedir2()) {
2828
+ const stopAt = await realOrResolved(home);
2829
+ for (let dir = await realOrResolved(cwd); ; dir = dirname2(dir)) {
2830
+ if (samePath(dir, stopAt)) {
2831
+ return { kind: "none" };
2832
+ }
2833
+ const files = /* @__PURE__ */ new Map();
2834
+ let readProblem;
2835
+ for (const target of AGENT_TARGETS) {
2836
+ try {
2837
+ files.set(target.path, await readConfigInside(dir, target.path));
2838
+ } catch (error) {
2839
+ readProblem = error instanceof Error ? error.message : String(error);
2840
+ }
2841
+ }
2842
+ if (readProblem !== void 0) {
2843
+ return { kind: "conflict", dir, reason: readProblem };
2844
+ }
2845
+ const result = rolesInFiles(files);
2846
+ if (result.found) {
2847
+ if (result.problems.length > 0) {
2848
+ return { kind: "conflict", dir, reason: result.problems.join("; ") };
2849
+ }
2850
+ if (result.roles.length > 1) {
2851
+ return {
2852
+ kind: "conflict",
2853
+ dir,
2854
+ reason: `agent configs disagree about the session role (${result.roles.join(", ")})`
2855
+ };
2856
+ }
2857
+ return { kind: "role", role: result.roles[0], dir };
2858
+ }
2859
+ if (dirname2(dir) === dir) {
2860
+ return { kind: "none" };
2861
+ }
2862
+ }
2863
+ }
2864
+ async function askTheUserToFixRole(dir, reason) {
2865
+ const gitRoot = await findGitRoot(dir);
2866
+ const roles = HARNESS_ROLES.join(", ");
2867
+ if (gitRoot !== void 0 && !samePath(await realOrResolved(gitRoot), await realOrResolved(dir))) {
2868
+ return `Cannot tell this folder's AI session role: ${reason} (in ${dir}, inside the git repository at ${gitRoot}). Ask the user how that AiNecto setting should look \u2014 \`ainecto init\` only sets up a repository root (${gitRoot}).`;
2869
+ }
2870
+ return `Cannot tell this repository's AI session role: ${reason} (in ${dir}). Ask the user which role this repository's AI sessions should have (${roles}), then run \`ainecto init --role <role>\` in ${dir}.`;
2871
+ }
2872
+ async function realOrResolved(path) {
2873
+ try {
2874
+ return await realpath(path);
2875
+ } catch {
2876
+ return resolve(path);
2877
+ }
2878
+ }
2879
+ function samePath(left, right) {
2880
+ return process.platform === "win32" ? left.toLowerCase() === right.toLowerCase() : left === right;
2881
+ }
2882
+ async function readConfigInside(dir, path) {
2883
+ let real;
2884
+ try {
2885
+ real = await realpath(join2(dir, path));
2886
+ } catch (error) {
2887
+ if (isNodeError2(error, "ENOENT") || isNodeError2(error, "ENOTDIR")) {
2888
+ return void 0;
2889
+ }
2890
+ throw error;
2891
+ }
2892
+ const realDir = await realOrResolved(dir);
2893
+ if (!samePath(real, realDir) && !real.startsWith(realDir + sep) && !(process.platform === "win32" && real.toLowerCase().startsWith((realDir + sep).toLowerCase()))) {
2894
+ throw new Error(`${path} points outside ${dir} (${real})`);
2895
+ }
2896
+ return readFile4(real, "utf8");
2897
+ }
2898
+ async function findGitRoot(cwd) {
2899
+ for (let at = resolve(cwd); ; at = dirname2(at)) {
2900
+ try {
2901
+ await lstat(join2(at, ".git"));
2902
+ return at;
2903
+ } catch {
2904
+ }
2905
+ if (dirname2(at) === at) {
2906
+ return void 0;
2907
+ }
2908
+ }
2909
+ }
2910
+ async function readManagedFile(root, path) {
2911
+ await assertInsideRepository(root, path);
2912
+ return readIfExists(join2(root, path));
2913
+ }
2914
+ async function readIfExists(absolutePath) {
2915
+ try {
2916
+ return await readFile4(absolutePath, "utf8");
2917
+ } catch (error) {
2918
+ if (isNodeError2(error, "ENOENT")) {
2919
+ return void 0;
2920
+ }
2921
+ throw error;
2922
+ }
2923
+ }
2924
+ async function assertInsideRepository(root, relativePath) {
2925
+ const absolutePath = join2(root, relativePath);
2926
+ const realRoot = await realpath(root);
2927
+ let realParent;
2928
+ try {
2929
+ realParent = await realpath(dirname2(absolutePath));
2930
+ } catch (error) {
2931
+ if (isNodeError2(error, "ENOENT")) {
2932
+ return;
2933
+ }
2934
+ throw error;
2935
+ }
2936
+ if (realParent !== realRoot && !realParent.startsWith(realRoot + sep)) {
2937
+ throw new Error(
2938
+ `${relativePath} resolves outside the repository (${realParent}); refusing to touch it.`
2939
+ );
2940
+ }
2941
+ try {
2942
+ if ((await lstat(absolutePath)).isSymbolicLink()) {
2943
+ throw new Error(`${relativePath} is a symbolic link; refusing to write through it.`);
2944
+ }
2945
+ } catch (error) {
2946
+ if (!isNodeError2(error, "ENOENT")) {
2947
+ throw error;
2948
+ }
2949
+ }
2950
+ }
2951
+ function isNodeError2(error, code) {
2952
+ return typeof error === "object" && error !== null && error.code === code;
2953
+ }
2954
+
2955
+ // src/core/harness/initPlan.ts
2956
+ var NOTE_TARGETS = [
2957
+ { path: "AGENTS.md", createIfMissing: true },
2958
+ { path: "CLAUDE.md", createIfMissing: false }
2959
+ ];
2960
+ var MANAGED_PATHS = [
2961
+ ...AGENT_TARGETS.map((target) => target.path),
2962
+ ...NOTE_TARGETS.map((target) => target.path),
2963
+ HARNESS_DOC_PATH,
2964
+ HARNESS_CONFIG_PATH,
2965
+ HARNESS_RECORD_PATH
2966
+ ];
2967
+ function ownedJson(value, present) {
2968
+ return present ? { at: "json", value } : ABSENT;
2969
+ }
2970
+ function ownedText(text) {
2971
+ return text === void 0 ? ABSENT : { at: "text", text };
2972
+ }
2973
+ function sameOwned(left, right) {
2974
+ if (left.at !== right.at) {
2975
+ return false;
2976
+ }
2977
+ if (left.at === "absent") {
2978
+ return true;
2979
+ }
2980
+ if (left.at === "text" && right.at === "text") {
2981
+ return left.text === right.text;
2982
+ }
2983
+ return left.at === "json" && right.at === "json" && stableStringify2(left.value) === stableStringify2(right.value);
2984
+ }
2985
+ function contentFingerprint(content) {
2986
+ return createHash4("sha256").update(content).digest("hex");
2987
+ }
2988
+ function serialize(body) {
2989
+ return `${JSON.stringify(body, null, 2)}
2990
+ `;
2991
+ }
2992
+ function fingerprintFor(path, content) {
2993
+ if (!path.endsWith(".json")) {
2994
+ return contentFingerprint(content);
2995
+ }
2996
+ try {
2997
+ return contentFingerprint(stableStringify2(JSON.parse(content)));
2998
+ } catch {
2999
+ return contentFingerprint(content);
3000
+ }
3001
+ }
3002
+ function parseJsonObject(content) {
3003
+ try {
3004
+ const parsed = JSON.parse(content);
3005
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
3006
+ } catch {
3007
+ return void 0;
3008
+ }
3009
+ }
3010
+ function configBodyOf(values) {
3011
+ return {
3012
+ version: 1,
3013
+ project: { uuid: values.projectUuid, name: values.projectName },
3014
+ endpoint: values.endpoint,
3015
+ generatedBy: values.cliVersion,
3016
+ promptVersions: values.versions
3017
+ };
3018
+ }
3019
+ var OUR_CONFIG_FIELDS = Object.keys(configBodyOf({
3020
+ projectUuid: "",
3021
+ projectName: "",
3022
+ endpoint: "",
3023
+ cliVersion: "",
3024
+ versions: {}
3025
+ }));
3026
+ var hasField = (object, field) => Object.prototype.hasOwnProperty.call(object, field);
3027
+ function initRefusal(files, previous) {
3028
+ const state = previous ?? readRecord(files.get(HARNESS_RECORD_PATH));
3029
+ if (state.state === "corrupt" || state.state === "legacy") {
3030
+ return `${state.reason}. Nothing was changed \u2014 move that file aside (or restore it) and run again.`;
3031
+ }
3032
+ if (state.state === "absent" && legacyManagedRecord(files.get(HARNESS_CONFIG_PATH))) {
3033
+ return legacyManagedNotice(HARNESS_CONFIG_PATH);
3034
+ }
3035
+ const carried = state.state === "valid" ? state.record : emptyRecord();
3036
+ for (const [path, contract] of Object.entries(carried.files)) {
3037
+ if (contract.scope !== "fields") {
3038
+ continue;
3039
+ }
3040
+ const problem = fieldsContractProblem(contract, OUR_CONFIG_FIELDS);
3041
+ if (problem !== void 0) {
3042
+ return `${HARNESS_RECORD_PATH}'s record for ${path} ${problem}. Nothing was changed \u2014 restore that file (or move it aside) and run again.`;
3043
+ }
3044
+ }
3045
+ return void 0;
3046
+ }
3047
+ function planInit(input) {
3048
+ const previous = input.previous ?? readRecord(input.files.get(HARNESS_RECORD_PATH));
3049
+ const refusal = initRefusal(input.files, previous);
3050
+ if (refusal !== void 0) {
3051
+ return {
3052
+ writes: [],
3053
+ deletes: [],
3054
+ notes: [],
3055
+ codexCommand: codexAddCommand({ role: input.role, env: input.env, endpoint: input.endpoint }),
3056
+ refusal
3057
+ };
3058
+ }
3059
+ const carriedRecord = previous.state === "valid" ? previous.record : emptyRecord();
3060
+ const entry = buildServerEntry({ role: input.role, env: input.env, endpoint: input.endpoint });
3061
+ const writes = [];
3062
+ const notes = [];
3063
+ const record = {
3064
+ version: carriedRecord.version,
3065
+ files: { ...carriedRecord.files },
3066
+ createdDirectories: [...carriedRecord.createdDirectories]
3067
+ };
3068
+ const priorOf = (path, current) => {
3069
+ const prior = record.files[path];
3070
+ if (prior === void 0) {
3071
+ return void 0;
3072
+ }
3073
+ if (sameOwned(prior.written, current) || sameOwned(prior.original, current)) {
3074
+ return prior;
3075
+ }
3076
+ delete record.files[path];
3077
+ notes.push(`${path}: it changed outside ainecto, so the older backup was dropped and this is the new baseline.`);
3078
+ return void 0;
3079
+ };
3080
+ for (const target of AGENT_TARGETS) {
3081
+ const existing = input.files.get(target.path);
3082
+ const currentEntry = existing === void 0 ? void 0 : readServerEntry(existing);
3083
+ const prior = priorOf(target.path, ownedJson(currentEntry, currentEntry !== void 0));
3084
+ const merged = mergeServerEntry(existing, entry);
3085
+ if (existing === merged.content) {
3086
+ continue;
3087
+ }
3088
+ const before = previousRole(target.path, existing);
3089
+ if (merged.replaced && before && before !== input.role) {
3090
+ notes.push(`${target.path}: role ${before} \u2192 ${input.role}`);
3091
+ }
3092
+ const original = prior?.original ?? ownedJson(merged.replacedEntry, merged.replaced);
3093
+ if (prior === void 0 && original.at !== "absent") {
3094
+ notes.push(`${target.path}: your existing "${SERVER_NAME}" entry was saved and will be restored by --undo`);
3095
+ }
3096
+ record.files[target.path] = {
3097
+ scope: "entry",
3098
+ createdByUs: prior?.createdByUs ?? existing === void 0,
3099
+ original,
3100
+ written: ownedJson(readServerEntry(merged.content), true),
3101
+ fileFingerprint: fingerprintFor(target.path, merged.content)
3102
+ };
3103
+ writes.push({ path: target.path, content: merged.content, existed: existing !== void 0 });
3104
+ }
3105
+ const existingDoc = input.files.get(HARNESS_DOC_PATH);
3106
+ const priorDoc = priorOf(HARNESS_DOC_PATH, ownedText(existingDoc));
3107
+ const docContent = renderHarnessDoc({
3108
+ projectName: input.projectName,
3109
+ projectUuid: input.projectUuid,
3110
+ endpoint: input.endpoint
3111
+ }, input.documents?.doc);
3112
+ const docOriginal = priorDoc?.original ?? ownedText(existingDoc);
3113
+ if (priorDoc === void 0 && docOriginal.at !== "absent") {
3114
+ notes.push(`${HARNESS_DOC_PATH} already existed; its original was saved and --undo will restore it.`);
3115
+ }
3116
+ record.files[HARNESS_DOC_PATH] = {
3117
+ scope: "whole",
3118
+ createdByUs: priorDoc?.createdByUs ?? existingDoc === void 0,
3119
+ original: docOriginal,
3120
+ written: ownedText(docContent),
3121
+ fileFingerprint: fingerprintFor(HARNESS_DOC_PATH, docContent)
3122
+ };
3123
+ if (docContent !== existingDoc) {
3124
+ writes.push({ path: HARNESS_DOC_PATH, content: docContent, existed: existingDoc !== void 0 });
3125
+ }
3126
+ const note = renderAgentNote(input.documents?.agentNote);
3127
+ for (const target of NOTE_TARGETS) {
3128
+ const existing = input.files.get(target.path);
3129
+ if (existing === void 0 && !target.createIfMissing) {
3130
+ notes.push(`${target.path} not found \u2014 skipped; AGENTS.md already reaches this agent.`);
3131
+ continue;
3132
+ }
3133
+ const blockNow = existing === void 0 ? void 0 : existingMarkerBlock(existing);
3134
+ const carried = record.files[target.path];
3135
+ let content;
3136
+ try {
3137
+ content = upsertMarkerBlock(existing, note);
3138
+ } catch (error) {
3139
+ notes.push(`${target.path}: ${error instanceof Error ? error.message : String(error)}`);
3140
+ continue;
3141
+ }
3142
+ if (carried?.scope === "block" && unexplainedMarkers(existing, carried) > 0) {
3143
+ notes.push(
3144
+ `${target.path}: this file has an ainecto marker block that is not where ainecto can find it again (inside a code fence, a quote, or edited). Left untouched and the earlier backup kept \u2014 move that block back to the top level, or remove it, then run again.`
3145
+ );
3146
+ continue;
3147
+ }
3148
+ const prior = blockNow === void 0 ? carried : priorOf(target.path, ownedText(blockNow));
3149
+ if (content === existing) {
3150
+ continue;
3151
+ }
3152
+ const original = prior?.original ?? ownedText(blockNow);
3153
+ if (prior === void 0 && original.at !== "absent") {
3154
+ notes.push(`${target.path}: an existing ainecto block was saved and will be restored by --undo`);
3155
+ }
3156
+ record.files[target.path] = {
3157
+ scope: "block",
3158
+ createdByUs: prior?.createdByUs ?? existing === void 0,
3159
+ original,
3160
+ written: ownedText(existingMarkerBlock(content)),
3161
+ fileFingerprint: fingerprintFor(target.path, content),
3162
+ // init «전»에 이 파일에 있던 관리 블록들의 신원. 되돌리기가 「남은 마커를 설명할 수 있나」를
3163
+ // 이것으로 묻는다. ★재init 은 이어받는다 — 다시 재면 우리가 쓴 블록이 «원래 있던 것»이 된다.
3164
+ // ★{@code carried} 를 먼저 본다 — {@code priorOf} 가 새 기준을 세웠다는 것이 「남의 예제
3165
+ // 목록을 다시 모아도 된다」는 허가가 아니다. 다시 모으면 «우리가 옮겨 둔 사본»이
3166
+ // 「원래 있던 예제」로 등록되고, 그다음 되돌리기가 그것을 남긴 채 끝났다(3차 리뷰 B2).
3167
+ foreignBlocks: carried?.foreignBlocks ?? prior?.foreignBlocks ?? markerEvidence(existing)
3168
+ };
3169
+ writes.push({ path: target.path, content, existed: existing !== void 0 });
3170
+ }
3171
+ const existingConfig = input.files.get(HARNESS_CONFIG_PATH);
3172
+ const ourFields = configBodyOf({
3173
+ projectUuid: input.projectUuid,
3174
+ projectName: input.projectName,
3175
+ endpoint: input.endpoint,
3176
+ cliVersion: input.cliVersion,
3177
+ // ★어느 판의 규칙으로 썼나. 이게 없으면 「무엇이 적혀 있었나」를 나중에 못 되짚는다.
3178
+ versions: input.documents?.versions ?? {}
3179
+ });
3180
+ const theirConfig = existingConfig === void 0 ? void 0 : parseJsonObject(existingConfig);
3181
+ if (existingConfig !== void 0 && theirConfig === void 0) {
3182
+ notes.push(`${HARNESS_CONFIG_PATH} is not a JSON object \u2014 left untouched.`);
3183
+ } else {
3184
+ const priorConfig = record.files[HARNESS_CONFIG_PATH];
3185
+ const originalFields = {};
3186
+ for (const field of OUR_CONFIG_FIELDS) {
3187
+ const presentNow = theirConfig !== void 0 && hasField(theirConfig, field);
3188
+ const currentValue = presentNow ? theirConfig[field] : void 0;
3189
+ const priorOriginal = priorConfig?.originalFields?.[field];
3190
+ const priorWritten = priorConfig?.writtenFields;
3191
+ const untouchedByUser = priorWritten !== void 0 && hasField(priorWritten, field) && stableStringify2(currentValue) === stableStringify2(priorWritten[field]);
3192
+ const next = untouchedByUser && priorOriginal !== void 0 ? priorOriginal : ownedJson(currentValue, presentNow);
3193
+ originalFields[field] = next;
3194
+ if (priorOriginal !== void 0 && !sameOwned(next, priorOriginal)) {
3195
+ notes.push(`${HARNESS_CONFIG_PATH}: you changed ${field} \u2014 ainecto now treats your value as the one to put back.`);
3196
+ }
3197
+ }
3198
+ const mergedConfig = serialize({ ...theirConfig ?? {}, ...ourFields });
3199
+ record.files[HARNESS_CONFIG_PATH] = {
3200
+ scope: "fields",
3201
+ createdByUs: priorConfig?.createdByUs ?? existingConfig === void 0,
3202
+ original: ABSENT,
3203
+ written: ownedText(mergedConfig),
3204
+ fileFingerprint: fingerprintFor(HARNESS_CONFIG_PATH, mergedConfig),
3205
+ writtenFields: { ...ourFields },
3206
+ originalFields
3207
+ };
3208
+ if (mergedConfig !== existingConfig) {
3209
+ writes.push({
3210
+ path: HARNESS_CONFIG_PATH,
3211
+ content: mergedConfig,
3212
+ existed: existingConfig !== void 0
3213
+ });
3214
+ }
3215
+ }
3216
+ const existingRecord = input.files.get(HARNESS_RECORD_PATH);
3217
+ const recordContent = serializeRecord(record);
3218
+ if (recordContent !== existingRecord) {
3219
+ writes.push({
3220
+ path: HARNESS_RECORD_PATH,
3221
+ content: recordContent,
3222
+ existed: existingRecord !== void 0
3223
+ });
3224
+ }
3225
+ return {
3226
+ writes,
3227
+ deletes: [],
3228
+ codexCommand: codexAddCommand({ role: input.role, env: input.env, endpoint: input.endpoint }),
3229
+ codexProfile: codexProfileSetup({ role: input.role, env: input.env, endpoint: input.endpoint }),
3230
+ notes,
3231
+ record
3232
+ };
3233
+ }
3234
+ function planUndo(input) {
3235
+ const state = input.record ?? readRecord(input.files.get(HARNESS_RECORD_PATH));
3236
+ const writes = [];
3237
+ const deletes = [];
3238
+ const notes = [];
3239
+ const tail = () => {
3240
+ notes.push(`Codex keeps its own global config \u2014 remove it with: ${codexRemoveCommand()}`);
3241
+ notes.push(`If you set ${ROLE_ENV_VAR} (or the older ${LEGACY_ROLE_ENV_VAR}) anywhere, unset it too.`);
3242
+ };
3243
+ if (state.state === "absent") {
3244
+ notes.push(legacyManagedRecord(input.files.get(HARNESS_CONFIG_PATH)) ? legacyManagedNotice(HARNESS_CONFIG_PATH) : `No record of a previous init in ${HARNESS_RECORD_PATH} \u2014 no local file was changed.`);
3245
+ tail();
3246
+ return { writes, deletes, codexCommand: codexRemoveCommand(), notes };
3247
+ }
3248
+ if (state.state !== "valid") {
3249
+ notes.push(
3250
+ `${state.reason}. Nothing was changed \u2014 that file is kept because it may hold backups --undo still needs. Restore it, or finish the undo with the CLI version that wrote it.`
3251
+ );
3252
+ tail();
3253
+ return { writes, deletes, codexCommand: codexRemoveCommand(), notes };
3254
+ }
3255
+ const record = state.record;
3256
+ const remaining = { ...record.files };
3257
+ for (const [path, contract] of Object.entries(record.files)) {
3258
+ const outcome = undoOne(path, contract, input.files.get(path));
3259
+ if (outcome.kind !== "unresolved" && outcome.kind !== "keep" && outcome.note !== void 0) {
3260
+ notes.push(outcome.note);
3261
+ } else if (outcome.kind === "unresolved" || outcome.kind === "keep") {
3262
+ notes.push(outcome.note);
3263
+ }
3264
+ if (outcome.kind === "unresolved") {
3265
+ continue;
3266
+ }
3267
+ delete remaining[path];
3268
+ if (outcome.kind === "write") {
3269
+ writes.push({ path, content: outcome.content, existed: true });
3270
+ } else if (outcome.kind === "delete") {
3271
+ deletes.push(path);
3272
+ }
3273
+ }
3274
+ tail();
3275
+ if (input.files.get(HARNESS_RECORD_PATH) !== void 0) {
3276
+ if (Object.keys(remaining).length > 0) {
3277
+ const reduced = { ...record, files: remaining };
3278
+ writes.push({ path: HARNESS_RECORD_PATH, content: serializeRecord(reduced), existed: true });
3279
+ notes.push(
3280
+ `${HARNESS_RECORD_PATH}: kept, with the finished items removed \u2014 it still holds the backups --undo needs. Run --undo again once the items above are resolved.`
3281
+ );
3282
+ } else {
3283
+ deletes.push(HARNESS_RECORD_PATH);
3284
+ }
3285
+ }
3286
+ const removableDirectories = parentsOf(deletes).filter((directory) => record.createdDirectories.includes(directory));
3287
+ return { writes, deletes, codexCommand: codexRemoveCommand(), notes, removableDirectories };
3288
+ }
3289
+ function unexplainedMarkers(existing, contract) {
3290
+ const recorded = [...contract.foreignBlocks ?? []];
3291
+ let unexplained = 0;
3292
+ for (const print of markerEvidence(existing)) {
3293
+ const at = recorded.indexOf(print);
3294
+ if (at >= 0) {
3295
+ recorded.splice(at, 1);
3296
+ } else {
3297
+ unexplained += 1;
3298
+ }
3299
+ }
3300
+ return unexplained;
3301
+ }
3302
+ function undoOne(path, contract, existing) {
3303
+ if (existing === void 0) {
3304
+ if (contract.original.at !== "absent" || contract.scope === "fields") {
3305
+ return {
3306
+ kind: "unresolved",
3307
+ note: `${path} is gone, so what was there before init could not be put back \u2014 the backup is kept. Restore the file and run --undo again.`
3308
+ };
3309
+ }
3310
+ return { kind: "done" };
3311
+ }
3312
+ switch (contract.scope) {
3313
+ case "entry":
3314
+ return undoEntry(path, contract, existing);
3315
+ case "block":
3316
+ return undoBlock(path, contract, existing);
3317
+ case "fields":
3318
+ return undoFields(path, contract, existing);
3319
+ case "whole":
3320
+ return undoWhole(path, contract, existing);
3321
+ }
3322
+ }
3323
+ function undoEntry(path, contract, existing) {
3324
+ const current = readServerEntry(existing);
3325
+ const currentOwned = ownedJson(current, current !== void 0);
3326
+ if (sameOwned(currentOwned, contract.original)) {
3327
+ return {
3328
+ kind: "done",
3329
+ note: contract.original.at === "absent" ? `${path}: the "${SERVER_NAME}" entry ainecto added is already gone.` : `${path}: your original "${SERVER_NAME}" entry is already back in place.`
3330
+ };
3331
+ }
3332
+ const goneButWeHoldTheOriginal = current === void 0 && contract.original.at !== "absent";
3333
+ if (!goneButWeHoldTheOriginal && !sameOwned(currentOwned, contract.written)) {
3334
+ return {
3335
+ kind: "unresolved",
3336
+ note: `${path}: the "${SERVER_NAME}" entry was edited after init \u2014 left in place. Remove it by hand if you meant to, then run --undo again.`
3337
+ };
3338
+ }
3339
+ const restore = contract.original.at === "json" ? { value: contract.original.value } : void 0;
3340
+ const result = removeServerEntry(existing, restore);
3341
+ if (result.emptied && contract.createdByUs) {
3342
+ return { kind: "delete" };
3343
+ }
3344
+ if (result.content !== void 0 && result.content !== existing) {
3345
+ return {
3346
+ kind: "write",
3347
+ content: result.content,
3348
+ note: restore === void 0 ? void 0 : `${path}: restored your original "${SERVER_NAME}" entry.`
3349
+ };
3350
+ }
3351
+ return { kind: "done" };
3352
+ }
3353
+ function undoBlock(path, contract, existing) {
3354
+ const current = existingMarkerBlock(existing);
3355
+ const currentOwned = ownedText(current);
3356
+ if (unexplainedMarkers(existing, contract) > 0) {
3357
+ return {
3358
+ kind: "unresolved",
3359
+ note: `${path}: this file has an ainecto marker block that --undo cannot account for (inside a code fence, a quote, or edited). Remove it by hand, then run --undo again.`
3360
+ };
3361
+ }
3362
+ if (sameOwned(currentOwned, contract.original)) {
3363
+ return {
3364
+ kind: "done",
3365
+ note: contract.original.at === "absent" ? `${path}: the ainecto block is already gone.` : `${path}: the ainecto block that was there before is already back in place.`
3366
+ };
3367
+ }
3368
+ if (current === void 0 && contract.original.at === "text") {
3369
+ const body = existing.trim() ? `${existing.replace(/\s*$/, "")}
3370
+
3371
+ ${contract.original.text}
3372
+ ` : `${contract.original.text}
3373
+ `;
3374
+ return { kind: "write", content: body, note: `${path}: restored the ainecto block that was there before.` };
3375
+ }
3376
+ if (!sameOwned(currentOwned, contract.written)) {
3377
+ return {
3378
+ kind: "unresolved",
3379
+ note: `${path}: the ainecto block was edited after init \u2014 left in place. Remove the block by hand if you meant to, then run --undo again.`
3380
+ };
3381
+ }
3382
+ let result;
3383
+ try {
3384
+ result = contract.original.at === "text" ? { content: replaceMarkerBlockWith(existing, contract.original.text), removed: true, emptied: false } : removeMarkerBlock(existing);
3385
+ } catch (error) {
3386
+ return { kind: "unresolved", note: `${path}: ${error instanceof Error ? error.message : String(error)}` };
3387
+ }
3388
+ if (!result.removed) {
3389
+ return {
3390
+ kind: "unresolved",
3391
+ note: `${path}: the ainecto block could not be removed \u2014 it is still in the file. Remove it by hand, then run --undo again.`
3392
+ };
3393
+ }
3394
+ if (result.emptied && contract.createdByUs) {
3395
+ return { kind: "delete" };
3396
+ }
3397
+ return {
3398
+ kind: "write",
3399
+ content: result.content,
3400
+ note: contract.original.at === "text" ? `${path}: restored the ainecto block that was there before.` : void 0
3401
+ };
3402
+ }
3403
+ function undoWhole(path, contract, existing) {
3404
+ if (sameOwned(ownedText(existing), contract.original)) {
3405
+ return { kind: "done", note: `${path} is already the file that existed before init.` };
3406
+ }
3407
+ if (!sameOwned(ownedText(existing), contract.written)) {
3408
+ if (contract.original.at === "text") {
3409
+ return {
3410
+ kind: "unresolved",
3411
+ note: `${path} was edited after init \u2014 left in place instead of restoring the pre-init file. The backup is kept; run --undo again once you have resolved it.`
3412
+ };
3413
+ }
3414
+ return { kind: "keep", note: `${path} was edited after init \u2014 left in place instead of deleting it.` };
3415
+ }
3416
+ if (contract.original.at === "text") {
3417
+ return {
3418
+ kind: "write",
3419
+ content: contract.original.text,
3420
+ note: `${path}: restored the file that existed before init.`
3421
+ };
3422
+ }
3423
+ return contract.createdByUs ? { kind: "delete" } : { kind: "done" };
3424
+ }
3425
+ function undoFields(path, contract, existing) {
3426
+ const parsed = parseJsonObject(existing);
3427
+ if (parsed === void 0) {
3428
+ return {
3429
+ kind: "unresolved",
3430
+ note: `${path} is not a JSON object any more, so ainecto could not take its fields back out \u2014 the backup is kept. Fix the file and run --undo again.`
3431
+ };
3432
+ }
3433
+ const contractProblem = fieldsContractProblem(contract, OUR_CONFIG_FIELDS);
3434
+ if (contractProblem !== void 0) {
3435
+ return {
3436
+ kind: "unresolved",
3437
+ note: `${path}: ainecto's record of the fields it wrote ${contractProblem}, so its fields were left in place \u2014 the backup is kept. Restore that record, or remove the fields by hand.`
3438
+ };
3439
+ }
3440
+ const written = contract.writtenFields;
3441
+ const originals = contract.originalFields;
3442
+ const next = {};
3443
+ const kept = [];
3444
+ for (const [field, value] of Object.entries(parsed)) {
3445
+ if (!OUR_CONFIG_FIELDS.includes(field)) {
3446
+ next[field] = value;
3447
+ continue;
3448
+ }
3449
+ if (!hasField(written, field)) {
3450
+ return {
3451
+ kind: "unresolved",
3452
+ note: `${path}: ainecto has no record of what it wrote for ${field}, so its fields were left in place \u2014 the backup is kept. Restore that record, or remove the field by hand.`
3453
+ };
3454
+ }
3455
+ const stillOurs = stableStringify2(value) === stableStringify2(written[field]);
3456
+ if (!stillOurs) {
3457
+ next[field] = value;
3458
+ kept.push(field);
3459
+ continue;
3460
+ }
3461
+ const original = originals[field];
3462
+ if (original === void 0 || original.at !== "json" && original.at !== "absent") {
3463
+ return {
3464
+ kind: "unresolved",
3465
+ note: `${path}: ainecto cannot tell what was in ${field} before it wrote there, so the file was left as it is \u2014 the backup is kept. Remove the field by hand if you meant to.`
3466
+ };
3467
+ }
3468
+ if (original.at === "json") {
3469
+ next[field] = original.value;
3470
+ }
3471
+ }
3472
+ const note = kept.length === 0 ? void 0 : `${path}: you changed ${kept.join(", ")} after init \u2014 left as you set it instead of putting the pre-init value back.`;
3473
+ if (Object.keys(next).length === 0 && contract.createdByUs) {
3474
+ return { kind: "delete", note };
3475
+ }
3476
+ const body = serialize(next);
3477
+ return body === existing ? { kind: "done", note } : {
3478
+ kind: "write",
3479
+ content: body,
3480
+ note: note ?? `${path}: removed what ainecto wrote and kept the rest.`
3481
+ };
3482
+ }
3483
+ function withCreatedDirectories(current, directories) {
3484
+ const state = readRecord(current);
3485
+ if (state.state !== "valid") {
3486
+ return current;
3487
+ }
3488
+ return serializeRecord({ ...state.record, createdDirectories: [...directories] });
3489
+ }
3490
+ function parentsOf(paths) {
3491
+ const directories = /* @__PURE__ */ new Set();
3492
+ for (const path of paths) {
3493
+ const parent = path.includes("/") ? path.slice(0, path.lastIndexOf("/")) : "";
3494
+ if (parent) {
3495
+ directories.add(parent);
3496
+ }
3497
+ }
3498
+ return [...directories].sort((left, right) => right.length - left.length);
3499
+ }
3500
+ function previousRole(path, existing) {
3501
+ const read = readConfigRole(path, existing);
3502
+ return read.kind === "role" ? read.role : void 0;
3503
+ }
3504
+
3505
+ // src/core/harness/legacyLayout.ts
3506
+ var LEGACY_PATHS = new Map(
3507
+ [HARNESS_DOC_PATH, HARNESS_CONFIG_PATH, HARNESS_RECORD_PATH].map((path) => [path, toLegacy(path)])
3508
+ );
3509
+ var LEGACY_RECORD_PATH = toLegacy(HARNESS_RECORD_PATH);
3510
+ var LEGACY_SNAPSHOT_PATHS = [...LEGACY_PATHS.values()];
3511
+ function adoptLegacyLayout(disk) {
3512
+ const files = /* @__PURE__ */ new Map();
3513
+ for (const [path, content] of disk) {
3514
+ if (!LEGACY_SNAPSHOT_PATHS.includes(path)) {
3515
+ files.set(path, content);
3516
+ }
3517
+ }
3518
+ const view = { files, moves: [], legacyDirCreatedByUs: false, notes: [] };
3519
+ if (!LEGACY_SNAPSHOT_PATHS.some((path) => disk.get(path) !== void 0)) {
3520
+ return view;
3521
+ }
3522
+ const legacyRecord = disk.get(LEGACY_RECORD_PATH);
3523
+ if (legacyRecord === void 0) {
3524
+ const legacyConfig = LEGACY_PATHS.get(HARNESS_CONFIG_PATH);
3525
+ if (disk.get(HARNESS_RECORD_PATH) === void 0 && legacyManagedRecord(disk.get(legacyConfig))) {
3526
+ return { ...view, refusal: legacyManagedNotice(legacyConfig) };
3527
+ }
3528
+ view.notes.push(
3529
+ `${LEGACY_HARNESS_DIR}/ was left in place \u2014 there is no ${LEGACY_RECORD_PATH} showing that an earlier version of this CLI wrote it. Remove it by hand if it is a leftover.`
3530
+ );
3531
+ return view;
3532
+ }
3533
+ const state = readRecord(legacyRecord, LEGACY_RECORD_PATH);
3534
+ if (state.state !== "valid") {
3535
+ return {
3536
+ ...view,
3537
+ refusal: state.state === "absent" ? `${LEGACY_RECORD_PATH} could not be read. Nothing was changed.` : `${state.reason}. Nothing was changed \u2014 move that file aside (or restore it) and run again.`
3538
+ };
3539
+ }
3540
+ const clash = [...LEGACY_PATHS].filter(([to, from]) => disk.get(to) !== void 0 && disk.get(from) !== void 0).map(([to]) => to);
3541
+ if (clash.length > 0) {
3542
+ return {
3543
+ ...view,
3544
+ refusal: `Both ${LEGACY_HARNESS_DIR}/ and ${HARNESS_DIR}/ hold AiNecto setup files (${clash.join(", ")}): an older version of this CLI (0.4.x) ran here after this one, or a move was interrupted. Nothing was changed. Undo the older setup with \`npx -y -p @ai-erd/mcp@0.4.6 ai-erd init --undo\` (or remove the ${LEGACY_HARNESS_DIR}/ leftovers by hand), then run again.`
3545
+ };
3546
+ }
3547
+ const record = state.record;
3548
+ const renamed = {
3549
+ ...record,
3550
+ files: Object.fromEntries(Object.entries(record.files).map(([path, contract]) => [toCurrent(path), contract])),
3551
+ // «하네스 폴더를 우리가 만들었다»는 같은 사실이다 — 이름만 옮긴다. 새 폴더를 이번에 만들면 적용이 또 적는다.
3552
+ createdDirectories: record.createdDirectories.map(toCurrent)
3553
+ };
3554
+ const moves = [];
3555
+ for (const [to, from] of LEGACY_PATHS) {
3556
+ const content = disk.get(from);
3557
+ if (content === void 0) {
3558
+ continue;
3559
+ }
3560
+ files.set(to, to === HARNESS_RECORD_PATH ? serializeRecord(renamed) : content);
3561
+ moves.push({ from, to });
3562
+ }
3563
+ return {
3564
+ files,
3565
+ moves,
3566
+ legacyDirCreatedByUs: record.createdDirectories.includes(LEGACY_HARNESS_DIR),
3567
+ notes: [`${LEGACY_HARNESS_DIR}/ \u2192 ${HARNESS_DIR}/: moved what an earlier version of this CLI wrote (the folder was renamed).`]
3568
+ };
3569
+ }
3570
+ function withLegacyMoves(plan, view) {
3571
+ if (view.moves.length === 0) {
3572
+ return view.notes.length === 0 ? plan : { ...plan, notes: [...view.notes, ...plan.notes] };
3573
+ }
3574
+ const movedTo = new Set(view.moves.map((move) => move.to));
3575
+ const planned = /* @__PURE__ */ new Set([...plan.writes.map((write) => write.path), ...plan.deletes]);
3576
+ const carried = view.moves.filter((move) => !planned.has(move.to)).map((move) => ({ path: move.to, content: view.files.get(move.to), existed: false }));
3577
+ return {
3578
+ ...plan,
3579
+ // 새 경로는 디스크에 아직 없다 — «만든다»가 사실이다.
3580
+ writes: [...plan.writes.map((write) => movedTo.has(write.path) ? { ...write, existed: false } : write), ...carried],
3581
+ // 새 경로의 삭제는 디스크에 없는 파일이다 — 대신 옛 파일을 지운다.
3582
+ deletes: [...plan.deletes.filter((path) => !movedTo.has(path)), ...view.moves.map((move) => move.from)],
3583
+ notes: [...view.notes, ...plan.notes],
3584
+ removableDirectories: view.legacyDirCreatedByUs ? [...plan.removableDirectories ?? [], LEGACY_HARNESS_DIR] : plan.removableDirectories
3585
+ };
3586
+ }
3587
+ function toLegacy(path) {
3588
+ return `${LEGACY_HARNESS_DIR}${path.slice(HARNESS_DIR.length)}`;
3589
+ }
3590
+ function toCurrent(path) {
3591
+ return path === LEGACY_HARNESS_DIR || path.startsWith(`${LEGACY_HARNESS_DIR}/`) ? `${HARNESS_DIR}${path.slice(LEGACY_HARNESS_DIR.length)}` : path;
3592
+ }
3593
+
3594
+ // src/core/harness/documentFetch.ts
3595
+ var DOC_KEY2 = "HARNESS.DOC";
3596
+ var AGENT_NOTE_KEY2 = "HARNESS.AGENT_NOTE";
3597
+ async function fetchHarnessDocuments(input) {
3598
+ const url = new URL(`${input.endpoint.replace(/\/$/, "")}/harness/documents`);
3599
+ assertTrustedEndpointUrl(url, "harness documents URL");
3600
+ if (input.client) {
3601
+ url.searchParams.set("client", input.client);
3602
+ }
3603
+ const fetchImpl = input.fetchImpl ?? fetch;
3604
+ try {
3605
+ const response = await fetchImpl(url.toString(), {
3606
+ headers: {
3607
+ accept: "application/json",
3608
+ ...input.accessToken ? { authorization: `Bearer ${input.accessToken}` } : {}
3609
+ }
3610
+ });
3611
+ if (!response.ok) {
3612
+ return { versions: {}, fallbacks: {}, unavailableReason: `server answered HTTP ${response.status}` };
3613
+ }
3614
+ const payload = await response.json();
3615
+ const documents = payload.documents ?? {};
3616
+ const versions = {};
3617
+ const fallbacks = {};
3618
+ const bodies = {};
3619
+ for (const key of [DOC_KEY2, AGENT_NOTE_KEY2]) {
3620
+ const entry = documents[key];
3621
+ const problem = usableBodyProblem(key, entry);
3622
+ if (problem) {
3623
+ fallbacks[key] = typeof entry?.reason === "string" && entry.reason ? entry.reason : problem;
3624
+ continue;
3625
+ }
3626
+ bodies[key] = entry.body;
3627
+ if (typeof entry?.version === "string") {
3628
+ versions[key] = entry.version;
3629
+ }
3630
+ }
3631
+ return { doc: bodies[DOC_KEY2], agentNote: bodies[AGENT_NOTE_KEY2], versions, fallbacks };
3632
+ } catch (error) {
3633
+ return {
3634
+ versions: {},
3635
+ fallbacks: {},
3636
+ unavailableReason: error instanceof Error ? error.message : String(error)
3637
+ };
3638
+ }
3639
+ }
3640
+ function usableBodyProblem(key, entry) {
3641
+ if (typeof entry?.body !== "string" || !entry.body.trim()) {
3642
+ return `server returned no body for ${key}`;
3643
+ }
3644
+ return documentContractProblem(key, entry.body);
3645
+ }
3646
+
3647
+ // src/adapters/cli/prompter.ts
3648
+ import { createInterface as createInterface3 } from "readline";
3649
+ var PromptCancelled = class extends Error {
3650
+ constructor() {
3651
+ super("Cancelled \u2014 no repository files were written. A sign-in you already completed stays saved.");
3652
+ this.name = "PromptCancelled";
3653
+ }
3654
+ };
3655
+ var AGENT_ENV_MARKERS = ["CLAUDECODE", "CI", "GEMINI_CLI", "CODEX_SANDBOX", "CURSOR_AGENT"];
3656
+ function agentEnvironment(env = process.env) {
3657
+ return AGENT_ENV_MARKERS.find((name) => {
3658
+ const value = env[name]?.trim().toLowerCase();
3659
+ return value !== void 0 && value !== "" && value !== "0" && value !== "false";
3660
+ });
3661
+ }
3662
+ function isInteractive(io, json, env = process.env) {
3663
+ return io.stdin?.isTTY === true && io.stdout?.isTTY === true && !json && agentEnvironment(env) === void 0;
3664
+ }
3665
+ function createReadlinePrompter(io) {
3666
+ let rl;
3667
+ let lines;
3668
+ const readLine = async (prompt) => {
3669
+ io.stderr.write(prompt);
3670
+ if (!rl) {
3671
+ rl = createInterface3({ input: io.stdin, terminal: false });
3672
+ lines = rl[Symbol.asyncIterator]();
3673
+ }
3674
+ const next = await lines.next();
3675
+ if (next.done) {
3676
+ io.stderr.write("\n");
3677
+ throw new PromptCancelled();
3678
+ }
3679
+ return next.value;
3680
+ };
3681
+ return {
3682
+ choose: (question, choices, defaultIndex) => chooseWith(readLine, (text) => io.stderr.write(text), question, choices, defaultIndex),
3683
+ text: async (question, defaultValue) => {
3684
+ const answer = (await readLine(`${question} [${defaultValue}]: `)).trim();
3685
+ return answer || defaultValue;
3686
+ },
3687
+ close: () => {
3688
+ rl?.close();
3689
+ }
3690
+ };
3691
+ }
3692
+ async function chooseWith(readLine, write, question, choices, defaultIndex) {
3693
+ if (choices.length === 0) {
3694
+ throw new Error(`Nothing to choose for: ${question}`);
3695
+ }
3696
+ write(`${question}
3697
+ `);
3698
+ choices.forEach((choice, index) => write(` [${index + 1}] ${choice.label}
3699
+ `));
3700
+ const hint = defaultIndex === void 0 ? "" : ` [${defaultIndex + 1}]`;
3701
+ for (; ; ) {
3702
+ const answer = (await readLine(`Enter a number (1-${choices.length})${hint}: `)).trim();
3703
+ if (answer === "" && defaultIndex !== void 0) {
3704
+ return choices[defaultIndex].value;
3705
+ }
3706
+ const picked = /^\d+$/.test(answer) ? Number(answer) : NaN;
3707
+ if (picked >= 1 && picked <= choices.length) {
3708
+ return choices[picked - 1].value;
3709
+ }
3710
+ write(answer === "" ? "Please choose one \u2014 there is no default here.\n" : `"${answer}" is not one of the numbers above.
3711
+ `);
3712
+ }
3713
+ }
3714
+
3715
+ // src/adapters/cli/initCommand.ts
3716
+ function singleRole(roles) {
3717
+ return roles.length === 1 ? roles[0] : void 0;
3718
+ }
3719
+ async function executeInitCommand(options) {
3720
+ try {
3721
+ return await runInit(options);
3722
+ } catch (error) {
3723
+ if (error instanceof PromptCancelled) {
3724
+ options.io.stderr.write(`${error.message}
3725
+ `);
3726
+ return 130;
3727
+ }
3728
+ throw error;
3729
+ } finally {
3730
+ options.prompter?.close();
3731
+ }
3732
+ }
3733
+ async function runInit(initialOptions) {
3734
+ let options = initialOptions;
3735
+ const args = parseInitArgs(options.argv);
3736
+ const root = resolve2(options.cwd);
3737
+ const rootNotes = await repositoryRootNotes(root);
3738
+ const disk = await readSnapshot(root);
3739
+ const layout = adoptLegacyLayout(disk);
3740
+ const files = layout.files;
3741
+ if (args.undo) {
3742
+ if (layout.refusal !== void 0) {
3743
+ options.io.stdout.write(renderSuccess(
3744
+ { action: args.dryRun ? "undo (dry-run)" : "undo", updated: [], removed: [], notes: [...rootNotes, layout.refusal] },
3745
+ { json: options.json }
3746
+ ));
3747
+ return 0;
3748
+ }
3749
+ const plan2 = withLegacyMoves(planUndo({ files }), layout);
3750
+ await applyPlan(root, plan2, args.dryRun, disk, false);
3751
+ options.io.stdout.write(renderSuccess(
3752
+ {
3753
+ action: args.dryRun ? "undo (dry-run)" : "undo",
3754
+ updated: plan2.writes.map((write) => write.path),
3755
+ removed: plan2.deletes,
3756
+ notes: [...rootNotes, ...plan2.notes]
3757
+ },
3758
+ { json: options.json }
3759
+ ));
3760
+ return 0;
3761
+ }
3762
+ const refusal = layout.refusal ?? initRefusal(files);
3763
+ if (refusal !== void 0) {
3764
+ throw new Error(refusal);
3765
+ }
3766
+ const prompter = options.prompter;
3767
+ const role = options.role ?? (prompter ? await askRole(prompter, singleRole(rolesInFiles(files).roles)) : currentRole(files));
3768
+ const previousRole2 = (() => {
3769
+ try {
3770
+ return currentRole(files);
3771
+ } catch {
3772
+ return void 0;
3773
+ }
3774
+ })();
3775
+ if (!role) {
3776
+ throw new Error(
3777
+ `No role given. Ask the user which role this repository's AI sessions should have (${HARNESS_ROLES.join(", ")}), then re-run with --role <role>.`
3778
+ );
3779
+ }
3780
+ const connected = connectAs(options, role);
3781
+ options = connected;
3782
+ await ensureSignedIn(connected, role, args.dryRun);
3783
+ const project = await resolveProject(connected, args, files, root, role);
3784
+ if ("choices" in project) {
3785
+ options.io.stdout.write(renderSuccess(
3786
+ {
3787
+ message: "More than one project found. Re-run with --project <uuid>.",
3788
+ projects: project.choices
3789
+ },
3790
+ { json: options.json }
3791
+ ));
3792
+ return 1;
3793
+ }
3794
+ const documents = args.dryRun ? { versions: {}, fallbacks: {}, unavailableReason: "skipped in --dry-run" } : await fetchHarnessDocuments({
3795
+ endpoint: options.endpoint,
3796
+ accessToken: await options.accessToken?.(),
3797
+ // ⛔이 이름은 바꾸지 않는다 — 서버가 문서의 클라이언트별 변형 키(HARNESS.DOC.AI_ERD_CLI 등)를 이것으로
3798
+ // 찾는다. 사람에게 안 보이는 서버 쪽 식별자다(역할 헤더·scope 와 같은 이유, 2026-10-11).
3799
+ client: "ai-erd-cli",
3800
+ fetchImpl: options.fetchImpl
3801
+ });
3802
+ const plan = withLegacyMoves(planInit({
3803
+ role,
3804
+ env: options.env,
3805
+ projectName: project.name,
3806
+ projectUuid: project.uuid,
3807
+ endpoint: options.endpoint,
3808
+ cliVersion: options.cliVersion,
3809
+ files,
3810
+ documents
3811
+ }), layout);
3812
+ if (plan.refusal !== void 0) {
3813
+ throw new Error(plan.refusal);
3814
+ }
3815
+ await applyPlan(root, plan, args.dryRun, disk);
3816
+ if (!args.dryRun && previousRole2 !== void 0 && previousRole2 !== role) {
3817
+ options.io.stderr.write(`Role changes from ${previousRole2} to ${role}: \`ainecto\` commands now, MCP tools from a new session.
3818
+ `);
3819
+ }
3820
+ options.io.stdout.write(renderSuccess(
3821
+ {
3822
+ action: args.dryRun ? "init (dry-run)" : "init",
3823
+ role,
3824
+ project: { uuid: project.uuid, name: project.name },
3825
+ endpoint: options.endpoint,
3826
+ written: plan.writes.map((write) => `${write.existed ? "updated" : "created"} ${write.path}`),
3827
+ // 옛 폴더(.ai-erd/)를 옮길 때만 생긴다 — 지운 것을 말하지 않으면 사용자는 옛 파일이 어디 갔는지 모른다.
3828
+ ...plan.deletes.length > 0 ? { removed: plan.deletes } : {},
3829
+ codex: `Codex keeps a single global config, so init does not touch it. Run: ${plan.codexCommand}`,
3830
+ codexPerSession: plan.codexProfile,
3831
+ codexEnvFallback: `Without a profile, start the session with ${ROLE_ENV_VAR}=${role}.`,
3832
+ // ⚠조용히 기본값으로 떨어지지 않는다 — 서버에서 규칙을 고쳐도 안 바뀌는 이유를
3833
+ // 사용자가 영영 못 찾게 된다. ★요청 자체의 실패와 «문서별» 되돌림을 둘 다 적는다
3834
+ // (2026-09-23 독립 재리뷰 I5: HTTP 200 으로 온 빈 문서가 조용히 패키지 판이 됐다).
3835
+ notes: [...rootNotes, ...plan.notes, ...fallbackNotes(documents)],
3836
+ // ★「Enforce this on the server too: ainecto auth login …」 줄은 뺐다. init 이 이미 그 역할로
3837
+ // 로그인했으므로 참이 아니고, 남아 있으면 에이전트가 따라 쳐서 사람이 한 번 더 승인하게 된다
3838
+ // (2026-09-29 독립 리뷰 P1-1).
3839
+ // ★재시작 없이 이어간다(2026-09-30 사용자 결정). 셸의 `ainecto` 는 부를 때마다 저장소 역할을 읽어
3840
+ // 역할 헤더로 싣는다 — 서버가 요청마다 강제하므로 MCP 연결과 무관하게 이 세션에서 바로 맞다.
3841
+ // 재시작은 «그 역할의 MCP 도구»를 더할 뿐인 선택이다.
3842
+ next: [
3843
+ `Keep working in this session \u2014 no restart is needed. \`ainecto\` commands run from this repository use the ${role} role at once.`,
3844
+ `Read ${HARNESS_DOC_PATH} first, then \`ainecto tools list\` shows the role's tools and \`ainecto tools call <tool> '<json>'\` calls one.`,
3845
+ "Do not use MCP tools this session started with for this work \u2014 they keep the role (or no role) the session started with.",
3846
+ "Optional, to add the role's MCP tools: start a new session.",
3847
+ `Claude Code: exit and run \`claude -c\` in this folder, then approve the "${SERVER_NAME}" server from .mcp.json when asked.`,
3848
+ `Codex: save the profile above as $CODEX_HOME/${role}.config.toml and start with \`codex -p ${role}\`.`,
3849
+ role === "design" ? "To check the role, change a table through `ainecto` \u2014 Design may, and the change lands in AiNecto." : `To check the role, try changing a table through \`ainecto\` \u2014 ${role} is told to stop.`,
3850
+ `If the new Claude Code session still has no role, run \`claude mcp get ${SERVER_NAME}\`: a local-scope "${SERVER_NAME}" entry hides .mcp.json (remove it with \`claude mcp remove ${SERVER_NAME} -s local\`).`,
3851
+ // ★서버 이름이 바뀌었다(0.5.0). 예전에 같은 이름("ai-erd")이라 .mcp.json 이 덮던 사용자·Codex 전역 항목이
3852
+ // 이제는 «따로» 붙어, 그 세션이 역할 없는 서버를 하나 더 본다. 그 항목을 지우는 법을 같이 말한다.
3853
+ `If you added AiNecto earlier as "${LEGACY_SERVER_NAME}" (Claude Code user scope, or Codex), remove it \u2014 it no longer hides behind this "${SERVER_NAME}" entry: \`claude mcp remove ${LEGACY_SERVER_NAME} -s user\`, \`codex mcp remove ${LEGACY_SERVER_NAME}\`.`,
3854
+ "Undo everything with: ainecto init --undo"
3855
+ ]
3856
+ },
3857
+ { json: options.json }
3858
+ ));
3859
+ return 0;
3860
+ }
3861
+ function currentRole(files) {
3862
+ const unique = rolesInFiles(files).roles;
3863
+ if (unique.length > 1) {
3864
+ throw new Error(
3865
+ `Agent configs in this repository disagree about the session role (${unique.join(", ")}). Ask the user which role this repository's AI sessions should have (${HARNESS_ROLES.join(", ")}), then re-run with --role <role> to line them up.`
3866
+ );
3867
+ }
3868
+ return unique[0];
3869
+ }
3870
+ function fallbackNotes(documents) {
3871
+ const notes = [];
3872
+ if (documents.unavailableReason) {
3873
+ notes.push(`Harness rules came from this package, not the server (${documents.unavailableReason}).`);
3874
+ }
3875
+ for (const [key, reason] of Object.entries(documents.fallbacks)) {
3876
+ notes.push(`${key} came from this package, not the server (${reason}).`);
3877
+ }
3878
+ return notes;
3879
+ }
3880
+ async function ensureSignedIn(options, role, dryRun) {
3881
+ if (options.login === void 0) {
3882
+ return;
3883
+ }
3884
+ if (await options.accessToken?.()) {
3885
+ return;
3886
+ }
3887
+ if (dryRun) {
3888
+ throw new Error(
3889
+ `No usable AiNecto sign-in on this machine, and --dry-run neither refreshes it nor opens a browser.
3890
+
3891
+ ${loginCommand(options)}
3892
+
3893
+ Then run init again.`
3894
+ );
3895
+ }
3896
+ await options.login();
3897
+ options.io.stderr.write("Signed in.\n");
3898
+ }
3899
+ async function repositoryRootNotes(cwd) {
3900
+ const gitRoot = await findGitRoot(cwd);
3901
+ if (gitRoot === void 0) {
3902
+ return [`${cwd} is not inside a git repository, so this folder was used as the repository root.`];
3903
+ }
3904
+ if (gitRoot !== cwd) {
3905
+ throw new Error(
3906
+ `This folder is inside the git repository at ${gitRoot}, not at its root. Agents read their MCP config from the repository root, so run init there:
3907
+
3908
+ cd ${gitRoot}
3909
+
3910
+ Nothing was changed.`
3911
+ );
3912
+ }
3913
+ return [];
3914
+ }
3915
+ function connectAs(options, role) {
3916
+ if (options.connect) {
3917
+ const connection = options.connect(role);
3918
+ return { ...options, ...connection };
3919
+ }
3920
+ if (!options.client) {
3921
+ throw new Error("init needs either a client or a way to connect once the role is known.");
3922
+ }
3923
+ return { ...options, client: options.client };
3924
+ }
3925
+ var ROLE_CHOICES = [
3926
+ { label: "Design requirements, ERD, boundaries, tasks", value: "design" },
3927
+ { label: "Development product code, following the approved design", value: "development" },
3928
+ { label: "Test test scenarios and test code", value: "test" },
3929
+ { label: "Validation judges the result; changes nothing", value: "validation" }
3930
+ ];
3931
+ async function askRole(prompter, current) {
3932
+ const index = current === void 0 ? void 0 : ROLE_CHOICES.findIndex((choice) => choice.value === current);
3933
+ return prompter.choose(
3934
+ current === void 0 ? "Which role should AI sessions in this repository have?" : `Which role should AI sessions in this repository have? (now: ${current})`,
3935
+ ROLE_CHOICES,
3936
+ index
3937
+ );
3938
+ }
3939
+ async function askProject(options, args, projects, bound, root, role, prompter) {
3940
+ const canCreate = role === "design" && !args.dryRun;
3941
+ const askNameAndCreate = async () => {
3942
+ const name = await prompter.text("New project name", args.projectName ?? basenameOf(root));
3943
+ return createProject(options, name);
3944
+ };
3945
+ if (projects.length === 0) {
3946
+ if (role !== "design") {
3947
+ throw new Error(noProjectForRole(options, role));
3948
+ }
3949
+ if (args.dryRun) {
3950
+ throw new Error(dryRunCannotCreate(args.projectName ?? basenameOf(root)));
3951
+ }
3952
+ return askNameAndCreate();
3953
+ }
3954
+ const choices = projects.map((project) => ({
3955
+ label: `${project.name} (${project.uuid})`,
3956
+ value: { project }
3957
+ }));
3958
+ if (canCreate) {
3959
+ choices.push({ label: "Create a new project", value: { create: true } });
3960
+ }
3961
+ const boundIndex = bound === void 0 ? -1 : projects.findIndex((project) => project.uuid === bound);
3962
+ const defaultIndex = boundIndex >= 0 ? boundIndex : projects.length === 1 ? 0 : void 0;
3963
+ const picked = await prompter.choose("Which AiNecto project should this repository use?", choices, defaultIndex);
3964
+ return "create" in picked ? askNameAndCreate() : picked.project;
3965
+ }
3966
+ function noProjectForRole(options, role) {
3967
+ return `No project found in your AiNecto account, and only a Design session can create one. Ask the user: create the project at ${new URL(options.endpoint).origin} or in a Design session, then run init again with --role ${role}.`;
3968
+ }
3969
+ function dryRunCannotCreate(name) {
3970
+ return `--dry-run cannot continue: there is no project to bind, and creating "${name}" would be a real change. Re-run without --dry-run, or pass --project <uuid>.`;
3971
+ }
3972
+ async function resolveProject(options, args, files, root, role) {
3973
+ const projects = await listProjects(options);
3974
+ if (args.projectUuid) {
3975
+ const found = projects.find((project) => project.uuid === args.projectUuid);
3976
+ return found ?? { uuid: args.projectUuid, name: args.projectName ?? args.projectUuid };
3977
+ }
3978
+ const bound = boundProjectUuid(files.get(HARNESS_CONFIG_PATH));
3979
+ if (options.prompter) {
3980
+ return askProject(options, args, projects, bound, root, role, options.prompter);
3981
+ }
3982
+ if (bound) {
3983
+ const found = projects.find((project) => project.uuid === bound);
3984
+ if (found) {
3985
+ return found;
3986
+ }
3987
+ }
3988
+ if (projects.length === 1) {
3989
+ return projects[0];
3990
+ }
3991
+ if (projects.length > 1) {
3992
+ return { choices: projects };
3993
+ }
3994
+ if (role !== "design") {
3995
+ throw new Error(noProjectForRole(options, role));
3996
+ }
3997
+ const name = args.projectName ?? basenameOf(root);
3998
+ if (!args.yes) {
3999
+ throw new Error(
4000
+ `No project found in your AiNecto account. Re-run with --yes to create "${name}", or --project-name <name> --yes to pick the name.`
4001
+ );
4002
+ }
4003
+ if (args.dryRun) {
4004
+ throw new Error(dryRunCannotCreate(name));
4005
+ }
4006
+ return createProject(options, name);
4007
+ }
4008
+ async function listProjects(options) {
4009
+ const result = await callOrExplainSignIn(options, () => options.client.toolsCall("list_projects", {}));
4010
+ return extractProjects(unwrapToolJson(result));
4011
+ }
4012
+ async function callOrExplainSignIn(options, run) {
4013
+ try {
4014
+ return await run();
4015
+ } catch (error) {
4016
+ if (!isUnauthorized(error)) {
4017
+ throw error;
4018
+ }
4019
+ throw new Error(
4020
+ `Not signed in, so ${options.endpoint} refused the request (HTTP 401).
4021
+
4022
+ ${loginCommand(options)}
4023
+
4024
+ Then run init again. Sign-in is one per machine; the role comes from this repository.`
4025
+ );
4026
+ }
4027
+ }
4028
+ function loginCommand(options) {
4029
+ const target = isDefaultEndpointFor(options.env, options.endpoint) ? options.env === "prod" ? "" : ` --env ${options.env}` : ` --endpoint ${options.endpoint}`;
4030
+ return `ainecto auth login${target}`;
4031
+ }
4032
+ function isUnauthorized(error) {
4033
+ return error instanceof McpRpcError && error.code === "MCP_HTTP_ERROR" && error.details?.status === 401;
4034
+ }
4035
+ async function createProject(options, name) {
4036
+ const result = await callOrExplainSignIn(options, () => options.client.toolsCall("create_projects", { items: [{ name }] }));
4037
+ const created = extractProjects(unwrapToolJson(result));
4038
+ const first = created[0];
4039
+ if (!first) {
4040
+ throw new Error("Project creation did not return a project uuid.");
4041
+ }
4042
+ options.io.stderr.write(`Created project ${first.name} (${first.uuid}).
4043
+ `);
4044
+ return first;
4045
+ }
4046
+ function unwrapToolJson(result) {
4047
+ if (!isRecord9(result) || !Array.isArray(result.content)) {
4048
+ return result;
4049
+ }
4050
+ const first = result.content.find((item) => isRecord9(item) && item.type === "text");
4051
+ if (!isRecord9(first) || typeof first.text !== "string") {
4052
+ return result;
4053
+ }
4054
+ try {
4055
+ return JSON.parse(first.text);
4056
+ } catch {
4057
+ return first.text;
4058
+ }
4059
+ }
4060
+ function extractProjects(value) {
4061
+ const candidates = Array.isArray(value) ? value : isRecord9(value) ? ["projects", "items", "created", "data", "hits"].flatMap((key) => {
4062
+ const nested = value[key];
4063
+ return Array.isArray(nested) ? nested : [];
4064
+ }) : [];
4065
+ const projects = [];
4066
+ for (const candidate of candidates) {
4067
+ if (isRecord9(candidate) && typeof candidate.uuid === "string") {
4068
+ projects.push({
4069
+ uuid: candidate.uuid,
4070
+ name: typeof candidate.name === "string" ? candidate.name : candidate.uuid
4071
+ });
4072
+ }
4073
+ }
4074
+ return projects;
4075
+ }
4076
+ function boundProjectUuid(config) {
4077
+ if (!config) {
4078
+ return void 0;
4079
+ }
4080
+ try {
4081
+ const parsed = JSON.parse(config);
4082
+ return typeof parsed.project?.uuid === "string" ? parsed.project.uuid : void 0;
4083
+ } catch {
4084
+ return void 0;
4085
+ }
4086
+ }
4087
+ async function readSnapshot(root) {
4088
+ const snapshot = /* @__PURE__ */ new Map();
4089
+ for (const path of [...MANAGED_PATHS, ...LEGACY_SNAPSHOT_PATHS]) {
4090
+ snapshot.set(path, await readManagedFile(root, path));
4091
+ }
4092
+ return snapshot;
4093
+ }
4094
+ async function applyPlan(root, plan, dryRun, snapshot, recordFirst = true) {
4095
+ for (const write of plan.writes) {
4096
+ await assertInsideRepository(root, write.path);
4097
+ await assertUnchangedSinceSnapshot(root, write.path, snapshot);
4098
+ }
4099
+ for (const path of plan.deletes) {
4100
+ await assertInsideRepository(root, path);
4101
+ await assertUnchangedSinceSnapshot(root, path, snapshot);
4102
+ }
4103
+ if (dryRun) {
4104
+ return;
4105
+ }
4106
+ const applied = [];
4107
+ try {
4108
+ const isRecord10 = (path) => !recordFirst && (path === HARNESS_RECORD_PATH || path === LEGACY_RECORD_PATH);
4109
+ const recordWrites = plan.writes.filter((write) => isRecord10(write.path));
4110
+ const recordDeletes = plan.deletes.filter((path) => isRecord10(path));
4111
+ const createdDirectories = [...plan.record?.createdDirectories ?? []];
4112
+ const rememberDirectories = (directories) => {
4113
+ for (const directory of directories) {
4114
+ if (!createdDirectories.includes(directory)) {
4115
+ createdDirectories.push(directory);
4116
+ }
4117
+ }
4118
+ };
4119
+ const recordActualDirectories = async () => {
4120
+ const write = plan.writes.find((w) => w.path === HARNESS_RECORD_PATH);
4121
+ if (!recordFirst || !plan.record || !write || !applied.includes(HARNESS_RECORD_PATH)) {
4122
+ return;
4123
+ }
4124
+ await writeManagedFile(root, {
4125
+ ...write,
4126
+ content: withCreatedDirectories(write.content, createdDirectories)
4127
+ });
4128
+ };
4129
+ const recordDirectoriesOrExplain = async (failing) => {
4130
+ try {
4131
+ await recordActualDirectories();
4132
+ } catch (error) {
4133
+ if (!failing) {
4134
+ throw error;
4135
+ }
4136
+ }
4137
+ };
4138
+ const apply = async (writes, deletes) => {
4139
+ for (const write of writes) {
4140
+ await assertUnchangedSinceSnapshot(root, write.path, snapshot);
4141
+ await writeManagedFile(root, write, rememberDirectories);
4142
+ applied.push(write.path);
4143
+ }
4144
+ for (const path of deletes) {
4145
+ await assertUnchangedSinceSnapshot(root, path, snapshot);
4146
+ await rm2(join3(root, path), { force: true });
4147
+ applied.push(path);
4148
+ }
4149
+ };
4150
+ try {
4151
+ await apply(
4152
+ orderedWrites(plan.writes.filter((write) => !isRecord10(write.path)), recordFirst),
4153
+ plan.deletes.filter((path) => !isRecord10(path))
4154
+ );
4155
+ await recordDirectoriesOrExplain(false);
4156
+ await apply(recordWrites, recordDeletes);
4157
+ } catch (error) {
4158
+ await recordDirectoriesOrExplain(true);
4159
+ throw error;
4160
+ }
4161
+ } catch (error) {
4162
+ const message = error instanceof Error ? error.message : String(error);
4163
+ throw new Error(
4164
+ `${message}
4165
+ Already applied before this failure: ${applied.join(", ") || "(nothing)"}`
4166
+ );
4167
+ }
4168
+ for (const directory of plan.removableDirectories ?? []) {
4169
+ await rmdir(join3(root, directory)).catch(() => void 0);
4170
+ }
4171
+ }
4172
+ function orderedWrites(writes, recordFirst) {
4173
+ const record = writes.filter((write) => write.path === HARNESS_RECORD_PATH);
4174
+ const rest = writes.filter((write) => write.path !== HARNESS_RECORD_PATH);
4175
+ return recordFirst ? [...record, ...rest] : [...rest, ...record];
4176
+ }
4177
+ async function assertUnchangedSinceSnapshot(root, relativePath, snapshot) {
4178
+ if (!snapshot.has(relativePath)) {
4179
+ return;
4180
+ }
4181
+ const now = await readIfExists(join3(root, relativePath));
4182
+ if (now !== snapshot.get(relativePath)) {
4183
+ throw new Error(
4184
+ `${relativePath} changed while init was running; nothing was written. Run init again.`
4185
+ );
4186
+ }
4187
+ }
4188
+ async function writeManagedFile(root, write, onDirectoriesCreated) {
4189
+ const absolutePath = join3(root, write.path);
4190
+ const targetDirectory = dirname3(absolutePath);
4191
+ const firstCreated = await mkdir2(targetDirectory, { recursive: true });
4192
+ const created = createdChain(root, firstCreated, targetDirectory);
4193
+ onDirectoriesCreated?.(created);
4194
+ const previousMode = await fileMode(absolutePath);
4195
+ const tmpPath = `${absolutePath}.${process.pid}.${Date.now()}.tmp`;
4196
+ await writeFile2(tmpPath, write.content, { encoding: "utf8", mode: previousMode ?? 384 });
4197
+ if (previousMode !== void 0) {
4198
+ await chmod2(tmpPath, previousMode);
4199
+ }
4200
+ await rename2(tmpPath, absolutePath);
4201
+ return created;
4202
+ }
4203
+ function createdChain(root, firstCreated, targetDirectory) {
4204
+ if (firstCreated === void 0) {
4205
+ return [];
4206
+ }
4207
+ const stop = resolve2(firstCreated);
4208
+ const made = [];
4209
+ for (let at = resolve2(targetDirectory); ; at = dirname3(at)) {
4210
+ const asRelative = relative(root, at);
4211
+ if (asRelative && !asRelative.startsWith("..")) {
4212
+ made.push(asRelative.split(sep2).join("/"));
4213
+ }
4214
+ if (at === stop || dirname3(at) === at) {
4215
+ break;
4216
+ }
4217
+ }
4218
+ return made;
4219
+ }
4220
+ async function fileMode(absolutePath) {
4221
+ try {
4222
+ return (await stat3(absolutePath)).mode & 4095;
4223
+ } catch (error) {
4224
+ if (isRecord9(error) && error.code === "ENOENT") {
4225
+ return void 0;
4226
+ }
4227
+ throw error;
4228
+ }
4229
+ }
4230
+ function parseInitArgs(argv) {
4231
+ const args = { dryRun: false, undo: false, yes: false };
4232
+ for (let index = 0; index < argv.length; index += 1) {
4233
+ const arg = argv[index];
4234
+ if (!arg) {
4235
+ continue;
4236
+ }
4237
+ if (arg === "--project") {
4238
+ args.projectUuid = requireValue3(arg, argv[++index]);
4239
+ } else if (arg === "--project-name") {
4240
+ args.projectName = requireValue3(arg, argv[++index]);
4241
+ } else if (arg === "--dry-run") {
4242
+ args.dryRun = true;
4243
+ } else if (arg === "--undo") {
4244
+ args.undo = true;
4245
+ } else if (arg === "--yes" || arg === "-y") {
4246
+ args.yes = true;
4247
+ } else if (arg === "--json") {
4248
+ } else {
4249
+ throw new Error(`Unknown argument: ${arg}`);
4250
+ }
4251
+ }
4252
+ return args;
4253
+ }
4254
+ function requireValue3(flag, value) {
4255
+ if (!value || value.startsWith("--")) {
4256
+ throw new Error(`${flag} requires a value.`);
4257
+ }
4258
+ return value;
4259
+ }
4260
+ function basenameOf(path) {
4261
+ const parts = path.split(/[\\/]/).filter(Boolean);
4262
+ return parts[parts.length - 1] ?? "ainecto-project";
4263
+ }
4264
+ function isRecord9(value) {
4265
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4266
+ }
4267
+
4268
+ // src/adapters/cli/ainectoCli.ts
4269
+ async function runAinectoCli(argv, io) {
4270
+ let parsed = { json: argv.includes("--json"), help: false, version: false, positionals: [] };
4271
+ try {
4272
+ parsed = parseGlobalArgs(argv);
4273
+ if (parsed.version || parsed.positionals[0] === "version") {
4274
+ io.stdout.write(renderSuccess({ version: CLI_VERSION }, { json: parsed.json }));
4275
+ return 0;
4276
+ }
4277
+ if (parsed.help || parsed.positionals.length === 0) {
4278
+ io.stdout.write(helpText());
4279
+ return 0;
4280
+ }
4281
+ const resolved = resolveEndpoint({ env: parsed.env, endpoint: parsed.endpoint });
4282
+ const explicitRole = () => resolveRole({ role: parsed.role });
4283
+ const tokenStore = new FileTokenStore();
4284
+ const connect = (role) => {
4285
+ const auth = new OAuthClient({
4286
+ endpoint: resolved.endpoint,
4287
+ tokenStore,
4288
+ // ★init --dry-run 은 토큰을 갱신하지도 않는다 — 「아무것도 바꾸지 않는다」에 로그인 상태도 든다.
4289
+ // (init 의 플래그는 positional 로 흘러온다.)
4290
+ readOnly: parsed.positionals[0] === "init" && parsed.positionals.includes("--dry-run"),
4291
+ // ★URL 을 «브라우저를 열기 전에» 알린다. stdout 은 결과 전용이라 stderr 에 쓴다.
4292
+ // --json 이면 JSON 한 줄이다(설계 §21-3 (가)). 로그인이 역할과 무관해져 role 칸은 없다(0.4.2 §2-5).
4293
+ onAuthorizeUrl: (url) => {
4294
+ io.stderr.write(parsed.json ? `${JSON.stringify({ event: "login_url", url })}
4295
+ ` : `Opening your browser to sign in to AiNecto. If it does not open, visit:
4296
+ ${url}
4297
+ `);
4298
+ }
4299
+ });
4300
+ const client2 = new McpRpcClient({ endpoint: resolved.endpoint, tokenProvider: auth, role });
4301
+ return { auth, client: client2 };
4302
+ };
4303
+ const [domain, action, ...rest] = parsed.positionals;
4304
+ if (domain === "mcp") {
4305
+ await runConnector({
4306
+ endpoint: resolved.endpoint,
4307
+ // 브리지의 역할은 인자·환경변수에서만 온다 — bin/mcp.ts 와 같은 규칙. 저장소를 적용하지 않는다.
4308
+ role: explicitRole().role ?? null,
4309
+ input: io.stdin,
4310
+ output: io.stdout,
4311
+ errorOutput: io.stderr
4312
+ });
4313
+ return 0;
4314
+ }
4315
+ if (domain === "init") {
4316
+ return await executeInitCommand({
4317
+ argv: parsed.positionals.slice(1),
4318
+ role: explicitRole().role,
4319
+ connect: (role) => {
4320
+ const { auth, client: client2 } = connect(role);
4321
+ return {
4322
+ client: client2,
4323
+ // 문서는 인증 후에 받는다 — 토큰이 없으면 패키지 기본값으로 가고, 그 사실을 보고한다.
4324
+ // ★«필요할 때» 가져온다. --undo 는 로컬 작업이라 토큰을 건드릴 이유가 없다(S1) — undo 는
4325
+ // connect 자체를 부르지 않는다.
4326
+ // ★갱신 실패는 «토큰 없음»(→ 로그인)이지만, AINECTO_TOKEN 을 못 쓰는 주소라는 거절은 삼키지 않는다 —
4327
+ // 삼키면 그 설정 오류가 브라우저 로그인으로 둔갑한다.
4328
+ accessToken: async () => {
4329
+ try {
4330
+ return await auth.getAccessToken();
4331
+ } catch (error) {
4332
+ if (process.env.AINECTO_TOKEN) {
4333
+ throw error;
4334
+ }
4335
+ return void 0;
4336
+ }
4337
+ },
4338
+ // 로그인한다 — `ainecto auth login` 과 같은 함수다. 서버마다 한 번이다(0.4.2).
4339
+ login: async () => {
4340
+ await auth.login();
4341
+ }
4342
+ };
4343
+ },
4344
+ // ★대화형일 때만 묻는다(판정은 isInteractive 한 곳). 에이전트·CI 는 TTY 가 아니다.
4345
+ prompter: isInteractive(io, parsed.json) ? createReadlinePrompter(io) : void 0,
4346
+ endpoint: resolved.endpoint,
4347
+ env: resolved.env,
4348
+ cliVersion: `${PACKAGE_NAME} ${CLI_VERSION}`,
4349
+ cwd: process.cwd(),
4350
+ json: parsed.json,
4351
+ io
4352
+ });
4353
+ }
4354
+ if (domain === "auth") {
4355
+ if (parsed.role !== void 0) {
4356
+ io.stderr.write("Sign-in is one per machine; --role is not used by auth.\n");
4357
+ }
4358
+ return await handleAuth(action, connect(null).auth, resolved.endpoint, parsed.json, io);
4359
+ }
4360
+ const env = parsed.env ?? "prod";
4361
+ if (domain === "tools" && action === "catalog") {
4362
+ return await printCatalog(parsed, io, resolved.endpoint, async () => {
4363
+ const explicit2 = explicitRole();
4364
+ return connect(await commandRole(explicit2, process.cwd())).client;
4365
+ });
4366
+ }
4367
+ let routeTools = getGeneratedTools(env);
4368
+ let remote = isAttachmentsUploadCommand(domain, action) || domain === "tools" && (action === "list" || action === "call") || matchGeneratedCommand(routeTools, parsed.positionals) !== void 0;
4369
+ if (!remote) {
4370
+ const live = await loadLiveTools(env, connect(null).client).catch(() => void 0);
4371
+ if (live && matchGeneratedCommand(live, parsed.positionals)) {
4372
+ routeTools = live;
4373
+ remote = true;
4374
+ }
4375
+ }
4376
+ if (!remote) {
4377
+ throw new Error(domain === "tools" ? "Expected tools list, tools call <mcpName>, or tools catalog." : `Unknown command "${domain}".`);
4378
+ }
4379
+ const explicit = explicitRole();
4380
+ const { client } = connect(await commandRole(explicit, process.cwd()));
4381
+ if (isAttachmentsUploadCommand(domain, action)) {
4382
+ return await executeAttachmentsUploadCommand({
4383
+ argv: rest,
4384
+ client,
4385
+ json: parsed.json,
4386
+ io
4387
+ });
4388
+ }
4389
+ const generatedResult = await executeGeneratedCommand({
4390
+ tools: routeTools,
4391
+ argv: parsed.positionals,
4392
+ client,
4393
+ json: parsed.json,
4394
+ io
4395
+ });
4396
+ if (generatedResult !== 2) {
4397
+ return generatedResult;
4398
+ }
4399
+ if (domain === "tools") {
4400
+ return await handleTools(action, rest, parsed, client, io);
4401
+ }
4402
+ throw new Error(`Unknown command "${domain}".`);
4403
+ } catch (error) {
4404
+ io.stderr.write(renderError(error, { json: parsed.json }));
4405
+ return 1;
4406
+ }
4407
+ }
4408
+ async function commandRole(explicit, cwd) {
4409
+ const explicitRole = explicit.role ?? null;
4410
+ const found = await findRepositoryRole(cwd);
4411
+ if (found.kind === "conflict") {
4412
+ throw new Error(await askTheUserToFixRole(found.dir, found.reason));
4413
+ }
4414
+ if (found.kind === "none") {
4415
+ return explicitRole;
4416
+ }
4417
+ if (explicitRole !== null && explicitRole !== found.role) {
4418
+ const given = explicit.source === "env" ? `${explicit.envVar}=${explicitRole}` : `--role ${explicitRole}`;
4419
+ throw new Error(
4420
+ `This repository's AI sessions have the role ${found.role} (set in ${found.dir}). ${given} does not match \u2014 the role changes only with \`ainecto init --role <role>\`, after asking the user.`
4421
+ );
4422
+ }
4423
+ return found.role;
4424
+ }
4425
+ async function handleAuth(action, auth, endpoint, json, io) {
4426
+ if (action === "login") {
4427
+ await auth.login();
4428
+ io.stdout.write(renderSuccess({ ...await nextStep(), endpoint, authenticated: true }, { json }));
4429
+ return 0;
4430
+ }
4431
+ if (action === "status") {
4432
+ const token = await auth.getAccessToken();
4433
+ io.stdout.write(renderSuccess({ ...await nextStep(), endpoint, authenticated: Boolean(token) }, { json }));
4434
+ return 0;
4435
+ }
4436
+ if (action === "logout") {
4437
+ await auth.logout();
4438
+ io.stdout.write(renderSuccess({ endpoint, authenticated: false }, { json }));
4439
+ return 0;
4440
+ }
4441
+ throw new Error("Expected auth login, auth status, or auth logout.");
4442
+ }
4443
+ async function printCatalog(parsed, io, endpoint, liveClient) {
4444
+ const catalogEnv = parsed.env ?? "prod";
4445
+ try {
4446
+ const tools = await loadLiveTools(catalogEnv, await liveClient());
4447
+ io.stdout.write(renderSuccess(tools, { json: parsed.json }));
4448
+ } catch (error) {
4449
+ const reason = error instanceof Error ? error.message : String(error);
4450
+ io.stdout.write(renderSuccess(getGeneratedTools(catalogEnv), {
4451
+ json: parsed.json,
4452
+ warnings: [`Could not read the live tool list from ${endpoint} (${reason}). Showing the ${catalogEnv} catalog checked in when this CLI version was built \u2014 a snapshot of that server's tools/list that may miss newer document types and tools. Run \`ainecto auth login\`, then retry.`]
4453
+ }));
4454
+ }
4455
+ return 0;
4456
+ }
4457
+ async function handleTools(action, rest, parsed, client, io) {
4458
+ if (action === "list") {
4459
+ const tools = await client.toolsList();
4460
+ io.stdout.write(renderSuccess(tools, { json: parsed.json }));
4461
+ return 0;
4462
+ }
4463
+ if (action === "call") {
4464
+ const toolName = rest[0];
4465
+ if (!toolName) {
4466
+ throw new Error("Expected MCP tool name after tools call.");
4467
+ }
4468
+ const toolArgs = rest.slice(1);
4469
+ const payloadOptions = parsePayloadArgs(toolArgs);
4470
+ const payload = await parsePayloadSource({
4471
+ file: payloadOptions.file,
4472
+ inline: payloadOptions.inline,
4473
+ stdin: io.stdin
4474
+ });
4475
+ const result = await client.toolsCall(toolName, payload.value);
4476
+ io.stdout.write(renderSuccess(result, { json: parsed.json, warnings: payload.warnings }));
4477
+ return isToolError(result) ? 1 : 0;
4478
+ }
4479
+ throw new Error("Expected tools list, tools call <mcpName>, or tools catalog.");
4480
+ }
4481
+ function parseGlobalArgs(argv) {
4482
+ const result = { json: false, help: false, version: false, positionals: [] };
4483
+ for (let index = 0; index < argv.length; index += 1) {
4484
+ const arg = argv[index];
4485
+ if (!arg) {
4486
+ continue;
4487
+ }
4488
+ if (arg === "--env") {
4489
+ result.env = assertEnv(requireValue4(arg, argv[++index]));
4490
+ } else if (arg === "--endpoint") {
4491
+ result.endpoint = requireValue4(arg, argv[++index]);
4492
+ } else if (arg === "--role") {
4493
+ result.role = assertRole(requireValue4(arg, argv[++index]));
4494
+ } else if (arg === "--json") {
4495
+ result.json = true;
4496
+ } else if (arg === "--help" || arg === "-h") {
4497
+ result.help = true;
4498
+ } else if (arg === "--version" || arg === "-v" || arg === "-V") {
4499
+ result.version = true;
4500
+ } else {
4501
+ result.positionals.push(arg);
4502
+ }
4503
+ }
4504
+ return result;
4505
+ }
4506
+ function parsePayloadArgs(argv) {
4507
+ const result = {};
4508
+ const inline = [];
4509
+ for (let index = 0; index < argv.length; index += 1) {
4510
+ const arg = argv[index];
4511
+ if (!arg) {
4512
+ continue;
4513
+ }
4514
+ if (arg === "--file" || arg === "-f") {
4515
+ result.file = requireValue4(arg, argv[++index]);
4516
+ } else {
4517
+ inline.push(arg);
4518
+ }
4519
+ }
4520
+ if (inline.length > 0) {
4521
+ result.inline = inline.join(" ");
4522
+ }
4523
+ return result;
4524
+ }
4525
+ function requireValue4(flag, value) {
4526
+ if (!value || value.startsWith("--")) {
4527
+ throw new Error(`${flag} requires a value.`);
4528
+ }
4529
+ return value;
4530
+ }
4531
+ async function nextStep() {
4532
+ const found = await findRepositoryRole(process.cwd());
4533
+ return found.kind === "none" ? { setup: "incomplete", next: NEXT_STEP_INIT } : {};
4534
+ }
4535
+ var NEXT_STEP_INIT = [
4536
+ "AiNecto setup is not finished until `npx -y ainecto@latest init` runs in the repository.",
4537
+ "AI agents: tell the user this now, even if they only asked to install, update, or sign in. Then ask",
4538
+ "which role this repository's AI sessions should have \u2014 Design, Development, Test, or Validation \u2014",
4539
+ "never pick one yourself, and run `npx -y ainecto@latest init --role <role>` from the repository root.",
4540
+ "In a terminal: `npx -y ainecto@latest init` asks for the role and the project."
4541
+ ].join("\n");
4542
+ function helpText() {
4543
+ return [
4544
+ "AiNecto CLI",
4545
+ "",
4546
+ NEXT_STEP_INIT,
4547
+ "",
4548
+ "Usage:",
4549
+ " ainecto auth login|status|logout [--env prod|dev] [--json] (one sign-in per machine)",
4550
+ " ainecto tools list [--env prod|dev] [--endpoint URL] [--json]",
4551
+ " ainecto tools call <mcpName> [-f payload.json|@-] [inline-json] [--json]",
1861
4552
  " ainecto attachments upload --document-uuid <uuid> <file...> [--json]",
1862
4553
  " ainecto <generated-command> [flags] [-f payload.json] [--yes] [--json]",
1863
- " ainecto mcp [--env prod|dev] [--endpoint URL]",
4554
+ " ainecto mcp [--env prod|dev] [--endpoint URL] [--role design|development|test|validation]",
4555
+ "",
4556
+ "Harness:",
4557
+ " ainecto init [--role <design|development|test|validation>] [--project <uuid>] [--dry-run]",
4558
+ " In a terminal, asks for the role and project when they are not given.",
4559
+ " ainecto init --undo",
4560
+ "",
4561
+ "`ai-erd` (the older name) is the same command and still works.",
1864
4562
  ""
1865
4563
  ].join("\n");
1866
4564
  }