@ainecto/mcp 0.3.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.
- package/README.md +246 -86
- package/dist/bin/ainecto.js +1271 -414
- package/dist/bin/ainecto.js.map +1 -1
- package/dist/bin/mcp.js +186 -67
- package/dist/bin/mcp.js.map +1 -1
- package/package.json +3 -4
package/dist/bin/ainecto.js
CHANGED
|
@@ -5,23 +5,55 @@ 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")) {
|
|
11
42
|
this.filePath = join(rootDir, "tokens.json");
|
|
12
43
|
}
|
|
13
|
-
async load(endpoint
|
|
44
|
+
async load(endpoint) {
|
|
14
45
|
const file = await this.readFile();
|
|
15
|
-
return file[tokenKey(endpoint
|
|
46
|
+
return file[tokenKey(endpoint)];
|
|
16
47
|
}
|
|
17
|
-
async save(endpoint, token
|
|
48
|
+
async save(endpoint, token) {
|
|
49
|
+
assertNoRoleScope(token);
|
|
18
50
|
const file = await this.readFile();
|
|
19
|
-
file[tokenKey(endpoint
|
|
51
|
+
file[tokenKey(endpoint)] = { ...token, endpoint, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
20
52
|
await this.writeFile(file);
|
|
21
53
|
}
|
|
22
|
-
async delete(endpoint
|
|
54
|
+
async delete(endpoint) {
|
|
23
55
|
const file = await this.readFile();
|
|
24
|
-
delete file[tokenKey(endpoint
|
|
56
|
+
delete file[tokenKey(endpoint)];
|
|
25
57
|
await this.writeFile(file);
|
|
26
58
|
}
|
|
27
59
|
async readFile() {
|
|
@@ -56,9 +88,15 @@ var FileTokenStore = class {
|
|
|
56
88
|
await chmodIfPermissive(this.filePath, 384);
|
|
57
89
|
}
|
|
58
90
|
};
|
|
59
|
-
function tokenKey(endpoint
|
|
60
|
-
|
|
61
|
-
|
|
91
|
+
function tokenKey(endpoint) {
|
|
92
|
+
return createHash("sha256").update(endpoint).digest("hex");
|
|
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
|
+
}
|
|
62
100
|
}
|
|
63
101
|
function isNodeError(error, code) {
|
|
64
102
|
return error instanceof Error && "code" in error && error.code === code;
|
|
@@ -85,13 +123,20 @@ import { URLSearchParams } from "url";
|
|
|
85
123
|
|
|
86
124
|
// src/core/config/endpoints.ts
|
|
87
125
|
var DEFAULT_ENDPOINTS = {
|
|
88
|
-
prod: "https://
|
|
126
|
+
prod: "https://ainecto.com/mcp",
|
|
89
127
|
dev: "https://dev.ai-erd.com/mcp"
|
|
90
128
|
};
|
|
129
|
+
var LEGACY_PROD_ENDPOINT = "https://ai-erd.com/mcp";
|
|
91
130
|
var LEGACY_DEFAULT_ENDPOINTS = [
|
|
92
|
-
|
|
131
|
+
LEGACY_PROD_ENDPOINT,
|
|
93
132
|
"https://dev.ainecto.com/mcp"
|
|
94
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
|
+
}
|
|
95
140
|
function resolveEndpoint(input = {}) {
|
|
96
141
|
const envVars = input.envVars ?? process.env;
|
|
97
142
|
const env = input.env ?? "prod";
|
|
@@ -154,7 +199,7 @@ async function registerPublicClient(registrationEndpoint, redirectUri, fetchImpl
|
|
|
154
199
|
method: "POST",
|
|
155
200
|
headers: { "content-type": "application/json", accept: "application/json" },
|
|
156
201
|
body: JSON.stringify({
|
|
157
|
-
client_name: "
|
|
202
|
+
client_name: "AiNecto CLI",
|
|
158
203
|
redirect_uris: [redirectUri],
|
|
159
204
|
token_endpoint_auth_method: "none",
|
|
160
205
|
grant_types: ["authorization_code", "refresh_token"],
|
|
@@ -177,32 +222,16 @@ function isRecord(value) {
|
|
|
177
222
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
178
223
|
}
|
|
179
224
|
|
|
180
|
-
// src/core/harness/role.ts
|
|
181
|
-
var HARNESS_ROLES = ["design", "development", "test", "validation"];
|
|
182
|
-
var ROLE_SCOPE_PREFIX = "ai-erd:role:";
|
|
183
|
-
var ROLE_HEADER = "X-AI-ERD-Role";
|
|
184
|
-
var ROLE_ENV_VAR = "AI_ERD_ROLE";
|
|
185
|
-
function resolveRole(input = {}) {
|
|
186
|
-
if (input.role !== void 0) {
|
|
187
|
-
return { role: assertRole(input.role), source: "flag" };
|
|
188
|
-
}
|
|
189
|
-
const fromEnv = (input.envVars ?? process.env)[ROLE_ENV_VAR];
|
|
190
|
-
if (fromEnv && fromEnv.trim()) {
|
|
191
|
-
return { role: assertRole(fromEnv), source: "env" };
|
|
192
|
-
}
|
|
193
|
-
return { source: "none" };
|
|
194
|
-
}
|
|
195
|
-
function assertRole(value) {
|
|
196
|
-
const normalized = value.trim().toLowerCase();
|
|
197
|
-
if (HARNESS_ROLES.includes(normalized)) {
|
|
198
|
-
return normalized;
|
|
199
|
-
}
|
|
200
|
-
throw new Error(
|
|
201
|
-
`Invalid role "${value}". Expected one of: ${HARNESS_ROLES.join(", ")}.`
|
|
202
|
-
);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
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
|
+
};
|
|
206
235
|
var OAuthClient = class {
|
|
207
236
|
constructor(options) {
|
|
208
237
|
this.options = options;
|
|
@@ -220,21 +249,27 @@ var OAuthClient = class {
|
|
|
220
249
|
assertEnvTokenEndpointAllowed(this.options.endpoint, this.envVars);
|
|
221
250
|
return envToken;
|
|
222
251
|
}
|
|
223
|
-
const stored = await this.
|
|
252
|
+
const stored = await this.loadStored();
|
|
224
253
|
if (!stored) {
|
|
225
254
|
return void 0;
|
|
226
255
|
}
|
|
227
256
|
if (!shouldRefresh(stored)) {
|
|
228
257
|
return stored.accessToken;
|
|
229
258
|
}
|
|
230
|
-
|
|
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 });
|
|
231
266
|
}
|
|
232
267
|
async refreshAfterUnauthorized() {
|
|
233
|
-
const stored = await this.
|
|
234
|
-
if (!stored?.refreshToken) {
|
|
268
|
+
const stored = await this.loadStored();
|
|
269
|
+
if (this.options.readOnly || !stored?.refreshToken) {
|
|
235
270
|
return void 0;
|
|
236
271
|
}
|
|
237
|
-
return this.refreshStoredToken(stored);
|
|
272
|
+
return this.refreshStoredToken({ ...stored, refreshToken: stored.refreshToken });
|
|
238
273
|
}
|
|
239
274
|
async login() {
|
|
240
275
|
const pkce = generatePkcePair();
|
|
@@ -251,12 +286,14 @@ var OAuthClient = class {
|
|
|
251
286
|
authorizeUrl.searchParams.set("code_challenge_method", "S256");
|
|
252
287
|
authorizeUrl.searchParams.set("resource", this.options.endpoint);
|
|
253
288
|
authorizeUrl.searchParams.set("state", state);
|
|
254
|
-
if (this.options.role) {
|
|
255
|
-
authorizeUrl.searchParams.set("scope", `mcp ${ROLE_SCOPE_PREFIX}${this.options.role}`);
|
|
256
|
-
}
|
|
257
289
|
assertTrustedEndpointUrl(authorizeUrl, "OAuth authorization URL");
|
|
258
|
-
|
|
259
|
-
|
|
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
|
+
);
|
|
260
297
|
const token = await exchangeToken(metadata.tokenEndpoint, {
|
|
261
298
|
grant_type: "authorization_code",
|
|
262
299
|
code,
|
|
@@ -267,19 +304,38 @@ var OAuthClient = class {
|
|
|
267
304
|
...registration.clientSecret ? { client_secret: registration.clientSecret } : {}
|
|
268
305
|
}, this.fetchImpl);
|
|
269
306
|
const stored = toStoredToken(this.options.endpoint, token, metadata, registration);
|
|
270
|
-
await this.options.tokenStore.save(this.options.endpoint, stored
|
|
307
|
+
await this.options.tokenStore.save(this.options.endpoint, stored);
|
|
271
308
|
return stored;
|
|
272
309
|
} finally {
|
|
273
310
|
await loopback.close();
|
|
274
311
|
}
|
|
275
312
|
}
|
|
313
|
+
/**
|
|
314
|
+
* ★대체로 읽는 옛 칸도 함께 지운다. 새 칸만 지우면 다음 읽기가 곧바로 옛 칸으로 대체돼
|
|
315
|
+
* «로그아웃했는데 여전히 로그인됨»이 된다. 로그인은 기계당 한 번이므로 옛 CLI 의 칸도 같이 끝난다.
|
|
316
|
+
*/
|
|
276
317
|
async logout() {
|
|
277
|
-
await this.options.tokenStore.delete(this.options.endpoint
|
|
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
|
+
}
|
|
278
322
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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
|
+
}
|
|
282
334
|
}
|
|
335
|
+
return void 0;
|
|
336
|
+
}
|
|
337
|
+
/** 부르는 쪽이 refresh token 이 있음을 이미 확인했다 — 타입이 그것을 요구한다. */
|
|
338
|
+
async refreshStoredToken(stored) {
|
|
283
339
|
const metadata = await discoverOAuthMetadata(this.options.endpoint, this.fetchImpl);
|
|
284
340
|
const token = await exchangeToken(metadata.tokenEndpoint, {
|
|
285
341
|
grant_type: "refresh_token",
|
|
@@ -289,7 +345,7 @@ var OAuthClient = class {
|
|
|
289
345
|
resource: this.options.endpoint
|
|
290
346
|
}, this.fetchImpl);
|
|
291
347
|
const next = toStoredToken(this.options.endpoint, token, metadata, stored);
|
|
292
|
-
await this.options.tokenStore.save(this.options.endpoint, next
|
|
348
|
+
await this.options.tokenStore.save(this.options.endpoint, next);
|
|
293
349
|
return next.accessToken;
|
|
294
350
|
}
|
|
295
351
|
};
|
|
@@ -301,6 +357,27 @@ function generatePkcePair() {
|
|
|
301
357
|
function generateOAuthState() {
|
|
302
358
|
return randomBytes(32).toString("base64url");
|
|
303
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
|
+
}
|
|
304
381
|
function shouldRefresh(token, now = Date.now()) {
|
|
305
382
|
if (!token.expiresAt) {
|
|
306
383
|
return false;
|
|
@@ -393,11 +470,11 @@ function toStoredToken(endpoint, token, metadata, previous) {
|
|
|
393
470
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
394
471
|
};
|
|
395
472
|
}
|
|
396
|
-
async function createLoopbackReceiver(expectedState) {
|
|
473
|
+
async function createLoopbackReceiver(expectedState, host = "127.0.0.1") {
|
|
397
474
|
let resolveCode;
|
|
398
475
|
let rejectCode;
|
|
399
|
-
const codePromise = new Promise((
|
|
400
|
-
resolveCode =
|
|
476
|
+
const codePromise = new Promise((resolve3, reject) => {
|
|
477
|
+
resolveCode = resolve3;
|
|
401
478
|
rejectCode = reject;
|
|
402
479
|
});
|
|
403
480
|
codePromise.catch(() => void 0);
|
|
@@ -408,13 +485,13 @@ async function createLoopbackReceiver(expectedState) {
|
|
|
408
485
|
const state = url.searchParams.get("state");
|
|
409
486
|
if (error) {
|
|
410
487
|
res.writeHead(400, { "content-type": "text/plain" });
|
|
411
|
-
res.end("
|
|
488
|
+
res.end("AiNecto CLI authorization failed. You can close this tab.");
|
|
412
489
|
rejectCode(new Error(`OAuth authorization failed: ${error}`));
|
|
413
490
|
return;
|
|
414
491
|
}
|
|
415
492
|
if (!state || state !== expectedState) {
|
|
416
493
|
res.writeHead(400, { "content-type": "text/plain" });
|
|
417
|
-
res.end("
|
|
494
|
+
res.end("AiNecto CLI authorization state mismatch. You can close this tab.");
|
|
418
495
|
rejectCode(new Error("OAuth state mismatch."));
|
|
419
496
|
return;
|
|
420
497
|
}
|
|
@@ -425,10 +502,16 @@ async function createLoopbackReceiver(expectedState) {
|
|
|
425
502
|
return;
|
|
426
503
|
}
|
|
427
504
|
res.writeHead(200, { "content-type": "text/plain" });
|
|
428
|
-
res.end("
|
|
505
|
+
res.end("AiNecto CLI authorization complete. You can close this tab.");
|
|
429
506
|
resolveCode(code);
|
|
430
507
|
});
|
|
431
|
-
await new Promise((
|
|
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
|
+
});
|
|
432
515
|
const address = server.address();
|
|
433
516
|
if (!address || typeof address === "string") {
|
|
434
517
|
throw new Error("Loopback OAuth server did not bind to a TCP port.");
|
|
@@ -436,7 +519,10 @@ async function createLoopbackReceiver(expectedState) {
|
|
|
436
519
|
return {
|
|
437
520
|
redirectUri: `http://127.0.0.1:${address.port}/callback`,
|
|
438
521
|
waitForCode: () => codePromise,
|
|
439
|
-
close: () => new Promise((
|
|
522
|
+
close: () => new Promise((resolve3, reject) => {
|
|
523
|
+
server.closeAllConnections?.();
|
|
524
|
+
server.close((error) => error ? reject(error) : resolve3());
|
|
525
|
+
})
|
|
440
526
|
};
|
|
441
527
|
}
|
|
442
528
|
function parseStringArray(value) {
|
|
@@ -460,10 +546,39 @@ function getBrowserOpenCommand(url, platform = process.platform) {
|
|
|
460
546
|
}
|
|
461
547
|
return { command: "xdg-open", args: [url] };
|
|
462
548
|
}
|
|
463
|
-
|
|
464
|
-
const
|
|
465
|
-
const
|
|
466
|
-
|
|
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);
|
|
467
582
|
}
|
|
468
583
|
function assertEnvTokenEndpointAllowed(endpoint, envVars) {
|
|
469
584
|
if (envVars.AINECTO_ALLOW_CUSTOM_ENDPOINT_TOKEN === "1") {
|
|
@@ -534,6 +649,12 @@ async function readAllStdin(stdin) {
|
|
|
534
649
|
return text;
|
|
535
650
|
}
|
|
536
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
|
+
|
|
537
658
|
// src/core/mcp/rpcClient.ts
|
|
538
659
|
var McpRpcError = class extends Error {
|
|
539
660
|
constructor(message, code, details) {
|
|
@@ -556,7 +677,7 @@ var McpRpcClient = class {
|
|
|
556
677
|
return this.request("initialize", {
|
|
557
678
|
protocolVersion: "2024-11-05",
|
|
558
679
|
capabilities: {},
|
|
559
|
-
clientInfo: { name:
|
|
680
|
+
clientInfo: { name: PACKAGE_NAME, version: CLI_VERSION }
|
|
560
681
|
});
|
|
561
682
|
}
|
|
562
683
|
async toolsList() {
|
|
@@ -731,55 +852,67 @@ function redactSecretString(value) {
|
|
|
731
852
|
|
|
732
853
|
// src/core/catalog/generated.prod.ts
|
|
733
854
|
var generatedTools = [
|
|
734
|
-
{ "mcpName": "
|
|
735
|
-
{ "mcpName": "
|
|
736
|
-
{ "mcpName": "
|
|
737
|
-
{ "mcpName": "
|
|
738
|
-
{ "mcpName": "
|
|
739
|
-
{ "mcpName": "
|
|
740
|
-
{ "mcpName": "
|
|
741
|
-
{ "mcpName": "
|
|
742
|
-
{ "mcpName": "
|
|
743
|
-
{ "mcpName": "
|
|
744
|
-
{ "mcpName": "
|
|
745
|
-
{ "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" },
|
|
746
|
-
{ "mcpName": "
|
|
747
|
-
{ "mcpName": "
|
|
748
|
-
{ "mcpName": "
|
|
749
|
-
{ "mcpName": "
|
|
750
|
-
{ "mcpName": "
|
|
751
|
-
{ "mcpName": "
|
|
752
|
-
{ "mcpName": "
|
|
753
|
-
{ "mcpName": "
|
|
754
|
-
{ "mcpName": "
|
|
755
|
-
{ "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" },
|
|
756
|
-
{ "mcpName": "
|
|
757
|
-
{ "mcpName": "
|
|
758
|
-
{ "mcpName": "
|
|
759
|
-
{ "mcpName": "
|
|
760
|
-
{ "mcpName": "
|
|
761
|
-
{ "mcpName": "
|
|
762
|
-
{ "mcpName": "
|
|
763
|
-
{ "mcpName": "
|
|
764
|
-
{ "mcpName": "
|
|
765
|
-
{ "mcpName": "
|
|
766
|
-
{ "mcpName": "
|
|
767
|
-
{ "mcpName": "
|
|
768
|
-
{ "mcpName": "
|
|
769
|
-
{ "mcpName": "
|
|
770
|
-
{ "mcpName": "
|
|
771
|
-
{ "mcpName": "
|
|
772
|
-
{ "mcpName": "
|
|
773
|
-
{ "mcpName": "
|
|
774
|
-
{ "mcpName": "
|
|
775
|
-
{ "mcpName": "
|
|
776
|
-
{ "mcpName": "
|
|
777
|
-
{ "mcpName": "
|
|
778
|
-
{ "mcpName": "
|
|
779
|
-
{ "mcpName": "
|
|
780
|
-
{ "mcpName": "
|
|
781
|
-
{ "mcpName": "
|
|
782
|
-
{ "mcpName": "
|
|
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" }
|
|
783
916
|
];
|
|
784
917
|
|
|
785
918
|
// src/core/catalog/generated.dev.ts
|
|
@@ -872,10 +1005,147 @@ var generatedTools2 = [
|
|
|
872
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" }
|
|
873
1006
|
];
|
|
874
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
|
+
|
|
875
1142
|
// src/core/catalog/index.ts
|
|
876
1143
|
function getGeneratedTools(env) {
|
|
877
1144
|
return env === "dev" ? generatedTools2 : generatedTools;
|
|
878
1145
|
}
|
|
1146
|
+
async function loadLiveTools(env, client) {
|
|
1147
|
+
return generateCatalog(await client.toolsList(), env);
|
|
1148
|
+
}
|
|
879
1149
|
|
|
880
1150
|
// src/core/mcp/stdioBridge.ts
|
|
881
1151
|
import { createInterface } from "readline";
|
|
@@ -905,7 +1175,7 @@ async function runStdioBridge(options) {
|
|
|
905
1175
|
}
|
|
906
1176
|
function parseJsonRpcRequest(line) {
|
|
907
1177
|
const parsed = JSON.parse(line);
|
|
908
|
-
if (!
|
|
1178
|
+
if (!isRecord4(parsed) || parsed.jsonrpc !== "2.0" || typeof parsed.method !== "string") {
|
|
909
1179
|
throw new Error("Invalid JSON-RPC request.");
|
|
910
1180
|
}
|
|
911
1181
|
return {
|
|
@@ -915,14 +1185,13 @@ function parseJsonRpcRequest(line) {
|
|
|
915
1185
|
params: parsed.params
|
|
916
1186
|
};
|
|
917
1187
|
}
|
|
918
|
-
function
|
|
1188
|
+
function isRecord4(value) {
|
|
919
1189
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
920
1190
|
}
|
|
921
1191
|
|
|
922
1192
|
// src/adapters/mcp/connector.ts
|
|
923
1193
|
async function runConnector(options) {
|
|
924
|
-
const
|
|
925
|
-
const auth = new OAuthClient({ endpoint: options.endpoint, tokenStore, role: options.role });
|
|
1194
|
+
const auth = new OAuthClient({ endpoint: options.endpoint, tokenStore: new FileTokenStore() });
|
|
926
1195
|
const client = new McpRpcClient({
|
|
927
1196
|
endpoint: options.endpoint,
|
|
928
1197
|
tokenProvider: auth,
|
|
@@ -939,85 +1208,85 @@ async function runConnector(options) {
|
|
|
939
1208
|
// src/core/catalog/enrichments.ts
|
|
940
1209
|
var enrichments = [
|
|
941
1210
|
{
|
|
942
|
-
mcpName: "
|
|
1211
|
+
mcpName: "list_projects",
|
|
943
1212
|
outputHint: "table",
|
|
944
1213
|
tableColumns: ["uuid", "name", "projectKey", "workspaceUuid", "updatedAt"],
|
|
945
1214
|
displayName: "List projects",
|
|
946
1215
|
examples: ["ainecto projects list --env dev"]
|
|
947
1216
|
},
|
|
948
1217
|
{
|
|
949
|
-
mcpName: "
|
|
1218
|
+
mcpName: "list_workspaces",
|
|
950
1219
|
outputHint: "table",
|
|
951
1220
|
tableColumns: ["uuid", "name", "updatedAt"],
|
|
952
1221
|
displayName: "List workspaces"
|
|
953
1222
|
},
|
|
954
1223
|
{
|
|
955
|
-
mcpName: "
|
|
1224
|
+
mcpName: "list_folders",
|
|
956
1225
|
outputHint: "table",
|
|
957
1226
|
tableColumns: ["uuid", "name", "projectUuid", "parentFolderUuid", "updatedAt"],
|
|
958
1227
|
displayName: "List folders"
|
|
959
1228
|
},
|
|
960
1229
|
{
|
|
961
|
-
mcpName: "
|
|
1230
|
+
mcpName: "list_documents",
|
|
962
1231
|
outputHint: "table",
|
|
963
1232
|
tableColumns: ["uuid", "title", "type", "projectUuid", "folderUuid", "updatedAt"],
|
|
964
1233
|
displayName: "List documents"
|
|
965
1234
|
},
|
|
966
1235
|
{
|
|
967
|
-
mcpName: "
|
|
1236
|
+
mcpName: "search_documents",
|
|
968
1237
|
outputHint: "table",
|
|
969
1238
|
tableColumns: ["uuid", "title", "type", "projectUuid", "updatedAt"],
|
|
970
1239
|
displayName: "Search documents"
|
|
971
1240
|
},
|
|
972
1241
|
{
|
|
973
|
-
mcpName: "
|
|
1242
|
+
mcpName: "list_attachments",
|
|
974
1243
|
outputHint: "table",
|
|
975
1244
|
tableColumns: ["uuid", "filename", "contentType", "sizeBytes", "documentUuid"],
|
|
976
1245
|
displayName: "List attachments"
|
|
977
1246
|
},
|
|
978
1247
|
{
|
|
979
|
-
mcpName: "
|
|
1248
|
+
mcpName: "erd_list_tables",
|
|
980
1249
|
outputHint: "table",
|
|
981
1250
|
tableColumns: ["uuid", "name", "schema_name", "display_order"],
|
|
982
1251
|
displayName: "List ERD tables"
|
|
983
1252
|
},
|
|
984
1253
|
{
|
|
985
|
-
mcpName: "
|
|
1254
|
+
mcpName: "erd_list_refs",
|
|
986
1255
|
outputHint: "table",
|
|
987
1256
|
tableColumns: ["uuid", "name", "from_table_uuid", "to_table_uuid", "relationship"],
|
|
988
1257
|
displayName: "List ERD refs"
|
|
989
1258
|
},
|
|
990
1259
|
{
|
|
991
|
-
mcpName: "
|
|
1260
|
+
mcpName: "erd_list_enums",
|
|
992
1261
|
outputHint: "table",
|
|
993
1262
|
tableColumns: ["uuid", "name", "display_order"],
|
|
994
1263
|
displayName: "List ERD enums"
|
|
995
1264
|
},
|
|
996
1265
|
{
|
|
997
|
-
mcpName: "
|
|
1266
|
+
mcpName: "erd_list_table_groups",
|
|
998
1267
|
outputHint: "table",
|
|
999
1268
|
tableColumns: ["uuid", "name", "display_order"],
|
|
1000
1269
|
displayName: "List ERD table groups"
|
|
1001
1270
|
},
|
|
1002
1271
|
{
|
|
1003
|
-
mcpName: "
|
|
1272
|
+
mcpName: "task_list_tasks",
|
|
1004
1273
|
outputHint: "table",
|
|
1005
1274
|
tableColumns: ["uuid", "title", "statusUuid", "assigneeId", "updatedAt"],
|
|
1006
1275
|
displayName: "List tasks"
|
|
1007
1276
|
},
|
|
1008
1277
|
{
|
|
1009
|
-
mcpName: "
|
|
1278
|
+
mcpName: "task_list_statuses",
|
|
1010
1279
|
outputHint: "table",
|
|
1011
1280
|
tableColumns: ["uuid", "name", "displayOrder"],
|
|
1012
1281
|
displayName: "List task statuses"
|
|
1013
1282
|
},
|
|
1014
1283
|
{
|
|
1015
|
-
mcpName: "
|
|
1284
|
+
mcpName: "enable_shares",
|
|
1016
1285
|
aliases: ["shares enable"],
|
|
1017
1286
|
displayName: "Enable shares"
|
|
1018
1287
|
},
|
|
1019
1288
|
{
|
|
1020
|
-
mcpName: "
|
|
1289
|
+
mcpName: "disable_shares",
|
|
1021
1290
|
aliases: ["shares disable"],
|
|
1022
1291
|
displayName: "Disable shares"
|
|
1023
1292
|
}
|
|
@@ -1026,7 +1295,7 @@ var enrichments = [
|
|
|
1026
1295
|
// src/core/output/table.ts
|
|
1027
1296
|
function renderTable(data, options = {}) {
|
|
1028
1297
|
const rows = extractRows(data);
|
|
1029
|
-
if (!rows.length || !rows.every(
|
|
1298
|
+
if (!rows.length || !rows.every(isRecord5)) {
|
|
1030
1299
|
return void 0;
|
|
1031
1300
|
}
|
|
1032
1301
|
const columns = options.columns?.length ? options.columns : inferColumns(rows);
|
|
@@ -1043,7 +1312,7 @@ function extractRows(data) {
|
|
|
1043
1312
|
if (Array.isArray(data)) {
|
|
1044
1313
|
return data;
|
|
1045
1314
|
}
|
|
1046
|
-
if (
|
|
1315
|
+
if (isRecord5(data)) {
|
|
1047
1316
|
for (const key of ["items", "projects", "workspaces", "folders", "documents", "tasks", "attachments", "results", "data"]) {
|
|
1048
1317
|
const value = data[key];
|
|
1049
1318
|
if (Array.isArray(value)) {
|
|
@@ -1071,7 +1340,7 @@ function formatCell(value) {
|
|
|
1071
1340
|
}
|
|
1072
1341
|
return String(value);
|
|
1073
1342
|
}
|
|
1074
|
-
function
|
|
1343
|
+
function isRecord5(value) {
|
|
1075
1344
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1076
1345
|
}
|
|
1077
1346
|
|
|
@@ -1124,7 +1393,7 @@ function payloadToArgs(value) {
|
|
|
1124
1393
|
if (value === void 0 || value === null) {
|
|
1125
1394
|
return {};
|
|
1126
1395
|
}
|
|
1127
|
-
if (!
|
|
1396
|
+
if (!isRecord6(value)) {
|
|
1128
1397
|
throw new CliCommandError("INVALID_PAYLOAD", "Generated command payload must be a JSON object.", { value });
|
|
1129
1398
|
}
|
|
1130
1399
|
return { ...value };
|
|
@@ -1228,12 +1497,12 @@ function coerceFlagValue(value, schema, key) {
|
|
|
1228
1497
|
}
|
|
1229
1498
|
function getSchemaProperties(schema) {
|
|
1230
1499
|
const properties = schema.properties;
|
|
1231
|
-
if (!
|
|
1500
|
+
if (!isRecord6(properties)) {
|
|
1232
1501
|
return {};
|
|
1233
1502
|
}
|
|
1234
1503
|
const result = {};
|
|
1235
1504
|
for (const [key, value] of Object.entries(properties)) {
|
|
1236
|
-
if (
|
|
1505
|
+
if (isRecord6(value)) {
|
|
1237
1506
|
result[key] = value;
|
|
1238
1507
|
}
|
|
1239
1508
|
}
|
|
@@ -1248,7 +1517,7 @@ function requireValue(flag, value) {
|
|
|
1248
1517
|
}
|
|
1249
1518
|
return value;
|
|
1250
1519
|
}
|
|
1251
|
-
function
|
|
1520
|
+
function isRecord6(value) {
|
|
1252
1521
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1253
1522
|
}
|
|
1254
1523
|
|
|
@@ -1290,9 +1559,8 @@ async function promptConfirmation(io, message) {
|
|
|
1290
1559
|
var ATTACHMENTS_UPLOAD_COMMAND_PATH = ["attachments", "upload"];
|
|
1291
1560
|
|
|
1292
1561
|
// src/adapters/cli/generatedCommandRouter.ts
|
|
1293
|
-
function matchGeneratedCommand(
|
|
1562
|
+
function matchGeneratedCommand(tools, argv) {
|
|
1294
1563
|
const commandTokens = argv.filter((arg) => !arg.startsWith("-"));
|
|
1295
|
-
const tools = getGeneratedTools(env);
|
|
1296
1564
|
const entries = buildRouteEntries(tools, enrichments);
|
|
1297
1565
|
const match = entries.filter((entry) => isPrefix(entry.path, commandTokens)).sort((a, b) => b.path.length - a.path.length)[0];
|
|
1298
1566
|
if (!match) {
|
|
@@ -1308,7 +1576,7 @@ function matchGeneratedCommand(env, argv) {
|
|
|
1308
1576
|
};
|
|
1309
1577
|
}
|
|
1310
1578
|
async function executeGeneratedCommand(options) {
|
|
1311
|
-
const route = matchGeneratedCommand(options.
|
|
1579
|
+
const route = matchGeneratedCommand(options.tools, options.argv);
|
|
1312
1580
|
if (!route) {
|
|
1313
1581
|
return 2;
|
|
1314
1582
|
}
|
|
@@ -1471,7 +1739,7 @@ async function requestUploadToken(client, metadata, context) {
|
|
|
1471
1739
|
requestArgs.ttlSeconds = context.ttlSeconds;
|
|
1472
1740
|
}
|
|
1473
1741
|
const response = unwrapMcpToolPayload(await client.toolsCall(context.requestToolName, requestArgs));
|
|
1474
|
-
if (!
|
|
1742
|
+
if (!isRecord7(response)) {
|
|
1475
1743
|
throw new AttachmentUploadError("UPLOAD_TOKEN_PROTOCOL_ERROR", "request_upload_token response was not an object.");
|
|
1476
1744
|
}
|
|
1477
1745
|
const token = response.token;
|
|
@@ -1541,7 +1809,7 @@ function parsePutResponse(text) {
|
|
|
1541
1809
|
} catch {
|
|
1542
1810
|
throw new AttachmentUploadError("ATTACHMENT_UPLOAD_PROTOCOL_ERROR", "Attachment PUT response was not valid JSON.");
|
|
1543
1811
|
}
|
|
1544
|
-
if (!
|
|
1812
|
+
if (!isRecord7(parsed)) {
|
|
1545
1813
|
throw new AttachmentUploadError("ATTACHMENT_UPLOAD_PROTOCOL_ERROR", "Attachment PUT response was not an object.");
|
|
1546
1814
|
}
|
|
1547
1815
|
if (typeof parsed.storageKey !== "string") {
|
|
@@ -1574,10 +1842,10 @@ function validatePutResult(token, metadata, putResult) {
|
|
|
1574
1842
|
}
|
|
1575
1843
|
}
|
|
1576
1844
|
function unwrapMcpToolPayload(value) {
|
|
1577
|
-
if (!
|
|
1845
|
+
if (!isRecord7(value) || !Array.isArray(value.content)) {
|
|
1578
1846
|
return value;
|
|
1579
1847
|
}
|
|
1580
|
-
const text = value.content.map((item) =>
|
|
1848
|
+
const text = value.content.map((item) => isRecord7(item) && typeof item.text === "string" ? item.text : "").find((itemText) => itemText.trim().startsWith("{"));
|
|
1581
1849
|
if (!text) {
|
|
1582
1850
|
return value;
|
|
1583
1851
|
}
|
|
@@ -1619,7 +1887,7 @@ var MIME_BY_EXTENSION = {
|
|
|
1619
1887
|
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
1620
1888
|
".zip": "application/zip"
|
|
1621
1889
|
};
|
|
1622
|
-
function
|
|
1890
|
+
function isRecord7(value) {
|
|
1623
1891
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1624
1892
|
}
|
|
1625
1893
|
|
|
@@ -1744,12 +2012,13 @@ function renderAttachmentUploadResult(result, json) {
|
|
|
1744
2012
|
}
|
|
1745
2013
|
|
|
1746
2014
|
// src/adapters/cli/initCommand.ts
|
|
1747
|
-
import { chmod as chmod2,
|
|
1748
|
-
import { dirname as
|
|
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";
|
|
1749
2017
|
|
|
1750
2018
|
// src/core/harness/agentTargets.ts
|
|
1751
|
-
|
|
1752
|
-
var
|
|
2019
|
+
import { basename as basename2 } from "path";
|
|
2020
|
+
var SERVER_NAME = "ainecto";
|
|
2021
|
+
var LEGACY_SERVER_NAME = "ai-erd";
|
|
1753
2022
|
var AGENT_TARGETS = [
|
|
1754
2023
|
{ id: "claude-code", label: "Claude Code", path: ".mcp.json" },
|
|
1755
2024
|
{ id: "cursor", label: "Cursor", path: ".cursor/mcp.json" }
|
|
@@ -1765,7 +2034,7 @@ function buildServerEntry(input) {
|
|
|
1765
2034
|
return { command: "npx", args };
|
|
1766
2035
|
}
|
|
1767
2036
|
function readRoleFromEntry(entry) {
|
|
1768
|
-
if (!
|
|
2037
|
+
if (!isRecord8(entry) || !Array.isArray(entry.args)) {
|
|
1769
2038
|
return void 0;
|
|
1770
2039
|
}
|
|
1771
2040
|
const index = entry.args.indexOf("--role");
|
|
@@ -1778,16 +2047,44 @@ function readServerEntry(existing) {
|
|
|
1778
2047
|
}
|
|
1779
2048
|
try {
|
|
1780
2049
|
const parsed = JSON.parse(existing);
|
|
1781
|
-
|
|
2050
|
+
const servers = parsed?.mcpServers;
|
|
2051
|
+
return isRecord8(servers) ? servers[ourKeyIn(servers) ?? SERVER_NAME] : void 0;
|
|
1782
2052
|
} catch {
|
|
1783
2053
|
return void 0;
|
|
1784
2054
|
}
|
|
1785
2055
|
}
|
|
2056
|
+
function ourKeyIn(servers) {
|
|
2057
|
+
if (Object.prototype.hasOwnProperty.call(servers, SERVER_NAME)) {
|
|
2058
|
+
return SERVER_NAME;
|
|
2059
|
+
}
|
|
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;
|
|
2068
|
+
}
|
|
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;
|
|
2074
|
+
}
|
|
2075
|
+
const bin = typeof command === "string" ? basename2(command) : "";
|
|
2076
|
+
return OUR_BINS.includes(bin);
|
|
2077
|
+
}
|
|
2078
|
+
var OUR_BINS = ["ainecto", "ai-erd", "ai-erd-mcp"];
|
|
1786
2079
|
function mergeServerEntry(existing, entry) {
|
|
1787
2080
|
const root = parseConfigObject(existing);
|
|
1788
2081
|
const servers = readServers(root);
|
|
1789
|
-
const
|
|
1790
|
-
const
|
|
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
|
+
}
|
|
1791
2088
|
servers[SERVER_NAME] = entry;
|
|
1792
2089
|
return { content: stringify({ ...root, mcpServers: servers }), replaced, replacedEntry };
|
|
1793
2090
|
}
|
|
@@ -1797,12 +2094,15 @@ function removeServerEntry(existing, restore) {
|
|
|
1797
2094
|
}
|
|
1798
2095
|
const root = parseConfigObject(existing);
|
|
1799
2096
|
const servers = readServers(root);
|
|
1800
|
-
const
|
|
2097
|
+
const ourKey = ourKeyIn(servers);
|
|
2098
|
+
const removed = ourKey !== void 0;
|
|
1801
2099
|
if (restore !== void 0) {
|
|
1802
|
-
servers[SERVER_NAME] = restore.value;
|
|
2100
|
+
servers[ourKey ?? SERVER_NAME] = restore.value;
|
|
1803
2101
|
return { content: stringify({ ...root, mcpServers: servers }), removed, emptied: false };
|
|
1804
2102
|
}
|
|
1805
|
-
|
|
2103
|
+
if (ourKey !== void 0) {
|
|
2104
|
+
delete servers[ourKey];
|
|
2105
|
+
}
|
|
1806
2106
|
const otherKeys = Object.keys(root).filter((key) => key !== "mcpServers");
|
|
1807
2107
|
const emptied = Object.keys(servers).length === 0 && otherKeys.length === 0;
|
|
1808
2108
|
return { content: stringify({ ...root, mcpServers: servers }), removed, emptied };
|
|
@@ -1832,7 +2132,7 @@ function readServers(root) {
|
|
|
1832
2132
|
if (servers === void 0) {
|
|
1833
2133
|
return {};
|
|
1834
2134
|
}
|
|
1835
|
-
if (!
|
|
2135
|
+
if (!isRecord8(servers)) {
|
|
1836
2136
|
throw new Error(
|
|
1837
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.'
|
|
1838
2138
|
);
|
|
@@ -1851,7 +2151,7 @@ function parseConfigObject(existing) {
|
|
|
1851
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)})`
|
|
1852
2152
|
);
|
|
1853
2153
|
}
|
|
1854
|
-
if (!
|
|
2154
|
+
if (!isRecord8(parsed)) {
|
|
1855
2155
|
throw new Error("Existing MCP config must be a JSON object; it was left untouched.");
|
|
1856
2156
|
}
|
|
1857
2157
|
return parsed;
|
|
@@ -1860,16 +2160,24 @@ function stringify(value) {
|
|
|
1860
2160
|
return `${JSON.stringify(value, null, 2)}
|
|
1861
2161
|
`;
|
|
1862
2162
|
}
|
|
1863
|
-
function
|
|
2163
|
+
function isRecord8(value) {
|
|
1864
2164
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1865
2165
|
}
|
|
1866
2166
|
|
|
1867
2167
|
// src/core/harness/harnessDoc.ts
|
|
1868
|
-
var MARKER_BEGIN = "<!--
|
|
1869
|
-
var MARKER_END = "<!--
|
|
1870
|
-
var
|
|
1871
|
-
var
|
|
1872
|
-
var
|
|
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`;
|
|
1873
2181
|
function renderHarnessDoc(input, template) {
|
|
1874
2182
|
return fillTemplate(template ?? DEFAULT_HARNESS_DOC, {
|
|
1875
2183
|
projectName: input.projectName,
|
|
@@ -1893,13 +2201,13 @@ function documentContractProblem(key, body) {
|
|
|
1893
2201
|
}
|
|
1894
2202
|
}
|
|
1895
2203
|
if (key === AGENT_NOTE_KEY) {
|
|
1896
|
-
return managedBlockProblem(key, body);
|
|
2204
|
+
return managedBlockProblem(key, withCurrentMarkers(body));
|
|
1897
2205
|
}
|
|
1898
2206
|
return void 0;
|
|
1899
2207
|
}
|
|
1900
|
-
var DEFAULT_HARNESS_DOC = `#
|
|
2208
|
+
var DEFAULT_HARNESS_DOC = `# AiNecto Harness
|
|
1901
2209
|
|
|
1902
|
-
This repository is wired to an
|
|
2210
|
+
This repository is wired to an AiNecto project. The design source of truth lives in AiNecto,
|
|
1903
2211
|
not in this repository's files.
|
|
1904
2212
|
|
|
1905
2213
|
- Project: **{{projectName}}** (\`{{projectUuid}}\`)
|
|
@@ -1908,9 +2216,11 @@ not in this repository's files.
|
|
|
1908
2216
|
## One session, one role
|
|
1909
2217
|
|
|
1910
2218
|
A session picks exactly one role and keeps it for the whole session. The role comes from the
|
|
1911
|
-
MCP server configuration \u2014 not from this file
|
|
1912
|
-
|
|
1913
|
-
|
|
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.
|
|
1914
2224
|
|
|
1915
2225
|
| Role | Owns | Must not |
|
|
1916
2226
|
| --- | --- | --- |
|
|
@@ -1937,7 +2247,7 @@ and which session should pick it up next.
|
|
|
1937
2247
|
|
|
1938
2248
|
## Not every change needs four sessions
|
|
1939
2249
|
|
|
1940
|
-
A small change that fits the design already recorded in
|
|
2250
|
+
A small change that fits the design already recorded in AiNecto starts at Development. The rule
|
|
1941
2251
|
is not "always run four steps" \u2014 it is that when a step is needed, it does not happen inside
|
|
1942
2252
|
another role's session.
|
|
1943
2253
|
|
|
@@ -1948,27 +2258,30 @@ Change the role in the MCP server configuration and start a new session:
|
|
|
1948
2258
|
${AGENT_TARGETS.map((target) => ` ${target.label.padEnd(12)} ${target.path}`).join("\n")}
|
|
1949
2259
|
Codex global config \u2014 use a profile: codex -p <role>, or set ${ROLE_ENV_VAR}
|
|
1950
2260
|
|
|
1951
|
-
Or run \`
|
|
2261
|
+
Or run \`ainecto init --role <design|development|test|validation>\` to rewrite them.
|
|
1952
2262
|
|
|
1953
2263
|
---
|
|
1954
|
-
Generated by \`
|
|
2264
|
+
Generated by \`ainecto init\`. Remove everything it wrote with \`ainecto init --undo\`.
|
|
1955
2265
|
`;
|
|
1956
2266
|
function renderAgentNote(template) {
|
|
1957
|
-
return fillTemplate(template ?? DEFAULT_AGENT_NOTE, {
|
|
2267
|
+
return fillTemplate(withCurrentMarkers(template ?? DEFAULT_AGENT_NOTE), {
|
|
1958
2268
|
harnessDocPath: HARNESS_DOC_PATH,
|
|
1959
2269
|
serverName: SERVER_NAME
|
|
1960
2270
|
});
|
|
1961
2271
|
}
|
|
2272
|
+
function withCurrentMarkers(template) {
|
|
2273
|
+
return template.split(LEGACY_MARKER_BEGIN).join(MARKER_BEGIN).split(LEGACY_MARKER_END).join(MARKER_END);
|
|
2274
|
+
}
|
|
1962
2275
|
var DEFAULT_AGENT_NOTE = `${MARKER_BEGIN}
|
|
1963
|
-
##
|
|
2276
|
+
## AiNecto Harness
|
|
1964
2277
|
|
|
1965
|
-
This repository uses
|
|
2278
|
+
This repository uses AiNecto as its design source of truth, and each session has one role
|
|
1966
2279
|
(Design / Development / Test / Validation). Read \`{{harnessDocPath}}\` before doing work that
|
|
1967
2280
|
touches requirements, the ERD, or architecture.
|
|
1968
2281
|
|
|
1969
|
-
The role of this session comes from the \`{{serverName}}\` MCP server configuration
|
|
1970
|
-
|
|
1971
|
-
|
|
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,
|
|
1972
2285
|
stop and report which session is required.
|
|
1973
2286
|
${MARKER_END}`;
|
|
1974
2287
|
function managedBlockProblem(key, body) {
|
|
@@ -2020,38 +2333,58 @@ function upsertMarkerBlock(existing, block) {
|
|
|
2020
2333
|
return composed;
|
|
2021
2334
|
}
|
|
2022
2335
|
function assertComposedBlockIsOurs(composed, existing) {
|
|
2023
|
-
if (ownedMarkerCount(composed,
|
|
2336
|
+
if (ownedMarkerCount(composed, "begin") === 1 && ownedMarkerCount(composed, "end") === 1) {
|
|
2024
2337
|
return;
|
|
2025
2338
|
}
|
|
2026
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";
|
|
2027
2340
|
throw new Error(
|
|
2028
|
-
`
|
|
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.`
|
|
2029
2342
|
);
|
|
2030
2343
|
}
|
|
2031
2344
|
function assertNoDanglingMarker(content) {
|
|
2032
|
-
const begins = ownedMarkerCount(content,
|
|
2033
|
-
const ends = ownedMarkerCount(content,
|
|
2345
|
+
const begins = ownedMarkerCount(content, "begin");
|
|
2346
|
+
const ends = ownedMarkerCount(content, "end");
|
|
2034
2347
|
if (begins !== ends || begins > 1) {
|
|
2035
2348
|
throw new Error(
|
|
2036
|
-
`
|
|
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.`
|
|
2037
2350
|
);
|
|
2038
2351
|
}
|
|
2039
2352
|
if (begins === 0) {
|
|
2040
2353
|
return;
|
|
2041
2354
|
}
|
|
2042
|
-
if (
|
|
2355
|
+
if (ownedMarker(content, "end", 0).at < ownedMarker(content, "begin", 0).at) {
|
|
2043
2356
|
throw new Error(
|
|
2044
|
-
`
|
|
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.`
|
|
2045
2358
|
);
|
|
2046
2359
|
}
|
|
2047
2360
|
}
|
|
2048
|
-
function ownedMarkerCount(content,
|
|
2361
|
+
function ownedMarkerCount(content, kind) {
|
|
2049
2362
|
let count = 0;
|
|
2050
|
-
for (let
|
|
2363
|
+
for (let hit = ownedMarker(content, kind, 0); hit !== void 0; hit = ownedMarker(content, kind, hit.at + 1)) {
|
|
2051
2364
|
count += 1;
|
|
2052
2365
|
}
|
|
2053
2366
|
return count;
|
|
2054
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
|
+
}
|
|
2055
2388
|
function isInsideFencedBlock(content, index) {
|
|
2056
2389
|
let open;
|
|
2057
2390
|
let offset = 0;
|
|
@@ -2116,24 +2449,24 @@ function markerEvidence(content) {
|
|
|
2116
2449
|
let begins = 0;
|
|
2117
2450
|
let ends = 0;
|
|
2118
2451
|
while (true) {
|
|
2119
|
-
const begin = rest
|
|
2120
|
-
const close = rest
|
|
2121
|
-
if (begin
|
|
2452
|
+
const begin = anyMarker(rest, "begin", cursor);
|
|
2453
|
+
const close = anyMarker(rest, "end", cursor);
|
|
2454
|
+
if (begin === void 0 && close === void 0) {
|
|
2122
2455
|
break;
|
|
2123
2456
|
}
|
|
2124
|
-
if (begin
|
|
2457
|
+
if (begin === void 0 || close !== void 0 && close.at < begin.at) {
|
|
2125
2458
|
ends += 1;
|
|
2126
|
-
cursor = close +
|
|
2459
|
+
cursor = close.at + close.length;
|
|
2127
2460
|
continue;
|
|
2128
2461
|
}
|
|
2129
|
-
const pairEnd = rest
|
|
2130
|
-
if (pairEnd
|
|
2462
|
+
const pairEnd = anyMarker(rest, "end", begin.at + begin.length);
|
|
2463
|
+
if (pairEnd === void 0) {
|
|
2131
2464
|
begins += 1;
|
|
2132
|
-
cursor = begin +
|
|
2465
|
+
cursor = begin.at + begin.length;
|
|
2133
2466
|
continue;
|
|
2134
2467
|
}
|
|
2135
|
-
evidence.push(blockShape(rest.slice(begin, pairEnd +
|
|
2136
|
-
cursor = pairEnd +
|
|
2468
|
+
evidence.push(blockShape(rest.slice(begin.at, pairEnd.at + pairEnd.length)));
|
|
2469
|
+
cursor = pairEnd.at + pairEnd.length;
|
|
2137
2470
|
}
|
|
2138
2471
|
for (let i = 0; i < begins; i += 1) evidence.push(UNPAIRED_BEGIN);
|
|
2139
2472
|
for (let i = 0; i < ends; i += 1) evidence.push(UNPAIRED_END);
|
|
@@ -2150,15 +2483,15 @@ function existingMarkerBlock(content) {
|
|
|
2150
2483
|
return range ? content.slice(range.start, range.end) : void 0;
|
|
2151
2484
|
}
|
|
2152
2485
|
function markerRange(content) {
|
|
2153
|
-
const start =
|
|
2154
|
-
if (start
|
|
2486
|
+
const start = ownedMarker(content, "begin", 0);
|
|
2487
|
+
if (start === void 0) {
|
|
2155
2488
|
return void 0;
|
|
2156
2489
|
}
|
|
2157
|
-
const endMarker =
|
|
2158
|
-
if (endMarker
|
|
2490
|
+
const endMarker = ownedMarker(content, "end", start.at);
|
|
2491
|
+
if (endMarker === void 0) {
|
|
2159
2492
|
return void 0;
|
|
2160
2493
|
}
|
|
2161
|
-
return { start, end: endMarker +
|
|
2494
|
+
return { start: start.at, end: endMarker.at + endMarker.length };
|
|
2162
2495
|
}
|
|
2163
2496
|
function ownedMarkerIndex(content, marker, from) {
|
|
2164
2497
|
for (let at = content.indexOf(marker, from); at >= 0; at = content.indexOf(marker, at + 1)) {
|
|
@@ -2180,13 +2513,15 @@ function ownedMarkerIndex(content, marker, from) {
|
|
|
2180
2513
|
}
|
|
2181
2514
|
|
|
2182
2515
|
// src/core/harness/initRecord.ts
|
|
2183
|
-
var RECORD_KIND = "
|
|
2516
|
+
var RECORD_KIND = "ainecto/init-record";
|
|
2517
|
+
var LEGACY_RECORD_KIND = "ai-erd/init-record";
|
|
2184
2518
|
var RECORD_VERSION = 2;
|
|
2185
2519
|
var ABSENT = { at: "absent" };
|
|
2186
2520
|
function emptyRecord() {
|
|
2187
2521
|
return { version: RECORD_VERSION, files: {}, createdDirectories: [] };
|
|
2188
2522
|
}
|
|
2189
|
-
function readRecord(content) {
|
|
2523
|
+
function readRecord(content, recordPath = HARNESS_RECORD_PATH) {
|
|
2524
|
+
const HARNESS_RECORD_PATH2 = recordPath;
|
|
2190
2525
|
if (content === void 0) {
|
|
2191
2526
|
return { state: "absent" };
|
|
2192
2527
|
}
|
|
@@ -2194,26 +2529,26 @@ function readRecord(content) {
|
|
|
2194
2529
|
try {
|
|
2195
2530
|
parsed = JSON.parse(content);
|
|
2196
2531
|
} catch {
|
|
2197
|
-
return { state: "corrupt", reason: `${
|
|
2532
|
+
return { state: "corrupt", reason: `${HARNESS_RECORD_PATH2} is not valid JSON` };
|
|
2198
2533
|
}
|
|
2199
2534
|
if (!isObject(parsed)) {
|
|
2200
|
-
return { state: "corrupt", reason: `${
|
|
2535
|
+
return { state: "corrupt", reason: `${HARNESS_RECORD_PATH2} is not a JSON object` };
|
|
2201
2536
|
}
|
|
2202
|
-
if (parsed.kind !== RECORD_KIND) {
|
|
2537
|
+
if (parsed.kind !== RECORD_KIND && parsed.kind !== LEGACY_RECORD_KIND) {
|
|
2203
2538
|
return {
|
|
2204
2539
|
state: "corrupt",
|
|
2205
|
-
reason: `${
|
|
2540
|
+
reason: `${HARNESS_RECORD_PATH2} exists but is not an AiNecto init record (kind=${JSON.stringify(parsed.kind)})`
|
|
2206
2541
|
};
|
|
2207
2542
|
}
|
|
2208
2543
|
if (parsed.version !== RECORD_VERSION) {
|
|
2209
2544
|
return {
|
|
2210
2545
|
state: "legacy",
|
|
2211
|
-
reason: `${
|
|
2546
|
+
reason: `${HARNESS_RECORD_PATH2} was written by another version of this CLI (record version ${JSON.stringify(parsed.version)}, this CLI reads ${RECORD_VERSION})`
|
|
2212
2547
|
};
|
|
2213
2548
|
}
|
|
2214
2549
|
const files = parsed.files;
|
|
2215
2550
|
if (!isObject(files)) {
|
|
2216
|
-
return { state: "corrupt", reason: `${
|
|
2551
|
+
return { state: "corrupt", reason: `${HARNESS_RECORD_PATH2} has no "files" object` };
|
|
2217
2552
|
}
|
|
2218
2553
|
const out = {};
|
|
2219
2554
|
for (const [path, raw] of Object.entries(files)) {
|
|
@@ -2221,14 +2556,14 @@ function readRecord(content) {
|
|
|
2221
2556
|
if ("problem" in read) {
|
|
2222
2557
|
return {
|
|
2223
2558
|
state: "corrupt",
|
|
2224
|
-
reason: `${
|
|
2559
|
+
reason: `${HARNESS_RECORD_PATH2} has an unusable entry for ${path} \u2014 it ${read.problem}`
|
|
2225
2560
|
};
|
|
2226
2561
|
}
|
|
2227
2562
|
out[path] = read.contract;
|
|
2228
2563
|
}
|
|
2229
2564
|
const directories = parsed.createdDirectories;
|
|
2230
2565
|
if (directories !== void 0 && !isStringArray(directories)) {
|
|
2231
|
-
return { state: "corrupt", reason: `${
|
|
2566
|
+
return { state: "corrupt", reason: `${HARNESS_RECORD_PATH2} has a malformed createdDirectories` };
|
|
2232
2567
|
}
|
|
2233
2568
|
return {
|
|
2234
2569
|
state: "valid",
|
|
@@ -2274,10 +2609,10 @@ function legacyManagedRecord(configContent) {
|
|
|
2274
2609
|
return true;
|
|
2275
2610
|
}
|
|
2276
2611
|
const lastWritten = managed.lastWritten;
|
|
2277
|
-
return isObject(lastWritten) && Object.prototype.hasOwnProperty.call(lastWritten,
|
|
2612
|
+
return isObject(lastWritten) && [HARNESS_CONFIG_PATH, `${LEGACY_HARNESS_DIR}/config.json`].some((path) => Object.prototype.hasOwnProperty.call(lastWritten, path));
|
|
2278
2613
|
}
|
|
2279
2614
|
function legacyManagedNotice(configPath) {
|
|
2280
|
-
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 @ai-erd/mcp@0.2 ai-erd init --undo), then run this version again.`;
|
|
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.`;
|
|
2281
2616
|
}
|
|
2282
2617
|
function serializeRecord(record) {
|
|
2283
2618
|
return `${JSON.stringify({
|
|
@@ -2297,7 +2632,7 @@ function readContract(raw) {
|
|
|
2297
2632
|
return { problem: `has an unknown scope (${JSON.stringify(raw.scope)})` };
|
|
2298
2633
|
}
|
|
2299
2634
|
if (typeof raw.createdByUs !== "boolean") {
|
|
2300
|
-
return { problem: "does not say whether
|
|
2635
|
+
return { problem: "does not say whether ainecto created the file" };
|
|
2301
2636
|
}
|
|
2302
2637
|
const original = readOwned(raw.original);
|
|
2303
2638
|
const written = readOwned(raw.written);
|
|
@@ -2305,7 +2640,7 @@ function readContract(raw) {
|
|
|
2305
2640
|
return { problem: "has no readable record of what was there before" };
|
|
2306
2641
|
}
|
|
2307
2642
|
if (written === void 0) {
|
|
2308
|
-
return { problem: "has no readable record of what
|
|
2643
|
+
return { problem: "has no readable record of what ainecto wrote" };
|
|
2309
2644
|
}
|
|
2310
2645
|
if (raw.fileFingerprint !== void 0 && typeof raw.fileFingerprint !== "string") {
|
|
2311
2646
|
return { problem: "has a malformed fileFingerprint" };
|
|
@@ -2390,7 +2725,7 @@ function fieldsContractProblem(contract, ownedFields) {
|
|
|
2390
2725
|
if (!Object.prototype.hasOwnProperty.call(body, key)) {
|
|
2391
2726
|
return `says it wrote ${key}, but that field is not in the file body it recorded`;
|
|
2392
2727
|
}
|
|
2393
|
-
if (
|
|
2728
|
+
if (stableStringify2(body[key]) !== stableStringify2(writtenFields[key])) {
|
|
2394
2729
|
return `records a different value for ${key} than the file body it wrote`;
|
|
2395
2730
|
}
|
|
2396
2731
|
}
|
|
@@ -2418,13 +2753,13 @@ function readOwned(raw) {
|
|
|
2418
2753
|
}
|
|
2419
2754
|
return void 0;
|
|
2420
2755
|
}
|
|
2421
|
-
function
|
|
2756
|
+
function stableStringify2(value) {
|
|
2422
2757
|
if (Array.isArray(value)) {
|
|
2423
|
-
return `[${value.map(
|
|
2758
|
+
return `[${value.map(stableStringify2).join(",")}]`;
|
|
2424
2759
|
}
|
|
2425
2760
|
if (value && typeof value === "object") {
|
|
2426
2761
|
const entries = Object.entries(value).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0);
|
|
2427
|
-
return `{${entries.map(([key, item]) => `${JSON.stringify(key)}:${
|
|
2762
|
+
return `{${entries.map(([key, item]) => `${JSON.stringify(key)}:${stableStringify2(item)}`).join(",")}}`;
|
|
2428
2763
|
}
|
|
2429
2764
|
return JSON.stringify(value) ?? "null";
|
|
2430
2765
|
}
|
|
@@ -2436,7 +2771,188 @@ function isStringArray(value) {
|
|
|
2436
2771
|
}
|
|
2437
2772
|
|
|
2438
2773
|
// src/core/harness/initPlan.ts
|
|
2439
|
-
import { createHash as
|
|
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
|
|
2440
2956
|
var NOTE_TARGETS = [
|
|
2441
2957
|
{ path: "AGENTS.md", createIfMissing: true },
|
|
2442
2958
|
{ path: "CLAUDE.md", createIfMissing: false }
|
|
@@ -2464,10 +2980,10 @@ function sameOwned(left, right) {
|
|
|
2464
2980
|
if (left.at === "text" && right.at === "text") {
|
|
2465
2981
|
return left.text === right.text;
|
|
2466
2982
|
}
|
|
2467
|
-
return left.at === "json" && right.at === "json" &&
|
|
2983
|
+
return left.at === "json" && right.at === "json" && stableStringify2(left.value) === stableStringify2(right.value);
|
|
2468
2984
|
}
|
|
2469
2985
|
function contentFingerprint(content) {
|
|
2470
|
-
return
|
|
2986
|
+
return createHash4("sha256").update(content).digest("hex");
|
|
2471
2987
|
}
|
|
2472
2988
|
function serialize(body) {
|
|
2473
2989
|
return `${JSON.stringify(body, null, 2)}
|
|
@@ -2478,7 +2994,7 @@ function fingerprintFor(path, content) {
|
|
|
2478
2994
|
return contentFingerprint(content);
|
|
2479
2995
|
}
|
|
2480
2996
|
try {
|
|
2481
|
-
return contentFingerprint(
|
|
2997
|
+
return contentFingerprint(stableStringify2(JSON.parse(content)));
|
|
2482
2998
|
} catch {
|
|
2483
2999
|
return contentFingerprint(content);
|
|
2484
3000
|
}
|
|
@@ -2558,7 +3074,7 @@ function planInit(input) {
|
|
|
2558
3074
|
return prior;
|
|
2559
3075
|
}
|
|
2560
3076
|
delete record.files[path];
|
|
2561
|
-
notes.push(`${path}: it changed outside
|
|
3077
|
+
notes.push(`${path}: it changed outside ainecto, so the older backup was dropped and this is the new baseline.`);
|
|
2562
3078
|
return void 0;
|
|
2563
3079
|
};
|
|
2564
3080
|
for (const target of AGENT_TARGETS) {
|
|
@@ -2569,7 +3085,7 @@ function planInit(input) {
|
|
|
2569
3085
|
if (existing === merged.content) {
|
|
2570
3086
|
continue;
|
|
2571
3087
|
}
|
|
2572
|
-
const before = previousRole(existing);
|
|
3088
|
+
const before = previousRole(target.path, existing);
|
|
2573
3089
|
if (merged.replaced && before && before !== input.role) {
|
|
2574
3090
|
notes.push(`${target.path}: role ${before} \u2192 ${input.role}`);
|
|
2575
3091
|
}
|
|
@@ -2625,7 +3141,7 @@ function planInit(input) {
|
|
|
2625
3141
|
}
|
|
2626
3142
|
if (carried?.scope === "block" && unexplainedMarkers(existing, carried) > 0) {
|
|
2627
3143
|
notes.push(
|
|
2628
|
-
`${target.path}: this file has an
|
|
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.`
|
|
2629
3145
|
);
|
|
2630
3146
|
continue;
|
|
2631
3147
|
}
|
|
@@ -2635,7 +3151,7 @@ function planInit(input) {
|
|
|
2635
3151
|
}
|
|
2636
3152
|
const original = prior?.original ?? ownedText(blockNow);
|
|
2637
3153
|
if (prior === void 0 && original.at !== "absent") {
|
|
2638
|
-
notes.push(`${target.path}: an existing
|
|
3154
|
+
notes.push(`${target.path}: an existing ainecto block was saved and will be restored by --undo`);
|
|
2639
3155
|
}
|
|
2640
3156
|
record.files[target.path] = {
|
|
2641
3157
|
scope: "block",
|
|
@@ -2672,11 +3188,11 @@ function planInit(input) {
|
|
|
2672
3188
|
const currentValue = presentNow ? theirConfig[field] : void 0;
|
|
2673
3189
|
const priorOriginal = priorConfig?.originalFields?.[field];
|
|
2674
3190
|
const priorWritten = priorConfig?.writtenFields;
|
|
2675
|
-
const untouchedByUser = priorWritten !== void 0 && hasField(priorWritten, field) &&
|
|
3191
|
+
const untouchedByUser = priorWritten !== void 0 && hasField(priorWritten, field) && stableStringify2(currentValue) === stableStringify2(priorWritten[field]);
|
|
2676
3192
|
const next = untouchedByUser && priorOriginal !== void 0 ? priorOriginal : ownedJson(currentValue, presentNow);
|
|
2677
3193
|
originalFields[field] = next;
|
|
2678
3194
|
if (priorOriginal !== void 0 && !sameOwned(next, priorOriginal)) {
|
|
2679
|
-
notes.push(`${HARNESS_CONFIG_PATH}: you changed ${field} \u2014
|
|
3195
|
+
notes.push(`${HARNESS_CONFIG_PATH}: you changed ${field} \u2014 ainecto now treats your value as the one to put back.`);
|
|
2680
3196
|
}
|
|
2681
3197
|
}
|
|
2682
3198
|
const mergedConfig = serialize({ ...theirConfig ?? {}, ...ourFields });
|
|
@@ -2722,7 +3238,7 @@ function planUndo(input) {
|
|
|
2722
3238
|
const notes = [];
|
|
2723
3239
|
const tail = () => {
|
|
2724
3240
|
notes.push(`Codex keeps its own global config \u2014 remove it with: ${codexRemoveCommand()}`);
|
|
2725
|
-
notes.push(`If you set ${ROLE_ENV_VAR} anywhere, unset it too.`);
|
|
3241
|
+
notes.push(`If you set ${ROLE_ENV_VAR} (or the older ${LEGACY_ROLE_ENV_VAR}) anywhere, unset it too.`);
|
|
2726
3242
|
};
|
|
2727
3243
|
if (state.state === "absent") {
|
|
2728
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.`);
|
|
@@ -2810,7 +3326,7 @@ function undoEntry(path, contract, existing) {
|
|
|
2810
3326
|
if (sameOwned(currentOwned, contract.original)) {
|
|
2811
3327
|
return {
|
|
2812
3328
|
kind: "done",
|
|
2813
|
-
note: contract.original.at === "absent" ? `${path}: the "${SERVER_NAME}" entry
|
|
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.`
|
|
2814
3330
|
};
|
|
2815
3331
|
}
|
|
2816
3332
|
const goneButWeHoldTheOriginal = current === void 0 && contract.original.at !== "absent";
|
|
@@ -2840,13 +3356,13 @@ function undoBlock(path, contract, existing) {
|
|
|
2840
3356
|
if (unexplainedMarkers(existing, contract) > 0) {
|
|
2841
3357
|
return {
|
|
2842
3358
|
kind: "unresolved",
|
|
2843
|
-
note: `${path}: this file has an
|
|
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.`
|
|
2844
3360
|
};
|
|
2845
3361
|
}
|
|
2846
3362
|
if (sameOwned(currentOwned, contract.original)) {
|
|
2847
3363
|
return {
|
|
2848
3364
|
kind: "done",
|
|
2849
|
-
note: contract.original.at === "absent" ? `${path}: the
|
|
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.`
|
|
2850
3366
|
};
|
|
2851
3367
|
}
|
|
2852
3368
|
if (current === void 0 && contract.original.at === "text") {
|
|
@@ -2855,12 +3371,12 @@ function undoBlock(path, contract, existing) {
|
|
|
2855
3371
|
${contract.original.text}
|
|
2856
3372
|
` : `${contract.original.text}
|
|
2857
3373
|
`;
|
|
2858
|
-
return { kind: "write", content: body, note: `${path}: restored the
|
|
3374
|
+
return { kind: "write", content: body, note: `${path}: restored the ainecto block that was there before.` };
|
|
2859
3375
|
}
|
|
2860
3376
|
if (!sameOwned(currentOwned, contract.written)) {
|
|
2861
3377
|
return {
|
|
2862
3378
|
kind: "unresolved",
|
|
2863
|
-
note: `${path}: the
|
|
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.`
|
|
2864
3380
|
};
|
|
2865
3381
|
}
|
|
2866
3382
|
let result;
|
|
@@ -2872,7 +3388,7 @@ ${contract.original.text}
|
|
|
2872
3388
|
if (!result.removed) {
|
|
2873
3389
|
return {
|
|
2874
3390
|
kind: "unresolved",
|
|
2875
|
-
note: `${path}: the
|
|
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.`
|
|
2876
3392
|
};
|
|
2877
3393
|
}
|
|
2878
3394
|
if (result.emptied && contract.createdByUs) {
|
|
@@ -2881,7 +3397,7 @@ ${contract.original.text}
|
|
|
2881
3397
|
return {
|
|
2882
3398
|
kind: "write",
|
|
2883
3399
|
content: result.content,
|
|
2884
|
-
note: contract.original.at === "text" ? `${path}: restored the
|
|
3400
|
+
note: contract.original.at === "text" ? `${path}: restored the ainecto block that was there before.` : void 0
|
|
2885
3401
|
};
|
|
2886
3402
|
}
|
|
2887
3403
|
function undoWhole(path, contract, existing) {
|
|
@@ -2911,14 +3427,14 @@ function undoFields(path, contract, existing) {
|
|
|
2911
3427
|
if (parsed === void 0) {
|
|
2912
3428
|
return {
|
|
2913
3429
|
kind: "unresolved",
|
|
2914
|
-
note: `${path} is not a JSON object any more, so
|
|
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.`
|
|
2915
3431
|
};
|
|
2916
3432
|
}
|
|
2917
3433
|
const contractProblem = fieldsContractProblem(contract, OUR_CONFIG_FIELDS);
|
|
2918
3434
|
if (contractProblem !== void 0) {
|
|
2919
3435
|
return {
|
|
2920
3436
|
kind: "unresolved",
|
|
2921
|
-
note: `${path}:
|
|
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.`
|
|
2922
3438
|
};
|
|
2923
3439
|
}
|
|
2924
3440
|
const written = contract.writtenFields;
|
|
@@ -2933,10 +3449,10 @@ function undoFields(path, contract, existing) {
|
|
|
2933
3449
|
if (!hasField(written, field)) {
|
|
2934
3450
|
return {
|
|
2935
3451
|
kind: "unresolved",
|
|
2936
|
-
note: `${path}:
|
|
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.`
|
|
2937
3453
|
};
|
|
2938
3454
|
}
|
|
2939
|
-
const stillOurs =
|
|
3455
|
+
const stillOurs = stableStringify2(value) === stableStringify2(written[field]);
|
|
2940
3456
|
if (!stillOurs) {
|
|
2941
3457
|
next[field] = value;
|
|
2942
3458
|
kept.push(field);
|
|
@@ -2946,7 +3462,7 @@ function undoFields(path, contract, existing) {
|
|
|
2946
3462
|
if (original === void 0 || original.at !== "json" && original.at !== "absent") {
|
|
2947
3463
|
return {
|
|
2948
3464
|
kind: "unresolved",
|
|
2949
|
-
note: `${path}:
|
|
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.`
|
|
2950
3466
|
};
|
|
2951
3467
|
}
|
|
2952
3468
|
if (original.at === "json") {
|
|
@@ -2961,7 +3477,7 @@ function undoFields(path, contract, existing) {
|
|
|
2961
3477
|
return body === existing ? { kind: "done", note } : {
|
|
2962
3478
|
kind: "write",
|
|
2963
3479
|
content: body,
|
|
2964
|
-
note: note ?? `${path}: removed what
|
|
3480
|
+
note: note ?? `${path}: removed what ainecto wrote and kept the rest.`
|
|
2965
3481
|
};
|
|
2966
3482
|
}
|
|
2967
3483
|
function withCreatedDirectories(current, directories) {
|
|
@@ -2981,27 +3497,98 @@ function parentsOf(paths) {
|
|
|
2981
3497
|
}
|
|
2982
3498
|
return [...directories].sort((left, right) => right.length - left.length);
|
|
2983
3499
|
}
|
|
2984
|
-
function
|
|
2985
|
-
const
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
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) {
|
|
2989
3558
|
continue;
|
|
2990
3559
|
}
|
|
2991
|
-
|
|
3560
|
+
files.set(to, to === HARNESS_RECORD_PATH ? serializeRecord(renamed) : content);
|
|
3561
|
+
moves.push({ from, to });
|
|
2992
3562
|
}
|
|
2993
|
-
return
|
|
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
|
+
};
|
|
2994
3569
|
}
|
|
2995
|
-
function
|
|
2996
|
-
if (
|
|
2997
|
-
return
|
|
2998
|
-
}
|
|
2999
|
-
try {
|
|
3000
|
-
const parsed = JSON.parse(existing);
|
|
3001
|
-
return readRoleFromEntry(parsed.mcpServers?.[SERVER_NAME]);
|
|
3002
|
-
} catch {
|
|
3003
|
-
return void 0;
|
|
3570
|
+
function withLegacyMoves(plan, view) {
|
|
3571
|
+
if (view.moves.length === 0) {
|
|
3572
|
+
return view.notes.length === 0 ? plan : { ...plan, notes: [...view.notes, ...plan.notes] };
|
|
3004
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;
|
|
3005
3592
|
}
|
|
3006
3593
|
|
|
3007
3594
|
// src/core/harness/documentFetch.ts
|
|
@@ -3057,36 +3644,143 @@ function usableBodyProblem(key, entry) {
|
|
|
3057
3644
|
return documentContractProblem(key, entry.body);
|
|
3058
3645
|
}
|
|
3059
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
|
+
|
|
3060
3715
|
// src/adapters/cli/initCommand.ts
|
|
3716
|
+
function singleRole(roles) {
|
|
3717
|
+
return roles.length === 1 ? roles[0] : void 0;
|
|
3718
|
+
}
|
|
3061
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;
|
|
3062
3735
|
const args = parseInitArgs(options.argv);
|
|
3063
|
-
const root =
|
|
3064
|
-
const
|
|
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;
|
|
3065
3741
|
if (args.undo) {
|
|
3066
|
-
|
|
3067
|
-
|
|
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);
|
|
3068
3751
|
options.io.stdout.write(renderSuccess(
|
|
3069
3752
|
{
|
|
3070
3753
|
action: args.dryRun ? "undo (dry-run)" : "undo",
|
|
3071
3754
|
updated: plan2.writes.map((write) => write.path),
|
|
3072
3755
|
removed: plan2.deletes,
|
|
3073
|
-
notes: plan2.notes
|
|
3756
|
+
notes: [...rootNotes, ...plan2.notes]
|
|
3074
3757
|
},
|
|
3075
3758
|
{ json: options.json }
|
|
3076
3759
|
));
|
|
3077
3760
|
return 0;
|
|
3078
3761
|
}
|
|
3079
|
-
const
|
|
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
|
+
})();
|
|
3080
3775
|
if (!role) {
|
|
3081
3776
|
throw new Error(
|
|
3082
|
-
|
|
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>.`
|
|
3083
3778
|
);
|
|
3084
3779
|
}
|
|
3085
|
-
const
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
const project = await resolveProject(options, args, files, root);
|
|
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);
|
|
3090
3784
|
if ("choices" in project) {
|
|
3091
3785
|
options.io.stdout.write(renderSuccess(
|
|
3092
3786
|
{
|
|
@@ -3100,10 +3794,12 @@ async function executeInitCommand(options) {
|
|
|
3100
3794
|
const documents = args.dryRun ? { versions: {}, fallbacks: {}, unavailableReason: "skipped in --dry-run" } : await fetchHarnessDocuments({
|
|
3101
3795
|
endpoint: options.endpoint,
|
|
3102
3796
|
accessToken: await options.accessToken?.(),
|
|
3797
|
+
// ⛔이 이름은 바꾸지 않는다 — 서버가 문서의 클라이언트별 변형 키(HARNESS.DOC.AI_ERD_CLI 등)를 이것으로
|
|
3798
|
+
// 찾는다. 사람에게 안 보이는 서버 쪽 식별자다(역할 헤더·scope 와 같은 이유, 2026-10-11).
|
|
3103
3799
|
client: "ai-erd-cli",
|
|
3104
3800
|
fetchImpl: options.fetchImpl
|
|
3105
3801
|
});
|
|
3106
|
-
const plan = planInit({
|
|
3802
|
+
const plan = withLegacyMoves(planInit({
|
|
3107
3803
|
role,
|
|
3108
3804
|
env: options.env,
|
|
3109
3805
|
projectName: project.name,
|
|
@@ -3112,11 +3808,15 @@ async function executeInitCommand(options) {
|
|
|
3112
3808
|
cliVersion: options.cliVersion,
|
|
3113
3809
|
files,
|
|
3114
3810
|
documents
|
|
3115
|
-
});
|
|
3811
|
+
}), layout);
|
|
3116
3812
|
if (plan.refusal !== void 0) {
|
|
3117
3813
|
throw new Error(plan.refusal);
|
|
3118
3814
|
}
|
|
3119
|
-
await applyPlan(root, plan, args.dryRun,
|
|
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
|
+
}
|
|
3120
3820
|
options.io.stdout.write(renderSuccess(
|
|
3121
3821
|
{
|
|
3122
3822
|
action: args.dryRun ? "init (dry-run)" : "init",
|
|
@@ -3124,42 +3824,45 @@ async function executeInitCommand(options) {
|
|
|
3124
3824
|
project: { uuid: project.uuid, name: project.name },
|
|
3125
3825
|
endpoint: options.endpoint,
|
|
3126
3826
|
written: plan.writes.map((write) => `${write.existed ? "updated" : "created"} ${write.path}`),
|
|
3827
|
+
// 옛 폴더(.ai-erd/)를 옮길 때만 생긴다 — 지운 것을 말하지 않으면 사용자는 옛 파일이 어디 갔는지 모른다.
|
|
3828
|
+
...plan.deletes.length > 0 ? { removed: plan.deletes } : {},
|
|
3127
3829
|
codex: `Codex keeps a single global config, so init does not touch it. Run: ${plan.codexCommand}`,
|
|
3128
3830
|
codexPerSession: plan.codexProfile,
|
|
3129
3831
|
codexEnvFallback: `Without a profile, start the session with ${ROLE_ENV_VAR}=${role}.`,
|
|
3130
3832
|
// ⚠조용히 기본값으로 떨어지지 않는다 — 서버에서 규칙을 고쳐도 안 바뀌는 이유를
|
|
3131
3833
|
// 사용자가 영영 못 찾게 된다. ★요청 자체의 실패와 «문서별» 되돌림을 둘 다 적는다
|
|
3132
3834
|
// (2026-09-23 독립 재리뷰 I5: HTTP 200 으로 온 빈 문서가 조용히 패키지 판이 됐다).
|
|
3133
|
-
notes: [...plan.notes, ...fallbackNotes(documents)],
|
|
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 도구»를 더할 뿐인 선택이다.
|
|
3134
3842
|
next: [
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
"
|
|
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"
|
|
3139
3855
|
]
|
|
3140
3856
|
},
|
|
3141
3857
|
{ json: options.json }
|
|
3142
3858
|
));
|
|
3143
3859
|
return 0;
|
|
3144
3860
|
}
|
|
3145
|
-
async function readRepositoryRole(cwd) {
|
|
3146
|
-
const root = resolve(cwd);
|
|
3147
|
-
const files = /* @__PURE__ */ new Map();
|
|
3148
|
-
for (const target of AGENT_TARGETS) {
|
|
3149
|
-
files.set(target.path, await readManagedFile(root, target.path));
|
|
3150
|
-
}
|
|
3151
|
-
try {
|
|
3152
|
-
return currentRole(files);
|
|
3153
|
-
} catch {
|
|
3154
|
-
return void 0;
|
|
3155
|
-
}
|
|
3156
|
-
}
|
|
3157
3861
|
function currentRole(files) {
|
|
3158
|
-
const
|
|
3159
|
-
const unique = [...new Set(found)];
|
|
3862
|
+
const unique = rolesInFiles(files).roles;
|
|
3160
3863
|
if (unique.length > 1) {
|
|
3161
3864
|
throw new Error(
|
|
3162
|
-
`Agent configs disagree about the session role (${unique.join(", ")}).
|
|
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.`
|
|
3163
3866
|
);
|
|
3164
3867
|
}
|
|
3165
3868
|
return unique[0];
|
|
@@ -3174,13 +3877,108 @@ function fallbackNotes(documents) {
|
|
|
3174
3877
|
}
|
|
3175
3878
|
return notes;
|
|
3176
3879
|
}
|
|
3177
|
-
async function
|
|
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) {
|
|
3178
3973
|
const projects = await listProjects(options);
|
|
3179
3974
|
if (args.projectUuid) {
|
|
3180
3975
|
const found = projects.find((project) => project.uuid === args.projectUuid);
|
|
3181
3976
|
return found ?? { uuid: args.projectUuid, name: args.projectName ?? args.projectUuid };
|
|
3182
3977
|
}
|
|
3183
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
|
+
}
|
|
3184
3982
|
if (bound) {
|
|
3185
3983
|
const found = projects.find((project) => project.uuid === bound);
|
|
3186
3984
|
if (found) {
|
|
@@ -3193,16 +3991,17 @@ async function resolveProject(options, args, files, root) {
|
|
|
3193
3991
|
if (projects.length > 1) {
|
|
3194
3992
|
return { choices: projects };
|
|
3195
3993
|
}
|
|
3994
|
+
if (role !== "design") {
|
|
3995
|
+
throw new Error(noProjectForRole(options, role));
|
|
3996
|
+
}
|
|
3196
3997
|
const name = args.projectName ?? basenameOf(root);
|
|
3197
3998
|
if (!args.yes) {
|
|
3198
3999
|
throw new Error(
|
|
3199
|
-
`No project found in your
|
|
4000
|
+
`No project found in your AiNecto account. Re-run with --yes to create "${name}", or --project-name <name> --yes to pick the name.`
|
|
3200
4001
|
);
|
|
3201
4002
|
}
|
|
3202
4003
|
if (args.dryRun) {
|
|
3203
|
-
throw new Error(
|
|
3204
|
-
`--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>.`
|
|
3205
|
-
);
|
|
4004
|
+
throw new Error(dryRunCannotCreate(name));
|
|
3206
4005
|
}
|
|
3207
4006
|
return createProject(options, name);
|
|
3208
4007
|
}
|
|
@@ -3218,17 +4017,17 @@ async function callOrExplainSignIn(options, run) {
|
|
|
3218
4017
|
throw error;
|
|
3219
4018
|
}
|
|
3220
4019
|
throw new Error(
|
|
3221
|
-
`Not signed in
|
|
4020
|
+
`Not signed in, so ${options.endpoint} refused the request (HTTP 401).
|
|
3222
4021
|
|
|
3223
|
-
${loginCommand(options
|
|
4022
|
+
${loginCommand(options)}
|
|
3224
4023
|
|
|
3225
|
-
Then run init again.
|
|
4024
|
+
Then run init again. Sign-in is one per machine; the role comes from this repository.`
|
|
3226
4025
|
);
|
|
3227
4026
|
}
|
|
3228
4027
|
}
|
|
3229
|
-
function loginCommand(options
|
|
4028
|
+
function loginCommand(options) {
|
|
3230
4029
|
const target = isDefaultEndpointFor(options.env, options.endpoint) ? options.env === "prod" ? "" : ` --env ${options.env}` : ` --endpoint ${options.endpoint}`;
|
|
3231
|
-
return `
|
|
4030
|
+
return `ainecto auth login${target}`;
|
|
3232
4031
|
}
|
|
3233
4032
|
function isUnauthorized(error) {
|
|
3234
4033
|
return error instanceof McpRpcError && error.code === "MCP_HTTP_ERROR" && error.details?.status === 401;
|
|
@@ -3240,14 +4039,16 @@ async function createProject(options, name) {
|
|
|
3240
4039
|
if (!first) {
|
|
3241
4040
|
throw new Error("Project creation did not return a project uuid.");
|
|
3242
4041
|
}
|
|
4042
|
+
options.io.stderr.write(`Created project ${first.name} (${first.uuid}).
|
|
4043
|
+
`);
|
|
3243
4044
|
return first;
|
|
3244
4045
|
}
|
|
3245
4046
|
function unwrapToolJson(result) {
|
|
3246
|
-
if (!
|
|
4047
|
+
if (!isRecord9(result) || !Array.isArray(result.content)) {
|
|
3247
4048
|
return result;
|
|
3248
4049
|
}
|
|
3249
|
-
const first = result.content.find((item) =>
|
|
3250
|
-
if (!
|
|
4050
|
+
const first = result.content.find((item) => isRecord9(item) && item.type === "text");
|
|
4051
|
+
if (!isRecord9(first) || typeof first.text !== "string") {
|
|
3251
4052
|
return result;
|
|
3252
4053
|
}
|
|
3253
4054
|
try {
|
|
@@ -3257,13 +4058,13 @@ function unwrapToolJson(result) {
|
|
|
3257
4058
|
}
|
|
3258
4059
|
}
|
|
3259
4060
|
function extractProjects(value) {
|
|
3260
|
-
const candidates = Array.isArray(value) ? value :
|
|
4061
|
+
const candidates = Array.isArray(value) ? value : isRecord9(value) ? ["projects", "items", "created", "data", "hits"].flatMap((key) => {
|
|
3261
4062
|
const nested = value[key];
|
|
3262
4063
|
return Array.isArray(nested) ? nested : [];
|
|
3263
4064
|
}) : [];
|
|
3264
4065
|
const projects = [];
|
|
3265
4066
|
for (const candidate of candidates) {
|
|
3266
|
-
if (
|
|
4067
|
+
if (isRecord9(candidate) && typeof candidate.uuid === "string") {
|
|
3267
4068
|
projects.push({
|
|
3268
4069
|
uuid: candidate.uuid,
|
|
3269
4070
|
name: typeof candidate.name === "string" ? candidate.name : candidate.uuid
|
|
@@ -3285,52 +4086,11 @@ function boundProjectUuid(config) {
|
|
|
3285
4086
|
}
|
|
3286
4087
|
async function readSnapshot(root) {
|
|
3287
4088
|
const snapshot = /* @__PURE__ */ new Map();
|
|
3288
|
-
for (const path of MANAGED_PATHS) {
|
|
4089
|
+
for (const path of [...MANAGED_PATHS, ...LEGACY_SNAPSHOT_PATHS]) {
|
|
3289
4090
|
snapshot.set(path, await readManagedFile(root, path));
|
|
3290
4091
|
}
|
|
3291
4092
|
return snapshot;
|
|
3292
4093
|
}
|
|
3293
|
-
async function readManagedFile(root, path) {
|
|
3294
|
-
await assertInsideRepository(root, path);
|
|
3295
|
-
return readIfExists(join2(root, path));
|
|
3296
|
-
}
|
|
3297
|
-
async function readIfExists(absolutePath) {
|
|
3298
|
-
try {
|
|
3299
|
-
return await readFile4(absolutePath, "utf8");
|
|
3300
|
-
} catch (error) {
|
|
3301
|
-
if (isRecord8(error) && error.code === "ENOENT") {
|
|
3302
|
-
return void 0;
|
|
3303
|
-
}
|
|
3304
|
-
throw error;
|
|
3305
|
-
}
|
|
3306
|
-
}
|
|
3307
|
-
async function assertInsideRepository(root, relativePath) {
|
|
3308
|
-
const absolutePath = join2(root, relativePath);
|
|
3309
|
-
const realRoot = await realpath(root);
|
|
3310
|
-
let realParent;
|
|
3311
|
-
try {
|
|
3312
|
-
realParent = await realpath(dirname2(absolutePath));
|
|
3313
|
-
} catch (error) {
|
|
3314
|
-
if (isRecord8(error) && error.code === "ENOENT") {
|
|
3315
|
-
return;
|
|
3316
|
-
}
|
|
3317
|
-
throw error;
|
|
3318
|
-
}
|
|
3319
|
-
if (realParent !== realRoot && !realParent.startsWith(realRoot + sep)) {
|
|
3320
|
-
throw new Error(
|
|
3321
|
-
`${relativePath} resolves outside the repository (${realParent}); refusing to touch it.`
|
|
3322
|
-
);
|
|
3323
|
-
}
|
|
3324
|
-
try {
|
|
3325
|
-
if ((await lstat(absolutePath)).isSymbolicLink()) {
|
|
3326
|
-
throw new Error(`${relativePath} is a symbolic link; refusing to write through it.`);
|
|
3327
|
-
}
|
|
3328
|
-
} catch (error) {
|
|
3329
|
-
if (!isRecord8(error) || error.code !== "ENOENT") {
|
|
3330
|
-
throw error;
|
|
3331
|
-
}
|
|
3332
|
-
}
|
|
3333
|
-
}
|
|
3334
4094
|
async function applyPlan(root, plan, dryRun, snapshot, recordFirst = true) {
|
|
3335
4095
|
for (const write of plan.writes) {
|
|
3336
4096
|
await assertInsideRepository(root, write.path);
|
|
@@ -3345,9 +4105,9 @@ async function applyPlan(root, plan, dryRun, snapshot, recordFirst = true) {
|
|
|
3345
4105
|
}
|
|
3346
4106
|
const applied = [];
|
|
3347
4107
|
try {
|
|
3348
|
-
const
|
|
3349
|
-
const recordWrites = plan.writes.filter((write) =>
|
|
3350
|
-
const recordDeletes = plan.deletes.filter((path) =>
|
|
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));
|
|
3351
4111
|
const createdDirectories = [...plan.record?.createdDirectories ?? []];
|
|
3352
4112
|
const rememberDirectories = (directories) => {
|
|
3353
4113
|
for (const directory of directories) {
|
|
@@ -3383,14 +4143,14 @@ async function applyPlan(root, plan, dryRun, snapshot, recordFirst = true) {
|
|
|
3383
4143
|
}
|
|
3384
4144
|
for (const path of deletes) {
|
|
3385
4145
|
await assertUnchangedSinceSnapshot(root, path, snapshot);
|
|
3386
|
-
await rm2(
|
|
4146
|
+
await rm2(join3(root, path), { force: true });
|
|
3387
4147
|
applied.push(path);
|
|
3388
4148
|
}
|
|
3389
4149
|
};
|
|
3390
4150
|
try {
|
|
3391
4151
|
await apply(
|
|
3392
|
-
orderedWrites(plan.writes.filter((write) => !
|
|
3393
|
-
plan.deletes.filter((path) => !
|
|
4152
|
+
orderedWrites(plan.writes.filter((write) => !isRecord10(write.path)), recordFirst),
|
|
4153
|
+
plan.deletes.filter((path) => !isRecord10(path))
|
|
3394
4154
|
);
|
|
3395
4155
|
await recordDirectoriesOrExplain(false);
|
|
3396
4156
|
await apply(recordWrites, recordDeletes);
|
|
@@ -3406,7 +4166,7 @@ Already applied before this failure: ${applied.join(", ") || "(nothing)"}`
|
|
|
3406
4166
|
);
|
|
3407
4167
|
}
|
|
3408
4168
|
for (const directory of plan.removableDirectories ?? []) {
|
|
3409
|
-
await rmdir(
|
|
4169
|
+
await rmdir(join3(root, directory)).catch(() => void 0);
|
|
3410
4170
|
}
|
|
3411
4171
|
}
|
|
3412
4172
|
function orderedWrites(writes, recordFirst) {
|
|
@@ -3418,7 +4178,7 @@ async function assertUnchangedSinceSnapshot(root, relativePath, snapshot) {
|
|
|
3418
4178
|
if (!snapshot.has(relativePath)) {
|
|
3419
4179
|
return;
|
|
3420
4180
|
}
|
|
3421
|
-
const now = await readIfExists(
|
|
4181
|
+
const now = await readIfExists(join3(root, relativePath));
|
|
3422
4182
|
if (now !== snapshot.get(relativePath)) {
|
|
3423
4183
|
throw new Error(
|
|
3424
4184
|
`${relativePath} changed while init was running; nothing was written. Run init again.`
|
|
@@ -3426,8 +4186,8 @@ async function assertUnchangedSinceSnapshot(root, relativePath, snapshot) {
|
|
|
3426
4186
|
}
|
|
3427
4187
|
}
|
|
3428
4188
|
async function writeManagedFile(root, write, onDirectoriesCreated) {
|
|
3429
|
-
const absolutePath =
|
|
3430
|
-
const targetDirectory =
|
|
4189
|
+
const absolutePath = join3(root, write.path);
|
|
4190
|
+
const targetDirectory = dirname3(absolutePath);
|
|
3431
4191
|
const firstCreated = await mkdir2(targetDirectory, { recursive: true });
|
|
3432
4192
|
const created = createdChain(root, firstCreated, targetDirectory);
|
|
3433
4193
|
onDirectoriesCreated?.(created);
|
|
@@ -3444,14 +4204,14 @@ function createdChain(root, firstCreated, targetDirectory) {
|
|
|
3444
4204
|
if (firstCreated === void 0) {
|
|
3445
4205
|
return [];
|
|
3446
4206
|
}
|
|
3447
|
-
const stop =
|
|
4207
|
+
const stop = resolve2(firstCreated);
|
|
3448
4208
|
const made = [];
|
|
3449
|
-
for (let at =
|
|
4209
|
+
for (let at = resolve2(targetDirectory); ; at = dirname3(at)) {
|
|
3450
4210
|
const asRelative = relative(root, at);
|
|
3451
4211
|
if (asRelative && !asRelative.startsWith("..")) {
|
|
3452
|
-
made.push(asRelative.split(
|
|
4212
|
+
made.push(asRelative.split(sep2).join("/"));
|
|
3453
4213
|
}
|
|
3454
|
-
if (at === stop ||
|
|
4214
|
+
if (at === stop || dirname3(at) === at) {
|
|
3455
4215
|
break;
|
|
3456
4216
|
}
|
|
3457
4217
|
}
|
|
@@ -3461,7 +4221,7 @@ async function fileMode(absolutePath) {
|
|
|
3461
4221
|
try {
|
|
3462
4222
|
return (await stat3(absolutePath)).mode & 4095;
|
|
3463
4223
|
} catch (error) {
|
|
3464
|
-
if (
|
|
4224
|
+
if (isRecord9(error) && error.code === "ENOENT") {
|
|
3465
4225
|
return void 0;
|
|
3466
4226
|
}
|
|
3467
4227
|
throw error;
|
|
@@ -3499,43 +4259,53 @@ function requireValue3(flag, value) {
|
|
|
3499
4259
|
}
|
|
3500
4260
|
function basenameOf(path) {
|
|
3501
4261
|
const parts = path.split(/[\\/]/).filter(Boolean);
|
|
3502
|
-
return parts[parts.length - 1] ?? "
|
|
4262
|
+
return parts[parts.length - 1] ?? "ainecto-project";
|
|
3503
4263
|
}
|
|
3504
|
-
function
|
|
4264
|
+
function isRecord9(value) {
|
|
3505
4265
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3506
4266
|
}
|
|
3507
4267
|
|
|
3508
|
-
// src/core/version.ts
|
|
3509
|
-
var CLI_VERSION = true ? "0.3.0" : "dev";
|
|
3510
|
-
var CLI_USER_AGENT = `@ai-erd/mcp ${CLI_VERSION}`;
|
|
3511
|
-
|
|
3512
4268
|
// src/adapters/cli/ainectoCli.ts
|
|
3513
4269
|
async function runAinectoCli(argv, io) {
|
|
3514
|
-
let parsed = { json: argv.includes("--json"), help: false, positionals: [] };
|
|
4270
|
+
let parsed = { json: argv.includes("--json"), help: false, version: false, positionals: [] };
|
|
3515
4271
|
try {
|
|
3516
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
|
+
}
|
|
3517
4277
|
if (parsed.help || parsed.positionals.length === 0) {
|
|
3518
4278
|
io.stdout.write(helpText());
|
|
3519
4279
|
return 0;
|
|
3520
4280
|
}
|
|
3521
4281
|
const resolved = resolveEndpoint({ env: parsed.env, endpoint: parsed.endpoint });
|
|
3522
|
-
|
|
3523
|
-
if (parsed.positionals[0] === "init" && sessionRole === void 0 && !argv.includes("--undo")) {
|
|
3524
|
-
sessionRole = await readRepositoryRole(process.cwd());
|
|
3525
|
-
}
|
|
4282
|
+
const explicitRole = () => resolveRole({ role: parsed.role });
|
|
3526
4283
|
const tokenStore = new FileTokenStore();
|
|
3527
|
-
const
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
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
|
+
};
|
|
3533
4303
|
const [domain, action, ...rest] = parsed.positionals;
|
|
3534
4304
|
if (domain === "mcp") {
|
|
3535
4305
|
await runConnector({
|
|
3536
4306
|
endpoint: resolved.endpoint,
|
|
3537
|
-
//
|
|
3538
|
-
role:
|
|
4307
|
+
// 브리지의 역할은 인자·환경변수에서만 온다 — bin/mcp.ts 와 같은 규칙. 저장소를 적용하지 않는다.
|
|
4308
|
+
role: explicitRole().role ?? null,
|
|
3539
4309
|
input: io.stdin,
|
|
3540
4310
|
output: io.stdout,
|
|
3541
4311
|
errorOutput: io.stderr
|
|
@@ -3545,22 +4315,69 @@ async function runAinectoCli(argv, io) {
|
|
|
3545
4315
|
if (domain === "init") {
|
|
3546
4316
|
return await executeInitCommand({
|
|
3547
4317
|
argv: parsed.positionals.slice(1),
|
|
3548
|
-
role:
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
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,
|
|
3553
4346
|
endpoint: resolved.endpoint,
|
|
3554
4347
|
env: resolved.env,
|
|
3555
|
-
cliVersion:
|
|
4348
|
+
cliVersion: `${PACKAGE_NAME} ${CLI_VERSION}`,
|
|
3556
4349
|
cwd: process.cwd(),
|
|
3557
4350
|
json: parsed.json,
|
|
3558
4351
|
io
|
|
3559
4352
|
});
|
|
3560
4353
|
}
|
|
3561
4354
|
if (domain === "auth") {
|
|
3562
|
-
|
|
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
|
+
}
|
|
3563
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()));
|
|
3564
4381
|
if (isAttachmentsUploadCommand(domain, action)) {
|
|
3565
4382
|
return await executeAttachmentsUploadCommand({
|
|
3566
4383
|
argv: rest,
|
|
@@ -3570,7 +4387,7 @@ async function runAinectoCli(argv, io) {
|
|
|
3570
4387
|
});
|
|
3571
4388
|
}
|
|
3572
4389
|
const generatedResult = await executeGeneratedCommand({
|
|
3573
|
-
|
|
4390
|
+
tools: routeTools,
|
|
3574
4391
|
argv: parsed.positionals,
|
|
3575
4392
|
client,
|
|
3576
4393
|
json: parsed.json,
|
|
@@ -3588,15 +4405,32 @@ async function runAinectoCli(argv, io) {
|
|
|
3588
4405
|
return 1;
|
|
3589
4406
|
}
|
|
3590
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
|
+
}
|
|
3591
4425
|
async function handleAuth(action, auth, endpoint, json, io) {
|
|
3592
4426
|
if (action === "login") {
|
|
3593
4427
|
await auth.login();
|
|
3594
|
-
io.stdout.write(renderSuccess({ endpoint, authenticated: true }, { json }));
|
|
4428
|
+
io.stdout.write(renderSuccess({ ...await nextStep(), endpoint, authenticated: true }, { json }));
|
|
3595
4429
|
return 0;
|
|
3596
4430
|
}
|
|
3597
4431
|
if (action === "status") {
|
|
3598
4432
|
const token = await auth.getAccessToken();
|
|
3599
|
-
io.stdout.write(renderSuccess({ endpoint, authenticated: Boolean(token) }, { json }));
|
|
4433
|
+
io.stdout.write(renderSuccess({ ...await nextStep(), endpoint, authenticated: Boolean(token) }, { json }));
|
|
3600
4434
|
return 0;
|
|
3601
4435
|
}
|
|
3602
4436
|
if (action === "logout") {
|
|
@@ -3606,6 +4440,20 @@ async function handleAuth(action, auth, endpoint, json, io) {
|
|
|
3606
4440
|
}
|
|
3607
4441
|
throw new Error("Expected auth login, auth status, or auth logout.");
|
|
3608
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
|
+
}
|
|
3609
4457
|
async function handleTools(action, rest, parsed, client, io) {
|
|
3610
4458
|
if (action === "list") {
|
|
3611
4459
|
const tools = await client.toolsList();
|
|
@@ -3628,18 +4476,10 @@ async function handleTools(action, rest, parsed, client, io) {
|
|
|
3628
4476
|
io.stdout.write(renderSuccess(result, { json: parsed.json, warnings: payload.warnings }));
|
|
3629
4477
|
return isToolError(result) ? 1 : 0;
|
|
3630
4478
|
}
|
|
3631
|
-
if (action === "catalog") {
|
|
3632
|
-
const catalogEnv = parsed.env ?? "prod";
|
|
3633
|
-
io.stdout.write(renderSuccess(getGeneratedTools(parsed.env ?? "prod"), {
|
|
3634
|
-
json: parsed.json,
|
|
3635
|
-
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."]
|
|
3636
|
-
}));
|
|
3637
|
-
return 0;
|
|
3638
|
-
}
|
|
3639
4479
|
throw new Error("Expected tools list, tools call <mcpName>, or tools catalog.");
|
|
3640
4480
|
}
|
|
3641
4481
|
function parseGlobalArgs(argv) {
|
|
3642
|
-
const result = { json: false, help: false, positionals: [] };
|
|
4482
|
+
const result = { json: false, help: false, version: false, positionals: [] };
|
|
3643
4483
|
for (let index = 0; index < argv.length; index += 1) {
|
|
3644
4484
|
const arg = argv[index];
|
|
3645
4485
|
if (!arg) {
|
|
@@ -3655,6 +4495,8 @@ function parseGlobalArgs(argv) {
|
|
|
3655
4495
|
result.json = true;
|
|
3656
4496
|
} else if (arg === "--help" || arg === "-h") {
|
|
3657
4497
|
result.help = true;
|
|
4498
|
+
} else if (arg === "--version" || arg === "-v" || arg === "-V") {
|
|
4499
|
+
result.version = true;
|
|
3658
4500
|
} else {
|
|
3659
4501
|
result.positionals.push(arg);
|
|
3660
4502
|
}
|
|
@@ -3686,13 +4528,25 @@ function requireValue4(flag, value) {
|
|
|
3686
4528
|
}
|
|
3687
4529
|
return value;
|
|
3688
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");
|
|
3689
4542
|
function helpText() {
|
|
3690
4543
|
return [
|
|
3691
|
-
"
|
|
4544
|
+
"AiNecto CLI",
|
|
4545
|
+
"",
|
|
4546
|
+
NEXT_STEP_INIT,
|
|
3692
4547
|
"",
|
|
3693
4548
|
"Usage:",
|
|
3694
|
-
" ainecto auth login
|
|
3695
|
-
" ainecto auth status|logout [--role <role>] [--env prod|dev] [--json]",
|
|
4549
|
+
" ainecto auth login|status|logout [--env prod|dev] [--json] (one sign-in per machine)",
|
|
3696
4550
|
" ainecto tools list [--env prod|dev] [--endpoint URL] [--json]",
|
|
3697
4551
|
" ainecto tools call <mcpName> [-f payload.json|@-] [inline-json] [--json]",
|
|
3698
4552
|
" ainecto attachments upload --document-uuid <uuid> <file...> [--json]",
|
|
@@ -3700,8 +4554,11 @@ function helpText() {
|
|
|
3700
4554
|
" ainecto mcp [--env prod|dev] [--endpoint URL] [--role design|development|test|validation]",
|
|
3701
4555
|
"",
|
|
3702
4556
|
"Harness:",
|
|
3703
|
-
"
|
|
3704
|
-
"
|
|
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.",
|
|
3705
4562
|
""
|
|
3706
4563
|
].join("\n");
|
|
3707
4564
|
}
|