@ainecto/mcp 0.7.0 → 0.8.1
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 +58 -83
- package/dist/bin/ainecto.js +1173 -885
- package/dist/bin/ainecto.js.map +1 -1
- package/dist/bin/mcp.js +81 -48
- package/dist/bin/mcp.js.map +1 -1
- package/package.json +2 -2
package/dist/bin/ainecto.js
CHANGED
|
@@ -5,34 +5,6 @@ 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 = "ainecto:role:";
|
|
12
|
-
var ROLE_HEADER = "X-AiNecto-Role";
|
|
13
|
-
var ROLE_ENV_VAR = "AINECTO_ROLE";
|
|
14
|
-
function resolveRole(input = {}) {
|
|
15
|
-
if (input.role !== void 0) {
|
|
16
|
-
return { role: assertRole(input.role), source: "flag" };
|
|
17
|
-
}
|
|
18
|
-
const envVars = input.envVars ?? process.env;
|
|
19
|
-
const fromEnv = envVars[ROLE_ENV_VAR];
|
|
20
|
-
if (fromEnv && fromEnv.trim()) {
|
|
21
|
-
return { role: assertRole(fromEnv), source: "env" };
|
|
22
|
-
}
|
|
23
|
-
return { source: "none" };
|
|
24
|
-
}
|
|
25
|
-
function assertRole(value) {
|
|
26
|
-
const normalized = value.trim().toLowerCase();
|
|
27
|
-
if (HARNESS_ROLES.includes(normalized)) {
|
|
28
|
-
return normalized;
|
|
29
|
-
}
|
|
30
|
-
throw new Error(
|
|
31
|
-
`Invalid role "${value}". Expected one of: ${HARNESS_ROLES.join(", ")}.`
|
|
32
|
-
);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// src/core/auth/tokenStore.ts
|
|
36
8
|
var FileTokenStore = class {
|
|
37
9
|
filePath;
|
|
38
10
|
constructor(rootDir = join(homedir(), ".ainecto")) {
|
|
@@ -43,7 +15,6 @@ var FileTokenStore = class {
|
|
|
43
15
|
return file[tokenKey(endpoint)];
|
|
44
16
|
}
|
|
45
17
|
async save(endpoint, token) {
|
|
46
|
-
assertNoRoleScope(token);
|
|
47
18
|
const file = await this.readFile();
|
|
48
19
|
file[tokenKey(endpoint)] = { ...token, endpoint, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
49
20
|
await this.writeFile(file);
|
|
@@ -88,13 +59,6 @@ var FileTokenStore = class {
|
|
|
88
59
|
function tokenKey(endpoint) {
|
|
89
60
|
return createHash("sha256").update(endpoint).digest("hex");
|
|
90
61
|
}
|
|
91
|
-
function assertNoRoleScope(token) {
|
|
92
|
-
if (token.scope?.split(/\s+/).some((scope) => scope.startsWith(ROLE_SCOPE_PREFIX))) {
|
|
93
|
-
throw new Error(
|
|
94
|
-
"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."
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
62
|
function isNodeError(error, code) {
|
|
99
63
|
return error instanceof Error && "code" in error && error.code === code;
|
|
100
64
|
}
|
|
@@ -176,6 +140,9 @@ function assertEnv(value) {
|
|
|
176
140
|
}
|
|
177
141
|
throw new Error(`Invalid --env value "${value}". Expected "prod" or "dev".`);
|
|
178
142
|
}
|
|
143
|
+
function profileSettingsUrl(endpoint) {
|
|
144
|
+
return `${new URL(endpoint).origin}/home/settings`;
|
|
145
|
+
}
|
|
179
146
|
|
|
180
147
|
// src/core/auth/clientRegistration.ts
|
|
181
148
|
async function registerPublicClient(registrationEndpoint, redirectUri, fetchImpl = fetch) {
|
|
@@ -218,6 +185,15 @@ var OAuthLoginError = class extends Error {
|
|
|
218
185
|
}
|
|
219
186
|
code;
|
|
220
187
|
};
|
|
188
|
+
var OAuthReauthRequiredError = class extends Error {
|
|
189
|
+
constructor(oauthError, status) {
|
|
190
|
+
super(`The stored sign-in was rejected by the server (${oauthError}, HTTP ${status}). Sign in again.`);
|
|
191
|
+
this.oauthError = oauthError;
|
|
192
|
+
this.name = "OAuthReauthRequiredError";
|
|
193
|
+
}
|
|
194
|
+
oauthError;
|
|
195
|
+
};
|
|
196
|
+
var REAUTH_REQUIRED_ERRORS = /* @__PURE__ */ new Set(["invalid_grant", "invalid_client", "unauthorized_client"]);
|
|
221
197
|
var OAuthClient = class {
|
|
222
198
|
constructor(options) {
|
|
223
199
|
this.options = options;
|
|
@@ -413,10 +389,32 @@ async function exchangeToken(tokenEndpoint, params, fetchImpl) {
|
|
|
413
389
|
body: new URLSearchParams(params)
|
|
414
390
|
});
|
|
415
391
|
if (!response.ok) {
|
|
416
|
-
|
|
392
|
+
const oauthError = await readOAuthErrorCode(response);
|
|
393
|
+
if (params.grant_type === "refresh_token" && oauthError !== void 0 && REAUTH_REQUIRED_ERRORS.has(oauthError)) {
|
|
394
|
+
throw new OAuthReauthRequiredError(oauthError, response.status);
|
|
395
|
+
}
|
|
396
|
+
throw new Error(`OAuth token exchange failed with HTTP ${response.status}${oauthError ? ` (${oauthError})` : ""}.`);
|
|
417
397
|
}
|
|
418
398
|
return response.json();
|
|
419
399
|
}
|
|
400
|
+
async function readOAuthErrorCode(response) {
|
|
401
|
+
try {
|
|
402
|
+
const body = await response.json();
|
|
403
|
+
return typeof body.error === "string" ? body.error : void 0;
|
|
404
|
+
} catch {
|
|
405
|
+
return void 0;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
async function usableAccessToken(auth) {
|
|
409
|
+
try {
|
|
410
|
+
return await auth.getAccessToken();
|
|
411
|
+
} catch (error) {
|
|
412
|
+
if (error instanceof OAuthReauthRequiredError) {
|
|
413
|
+
return void 0;
|
|
414
|
+
}
|
|
415
|
+
throw error;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
420
418
|
function toStoredToken(endpoint, token, metadata, previous) {
|
|
421
419
|
if (typeof token.access_token !== "string") {
|
|
422
420
|
throw new Error("OAuth token response did not include access_token.");
|
|
@@ -614,12 +612,28 @@ async function readAllStdin(stdin) {
|
|
|
614
612
|
return text;
|
|
615
613
|
}
|
|
616
614
|
|
|
615
|
+
// src/core/harness/legacyRole.ts
|
|
616
|
+
var LEGACY_ROLE_HEADER = "X-AiNecto-Role";
|
|
617
|
+
var LEGACY_ROLE_ENV_VAR = "AINECTO_ROLE";
|
|
618
|
+
var LEGACY_ROLE_NOTICE = "AiNecto session roles were removed; access follows your AiNecto permissions. Re-run `ainecto init` to tidy this repository.";
|
|
619
|
+
function legacyRoleInput(flag, envVars = process.env) {
|
|
620
|
+
const value = flag ?? envVars[LEGACY_ROLE_ENV_VAR];
|
|
621
|
+
return value !== void 0 && value.trim() ? value.trim() : void 0;
|
|
622
|
+
}
|
|
623
|
+
|
|
617
624
|
// src/core/version.ts
|
|
618
|
-
var
|
|
625
|
+
var UNBUNDLED_VERSION = "dev";
|
|
626
|
+
var CLI_VERSION = true ? "0.8.1" : UNBUNDLED_VERSION;
|
|
619
627
|
var PACKAGE_NAME = "@ainecto/mcp";
|
|
620
628
|
var CLI_USER_AGENT = `${PACKAGE_NAME} ${CLI_VERSION}`;
|
|
629
|
+
function bridgePackageSpec(version = CLI_VERSION) {
|
|
630
|
+
return version === UNBUNDLED_VERSION ? PACKAGE_NAME : `${PACKAGE_NAME}@${version}`;
|
|
631
|
+
}
|
|
621
632
|
|
|
622
633
|
// src/core/mcp/rpcClient.ts
|
|
634
|
+
var CHANNEL_HEADER = "X-AiNecto-Channel";
|
|
635
|
+
var AGENT_HEADER = "X-AiNecto-Agent";
|
|
636
|
+
var MAX_AGENT_NAME_LENGTH = 100;
|
|
623
637
|
var McpRpcError = class extends Error {
|
|
624
638
|
constructor(message, code, details) {
|
|
625
639
|
super(message);
|
|
@@ -637,6 +651,14 @@ var McpRpcClient = class {
|
|
|
637
651
|
options;
|
|
638
652
|
fetchImpl;
|
|
639
653
|
nextId = 1;
|
|
654
|
+
agent;
|
|
655
|
+
/**
|
|
656
|
+
* 브리지 전용 — 호스트 에이전트가 보낸 {@code initialize} 의 {@code clientInfo.name}(프로그램 고정 이름).
|
|
657
|
+
* 이후 모든 요청의 {@code X-AiNecto-Agent} 헤더로 싣는다. 직접 명령은 부르지 않는다(누가 셸을 돌렸는지 모른다).
|
|
658
|
+
*/
|
|
659
|
+
useAgent(clientName) {
|
|
660
|
+
this.agent = agentHeaderValue(clientName);
|
|
661
|
+
}
|
|
640
662
|
initialize() {
|
|
641
663
|
return this.request("initialize", {
|
|
642
664
|
protocolVersion: "2024-11-05",
|
|
@@ -706,8 +728,13 @@ var McpRpcClient = class {
|
|
|
706
728
|
if (token) {
|
|
707
729
|
headers.authorization = `Bearer ${token}`;
|
|
708
730
|
}
|
|
709
|
-
|
|
710
|
-
|
|
731
|
+
const legacyRole = printableHeaderValue(this.options.legacyRole);
|
|
732
|
+
if (legacyRole) {
|
|
733
|
+
headers[LEGACY_ROLE_HEADER.toLowerCase()] = legacyRole;
|
|
734
|
+
}
|
|
735
|
+
headers[CHANNEL_HEADER.toLowerCase()] = this.options.channel;
|
|
736
|
+
if (this.agent) {
|
|
737
|
+
headers[AGENT_HEADER.toLowerCase()] = this.agent;
|
|
711
738
|
}
|
|
712
739
|
return this.fetchImpl(this.options.endpoint, {
|
|
713
740
|
method: "POST",
|
|
@@ -719,6 +746,16 @@ var McpRpcClient = class {
|
|
|
719
746
|
function isToolError(result) {
|
|
720
747
|
return isRecord2(result) && result.isError === true;
|
|
721
748
|
}
|
|
749
|
+
function agentHeaderValue(clientName) {
|
|
750
|
+
return printableHeaderValue(clientName);
|
|
751
|
+
}
|
|
752
|
+
function printableHeaderValue(value) {
|
|
753
|
+
if (typeof value !== "string") {
|
|
754
|
+
return void 0;
|
|
755
|
+
}
|
|
756
|
+
const printable = value.replace(/[^\x20-\x7E]/g, "").trim().slice(0, MAX_AGENT_NAME_LENGTH).trim();
|
|
757
|
+
return printable || void 0;
|
|
758
|
+
}
|
|
722
759
|
function isJsonRpcResponse(value) {
|
|
723
760
|
return isRecord2(value) && value.jsonrpc === "2.0" && ("result" in value || "error" in value);
|
|
724
761
|
}
|
|
@@ -892,92 +929,81 @@ var generatedTools = [
|
|
|
892
929
|
|
|
893
930
|
// src/core/catalog/generated.dev.ts
|
|
894
931
|
var generatedTools2 = [
|
|
895
|
-
{ "mcpName": "
|
|
896
|
-
{ "mcpName": "
|
|
897
|
-
{ "mcpName": "
|
|
898
|
-
{ "mcpName": "
|
|
899
|
-
{ "mcpName": "
|
|
900
|
-
{ "mcpName": "
|
|
901
|
-
{ "mcpName": "
|
|
902
|
-
{ "mcpName": "
|
|
903
|
-
{ "mcpName": "
|
|
904
|
-
{ "mcpName": "
|
|
905
|
-
{ "mcpName": "
|
|
906
|
-
{ "mcpName": "
|
|
907
|
-
{ "mcpName": "
|
|
908
|
-
{ "mcpName": "
|
|
909
|
-
{ "mcpName": "
|
|
910
|
-
{ "mcpName": "
|
|
911
|
-
{ "mcpName": "
|
|
912
|
-
{ "mcpName": "
|
|
913
|
-
{ "mcpName": "
|
|
914
|
-
{ "mcpName": "
|
|
915
|
-
{ "mcpName": "
|
|
916
|
-
{ "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": "dev", "schemaHash": "dd64d1f996060ab1" },
|
|
917
|
-
{ "mcpName": "
|
|
918
|
-
{ "mcpName": "mcp__ainecto__erd_get_enum", "commandPath": ["erd", "get-enum"], "group": "erd", "description": "", "inputSchema": { "properties": { "documentUuid": { "description": "ERD document UUID", "type": "string" }, "enumUuid": { "description": "Enum UUID", "type": "string" } }, "required": ["documentUuid", "enumUuid"], "type": "object" }, "required": ["documentUuid", "enumUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "dev", "schemaHash": "1e477b3fadb50c6b" },
|
|
919
|
-
{ "mcpName": "
|
|
920
|
-
{ "mcpName": "
|
|
921
|
-
{ "mcpName": "
|
|
922
|
-
{ "mcpName": "
|
|
923
|
-
{ "mcpName": "
|
|
924
|
-
{ "mcpName": "
|
|
925
|
-
{ "mcpName": "
|
|
926
|
-
{ "mcpName": "
|
|
927
|
-
{ "mcpName": "
|
|
928
|
-
{ "mcpName": "
|
|
929
|
-
{ "mcpName": "
|
|
930
|
-
{ "mcpName": "
|
|
931
|
-
{ "mcpName": "
|
|
932
|
-
{ "mcpName": "
|
|
933
|
-
{ "mcpName": "
|
|
934
|
-
{ "mcpName": "
|
|
935
|
-
{ "mcpName": "
|
|
936
|
-
{ "mcpName": "
|
|
937
|
-
{ "mcpName": "
|
|
938
|
-
{ "mcpName": "
|
|
939
|
-
{ "mcpName": "
|
|
940
|
-
{ "mcpName": "
|
|
941
|
-
{ "mcpName": "
|
|
942
|
-
{ "mcpName": "
|
|
943
|
-
{ "mcpName": "
|
|
944
|
-
{ "mcpName": "
|
|
945
|
-
{ "mcpName": "
|
|
946
|
-
{ "mcpName": "
|
|
947
|
-
{ "mcpName": "
|
|
948
|
-
{ "mcpName": "
|
|
949
|
-
{ "mcpName": "
|
|
950
|
-
{ "mcpName": "
|
|
951
|
-
{ "mcpName": "
|
|
952
|
-
{ "mcpName": "
|
|
953
|
-
{ "mcpName": "
|
|
954
|
-
{ "mcpName": "
|
|
955
|
-
{ "mcpName": "
|
|
956
|
-
{ "mcpName": "
|
|
957
|
-
{ "mcpName": "
|
|
958
|
-
{ "mcpName": "
|
|
959
|
-
{ "mcpName": "
|
|
960
|
-
{ "mcpName": "
|
|
961
|
-
{ "mcpName": "
|
|
962
|
-
{ "mcpName": "
|
|
963
|
-
{ "mcpName": "
|
|
964
|
-
{ "mcpName": "
|
|
965
|
-
{ "mcpName": "
|
|
966
|
-
{ "mcpName": "
|
|
967
|
-
{ "mcpName": "
|
|
968
|
-
{ "mcpName": "
|
|
969
|
-
{ "mcpName": "
|
|
970
|
-
{ "mcpName": "mcp__ainecto__testcase_update_testcases", "commandPath": ["testcase", "update-testcases"], "group": "testcase", "description": "", "inputSchema": { "properties": { "items": { "description": "Updates", "items": { "properties": { "assigneeId": { "description": "Assignee", "type": "string" }, "expectedResult": { "description": "New expected result", "type": "string" }, "id": { "description": "Testcase id", "type": "integer" }, "precondition": { "description": "New precondition", "type": "string" }, "priority": { "description": "Priority", "enum": ["LOW", "MEDIUM", "HIGH", "CRITICAL"], "type": "string" }, "scenarioId": { "description": "New scenario id", "type": "integer" }, "status": { "description": "Status", "enum": ["DRAFT", "READY", "DEPRECATED"], "type": "string" }, "tags": { "description": "Tags", "type": "string" }, "testAccount": { "description": "SUT account", "type": "string" }, "title": { "description": "New title", "type": "string" }, "type": { "description": "Type", "enum": ["FUNCTIONAL", "REGRESSION", "SMOKE", "PERFORMANCE", "SECURITY", "E2E", "UNIT", "INTEGRATION"], "type": "string" } }, "required": ["id"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "dev", "schemaHash": "b562a7b860f6036c" },
|
|
971
|
-
{ "mcpName": "mcp__ainecto__testcase_upsert_step_checks", "commandPath": ["testcase", "upsert-step-checks"], "group": "testcase", "description": "", "inputSchema": { "properties": { "caseId": { "description": "Testcase id", "type": "integer" }, "items": { "description": "Step checks", "items": { "properties": { "checked": { "description": "Checked state", "type": "boolean" }, "comment": { "description": "Per-step note", "type": "string" }, "stepId": { "description": "TestStep id", "type": "integer" } }, "required": ["stepId", "checked"], "type": "object" }, "type": "array" }, "runId": { "description": "Run id", "type": "integer" } }, "required": ["runId", "caseId", "items"], "type": "object" }, "required": ["caseId", "items", "runId"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "dev", "schemaHash": "ac5768125a9fd15c" },
|
|
972
|
-
{ "mcpName": "mcp__ainecto__unarchive_documents", "commandPath": ["documents", "unarchive"], "group": "document", "description": "", "inputSchema": { "properties": { "uuids": { "description": "Document UUIDs", "items": { "type": "string" }, "type": "array" } }, "required": ["uuids"], "type": "object" }, "required": ["uuids"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "dev", "schemaHash": "4aae7df04fba8779" },
|
|
973
|
-
{ "mcpName": "mcp__ainecto__unarchive_folders", "commandPath": ["folders", "unarchive"], "group": "generic", "description": "", "inputSchema": { "properties": { "uuids": { "description": "Folder UUIDs", "items": { "type": "string" }, "type": "array" } }, "required": ["uuids"], "type": "object" }, "required": ["uuids"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "dev", "schemaHash": "e307cab0ebafd179" },
|
|
974
|
-
{ "mcpName": "mcp__ainecto__unarchive_projects", "commandPath": ["projects", "unarchive"], "group": "generic", "description": "", "inputSchema": { "properties": { "uuids": { "description": "Project UUIDs", "items": { "type": "string" }, "type": "array" } }, "required": ["uuids"], "type": "object" }, "required": ["uuids"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "dev", "schemaHash": "15ea4a409d1549de" },
|
|
975
|
-
{ "mcpName": "mcp__ainecto__unarchive_workspaces", "commandPath": ["workspaces", "unarchive"], "group": "workspace", "description": "", "inputSchema": { "properties": { "uuids": { "description": "Workspace UUIDs", "items": { "type": "string" }, "type": "array" } }, "required": ["uuids"], "type": "object" }, "required": ["uuids"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "dev", "schemaHash": "641de5b64ec75ab2" },
|
|
976
|
-
{ "mcpName": "mcp__ainecto__update_documents", "commandPath": ["documents", "update"], "group": "document", "description": "Update documents. If a replacement body is tens of KB or more, prefer request_upload_token (purpose=document.update) and upload the body with curl.", "inputSchema": { "properties": { "items": { "description": "Updates", "items": { "properties": { "body": { "description": "Deprecated compatibility alias for typePayload.body when document type is markdown. \uBCF8\uBB38\uC774 \uC218\uC2ED KB \uC774\uC0C1\uC73C\uB85C \uD06C\uBA74 LLM \uD1A0\uD070 \uC808\uC57D\uC744 \uC704\uD574 `request_upload_token (purpose=document.update)` \uB97C \uD638\uCD9C\uD574 uploadUrl \uC744 \uBC1B\uACE0 curl \uB85C \uC9C1\uC811 PUT \uC73C\uB85C \uBCF4\uB0BC \uAC83.", "type": "string" }, "title": { "description": "New title", "type": "string" }, "typePayload": { "description": "Type-specific payload for enabled document types. markdown: {body}; diagram: {diagramSource,sourceLanguage,edgeStyle,isModule,metadata}; testcase: {defaultEnvironment (max 50 chars), defaultTestAccount (max 100 chars)}; task: no typePayload; board and default statuses are seeded automatically.", "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": "dev", "schemaHash": "778908eec43aba81" },
|
|
977
|
-
{ "mcpName": "mcp__ainecto__update_folders", "commandPath": ["folders", "update"], "group": "generic", "description": "", "inputSchema": { "properties": { "items": { "description": "Updates", "items": { "properties": { "name": { "description": "New name", "type": "string" }, "uuid": { "description": "Folder UUID", "type": "string" } }, "required": ["uuid", "name"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "dev", "schemaHash": "7bef3b4702df1f00" },
|
|
978
|
-
{ "mcpName": "mcp__ainecto__update_projects", "commandPath": ["projects", "update"], "group": "generic", "description": "", "inputSchema": { "properties": { "items": { "description": "Updates", "items": { "properties": { "description": { "description": "New description", "type": "string" }, "name": { "description": "New name", "type": "string" }, "uuid": { "description": "Project UUID", "type": "string" } }, "required": ["uuid"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "dev", "schemaHash": "10cc0d84aeb2192b" },
|
|
979
|
-
{ "mcpName": "mcp__ainecto__update_workspaces", "commandPath": ["workspaces", "update"], "group": "workspace", "description": "", "inputSchema": { "properties": { "items": { "description": "Updates", "items": { "properties": { "description": { "description": "New description", "type": "string" }, "name": { "description": "New name", "type": "string" }, "uuid": { "description": "Workspace UUID", "type": "string" } }, "required": ["uuid"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "dev", "schemaHash": "723e4f6d36ee9d02" },
|
|
980
|
-
{ "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" }
|
|
932
|
+
{ "mcpName": "create_comments", "commandPath": ["comments", "create"], "group": "generic", "description": "Add comments, questions, or replies to a document or a kanban card (cardUuid). Anyone who can read it may comment and reply. Set parentCommentId to reply to a top-level comment. To mention people, get their userId and displayName from list_mention_candidates, write @<displayName> in the body, and list their userIds in mentionUserIds (clientMutationId is then required). Items are applied in order and each one commits on its own; results report each item (ok, or status and error).", "inputSchema": { "additionalProperties": false, "properties": { "cardUuid": { "description": "Kanban card UUID the comments belong to (from kanban_list_cards). Anyone who can read the card's board may comment. Give this or documentUuid, not both.", "type": "string" }, "documentUuid": { "description": "Document UUID the comments belong to. Give this or cardUuid, not both.", "type": "string" }, "items": { "description": "Comments to add, applied in order", "items": { "additionalProperties": false, "properties": { "body": { "description": "Comment text (plain text, 1..10000 characters). To mention someone, include @<displayName> exactly as returned by mcp__ainecto__list_mention_candidates.", "maxLength": 1e4, "minLength": 1, "type": "string" }, "clientMutationId": { "description": "Idempotency key (visible ASCII, at most 128 characters). Required when mentionUserIds is not empty; reuse the same value when retrying the same item so the mention email is not sent twice.", "maxLength": 128, "type": "string" }, "kind": { "description": "Top-level comments only: COMMENT (default) or QUESTION. A QUESTION can later be resolved with mcp__ainecto__resolve_comments.", "enum": ["COMMENT", "QUESTION"], "type": "string" }, "mentionUserIds": { "description": "userIds to mention (from mcp__ainecto__list_mention_candidates). Each one's @<displayName> must appear in body; newly mentioned people get a notification email.", "items": { "type": "string" }, "maxItems": 20, "type": "array", "uniqueItems": true }, "parentCommentId": { "description": "Reply to this top-level comment id (from mcp__ainecto__list_comments). Replies are one level deep: replying to a reply is rejected. The thread's original author and earlier repliers get a notification email (except people mentioned in this reply, who get the mention email instead).", "minimum": 1, "type": "integer" } }, "required": ["body"], "type": "object" }, "minItems": 1, "type": "array" }, "mailLanguage": { "description": "Language of the comment notification email (mention or reply). Defaults to en.", "enum": ["en", "ko"], "type": "string" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "dev", "schemaHash": "d44acdedf00dfe76" },
|
|
933
|
+
{ "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, diagram, markdown, openapi, html, file, kanban.", "enum": ["erd", "diagram", "markdown", "openapi", "html", "file", "kanban"], "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); file: no JSON payload \u2014 bytes go through request_upload_token(purpose=document.create, scopeJson={projectUuid,folderUuid?,type:"file",title,filename}) then one curl PUT (images\xB7PDF\xB7mp4/webm\xB7audio\xB7text/code\xB7CSV only); kanban: no payload \u2014 creates an empty board; add cards with kanban_apply_changes.', "type": "object" } }, "required": ["projectUuid", "type", "title"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "dev", "schemaHash": "65867cbf84bf78a8" },
|
|
934
|
+
{ "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": "dev", "schemaHash": "30f45b887259609f" },
|
|
935
|
+
{ "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": "dev", "schemaHash": "edf5cf688d7c039a" },
|
|
936
|
+
{ "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": "dev", "schemaHash": "d9d1b0d561828b8e" },
|
|
937
|
+
{ "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": "dev", "schemaHash": "b22c24bffdfe3d7d" },
|
|
938
|
+
{ "mcpName": "delete_comments", "commandPath": ["comments", "delete"], "group": "generic", "description": "Delete your own comments on a document or a kanban card (cardUuid). Only the author can delete. A resolved question and a top-level comment that still has replies cannot be deleted. Each id is applied on its own; results report each id.", "inputSchema": { "additionalProperties": false, "properties": { "cardUuid": { "description": "Kanban card UUID the comments belong to (from kanban_list_cards). Anyone who can read the card's board may comment. Give this or documentUuid, not both.", "type": "string" }, "documentUuid": { "description": "Document UUID the comments belong to. Give this or cardUuid, not both.", "type": "string" }, "ids": { "description": "Comment ids to delete (from mcp__ainecto__list_comments)", "items": { "minimum": 1, "type": "integer" }, "minItems": 1, "type": "array" } }, "required": ["ids"], "type": "object" }, "required": ["ids"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "dev", "schemaHash": "082e4b4d9ba97fe0" },
|
|
939
|
+
{ "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": "dev", "schemaHash": "3a725ae70956c270" },
|
|
940
|
+
{ "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": "dev", "schemaHash": "2254cde89c9e8f06" },
|
|
941
|
+
{ "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": "dev", "schemaHash": "6521c33a28cb5465" },
|
|
942
|
+
{ "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": "dev", "schemaHash": "3177868221137066" },
|
|
943
|
+
{ "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": "Omit (null) for a plain node-to-node edge. Set only when a group is an endpoint: GROUP_TO_GROUP|NODE_TO_GROUP|GROUP_TO_NODE", "enum": ["GROUP_TO_GROUP", "NODE_TO_GROUP", "GROUP_TO_NODE"], "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": "dev", "schemaHash": "2c829103f86f4168" },
|
|
944
|
+
{ "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": "dev", "schemaHash": "2c03c4f7daa69898" },
|
|
945
|
+
{ "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": "dev", "schemaHash": "55c5aa27b8cf489d" },
|
|
946
|
+
{ "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": "dev", "schemaHash": "646d553238328a9e" },
|
|
947
|
+
{ "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": "dev", "schemaHash": "2143d616cf7822bf" },
|
|
948
|
+
{ "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": "dev", "schemaHash": "4645085bde616381" },
|
|
949
|
+
{ "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": "dev", "schemaHash": "8a7ca45dd34d1aef" },
|
|
950
|
+
{ "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": "dev", "schemaHash": "d444066b5ca6eeb4" },
|
|
951
|
+
{ "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": "dev", "schemaHash": "870ace32642a770a" },
|
|
952
|
+
{ "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": "dev", "schemaHash": "9ad96e3dc5fd2a8f" },
|
|
953
|
+
{ "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": "dev", "schemaHash": "bd1b310200b78420" },
|
|
954
|
+
{ "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": "dev", "schemaHash": "18559d7c0af09d2c" },
|
|
955
|
+
{ "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": "dev", "schemaHash": "87f5bcf5a253815a" },
|
|
956
|
+
{ "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": "dev", "schemaHash": "651fd0d54e3635ff" },
|
|
957
|
+
{ "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": "dev", "schemaHash": "f0d79d7a1996939c" },
|
|
958
|
+
{ "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": "dev", "schemaHash": "5b47fe7d99e60d4d" },
|
|
959
|
+
{ "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": "dev", "schemaHash": "0dc13648bf3fbf8f" },
|
|
960
|
+
{ "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": "dev", "schemaHash": "3db246dfe8663370" },
|
|
961
|
+
{ "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": "dev", "schemaHash": "4d1f602fa487a0e4" },
|
|
962
|
+
{ "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": "dev", "schemaHash": "307d766c777bbe8c" },
|
|
963
|
+
{ "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": "dev", "schemaHash": "775bff53bbc21c9e" },
|
|
964
|
+
{ "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": "dev", "schemaHash": "34324dfc7ef461c8" },
|
|
965
|
+
{ "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": "dev", "schemaHash": "1e0e7f1bf33b669c" },
|
|
966
|
+
{ "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); file \u2192 file {filename, contentType, sizeBytes, viewerKind} + content for text/CSV (first 64 KB); diagram \u2192 diagramSource/sourceLanguage/edgeStyle/isModule/metadata; kanban \u2192 board {viewer, columns: each column's first 20 cards + hasMore} (more cards and card details via kanban_list_cards); 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": "dev", "schemaHash": "5c8e9d305fc15c1f" },
|
|
967
|
+
{ "mcpName": "get_my_work_summary", "commandPath": ["my-work-summary", "get"], "group": "generic", "description": `One-call summary of what is waiting for the signed-in user in AiNecto \u2014 call it when the user asks things like "do I have anything to do?", "anything new?", "anything to review?" or "did anyone ask me something?". Returns notifications {unread (not yet read by the person, newest 100 counted; unreadCapped when there are more), unreadByKind, newForThisConnection (not yet acknowledged by this connection \u2014 the same number as the line at the end of tool results)}, mentionsToRead {items the person has not read that mention them, each with target title and url}, and todos {open, leaseExpired, inProgress, items: questions and requests waiting for the user's agent} and cards {kanban cards assigned to the user on boards they can read now: assignedOpen, pendingAcceptance (assigned but not yet accepted or declined), inProgress, inReview, blocked, items: the first page}. For details use list_notifications, list_agent_todos and kanban_list_cards; to read the document and its comments use get_document with the target uuid. Read-only: nothing is marked read or acknowledged.`, "inputSchema": { "additionalProperties": false, "properties": { "projectUuid": { "description": "Older single-value form of projectUuids.", "type": "string" }, "projectUuids": { "description": "Only items in any of these projects. Use it when the user names a project or the project you are working in is clear. With workspaceUuids too, both must match. A workspace or project you cannot see matches nothing.", "items": { "type": "string" }, "type": "array", "uniqueItems": true }, "workspaceUuids": { "description": "Only items in any of these workspaces.", "items": { "type": "string" }, "type": "array", "uniqueItems": true } }, "type": "object" }, "required": [], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "dev", "schemaHash": "105cc4ba01f785d8" },
|
|
968
|
+
{ "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": "dev", "schemaHash": "192403c3484ac774" },
|
|
969
|
+
{ "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": "dev", "schemaHash": "4e308c1a8c7203d1" },
|
|
970
|
+
{ "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": "dev", "schemaHash": "db6d1b4879d57c56" },
|
|
971
|
+
{ "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": "dev", "schemaHash": "f29c86804eb91700" },
|
|
972
|
+
{ "mcpName": "kanban_apply_changes", "commandPath": ["kanban", "apply-changes"], "group": "kanban", "description": "Create, edit, reorder and link cards on one kanban board in a single atomic call (at most 100 operations; if one fails nothing is applied and the error carries operationIndex and code). Creating and editing cards needs edit access to the board; the assignee may also check acceptance criteria and add result links (DELIVERABLE, REVIEW_RESULT, TEST_EVIDENCE). To change a card's column, assign it, accept, approve, block, watch and similar, use kanban_card_action. Results list each operation's card uuid, revision, status and sortKey.", "inputSchema": { "additionalProperties": false, "properties": { "boardUuid": { "description": "Kanban board (document) UUID.", "type": "string" }, "mailLanguage": { "description": "Language of the emails these operations may send (review requests to newly named reviewers). Defaults to en.", "enum": ["en", "ko"], "type": "string" }, "operations": { "description": "Operations, applied in order in one transaction. If any fails, none is applied and the error names operationIndex.", "items": { "additionalProperties": false, "properties": { "afterCardUuid": { "description": "Place after this card in the column (create, move).", "type": "string" }, "beforeCardUuid": { "description": "move: place before this card in the column.", "type": "string" }, "cardType": { "description": "Card type (default TASK).", "enum": ["TASK", "REVIEW", "BUG", "DECISION"], "type": "string" }, "cardUuid": { "description": "Target card (update, move, link, unlink). It must be on this board.", "type": "string" }, "clientMutationId": { "description": "create: idempotency key \u2014 retrying with the same value returns the same card instead of creating another.", "type": "string" }, "criteria": { "description": "Acceptance criteria. update replaces the whole list; the assignee may only flip done.", "items": { "additionalProperties": false, "properties": { "done": { "description": "Checked (default false).", "type": "boolean" }, "text": { "description": "What must be true.", "type": "string" } }, "required": ["text"], "type": "object" }, "type": "array" }, "description": { "description": "Markdown description. Keep it short \u2014 link longer text as a document instead.", "type": "string" }, "documentUuid": { "description": "AiNecto document to link (you must be able to read it). Give this or url.", "type": "string" }, "dueAt": { "description": "Due time, ISO-8601 instant such as 2026-10-31T09:00:00Z.", "type": "string" }, "expectedRevision": { "description": "The card revision you last read. If the card changed since, the whole call fails with CARD_CHANGED and the current card. Send it when people may edit the card too.", "minimum": 0, "type": "integer" }, "label": { "description": "Optional label for an external URL.", "type": "string" }, "linkId": { "description": "unlink: the linkId from the card's links.", "minimum": 1, "type": "integer" }, "linkRole": { "description": "Link role. The assignee may add DELIVERABLE, REVIEW_RESULT and TEST_EVIDENCE links; other roles need edit access to the board.", "enum": ["REQUIREMENT", "REFERENCE", "DELIVERABLE", "REVIEW_RESULT", "DECISION", "TEST_EVIDENCE"], "type": "string" }, "links": { "description": "create only: links to attach.", "items": { "additionalProperties": false, "properties": { "documentUuid": { "description": "AiNecto document to link (you must be able to read it). Give this or url.", "type": "string" }, "label": { "description": "Optional label for an external URL.", "type": "string" }, "linkRole": { "description": "Link role. The assignee may add DELIVERABLE, REVIEW_RESULT and TEST_EVIDENCE links; other roles need edit access to the board.", "enum": ["REQUIREMENT", "REFERENCE", "DELIVERABLE", "REVIEW_RESULT", "DECISION", "TEST_EVIDENCE"], "type": "string" }, "url": { "description": "External http(s) URL to link, e.g. a pull request. Give this or documentUuid.", "type": "string" } }, "required": ["linkRole"], "type": "object" }, "type": "array" }, "op": { "description": "create: a new card in To Do (you become its requester). update: change only the fields you send (send null to clear description, dueAt). move: reorder within the card's column. link / unlink: attach or remove a document or an external URL.", "enum": ["create", "link", "move", "unlink", "update"], "type": "string" }, "priority": { "description": "Priority (default MEDIUM).", "enum": ["LOW", "MEDIUM", "HIGH", "URGENT"], "type": "string" }, "responderIds": { "description": "update only: who should answer while the card is blocked.", "items": { "type": "string" }, "type": "array", "uniqueItems": true }, "reviewerIds": { "description": "Reviewers (userIds from list_mention_candidates with cardUuid or the board's documentUuid). update replaces the whole list.", "items": { "type": "string" }, "type": "array", "uniqueItems": true }, "title": { "description": "Card title (required for create).", "type": "string" }, "url": { "description": "External http(s) URL to link, e.g. a pull request. Give this or documentUuid.", "type": "string" }, "watcherIds": { "description": "Watchers who get activity notifications. update replaces the whole list.", "items": { "type": "string" }, "type": "array", "uniqueItems": true } }, "required": ["op"], "type": "object" }, "maxItems": 100, "minItems": 1, "type": "array" } }, "required": ["boardUuid", "operations"], "type": "object" }, "required": ["boardUuid", "operations"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "dev", "schemaHash": "77de7bc105852b87" },
|
|
973
|
+
{ "mcpName": "kanban_card_action", "commandPath": ["kanban", "card-action"], "group": "kanban", "description": "Do one workflow action on a kanban card, the same as the buttons in the web app. \u2605Before assign (to someone other than the signed-in user), approve or quick_done, show the user what will happen and get their explicit confirmation \u2014 these change other people's work and notify them. Actions: assign / unassign; accept / decline (only the assignee \u2014 accepting closes the assignment to-do); start (To Do \u2192 In Progress; anyone who can edit the board, or the assignee \u2014 not while an assignment is waiting to be accepted); request_review (\u2192 In Review); approve (\u2192 Done) / request_changes (\u2192 In Progress) by the requester or a reviewer; quick_done (To Do \u2192 Done); reopen (Done \u2192 In Progress); block / unblock; archive / unarchive; set_requester; watch / unwatch (yourself). Doing the action is what closes the matching card to-dos \u2014 update_agent_todos cannot complete them. Returns the card after the action (status, assignment state, allowedActions) and reasonCommentId when a reason was saved.", "inputSchema": { "additionalProperties": false, "properties": { "action": { "description": "The action. Which ones you may do now is the card's allowedActions in kanban_list_cards (with cardUuids).", "enum": ["assign", "unassign", "accept", "decline", "start", "request_review", "approve", "request_changes", "quick_done", "reopen", "block", "unblock", "archive", "unarchive", "set_requester", "watch", "unwatch"], "type": "string" }, "assigneeUserId": { "description": "assign: who gets the card \u2014 someone who can read the board (list_mention_candidates with cardUuid). Your own userId assigns it to you and accepts at once.", "type": "string" }, "cardUuid": { "description": "Card UUID (from kanban_list_cards).", "type": "string" }, "expectedRevision": { "description": "The card revision you last read. If the card changed since, the call fails with CARD_CHANGED and the current card. Send it when people may act on the card too.", "minimum": 0, "type": "integer" }, "mailLanguage": { "description": "Language of the email this action may send (assignment or review request). Defaults to en.", "enum": ["en", "ko"], "type": "string" }, "reason": { "description": "Saved as a comment on the card. Required when the action's allowedActions entry says reasonRequired (e.g. decline, reopen).", "type": "string" }, "requesterUserId": { "description": "set_requester: the new requester.", "type": "string" }, "responderIds": { "description": "block: who should answer (people who can read the board). Omit to keep the card's current responders.", "items": { "type": "string" }, "type": "array", "uniqueItems": true } }, "required": ["cardUuid", "action"], "type": "object" }, "required": ["action", "cardUuid"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "dev", "schemaHash": "77da9c03fd1ce5f4" },
|
|
974
|
+
{ "mcpName": "kanban_list_cards", "commandPath": ["kanban", "list-cards"], "group": "kanban", "description": "Read kanban cards. Three ways: cardUuids \u2192 those cards in full, including allowedActions (what you may do now with kanban_card_action) and links; boardUuid \u2192 the board's columns To Do, In Progress, In Review, Done (each with hasMore and nextAfter); neither \u2192 cards across all boards you can read where you are the assignee (or requester / watcher), most recently updated first. Each card has uuid, title, status, assignee and assignmentState (PENDING = not yet accepted), blocked, revision and allowedTransitions.", "inputSchema": { "additionalProperties": false, "properties": { "boardUuid": { "description": "Board (document) UUID: list its columns, first page each (or one column with status).", "type": "string" }, "cardUuids": { "description": "Read these cards in full (description, people, links, allowedActions). Use alone.", "items": { "type": "string" }, "maxItems": 50, "minItems": 1, "type": "array", "uniqueItems": true }, "cursor": { "description": "Next page: nextCursor (without boardUuid) or a column's nextAfter (with boardUuid and status).", "type": "string" }, "includeDone": { "description": "Without boardUuid: also recently completed cards.", "type": "boolean" }, "limit": { "description": "Cards per page (per column with boardUuid). Default 20.", "maximum": 50, "minimum": 1, "type": "integer" }, "projectUuid": { "description": "Without boardUuid: only boards in this project.", "type": "string" }, "relation": { "description": "Without boardUuid: cards across boards where you are the assignee (default), the requester or a watcher.", "enum": ["ASSIGNEE", "REQUESTER", "WATCHER"], "type": "string" }, "status": { "description": "With boardUuid: only this column.", "enum": ["TODO", "IN_PROGRESS", "IN_REVIEW", "DONE"], "type": "string" } }, "type": "object" }, "required": [], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "dev", "schemaHash": "99ed9634a628eb3f" },
|
|
975
|
+
{ "mcpName": "list_agent_todos", "commandPath": ["agent-todos", "list"], "group": "generic", "description": "List the to-dos that ask the signed-in user's agent to act now (for example a question that mentions the user). This is the current state, not a feed: a to-do you have not finished comes back on every call until it is completed, dismissed, or closed because the person did it themselves. Each item has todoId, kind, obligation, openedAt, summary, target (title and url), action and state. To work on one, claim it with update_agent_todos and complete it with a one-line note. Actions that affect other people (assigning, approving, inviting) need the user's confirmation first.", "inputSchema": { "additionalProperties": false, "properties": { "after": { "description": "todoId of the last item of the previous page.", "type": "string" }, "kinds": { "description": "Only these kinds.", "items": { "enum": ["COMMENT_MENTIONED", "COMMENT_REPLIED", "QUESTION_ANSWERED", "QUESTION_RESOLVED", "QUESTION_MENTIONED", "CARD_CREATED", "CARD_UPDATED", "CARD_ASSIGNED", "CARD_UNASSIGNED", "CARD_ASSIGNMENT_ACCEPTED", "CARD_ASSIGNMENT_DECLINED", "CARD_STATUS_CHANGED", "CARD_REVIEW_REQUESTED", "CARD_CHANGES_REQUESTED", "CARD_APPROVED", "CARD_QUICK_DONE", "CARD_REOPENED", "CARD_BLOCKED", "CARD_UNBLOCKED", "CARD_ARCHIVED", "CARD_COMMENTED"], "type": "string" }, "type": "array", "uniqueItems": true }, "limit": { "description": "Items per call, default 20.", "maximum": 50, "minimum": 1, "type": "integer" }, "projectUuid": { "description": "Older single-value form of projectUuids.", "type": "string" }, "projectUuids": { "description": "Only to-dos in any of these projects. Use it when the user names a project or the project you are working in is clear. With workspaceUuids too, both must match. A workspace or project you cannot see matches nothing.", "items": { "type": "string" }, "type": "array", "uniqueItems": true }, "state": { "description": "OPEN (default): to-dos you can take, including ones whose lease expired. MINE: to-dos currently leased (claimed) and not expired.", "items": { "enum": ["OPEN", "MINE"], "type": "string" }, "type": "array", "uniqueItems": true }, "workspaceUuids": { "description": "Only to-dos in any of these workspaces.", "items": { "type": "string" }, "type": "array", "uniqueItems": true } }, "type": "object" }, "required": [], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "dev", "schemaHash": "ee1a536fc14ee727" },
|
|
976
|
+
{ "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": "dev", "schemaHash": "fae7152d1e4a930b" },
|
|
977
|
+
{ "mcpName": "list_comments", "commandPath": ["comments", "list"], "group": "generic", "description": "List the active comments on a document or a kanban card (cardUuid), oldest first, including replies (a reply has parentCommentId set; replies are one level deep). Each comment has id, kind (COMMENT or QUESTION), body, author, mentions (userId, label and position in the body), and for questions questionState (UNANSWERED, ANSWERED, RESOLVED) and resolvedAt. Anyone who can read it can list its comments.", "inputSchema": { "additionalProperties": false, "properties": { "cardUuid": { "description": "Kanban card UUID the comments belong to (from kanban_list_cards). Anyone who can read the card's board may comment. Give this or documentUuid, not both.", "type": "string" }, "documentUuid": { "description": "Document UUID the comments belong to. Give this or cardUuid, not both.", "type": "string" } }, "type": "object" }, "required": [], "payloadMode": "flags", "destructive": false, "sourceCatalog": "dev", "schemaHash": "21dbfc4740a62df5" },
|
|
978
|
+
{ "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, diagram, markdown, openapi, html, file, kanban.", "enum": ["erd", "diagram", "markdown", "openapi", "html", "file", "kanban"], "type": "string" } }, "type": "object" }, "required": [], "payloadMode": "flags", "destructive": false, "sourceCatalog": "dev", "schemaHash": "0d7288c249fc715c" },
|
|
979
|
+
{ "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": "dev", "schemaHash": "461dc6735ac92c3b" },
|
|
980
|
+
{ "mcpName": "list_history", "commandPath": ["history", "list"], "group": "generic", "description": "List recent activity (who did what, people and agents) in a workspace, a project, or on one resource, newest first. Give at least one of workspaceUuid, projectUuid, or resourceType with resourceUuid; you need editor access or higher in that scope. This is a list to refresh, not a gap-free feed: to see what is new, read the first page again and skip ids you have already seen; to get each new notification exactly once use list_notifications. Each item has id, kind, at, lastAt, coalesceCount, actor, target (title and url only when you can read it now), changes and summary.", "inputSchema": { "additionalProperties": false, "properties": { "actorType": { "description": "Only people (USER) or only agents (AGENT).", "enum": ["USER", "AGENT"], "type": "string" }, "before": { "description": "nextCursor of the previous page (older items).", "type": "string" }, "kinds": { "description": "Only these kinds.", "items": { "enum": ["COMMENT_MENTIONED", "COMMENT_REPLIED", "QUESTION_ANSWERED", "QUESTION_RESOLVED", "QUESTION_MENTIONED", "CARD_CREATED", "CARD_UPDATED", "CARD_ASSIGNED", "CARD_UNASSIGNED", "CARD_ASSIGNMENT_ACCEPTED", "CARD_ASSIGNMENT_DECLINED", "CARD_STATUS_CHANGED", "CARD_REVIEW_REQUESTED", "CARD_CHANGES_REQUESTED", "CARD_APPROVED", "CARD_QUICK_DONE", "CARD_REOPENED", "CARD_BLOCKED", "CARD_UNBLOCKED", "CARD_ARCHIVED", "CARD_COMMENTED"], "type": "string" }, "type": "array", "uniqueItems": true }, "limit": { "description": "Items per call, default 20.", "maximum": 50, "minimum": 1, "type": "integer" }, "projectUuid": { "description": "Activity in this project.", "type": "string" }, "resourceType": { "description": "With resourceUuid: activity on one resource.", "enum": ["DOCUMENT", "PROJECT", "KANBAN_CARD"], "type": "string" }, "resourceUuid": { "description": "UUID of the resource named by resourceType.", "type": "string" }, "workspaceUuid": { "description": "Activity in this workspace.", "type": "string" } }, "type": "object" }, "required": [], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "dev", "schemaHash": "eef3e3be41cfb6b4" },
|
|
981
|
+
{ "mcpName": "list_mention_candidates", "commandPath": ["mention-candidates", "list"], "group": "generic", "description": "List the people you can mention in comments on a document or a kanban card (cardUuid): userId and displayName. Only people who can read it are listed \u2014 for a card, the people who can read its board, which are also the people a card can be assigned to. To mention someone, write @<displayName> exactly in the comment body and pass their userId in mentionUserIds of create_comments or update_comments. Page with nextCursor while hasMore is true.", "inputSchema": { "additionalProperties": false, "properties": { "cardUuid": { "description": "Kanban card UUID the comments belong to (from kanban_list_cards). Anyone who can read the card's board may comment. Give this or documentUuid, not both.", "type": "string" }, "cursor": { "description": "nextCursor from the previous page (same parent and query).", "type": "string" }, "documentUuid": { "description": "Document UUID the comments belong to. Give this or cardUuid, not both.", "type": "string" }, "limit": { "description": "Page size, 1..50. Defaults to 20.", "maximum": 50, "minimum": 1, "type": "integer" }, "query": { "description": "Name filter, 0..100 characters. Omit or leave empty for the first page of everyone.", "maxLength": 100, "type": "string" } }, "type": "object" }, "required": [], "payloadMode": "flags", "destructive": false, "sourceCatalog": "dev", "schemaHash": "e7585805859ccb73" },
|
|
982
|
+
{ "mcpName": "list_notifications", "commandPath": ["notifications", "list"], "group": "generic", "description": "Read new notifications for the signed-in user, oldest first and without gaps \u2014 for an agent that checks the inbox periodically. The server remembers, per connection and per filter (kinds, workspaceUuids, projectUuids, directOnly), how far you have acknowledged; a new filter starts from now. Each response carries ackToken: pass it as ack in your next call (with the same filter) after you have handled the batch \u2014 if you do not, the next call returns the same batch again (identify duplicates by id and seq; an item that comes back with a higher seq and a larger groupCount is an update of the same entry). Each item has id, seq, kind, at, summary (one line), actor, target (title and url only when you can read it now), action (what the user is asked to do) and humanRead. An item you can no longer read comes back as {id, seq, at, redacted: true}. This tool never marks anything read for the person; open to-dos are in list_agent_todos. Calls closer than 30 seconds apart (except a call that carries ack) return retryAfterSeconds instead of items.", "inputSchema": { "additionalProperties": false, "properties": { "ack": { "description": "The ackToken of the previous response. Send it with the same filter once you have handled (or safely stored) that batch; only then does the saved position move past it. Without it, the same batch is returned again.", "type": "string" }, "cursor": { "description": 'Where to read from. "saved" (default): after what this connection last acknowledged for this same filter. "latest": from now on (moves the saved position to now). "earliest": everything still kept (90 days). Or a nextCursor from a previous response: read after it without using or changing the saved position.', "type": "string" }, "directOnly": { "description": "Only notifications that point at you directly (mentions, questions to you).", "type": "boolean" }, "kinds": { "description": "Only these notification kinds.", "items": { "enum": ["COMMENT_MENTIONED", "COMMENT_REPLIED", "QUESTION_ANSWERED", "QUESTION_RESOLVED", "QUESTION_MENTIONED", "CARD_CREATED", "CARD_UPDATED", "CARD_ASSIGNED", "CARD_UNASSIGNED", "CARD_ASSIGNMENT_ACCEPTED", "CARD_ASSIGNMENT_DECLINED", "CARD_STATUS_CHANGED", "CARD_REVIEW_REQUESTED", "CARD_CHANGES_REQUESTED", "CARD_APPROVED", "CARD_QUICK_DONE", "CARD_REOPENED", "CARD_BLOCKED", "CARD_UNBLOCKED", "CARD_ARCHIVED", "CARD_COMMENTED"], "type": "string" }, "type": "array", "uniqueItems": true }, "limit": { "description": "Items per call, default 20.", "maximum": 50, "minimum": 1, "type": "integer" }, "projectUuid": { "description": "Older single-value form of projectUuids.", "type": "string" }, "projectUuids": { "description": "Only notifications in any of these projects. Use it when the user names a project or the project you are working in is clear. With workspaceUuids too, both must match. A workspace or project you cannot see matches nothing.", "items": { "type": "string" }, "type": "array", "uniqueItems": true }, "workspaceUuids": { "description": "Only notifications in any of these workspaces.", "items": { "type": "string" }, "type": "array", "uniqueItems": true } }, "type": "object" }, "required": [], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "dev", "schemaHash": "bedcd4375c7f8735" },
|
|
983
|
+
{ "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": "dev", "schemaHash": "939bfcc088425771" },
|
|
984
|
+
{ "mcpName": "list_trash", "commandPath": ["trash", "list"], "group": "generic", "description": "", "inputSchema": { "properties": {}, "type": "object" }, "required": [], "payloadMode": "flags", "destructive": false, "sourceCatalog": "dev", "schemaHash": "329250aecc06a298" },
|
|
985
|
+
{ "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": "dev", "schemaHash": "f08f9faee3ba9107" },
|
|
986
|
+
{ "mcpName": "list_workspaces", "commandPath": ["workspaces", "list"], "group": "workspace", "description": "", "inputSchema": { "properties": {}, "type": "object" }, "required": [], "payloadMode": "flags", "destructive": false, "sourceCatalog": "dev", "schemaHash": "39dba0089fdbdf6a" },
|
|
987
|
+
{ "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": "dev", "schemaHash": "bf82401f055f29b0" },
|
|
988
|
+
{ "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": "dev", "schemaHash": "1e4724ea42f25743" },
|
|
989
|
+
{ "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": "dev", "schemaHash": "45899053743a0c1e" },
|
|
990
|
+
{ "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": "dev", "schemaHash": "eff4458d2f35587a" },
|
|
991
|
+
{ "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": "dev", "schemaHash": "abe0196da3c48ad5" },
|
|
992
|
+
{ "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, diagram, markdown, openapi, html, file, kanban. document.update/document.append \uB294 documentUuid \uB97C \uD3EC\uD568. type=file \uC740 filename \uB3C4 \uD3EC\uD568(\uD655\uC7A5\uC790\uB85C \uD615\uC2DD \uD310\uC815) \u2014 PUT \uC758 Content-Type \uC740 \uD30C\uC77C MIME, \uBCF8\uBB38\uC740 \uD30C\uC77C \uBC14\uC774\uD2B8. attachment.upload: {documentUuid, filename}; storageKey \uB294 \uC11C\uBC84\uAC00 \uBC1C\uAE09 scope \uC5D0 \uB123\uB294\uB2E4.", "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": "dev", "schemaHash": "99a4b6c77dd2368b" },
|
|
993
|
+
{ "mcpName": "resolve_comments", "commandPath": ["tools", "run", "resolve-comments"], "group": "generic", "description": "Mark questions on a document or a kanban card (cardUuid) as resolved. Only top-level comments with kind QUESTION can be resolved, and it needs edit access to the document or board (readers can comment but cannot resolve). Resolving cannot be undone. Each id is applied on its own; results report each id.", "inputSchema": { "additionalProperties": false, "properties": { "cardUuid": { "description": "Kanban card UUID the comments belong to (from kanban_list_cards). Anyone who can read the card's board may comment. Give this or documentUuid, not both.", "type": "string" }, "documentUuid": { "description": "Document UUID the comments belong to. Give this or cardUuid, not both.", "type": "string" }, "ids": { "description": "Question ids to resolve (top-level comments with kind QUESTION)", "items": { "minimum": 1, "type": "integer" }, "minItems": 1, "type": "array" } }, "required": ["ids"], "type": "object" }, "required": ["ids"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "dev", "schemaHash": "d996f083fd9e6acf" },
|
|
994
|
+
{ "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": "dev", "schemaHash": "005b5b08a19fc605" },
|
|
995
|
+
{ "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, diagram, markdown, openapi, html, file, kanban.", "enum": ["erd", "diagram", "markdown", "openapi", "html", "file", "kanban"], "type": "string" } }, "required": ["q"], "type": "object" }, "required": ["q"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "dev", "schemaHash": "40322499fb85e525" },
|
|
996
|
+
{ "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": "dev", "schemaHash": "f2810e71583a320f" },
|
|
997
|
+
{ "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": "dev", "schemaHash": "2a24cc44696b7906" },
|
|
998
|
+
{ "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": "dev", "schemaHash": "6925e444671afe19" },
|
|
999
|
+
{ "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": "dev", "schemaHash": "8dafe9bc6378c3f2" },
|
|
1000
|
+
{ "mcpName": "update_agent_todos", "commandPath": ["agent-todos", "update"], "group": "generic", "description": "Claim, renew, complete, release, or dismiss the signed-in user's agent to-dos. Only the session holding the current leaseToken may renew, release, or complete a claimed to-do \u2014 even another session of the same connection gets CLAIMED_BY_OTHER. Results are per item: {todoId, ok: true, state, leaseToken?, claimExpiresAt?} or {todoId, ok: false, error} with error one of CLAIMED_BY_OTHER, LEASE_LOST, ALREADY_DONE, CLOSED_BY_HUMAN, SUPERSEDED, ACCESS_LOST, BUSINESS_ACTION_REQUIRED, NOT_FOUND, INVALID \u2014 on CLOSED_BY_HUMAN or SUPERSEDED stop working on it. BUSINESS_ACTION_REQUIRED: card to-dos (CARD_*) cannot be completed here; doing the card action closes them. Retrying the same complete or dismiss returns ok again. There is no tool to mark the person's notifications read.", "inputSchema": { "additionalProperties": false, "properties": { "items": { "description": "To-dos to update; each item is applied and reported on its own.", "items": { "additionalProperties": false, "properties": { "action": { "description": "claim: take it (returns leaseToken, valid 30 minutes). renew: extend your lease. complete: done (with leaseToken if you claimed it; without one only while it is still OPEN). release: give it back. dismiss: not a to-do.", "enum": ["claim", "renew", "complete", "release", "dismiss"], "type": "string" }, "leaseToken": { "description": "Required for renew and release, and for complete or dismiss of a to-do you claimed.", "type": "string" }, "note": { "description": "For complete or dismiss: one line on what you did (at most 500 characters, no document bodies). The person sees it in the inbox.", "maxLength": 500, "type": "string" }, "todoId": { "description": "todoId from list_agent_todos.", "type": "string" } }, "required": ["todoId", "action"], "type": "object" }, "maxItems": 50, "minItems": 1, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "dev", "schemaHash": "d10fc0752ad55a12" },
|
|
1001
|
+
{ "mcpName": "update_comments", "commandPath": ["comments", "update"], "group": "generic", "description": "Edit your own comments on a document or a kanban card (cardUuid). Only the author can edit, and only while they can still read it. A resolved question and a top-level comment that already has replies are locked. Items are applied in order and each one commits on its own; results report each item.", "inputSchema": { "additionalProperties": false, "properties": { "cardUuid": { "description": "Kanban card UUID the comments belong to (from kanban_list_cards). Anyone who can read the card's board may comment. Give this or documentUuid, not both.", "type": "string" }, "documentUuid": { "description": "Document UUID the comments belong to. Give this or cardUuid, not both.", "type": "string" }, "items": { "description": "Edits, applied in order", "items": { "additionalProperties": false, "properties": { "body": { "description": "New comment text (replaces the old text).", "maxLength": 1e4, "minLength": 1, "type": "string" }, "clientMutationId": { "description": "Idempotency key (visible ASCII, at most 128 characters). Required when mentionUserIds is not empty; reuse the same value when retrying the same item so the mention email is not sent twice.", "maxLength": 128, "type": "string" }, "id": { "description": "Comment id (from mcp__ainecto__list_comments)", "minimum": 1, "type": "integer" }, "mentionUserIds": { "description": "The complete set of userIds mentioned after the edit; omit to remove all mentions. Each one's @<displayName> must appear in body. Only people who were not mentioned before get a notification email.", "items": { "type": "string" }, "maxItems": 20, "type": "array", "uniqueItems": true } }, "required": ["id", "body"], "type": "object" }, "minItems": 1, "type": "array" }, "mailLanguage": { "description": "Language of the comment notification email (mention or reply). Defaults to en.", "enum": ["en", "ko"], "type": "string" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "dev", "schemaHash": "a5bc050079e34361" },
|
|
1002
|
+
{ "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); file: {file:{filename,contentType,sizeBytes,viewerKind}} (+content for text/CSV, first 64 KB); kanban: no body \u2014 only the title can be updated; cards are changed with kanban_apply_changes and kanban_card_action.", "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": "dev", "schemaHash": "47485db860973df3" },
|
|
1003
|
+
{ "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": "dev", "schemaHash": "43c58a96ee632b5f" },
|
|
1004
|
+
{ "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": "dev", "schemaHash": "7d849ef0a830aa8c" },
|
|
1005
|
+
{ "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": "dev", "schemaHash": "553275e6da799c5a" },
|
|
1006
|
+
{ "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": "dev", "schemaHash": "deda4a56fbbe064f" }
|
|
981
1007
|
];
|
|
982
1008
|
|
|
983
1009
|
// src/core/catalog/generator.ts
|
|
@@ -995,7 +1021,6 @@ var DESTRUCTIVE_PREFIXES = [
|
|
|
995
1021
|
"move_",
|
|
996
1022
|
"erd_delete_",
|
|
997
1023
|
"erd_remove_",
|
|
998
|
-
"task_delete_",
|
|
999
1024
|
"testcase_delete_"
|
|
1000
1025
|
];
|
|
1001
1026
|
function generateCatalog(tools, sourceCatalog) {
|
|
@@ -1029,7 +1054,7 @@ function inferGroup(strippedName) {
|
|
|
1029
1054
|
if (strippedName.startsWith("testcase_")) return "testcase";
|
|
1030
1055
|
if (strippedName.startsWith("markdown_")) return "markdown";
|
|
1031
1056
|
if (strippedName.includes("attachment") || strippedName.includes("upload")) return "attachment";
|
|
1032
|
-
if (strippedName.startsWith("
|
|
1057
|
+
if (strippedName.startsWith("kanban_")) return "kanban";
|
|
1033
1058
|
if (strippedName.includes("workspace")) return "workspace";
|
|
1034
1059
|
if (strippedName.includes("document")) return "document";
|
|
1035
1060
|
return "generic";
|
|
@@ -1132,6 +1157,9 @@ async function runStdioBridge(options) {
|
|
|
1132
1157
|
}
|
|
1133
1158
|
try {
|
|
1134
1159
|
const request = parseJsonRpcRequest(line);
|
|
1160
|
+
if (request.method === "initialize") {
|
|
1161
|
+
options.client.useAgent(clientInfoName(request.params));
|
|
1162
|
+
}
|
|
1135
1163
|
const response = await options.client.sendRaw(request);
|
|
1136
1164
|
if (response && request.id !== void 0) {
|
|
1137
1165
|
options.output.write(`${JSON.stringify(response)}
|
|
@@ -1160,17 +1188,27 @@ function parseJsonRpcRequest(line) {
|
|
|
1160
1188
|
params: parsed.params
|
|
1161
1189
|
};
|
|
1162
1190
|
}
|
|
1191
|
+
function clientInfoName(params) {
|
|
1192
|
+
return isRecord4(params) && isRecord4(params.clientInfo) ? params.clientInfo.name : void 0;
|
|
1193
|
+
}
|
|
1163
1194
|
function isRecord4(value) {
|
|
1164
1195
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1165
1196
|
}
|
|
1166
1197
|
|
|
1167
1198
|
// src/adapters/mcp/connector.ts
|
|
1168
1199
|
async function runConnector(options) {
|
|
1200
|
+
const legacyRole = legacyRoleInput(options.legacyRoleFlag, options.envVars);
|
|
1201
|
+
if (legacyRole !== void 0) {
|
|
1202
|
+
options.errorOutput.write(`${LEGACY_ROLE_NOTICE}
|
|
1203
|
+
`);
|
|
1204
|
+
}
|
|
1169
1205
|
const auth = new OAuthClient({ endpoint: options.endpoint, tokenStore: new FileTokenStore() });
|
|
1170
1206
|
const client = new McpRpcClient({
|
|
1171
1207
|
endpoint: options.endpoint,
|
|
1172
1208
|
tokenProvider: auth,
|
|
1173
|
-
|
|
1209
|
+
legacyRole,
|
|
1210
|
+
// 에이전트의 MCP 연결 — 에이전트 이름은 브리지가 호스트의 initialize 에서 읽어 싣는다.
|
|
1211
|
+
channel: "mcp"
|
|
1174
1212
|
});
|
|
1175
1213
|
await runStdioBridge({
|
|
1176
1214
|
input: options.input,
|
|
@@ -1243,18 +1281,6 @@ var enrichments = [
|
|
|
1243
1281
|
tableColumns: ["uuid", "name", "display_order"],
|
|
1244
1282
|
displayName: "List ERD table groups"
|
|
1245
1283
|
},
|
|
1246
|
-
{
|
|
1247
|
-
mcpName: "task_list_tasks",
|
|
1248
|
-
outputHint: "table",
|
|
1249
|
-
tableColumns: ["uuid", "title", "statusUuid", "assigneeId", "updatedAt"],
|
|
1250
|
-
displayName: "List tasks"
|
|
1251
|
-
},
|
|
1252
|
-
{
|
|
1253
|
-
mcpName: "task_list_statuses",
|
|
1254
|
-
outputHint: "table",
|
|
1255
|
-
tableColumns: ["uuid", "name", "displayOrder"],
|
|
1256
|
-
displayName: "List task statuses"
|
|
1257
|
-
},
|
|
1258
1284
|
{
|
|
1259
1285
|
mcpName: "enable_shares",
|
|
1260
1286
|
aliases: ["shares enable"],
|
|
@@ -1646,6 +1672,22 @@ function toolErrorText(value) {
|
|
|
1646
1672
|
function isRecord7(value) {
|
|
1647
1673
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1648
1674
|
}
|
|
1675
|
+
function extractProjects(value) {
|
|
1676
|
+
const candidates = Array.isArray(value) ? value : isRecord7(value) ? ["projects", "items", "created", "data", "hits"].flatMap((key) => {
|
|
1677
|
+
const nested = value[key];
|
|
1678
|
+
return Array.isArray(nested) ? nested : [];
|
|
1679
|
+
}) : [];
|
|
1680
|
+
const projects = [];
|
|
1681
|
+
for (const candidate of candidates) {
|
|
1682
|
+
if (isRecord7(candidate) && typeof candidate.uuid === "string") {
|
|
1683
|
+
projects.push({
|
|
1684
|
+
uuid: candidate.uuid,
|
|
1685
|
+
name: typeof candidate.name === "string" ? candidate.name : candidate.uuid
|
|
1686
|
+
});
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
return projects;
|
|
1690
|
+
}
|
|
1649
1691
|
|
|
1650
1692
|
// src/core/upload/attachmentUpload.ts
|
|
1651
1693
|
var AttachmentUploadError = class extends Error {
|
|
@@ -2261,8 +2303,8 @@ var AGENT_TARGETS = [
|
|
|
2261
2303
|
{ id: "claude-code", label: "Claude Code", path: ".mcp.json" },
|
|
2262
2304
|
{ id: "cursor", label: "Cursor", path: ".cursor/mcp.json" }
|
|
2263
2305
|
];
|
|
2264
|
-
function buildServerEntry(input) {
|
|
2265
|
-
const args = ["-y",
|
|
2306
|
+
function buildServerEntry(input = {}) {
|
|
2307
|
+
const args = ["-y", input.packageSpec ?? bridgePackageSpec()];
|
|
2266
2308
|
if (input.env && input.env !== "prod") {
|
|
2267
2309
|
args.push("--env", input.env);
|
|
2268
2310
|
}
|
|
@@ -2271,13 +2313,15 @@ function buildServerEntry(input) {
|
|
|
2271
2313
|
}
|
|
2272
2314
|
return { command: "npx", args };
|
|
2273
2315
|
}
|
|
2274
|
-
function
|
|
2316
|
+
function entryCarriesRole(entry) {
|
|
2317
|
+
return isRecord10(entry) && Array.isArray(entry.args) && entry.args.includes("--role");
|
|
2318
|
+
}
|
|
2319
|
+
function pinnedBridgeVersion(entry) {
|
|
2275
2320
|
if (!isRecord10(entry) || !Array.isArray(entry.args)) {
|
|
2276
2321
|
return void 0;
|
|
2277
2322
|
}
|
|
2278
|
-
const
|
|
2279
|
-
|
|
2280
|
-
return typeof value === "string" && HARNESS_ROLES.includes(value) ? value : void 0;
|
|
2323
|
+
const pinned = entry.args.find((arg) => typeof arg === "string" && arg.startsWith(`${PACKAGE_NAME}@`));
|
|
2324
|
+
return pinned?.slice(PACKAGE_NAME.length + 1);
|
|
2281
2325
|
}
|
|
2282
2326
|
function readServerEntry(existing) {
|
|
2283
2327
|
if (existing === void 0) {
|
|
@@ -2331,26 +2375,6 @@ function removeServerEntry(existing, restore) {
|
|
|
2331
2375
|
const emptied = Object.keys(servers).length === 0 && otherKeys.length === 0;
|
|
2332
2376
|
return { content: stringify({ ...root, mcpServers: servers }), removed, emptied };
|
|
2333
2377
|
}
|
|
2334
|
-
function codexAddCommand(input) {
|
|
2335
|
-
const entry = buildServerEntry(input);
|
|
2336
|
-
return `codex mcp add ${SERVER_NAME} -- ${entry.command} ${entry.args.join(" ")}`;
|
|
2337
|
-
}
|
|
2338
|
-
function codexProfileSetup(input) {
|
|
2339
|
-
const entry = buildServerEntry(input);
|
|
2340
|
-
return [
|
|
2341
|
-
`Per-session roles in Codex use profiles. Put this in $CODEX_HOME/${input.role}.config.toml:`,
|
|
2342
|
-
"",
|
|
2343
|
-
` [mcp_servers.${SERVER_NAME}]`,
|
|
2344
|
-
` command = "${entry.command}"`,
|
|
2345
|
-
` args = ${JSON.stringify(entry.args)}`,
|
|
2346
|
-
"",
|
|
2347
|
-
`Then start that session with: codex -p ${input.role}`,
|
|
2348
|
-
"A profile entry with the same server name replaces the global one, so the session still sees exactly one."
|
|
2349
|
-
];
|
|
2350
|
-
}
|
|
2351
|
-
function codexRemoveCommand() {
|
|
2352
|
-
return `codex mcp remove ${SERVER_NAME}`;
|
|
2353
|
-
}
|
|
2354
2378
|
function readServers(root) {
|
|
2355
2379
|
const servers = root.mcpServers;
|
|
2356
2380
|
if (servers === void 0) {
|
|
@@ -2397,11 +2421,7 @@ var HARNESS_DOC_PATH = `${HARNESS_DIR}/HARNESS.md`;
|
|
|
2397
2421
|
var HARNESS_CONFIG_PATH = `${HARNESS_DIR}/config.json`;
|
|
2398
2422
|
var HARNESS_RECORD_PATH = `${HARNESS_DIR}/init-record.json`;
|
|
2399
2423
|
function renderHarnessDoc(input, template) {
|
|
2400
|
-
return fillTemplate(template ?? DEFAULT_HARNESS_DOC, {
|
|
2401
|
-
projectName: input.projectName,
|
|
2402
|
-
projectUuid: input.projectUuid,
|
|
2403
|
-
endpoint: input.endpoint
|
|
2404
|
-
});
|
|
2424
|
+
return fillTemplate(template ?? DEFAULT_HARNESS_DOC, { endpoint: input.endpoint });
|
|
2405
2425
|
}
|
|
2406
2426
|
function fillTemplate(template, values) {
|
|
2407
2427
|
return template.replace(/\{\{([a-zA-Z0-9_]+)}}/g, (whole, name) => Object.prototype.hasOwnProperty.call(values, name) ? values[name] : whole);
|
|
@@ -2409,15 +2429,21 @@ function fillTemplate(template, values) {
|
|
|
2409
2429
|
var DOC_KEY = "HARNESS.DOC";
|
|
2410
2430
|
var AGENT_NOTE_KEY = "HARNESS.AGENT_NOTE";
|
|
2411
2431
|
var REQUIRED_VARIABLES = {
|
|
2412
|
-
[DOC_KEY]: ["
|
|
2432
|
+
[DOC_KEY]: ["endpoint"],
|
|
2413
2433
|
[AGENT_NOTE_KEY]: ["harnessDocPath", "serverName"]
|
|
2414
2434
|
};
|
|
2415
2435
|
function documentContractProblem(key, body) {
|
|
2416
|
-
|
|
2436
|
+
const known = REQUIRED_VARIABLES[key] ?? [];
|
|
2437
|
+
for (const name of known) {
|
|
2417
2438
|
if (!body.includes(`{{${name}}}`)) {
|
|
2418
2439
|
return `${key} is missing the required {{${name}}} placeholder`;
|
|
2419
2440
|
}
|
|
2420
2441
|
}
|
|
2442
|
+
for (const [, name] of body.matchAll(/\{\{([a-zA-Z0-9_]+)}}/g)) {
|
|
2443
|
+
if (!known.includes(name)) {
|
|
2444
|
+
return `${key} has a {{${name}}} placeholder this CLI does not fill`;
|
|
2445
|
+
}
|
|
2446
|
+
}
|
|
2421
2447
|
if (key === AGENT_NOTE_KEY) {
|
|
2422
2448
|
return managedBlockProblem(key, body);
|
|
2423
2449
|
}
|
|
@@ -2425,58 +2451,32 @@ function documentContractProblem(key, body) {
|
|
|
2425
2451
|
}
|
|
2426
2452
|
var DEFAULT_HARNESS_DOC = `# AiNecto Harness
|
|
2427
2453
|
|
|
2428
|
-
This repository
|
|
2429
|
-
not in this repository's files.
|
|
2454
|
+
This repository works with AiNecto. The design source of truth \u2014 requirements, the ERD,
|
|
2455
|
+
diagrams, documents, and task boards \u2014 lives in AiNecto, not in this repository's files.
|
|
2430
2456
|
|
|
2431
|
-
- Project: **{{projectName}}** (\`{{projectUuid}}\`)
|
|
2432
2457
|
- MCP endpoint: {{endpoint}}
|
|
2433
2458
|
|
|
2434
|
-
##
|
|
2435
|
-
|
|
2436
|
-
A session picks exactly one role and keeps it for the whole session. The role comes from the
|
|
2437
|
-
MCP server configuration \u2014 not from this file \u2014 and it does not change during a session. The
|
|
2438
|
-
user picks the role; an agent applies a new one only when the user asks, and it takes effect in
|
|
2439
|
-
the next session. This is a working guardrail, not a security boundary. The server exposes only
|
|
2440
|
-
the tools that role is allowed to use, so a tool that is out of role is not merely discouraged:
|
|
2441
|
-
it is absent.
|
|
2442
|
-
|
|
2443
|
-
| Role | Owns | Must not |
|
|
2444
|
-
| --- | --- | --- |
|
|
2445
|
-
| Design | Requirements, ERD, domain and module boundaries, dependency direction, tasks | Write product or test code |
|
|
2446
|
-
| Development | Product code, following the approved design | Change requirements, ERD, architecture, or data ownership |
|
|
2447
|
-
| Test | Test scenarios and test code, running them and reading results | Change product code to make a test pass; change the design |
|
|
2448
|
-
| Validation | Judging the result against the design and the rules | Change anything; it reports PASS or FAIL with evidence |
|
|
2449
|
-
|
|
2450
|
-
Every role can **read** the whole source of truth. Reading the design is how the other roles
|
|
2451
|
-
do their job. A session that cannot do something reports it in the session, to you.
|
|
2452
|
-
|
|
2453
|
-
## When you are blocked
|
|
2454
|
-
|
|
2455
|
-
If the work needs something your role cannot do, **stop and say so**. Do not look for a way
|
|
2456
|
-
around it: not by editing files the tools would have written, not by asking for the role to be
|
|
2457
|
-
changed mid-session, not by deferring the decision into the implementation.
|
|
2459
|
+
## What you can do
|
|
2458
2460
|
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2461
|
+
Every AI session sees the same AiNecto tools. What a tool may change is decided by the signed-in
|
|
2462
|
+
user's AiNecto permissions on the workspace, project, or document it touches \u2014 the server refuses
|
|
2463
|
+
what the user may not do. A refusal is an answer: report it to the user instead of looking for a
|
|
2464
|
+
way around it.
|
|
2462
2465
|
|
|
2463
|
-
|
|
2464
|
-
|
|
2466
|
+
Find the project and document for the task with the tools (\`list_projects\`, \`search_documents\`,
|
|
2467
|
+
\`get_document\`) \u2014 this repository is not bound to one project.
|
|
2465
2468
|
|
|
2466
|
-
##
|
|
2469
|
+
## When a tool is missing
|
|
2467
2470
|
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
+
If a result names a tool that is not in your tool list, this session started before that tool
|
|
2472
|
+
was added. Start a new session, or call it from the repository with
|
|
2473
|
+
\`npx -y ainecto@latest tools call <tool> '<json>'\`.
|
|
2471
2474
|
|
|
2472
|
-
##
|
|
2475
|
+
## Commands
|
|
2473
2476
|
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
Codex global config \u2014 use a profile: codex -p <role>, or set ${ROLE_ENV_VAR}
|
|
2478
|
-
|
|
2479
|
-
Or run \`ainecto init --role <design|development|test|validation>\` to rewrite them.
|
|
2477
|
+
npx -y ainecto@latest setup connect this machine's coding agents (once per machine)
|
|
2478
|
+
npx -y ainecto@latest status who is signed in, and what is connected
|
|
2479
|
+
npx -y ainecto@latest doctor what is missing, and the command that fixes it
|
|
2480
2480
|
|
|
2481
2481
|
---
|
|
2482
2482
|
Generated by \`ainecto init\`. Remove everything it wrote with \`ainecto init --undo\`.
|
|
@@ -2490,14 +2490,11 @@ function renderAgentNote(template) {
|
|
|
2490
2490
|
var DEFAULT_AGENT_NOTE = `${MARKER_BEGIN}
|
|
2491
2491
|
## AiNecto Harness
|
|
2492
2492
|
|
|
2493
|
-
This repository uses AiNecto as its design source of truth
|
|
2494
|
-
|
|
2495
|
-
touches requirements, the ERD, or architecture.
|
|
2493
|
+
This repository uses AiNecto as its design source of truth. Read \`{{harnessDocPath}}\` before doing
|
|
2494
|
+
work that touches requirements, the ERD, or architecture.
|
|
2496
2495
|
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
that role's tools. If the work needs something outside the role,
|
|
2500
|
-
stop and report which session is required.
|
|
2496
|
+
AiNecto tools come from the \`{{serverName}}\` MCP server. Every session sees the same tools; the
|
|
2497
|
+
signed-in user's AiNecto permissions decide what they may change, and the server refuses the rest.
|
|
2501
2498
|
${MARKER_END}`;
|
|
2502
2499
|
function managedBlockProblem(key, body) {
|
|
2503
2500
|
const trimmed = body.trim();
|
|
@@ -2934,175 +2931,6 @@ function isStringArray(value) {
|
|
|
2934
2931
|
|
|
2935
2932
|
// src/core/harness/initPlan.ts
|
|
2936
2933
|
import { createHash as createHash4 } from "crypto";
|
|
2937
|
-
|
|
2938
|
-
// src/core/harness/repositoryRole.ts
|
|
2939
|
-
import { lstat, readFile as readFile5, realpath } from "fs/promises";
|
|
2940
|
-
import { homedir as homedir2 } from "os";
|
|
2941
|
-
import { dirname as dirname3, join as join2, resolve, sep } from "path";
|
|
2942
|
-
function readConfigRole(path, content) {
|
|
2943
|
-
if (content === void 0 || !content.trim()) {
|
|
2944
|
-
return { kind: "absent" };
|
|
2945
|
-
}
|
|
2946
|
-
let servers;
|
|
2947
|
-
try {
|
|
2948
|
-
const parsed = JSON.parse(content);
|
|
2949
|
-
const raw = parsed?.mcpServers;
|
|
2950
|
-
servers = typeof raw === "object" && raw !== null && !Array.isArray(raw) ? raw : {};
|
|
2951
|
-
} catch {
|
|
2952
|
-
return content.includes(SERVER_NAME) ? { kind: "problem", reason: `${path} is not valid JSON` } : { kind: "absent" };
|
|
2953
|
-
}
|
|
2954
|
-
const entry = servers[SERVER_NAME];
|
|
2955
|
-
if (!isOurBridge(entry)) {
|
|
2956
|
-
return { kind: "absent" };
|
|
2957
|
-
}
|
|
2958
|
-
const role = readRoleFromEntry(entry);
|
|
2959
|
-
if (role === void 0) {
|
|
2960
|
-
return { kind: "problem", reason: `${path} has the "${SERVER_NAME}" bridge without a valid --role` };
|
|
2961
|
-
}
|
|
2962
|
-
return { kind: "role", role };
|
|
2963
|
-
}
|
|
2964
|
-
function rolesInFiles(files) {
|
|
2965
|
-
const roles = [];
|
|
2966
|
-
const problems = [];
|
|
2967
|
-
for (const target of AGENT_TARGETS) {
|
|
2968
|
-
const read = readConfigRole(target.path, files.get(target.path));
|
|
2969
|
-
if (read.kind === "problem") {
|
|
2970
|
-
problems.push(read.reason);
|
|
2971
|
-
} else if (read.kind === "role" && !roles.includes(read.role)) {
|
|
2972
|
-
roles.push(read.role);
|
|
2973
|
-
}
|
|
2974
|
-
}
|
|
2975
|
-
return { roles, problems, found: roles.length > 0 || problems.length > 0 };
|
|
2976
|
-
}
|
|
2977
|
-
async function findRepositoryRole(cwd, home = homedir2()) {
|
|
2978
|
-
const stopAt = await realOrResolved(home);
|
|
2979
|
-
for (let dir = await realOrResolved(cwd); ; dir = dirname3(dir)) {
|
|
2980
|
-
if (samePath(dir, stopAt)) {
|
|
2981
|
-
return { kind: "none" };
|
|
2982
|
-
}
|
|
2983
|
-
const files = /* @__PURE__ */ new Map();
|
|
2984
|
-
let readProblem;
|
|
2985
|
-
for (const target of AGENT_TARGETS) {
|
|
2986
|
-
try {
|
|
2987
|
-
files.set(target.path, await readConfigInside(dir, target.path));
|
|
2988
|
-
} catch (error) {
|
|
2989
|
-
readProblem = error instanceof Error ? error.message : String(error);
|
|
2990
|
-
}
|
|
2991
|
-
}
|
|
2992
|
-
if (readProblem !== void 0) {
|
|
2993
|
-
return { kind: "conflict", dir, reason: readProblem };
|
|
2994
|
-
}
|
|
2995
|
-
const result = rolesInFiles(files);
|
|
2996
|
-
if (result.found) {
|
|
2997
|
-
if (result.problems.length > 0) {
|
|
2998
|
-
return { kind: "conflict", dir, reason: result.problems.join("; ") };
|
|
2999
|
-
}
|
|
3000
|
-
if (result.roles.length > 1) {
|
|
3001
|
-
return {
|
|
3002
|
-
kind: "conflict",
|
|
3003
|
-
dir,
|
|
3004
|
-
reason: `agent configs disagree about the session role (${result.roles.join(", ")})`
|
|
3005
|
-
};
|
|
3006
|
-
}
|
|
3007
|
-
return { kind: "role", role: result.roles[0], dir };
|
|
3008
|
-
}
|
|
3009
|
-
if (dirname3(dir) === dir) {
|
|
3010
|
-
return { kind: "none" };
|
|
3011
|
-
}
|
|
3012
|
-
}
|
|
3013
|
-
}
|
|
3014
|
-
async function askTheUserToFixRole(dir, reason) {
|
|
3015
|
-
const gitRoot = await findGitRoot(dir);
|
|
3016
|
-
const roles = HARNESS_ROLES.join(", ");
|
|
3017
|
-
if (gitRoot !== void 0 && !samePath(await realOrResolved(gitRoot), await realOrResolved(dir))) {
|
|
3018
|
-
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}).`;
|
|
3019
|
-
}
|
|
3020
|
-
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}.`;
|
|
3021
|
-
}
|
|
3022
|
-
async function realOrResolved(path) {
|
|
3023
|
-
try {
|
|
3024
|
-
return await realpath(path);
|
|
3025
|
-
} catch {
|
|
3026
|
-
return resolve(path);
|
|
3027
|
-
}
|
|
3028
|
-
}
|
|
3029
|
-
function samePath(left, right) {
|
|
3030
|
-
return process.platform === "win32" ? left.toLowerCase() === right.toLowerCase() : left === right;
|
|
3031
|
-
}
|
|
3032
|
-
async function readConfigInside(dir, path) {
|
|
3033
|
-
let real;
|
|
3034
|
-
try {
|
|
3035
|
-
real = await realpath(join2(dir, path));
|
|
3036
|
-
} catch (error) {
|
|
3037
|
-
if (isNodeError2(error, "ENOENT") || isNodeError2(error, "ENOTDIR")) {
|
|
3038
|
-
return void 0;
|
|
3039
|
-
}
|
|
3040
|
-
throw error;
|
|
3041
|
-
}
|
|
3042
|
-
const realDir = await realOrResolved(dir);
|
|
3043
|
-
if (!samePath(real, realDir) && !real.startsWith(realDir + sep) && !(process.platform === "win32" && real.toLowerCase().startsWith((realDir + sep).toLowerCase()))) {
|
|
3044
|
-
throw new Error(`${path} points outside ${dir} (${real})`);
|
|
3045
|
-
}
|
|
3046
|
-
return readFile5(real, "utf8");
|
|
3047
|
-
}
|
|
3048
|
-
async function findGitRoot(cwd) {
|
|
3049
|
-
for (let at = resolve(cwd); ; at = dirname3(at)) {
|
|
3050
|
-
try {
|
|
3051
|
-
await lstat(join2(at, ".git"));
|
|
3052
|
-
return at;
|
|
3053
|
-
} catch {
|
|
3054
|
-
}
|
|
3055
|
-
if (dirname3(at) === at) {
|
|
3056
|
-
return void 0;
|
|
3057
|
-
}
|
|
3058
|
-
}
|
|
3059
|
-
}
|
|
3060
|
-
async function readManagedFile(root, path) {
|
|
3061
|
-
await assertInsideRepository(root, path);
|
|
3062
|
-
return readIfExists(join2(root, path));
|
|
3063
|
-
}
|
|
3064
|
-
async function readIfExists(absolutePath) {
|
|
3065
|
-
try {
|
|
3066
|
-
return await readFile5(absolutePath, "utf8");
|
|
3067
|
-
} catch (error) {
|
|
3068
|
-
if (isNodeError2(error, "ENOENT")) {
|
|
3069
|
-
return void 0;
|
|
3070
|
-
}
|
|
3071
|
-
throw error;
|
|
3072
|
-
}
|
|
3073
|
-
}
|
|
3074
|
-
async function assertInsideRepository(root, relativePath) {
|
|
3075
|
-
const absolutePath = join2(root, relativePath);
|
|
3076
|
-
const realRoot = await realpath(root);
|
|
3077
|
-
let realParent;
|
|
3078
|
-
try {
|
|
3079
|
-
realParent = await realpath(dirname3(absolutePath));
|
|
3080
|
-
} catch (error) {
|
|
3081
|
-
if (isNodeError2(error, "ENOENT")) {
|
|
3082
|
-
return;
|
|
3083
|
-
}
|
|
3084
|
-
throw error;
|
|
3085
|
-
}
|
|
3086
|
-
if (realParent !== realRoot && !realParent.startsWith(realRoot + sep)) {
|
|
3087
|
-
throw new Error(
|
|
3088
|
-
`${relativePath} resolves outside the repository (${realParent}); refusing to touch it.`
|
|
3089
|
-
);
|
|
3090
|
-
}
|
|
3091
|
-
try {
|
|
3092
|
-
if ((await lstat(absolutePath)).isSymbolicLink()) {
|
|
3093
|
-
throw new Error(`${relativePath} is a symbolic link; refusing to write through it.`);
|
|
3094
|
-
}
|
|
3095
|
-
} catch (error) {
|
|
3096
|
-
if (!isNodeError2(error, "ENOENT")) {
|
|
3097
|
-
throw error;
|
|
3098
|
-
}
|
|
3099
|
-
}
|
|
3100
|
-
}
|
|
3101
|
-
function isNodeError2(error, code) {
|
|
3102
|
-
return typeof error === "object" && error !== null && error.code === code;
|
|
3103
|
-
}
|
|
3104
|
-
|
|
3105
|
-
// src/core/harness/initPlan.ts
|
|
3106
2934
|
var NOTE_TARGETS = [
|
|
3107
2935
|
{ path: "AGENTS.md", createIfMissing: true },
|
|
3108
2936
|
{ path: "CLAUDE.md", createIfMissing: false }
|
|
@@ -3159,20 +2987,22 @@ function parseJsonObject(content) {
|
|
|
3159
2987
|
}
|
|
3160
2988
|
function configBodyOf(values) {
|
|
3161
2989
|
return {
|
|
3162
|
-
version:
|
|
3163
|
-
project: { uuid: values.projectUuid, name: values.projectName },
|
|
2990
|
+
version: 2,
|
|
3164
2991
|
endpoint: values.endpoint,
|
|
3165
2992
|
generatedBy: values.cliVersion,
|
|
3166
2993
|
promptVersions: values.versions
|
|
3167
2994
|
};
|
|
3168
2995
|
}
|
|
3169
2996
|
var OUR_CONFIG_FIELDS = Object.keys(configBodyOf({
|
|
3170
|
-
projectUuid: "",
|
|
3171
|
-
projectName: "",
|
|
3172
2997
|
endpoint: "",
|
|
3173
2998
|
cliVersion: "",
|
|
3174
2999
|
versions: {}
|
|
3175
3000
|
}));
|
|
3001
|
+
var RETIRED_CONFIG_FIELDS = ["project"];
|
|
3002
|
+
function ownedConfigFields(contract) {
|
|
3003
|
+
const written = contract?.writtenFields ?? {};
|
|
3004
|
+
return [...OUR_CONFIG_FIELDS, ...RETIRED_CONFIG_FIELDS.filter((field) => hasField(written, field))];
|
|
3005
|
+
}
|
|
3176
3006
|
var hasField = (object, field) => Object.prototype.hasOwnProperty.call(object, field);
|
|
3177
3007
|
function initRefusal(files, previous) {
|
|
3178
3008
|
const state = previous ?? readRecord(files.get(HARNESS_RECORD_PATH));
|
|
@@ -3195,16 +3025,10 @@ function planInit(input) {
|
|
|
3195
3025
|
const previous = input.previous ?? readRecord(input.files.get(HARNESS_RECORD_PATH));
|
|
3196
3026
|
const refusal = initRefusal(input.files, previous);
|
|
3197
3027
|
if (refusal !== void 0) {
|
|
3198
|
-
return {
|
|
3199
|
-
writes: [],
|
|
3200
|
-
deletes: [],
|
|
3201
|
-
notes: [],
|
|
3202
|
-
codexCommand: codexAddCommand({ role: input.role, env: input.env, endpoint: input.endpoint }),
|
|
3203
|
-
refusal
|
|
3204
|
-
};
|
|
3028
|
+
return { writes: [], deletes: [], notes: [], refusal };
|
|
3205
3029
|
}
|
|
3206
3030
|
const carriedRecord = previous.state === "valid" ? previous.record : emptyRecord();
|
|
3207
|
-
const entry = buildServerEntry({
|
|
3031
|
+
const entry = buildServerEntry({ env: input.env, endpoint: input.endpoint, packageSpec: input.packageSpec });
|
|
3208
3032
|
const writes = [];
|
|
3209
3033
|
const notes = [];
|
|
3210
3034
|
const record = {
|
|
@@ -3227,15 +3051,14 @@ function planInit(input) {
|
|
|
3227
3051
|
for (const target of AGENT_TARGETS) {
|
|
3228
3052
|
const existing = input.files.get(target.path);
|
|
3229
3053
|
const currentEntry = existing === void 0 ? void 0 : readServerEntry(existing);
|
|
3054
|
+
if (!input.mcp && !isOurBridge(currentEntry)) {
|
|
3055
|
+
continue;
|
|
3056
|
+
}
|
|
3230
3057
|
const prior = priorOf(target.path, ownedJson(currentEntry, currentEntry !== void 0));
|
|
3231
3058
|
const merged = mergeServerEntry(existing, entry);
|
|
3232
3059
|
if (existing === merged.content) {
|
|
3233
3060
|
continue;
|
|
3234
3061
|
}
|
|
3235
|
-
const before = previousRole(target.path, existing);
|
|
3236
|
-
if (merged.replaced && before && before !== input.role) {
|
|
3237
|
-
notes.push(`${target.path}: role ${before} \u2192 ${input.role}`);
|
|
3238
|
-
}
|
|
3239
3062
|
const original = prior?.original ?? ownedJson(merged.replacedEntry, merged.replaced);
|
|
3240
3063
|
if (prior === void 0 && original.at !== "absent") {
|
|
3241
3064
|
notes.push(`${target.path}: your existing "${SERVER_NAME}" entry was saved and will be restored by --undo`);
|
|
@@ -3251,11 +3074,7 @@ function planInit(input) {
|
|
|
3251
3074
|
}
|
|
3252
3075
|
const existingDoc = input.files.get(HARNESS_DOC_PATH);
|
|
3253
3076
|
const priorDoc = priorOf(HARNESS_DOC_PATH, ownedText(existingDoc));
|
|
3254
|
-
const docContent = renderHarnessDoc({
|
|
3255
|
-
projectName: input.projectName,
|
|
3256
|
-
projectUuid: input.projectUuid,
|
|
3257
|
-
endpoint: input.endpoint
|
|
3258
|
-
}, input.documents?.doc);
|
|
3077
|
+
const docContent = renderHarnessDoc({ endpoint: input.endpoint }, input.documents?.doc);
|
|
3259
3078
|
const docOriginal = priorDoc?.original ?? ownedText(existingDoc);
|
|
3260
3079
|
if (priorDoc === void 0 && docOriginal.at !== "absent") {
|
|
3261
3080
|
notes.push(`${HARNESS_DOC_PATH} already existed; its original was saved and --undo will restore it.`);
|
|
@@ -3317,8 +3136,6 @@ function planInit(input) {
|
|
|
3317
3136
|
}
|
|
3318
3137
|
const existingConfig = input.files.get(HARNESS_CONFIG_PATH);
|
|
3319
3138
|
const ourFields = configBodyOf({
|
|
3320
|
-
projectUuid: input.projectUuid,
|
|
3321
|
-
projectName: input.projectName,
|
|
3322
3139
|
endpoint: input.endpoint,
|
|
3323
3140
|
cliVersion: input.cliVersion,
|
|
3324
3141
|
// ★어느 판의 규칙으로 썼나. 이게 없으면 「무엇이 적혀 있었나」를 나중에 못 되짚는다.
|
|
@@ -3342,7 +3159,24 @@ function planInit(input) {
|
|
|
3342
3159
|
notes.push(`${HARNESS_CONFIG_PATH}: you changed ${field} \u2014 ainecto now treats your value as the one to put back.`);
|
|
3343
3160
|
}
|
|
3344
3161
|
}
|
|
3345
|
-
const
|
|
3162
|
+
const retained = { ...theirConfig ?? {} };
|
|
3163
|
+
for (const field of RETIRED_CONFIG_FIELDS) {
|
|
3164
|
+
const priorWritten = priorConfig?.writtenFields;
|
|
3165
|
+
if (priorWritten === void 0 || !hasField(priorWritten, field) || !hasField(retained, field)) {
|
|
3166
|
+
continue;
|
|
3167
|
+
}
|
|
3168
|
+
if (stableStringify2(retained[field]) !== stableStringify2(priorWritten[field])) {
|
|
3169
|
+
notes.push(`${HARNESS_CONFIG_PATH}: you changed ${field} \u2014 left as you set it (ainecto no longer writes it).`);
|
|
3170
|
+
continue;
|
|
3171
|
+
}
|
|
3172
|
+
const original = priorConfig?.originalFields?.[field];
|
|
3173
|
+
if (original?.at === "json") {
|
|
3174
|
+
retained[field] = original.value;
|
|
3175
|
+
} else {
|
|
3176
|
+
delete retained[field];
|
|
3177
|
+
}
|
|
3178
|
+
}
|
|
3179
|
+
const mergedConfig = serialize({ ...retained, ...ourFields });
|
|
3346
3180
|
record.files[HARNESS_CONFIG_PATH] = {
|
|
3347
3181
|
scope: "fields",
|
|
3348
3182
|
createdByUs: priorConfig?.createdByUs ?? existingConfig === void 0,
|
|
@@ -3369,14 +3203,7 @@ function planInit(input) {
|
|
|
3369
3203
|
existed: existingRecord !== void 0
|
|
3370
3204
|
});
|
|
3371
3205
|
}
|
|
3372
|
-
return {
|
|
3373
|
-
writes,
|
|
3374
|
-
deletes: [],
|
|
3375
|
-
codexCommand: codexAddCommand({ role: input.role, env: input.env, endpoint: input.endpoint }),
|
|
3376
|
-
codexProfile: codexProfileSetup({ role: input.role, env: input.env, endpoint: input.endpoint }),
|
|
3377
|
-
notes,
|
|
3378
|
-
record
|
|
3379
|
-
};
|
|
3206
|
+
return { writes, deletes: [], notes, record };
|
|
3380
3207
|
}
|
|
3381
3208
|
function planUndo(input) {
|
|
3382
3209
|
const state = input.record ?? readRecord(input.files.get(HARNESS_RECORD_PATH));
|
|
@@ -3384,20 +3211,19 @@ function planUndo(input) {
|
|
|
3384
3211
|
const deletes = [];
|
|
3385
3212
|
const notes = [];
|
|
3386
3213
|
const tail = () => {
|
|
3387
|
-
notes.push(`
|
|
3388
|
-
notes.push(`If you set ${ROLE_ENV_VAR} anywhere, unset it too.`);
|
|
3214
|
+
notes.push("Agent connections that `ainecto setup` made on this machine are removed with `ainecto setup --undo`.");
|
|
3389
3215
|
};
|
|
3390
3216
|
if (state.state === "absent") {
|
|
3391
3217
|
notes.push(`No record of a previous init in ${HARNESS_RECORD_PATH} \u2014 no local file was changed.`);
|
|
3392
3218
|
tail();
|
|
3393
|
-
return { writes, deletes,
|
|
3219
|
+
return { writes, deletes, notes };
|
|
3394
3220
|
}
|
|
3395
3221
|
if (state.state !== "valid") {
|
|
3396
3222
|
notes.push(
|
|
3397
3223
|
`${state.reason}. Nothing was changed \u2014 that file is kept because it may hold backups --undo still needs. Restore it, or finish the undo with the CLI version that wrote it.`
|
|
3398
3224
|
);
|
|
3399
3225
|
tail();
|
|
3400
|
-
return { writes, deletes,
|
|
3226
|
+
return { writes, deletes, notes };
|
|
3401
3227
|
}
|
|
3402
3228
|
const record = state.record;
|
|
3403
3229
|
const remaining = { ...record.files };
|
|
@@ -3431,7 +3257,7 @@ function planUndo(input) {
|
|
|
3431
3257
|
}
|
|
3432
3258
|
}
|
|
3433
3259
|
const removableDirectories = parentsOf(deletes).filter((directory) => record.createdDirectories.includes(directory));
|
|
3434
|
-
return { writes, deletes,
|
|
3260
|
+
return { writes, deletes, notes, removableDirectories };
|
|
3435
3261
|
}
|
|
3436
3262
|
function unexplainedMarkers(existing, contract) {
|
|
3437
3263
|
const recorded = [...contract.foreignBlocks ?? []];
|
|
@@ -3577,7 +3403,8 @@ function undoFields(path, contract, existing) {
|
|
|
3577
3403
|
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.`
|
|
3578
3404
|
};
|
|
3579
3405
|
}
|
|
3580
|
-
const
|
|
3406
|
+
const owned = ownedConfigFields(contract);
|
|
3407
|
+
const contractProblem = fieldsContractProblem(contract, owned);
|
|
3581
3408
|
if (contractProblem !== void 0) {
|
|
3582
3409
|
return {
|
|
3583
3410
|
kind: "unresolved",
|
|
@@ -3589,7 +3416,7 @@ function undoFields(path, contract, existing) {
|
|
|
3589
3416
|
const next = {};
|
|
3590
3417
|
const kept = [];
|
|
3591
3418
|
for (const [field, value] of Object.entries(parsed)) {
|
|
3592
|
-
if (!
|
|
3419
|
+
if (!owned.includes(field)) {
|
|
3593
3420
|
next[field] = value;
|
|
3594
3421
|
continue;
|
|
3595
3422
|
}
|
|
@@ -3644,14 +3471,8 @@ function parentsOf(paths) {
|
|
|
3644
3471
|
}
|
|
3645
3472
|
return [...directories].sort((left, right) => right.length - left.length);
|
|
3646
3473
|
}
|
|
3647
|
-
function previousRole(path, existing) {
|
|
3648
|
-
const read = readConfigRole(path, existing);
|
|
3649
|
-
return read.kind === "role" ? read.role : void 0;
|
|
3650
|
-
}
|
|
3651
3474
|
|
|
3652
3475
|
// src/core/harness/documentFetch.ts
|
|
3653
|
-
var DOC_KEY2 = "HARNESS.DOC";
|
|
3654
|
-
var AGENT_NOTE_KEY2 = "HARNESS.AGENT_NOTE";
|
|
3655
3476
|
async function fetchHarnessDocuments(input) {
|
|
3656
3477
|
const url = new URL(`${input.endpoint.replace(/\/$/, "")}/harness/documents`);
|
|
3657
3478
|
assertTrustedEndpointUrl(url, "harness documents URL");
|
|
@@ -3674,7 +3495,7 @@ async function fetchHarnessDocuments(input) {
|
|
|
3674
3495
|
const versions = {};
|
|
3675
3496
|
const fallbacks = {};
|
|
3676
3497
|
const bodies = {};
|
|
3677
|
-
for (const key of [
|
|
3498
|
+
for (const key of [DOC_KEY, AGENT_NOTE_KEY]) {
|
|
3678
3499
|
const entry = documents[key];
|
|
3679
3500
|
const problem = usableBodyProblem(key, entry);
|
|
3680
3501
|
if (problem) {
|
|
@@ -3686,7 +3507,7 @@ async function fetchHarnessDocuments(input) {
|
|
|
3686
3507
|
versions[key] = entry.version;
|
|
3687
3508
|
}
|
|
3688
3509
|
}
|
|
3689
|
-
return { doc: bodies[
|
|
3510
|
+
return { doc: bodies[DOC_KEY], agentNote: bodies[AGENT_NOTE_KEY], versions, fallbacks };
|
|
3690
3511
|
} catch (error) {
|
|
3691
3512
|
return {
|
|
3692
3513
|
versions: {},
|
|
@@ -3702,157 +3523,108 @@ function usableBodyProblem(key, entry) {
|
|
|
3702
3523
|
return documentContractProblem(key, entry.body);
|
|
3703
3524
|
}
|
|
3704
3525
|
|
|
3705
|
-
// src/
|
|
3706
|
-
import {
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
function agentEnvironment(env = process.env) {
|
|
3715
|
-
return AGENT_ENV_MARKERS.find((name) => {
|
|
3716
|
-
const value = env[name]?.trim().toLowerCase();
|
|
3717
|
-
return value !== void 0 && value !== "" && value !== "0" && value !== "false";
|
|
3718
|
-
});
|
|
3719
|
-
}
|
|
3720
|
-
function isInteractive(io, json, env = process.env) {
|
|
3721
|
-
return io.stdin?.isTTY === true && io.stdout?.isTTY === true && !json && agentEnvironment(env) === void 0;
|
|
3722
|
-
}
|
|
3723
|
-
function createReadlinePrompter(io) {
|
|
3724
|
-
let rl;
|
|
3725
|
-
let lines;
|
|
3726
|
-
const readLine = async (prompt) => {
|
|
3727
|
-
io.stderr.write(prompt);
|
|
3728
|
-
if (!rl) {
|
|
3729
|
-
rl = createInterface3({ input: io.stdin, terminal: false });
|
|
3730
|
-
lines = rl[Symbol.asyncIterator]();
|
|
3731
|
-
}
|
|
3732
|
-
const next = await lines.next();
|
|
3733
|
-
if (next.done) {
|
|
3734
|
-
io.stderr.write("\n");
|
|
3735
|
-
throw new PromptCancelled();
|
|
3526
|
+
// src/core/harness/repositoryFiles.ts
|
|
3527
|
+
import { lstat, readFile as readFile5, realpath } from "fs/promises";
|
|
3528
|
+
import { dirname as dirname3, join as join2, resolve, sep } from "path";
|
|
3529
|
+
async function findGitRoot(cwd) {
|
|
3530
|
+
for (let at = resolve(cwd); ; at = dirname3(at)) {
|
|
3531
|
+
try {
|
|
3532
|
+
await lstat(join2(at, ".git"));
|
|
3533
|
+
return at;
|
|
3534
|
+
} catch {
|
|
3736
3535
|
}
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
return {
|
|
3740
|
-
choose: (question, choices, defaultIndex) => chooseWith(readLine, (text) => io.stderr.write(text), question, choices, defaultIndex),
|
|
3741
|
-
text: async (question, defaultValue) => {
|
|
3742
|
-
const answer = (await readLine(`${question} [${defaultValue}]: `)).trim();
|
|
3743
|
-
return answer || defaultValue;
|
|
3744
|
-
},
|
|
3745
|
-
close: () => {
|
|
3746
|
-
rl?.close();
|
|
3536
|
+
if (dirname3(at) === at) {
|
|
3537
|
+
return void 0;
|
|
3747
3538
|
}
|
|
3748
|
-
};
|
|
3749
|
-
}
|
|
3750
|
-
async function chooseWith(readLine, write, question, choices, defaultIndex) {
|
|
3751
|
-
if (choices.length === 0) {
|
|
3752
|
-
throw new Error(`Nothing to choose for: ${question}`);
|
|
3753
|
-
}
|
|
3754
|
-
write(`${question}
|
|
3755
|
-
`);
|
|
3756
|
-
choices.forEach((choice, index) => write(` [${index + 1}] ${choice.label}
|
|
3757
|
-
`));
|
|
3758
|
-
const hint = defaultIndex === void 0 ? "" : ` [${defaultIndex + 1}]`;
|
|
3759
|
-
for (; ; ) {
|
|
3760
|
-
const answer = (await readLine(`Enter a number (1-${choices.length})${hint}: `)).trim();
|
|
3761
|
-
if (answer === "" && defaultIndex !== void 0) {
|
|
3762
|
-
return choices[defaultIndex].value;
|
|
3763
|
-
}
|
|
3764
|
-
const picked = /^\d+$/.test(answer) ? Number(answer) : NaN;
|
|
3765
|
-
if (picked >= 1 && picked <= choices.length) {
|
|
3766
|
-
return choices[picked - 1].value;
|
|
3767
|
-
}
|
|
3768
|
-
write(answer === "" ? "Please choose one \u2014 there is no default here.\n" : `"${answer}" is not one of the numbers above.
|
|
3769
|
-
`);
|
|
3770
3539
|
}
|
|
3771
3540
|
}
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
return roles.length === 1 ? roles[0] : void 0;
|
|
3541
|
+
async function readManagedFile(root, path) {
|
|
3542
|
+
await assertInsideRepository(root, path);
|
|
3543
|
+
return readIfExists(join2(root, path));
|
|
3776
3544
|
}
|
|
3777
|
-
async function
|
|
3545
|
+
async function readIfExists(absolutePath) {
|
|
3778
3546
|
try {
|
|
3779
|
-
return await
|
|
3547
|
+
return await readFile5(absolutePath, "utf8");
|
|
3780
3548
|
} catch (error) {
|
|
3781
|
-
if (error
|
|
3782
|
-
|
|
3783
|
-
`);
|
|
3784
|
-
return 130;
|
|
3549
|
+
if (isNodeError2(error, "ENOENT")) {
|
|
3550
|
+
return void 0;
|
|
3785
3551
|
}
|
|
3786
3552
|
throw error;
|
|
3787
|
-
} finally {
|
|
3788
|
-
options.prompter?.close();
|
|
3789
3553
|
}
|
|
3790
3554
|
}
|
|
3791
|
-
async function
|
|
3792
|
-
|
|
3793
|
-
const
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
options.io.stdout.write(renderSuccess(
|
|
3801
|
-
{
|
|
3802
|
-
action: args.dryRun ? "undo (dry-run)" : "undo",
|
|
3803
|
-
updated: plan2.writes.map((write) => write.path),
|
|
3804
|
-
removed: plan2.deletes,
|
|
3805
|
-
notes: [...rootNotes, ...plan2.notes]
|
|
3806
|
-
},
|
|
3807
|
-
{ json: options.json }
|
|
3808
|
-
));
|
|
3809
|
-
return 0;
|
|
3810
|
-
}
|
|
3811
|
-
const refusal = initRefusal(files);
|
|
3812
|
-
if (refusal !== void 0) {
|
|
3813
|
-
throw new Error(refusal);
|
|
3814
|
-
}
|
|
3815
|
-
const prompter = options.prompter;
|
|
3816
|
-
const role = options.role ?? (prompter ? await askRole(prompter, singleRole(rolesInFiles(files).roles)) : currentRole(files));
|
|
3817
|
-
const previousRole2 = (() => {
|
|
3818
|
-
try {
|
|
3819
|
-
return currentRole(files);
|
|
3820
|
-
} catch {
|
|
3821
|
-
return void 0;
|
|
3555
|
+
async function assertInsideRepository(root, relativePath) {
|
|
3556
|
+
const absolutePath = join2(root, relativePath);
|
|
3557
|
+
const realRoot = await realpath(root);
|
|
3558
|
+
let realParent;
|
|
3559
|
+
try {
|
|
3560
|
+
realParent = await realpath(dirname3(absolutePath));
|
|
3561
|
+
} catch (error) {
|
|
3562
|
+
if (isNodeError2(error, "ENOENT")) {
|
|
3563
|
+
return;
|
|
3822
3564
|
}
|
|
3823
|
-
|
|
3824
|
-
|
|
3565
|
+
throw error;
|
|
3566
|
+
}
|
|
3567
|
+
if (realParent !== realRoot && !realParent.startsWith(realRoot + sep)) {
|
|
3825
3568
|
throw new Error(
|
|
3826
|
-
|
|
3569
|
+
`${relativePath} resolves outside the repository (${realParent}); refusing to touch it.`
|
|
3570
|
+
);
|
|
3571
|
+
}
|
|
3572
|
+
try {
|
|
3573
|
+
if ((await lstat(absolutePath)).isSymbolicLink()) {
|
|
3574
|
+
throw new Error(`${relativePath} is a symbolic link; refusing to write through it.`);
|
|
3575
|
+
}
|
|
3576
|
+
} catch (error) {
|
|
3577
|
+
if (!isNodeError2(error, "ENOENT")) {
|
|
3578
|
+
throw error;
|
|
3579
|
+
}
|
|
3580
|
+
}
|
|
3581
|
+
}
|
|
3582
|
+
function isNodeError2(error, code) {
|
|
3583
|
+
return typeof error === "object" && error !== null && error.code === code;
|
|
3584
|
+
}
|
|
3585
|
+
|
|
3586
|
+
// src/adapters/cli/initCommand.ts
|
|
3587
|
+
async function executeInitCommand(options) {
|
|
3588
|
+
const args = parseInitArgs(options.argv);
|
|
3589
|
+
if (args.ignored.length > 0) {
|
|
3590
|
+
options.io.stderr.write(
|
|
3591
|
+
`Ignored ${args.ignored.join(", ")}: init no longer asks for a role or a project \u2014 every session sees the same tools and your AiNecto permissions decide what they may change.
|
|
3592
|
+
`
|
|
3827
3593
|
);
|
|
3828
3594
|
}
|
|
3829
|
-
const
|
|
3830
|
-
|
|
3831
|
-
await
|
|
3832
|
-
|
|
3833
|
-
|
|
3595
|
+
const root = resolve2(options.cwd);
|
|
3596
|
+
const rootNotes = await repositoryRootNotes(root);
|
|
3597
|
+
const files = await readSnapshot(root);
|
|
3598
|
+
if (args.undo) {
|
|
3599
|
+
const plan2 = planUndo({ files });
|
|
3600
|
+
await applyPlan(root, plan2, args.dryRun, files, false);
|
|
3834
3601
|
options.io.stdout.write(renderSuccess(
|
|
3835
3602
|
{
|
|
3836
|
-
|
|
3837
|
-
|
|
3603
|
+
action: args.dryRun ? "undo (dry-run)" : "undo",
|
|
3604
|
+
updated: plan2.writes.map((write) => write.path),
|
|
3605
|
+
removed: plan2.deletes,
|
|
3606
|
+
notes: [...rootNotes, ...plan2.notes]
|
|
3838
3607
|
},
|
|
3839
3608
|
{ json: options.json }
|
|
3840
3609
|
));
|
|
3841
|
-
return
|
|
3610
|
+
return 0;
|
|
3611
|
+
}
|
|
3612
|
+
const refusal = initRefusal(files);
|
|
3613
|
+
if (refusal !== void 0) {
|
|
3614
|
+
throw new Error(refusal);
|
|
3842
3615
|
}
|
|
3843
|
-
const
|
|
3616
|
+
const signedIn = await ensureSignedIn(options, args.dryRun);
|
|
3617
|
+
const documents = signedIn ? await fetchHarnessDocuments({
|
|
3844
3618
|
endpoint: options.endpoint,
|
|
3845
3619
|
accessToken: await options.accessToken?.(),
|
|
3846
3620
|
// 서버가 문서의 클라이언트별 변형 키를 이 이름으로 찾는다.
|
|
3847
3621
|
client: "ainecto-cli",
|
|
3848
3622
|
fetchImpl: options.fetchImpl
|
|
3849
|
-
});
|
|
3623
|
+
}) : { versions: {}, fallbacks: {}, unavailableReason: args.dryRun ? "not signed in, and --dry-run does not sign in" : "not signed in" };
|
|
3850
3624
|
const plan = planInit({
|
|
3851
|
-
role,
|
|
3852
3625
|
env: options.env,
|
|
3853
|
-
projectName: project.name,
|
|
3854
|
-
projectUuid: project.uuid,
|
|
3855
3626
|
endpoint: options.endpoint,
|
|
3627
|
+
mcp: args.mcp,
|
|
3856
3628
|
cliVersion: options.cliVersion,
|
|
3857
3629
|
files,
|
|
3858
3630
|
documents
|
|
@@ -3861,39 +3633,18 @@ async function runInit(initialOptions) {
|
|
|
3861
3633
|
throw new Error(plan.refusal);
|
|
3862
3634
|
}
|
|
3863
3635
|
await applyPlan(root, plan, args.dryRun, files);
|
|
3864
|
-
|
|
3865
|
-
options.io.stderr.write(`Role changes from ${previousRole2} to ${role}: \`ainecto\` commands now, MCP tools from a new session.
|
|
3866
|
-
`);
|
|
3867
|
-
}
|
|
3636
|
+
const mcpWritten = plan.writes.some((write) => AGENT_TARGETS.some((target) => target.path === write.path));
|
|
3868
3637
|
options.io.stdout.write(renderSuccess(
|
|
3869
3638
|
{
|
|
3870
3639
|
action: args.dryRun ? "init (dry-run)" : "init",
|
|
3871
|
-
role,
|
|
3872
|
-
project: { uuid: project.uuid, name: project.name },
|
|
3873
3640
|
endpoint: options.endpoint,
|
|
3874
3641
|
written: plan.writes.map((write) => `${write.existed ? "updated" : "created"} ${write.path}`),
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
// ⚠조용히 기본값으로 떨어지지 않는다 — 서버에서 규칙을 고쳐도 안 바뀌는 이유를
|
|
3879
|
-
// 사용자가 영영 못 찾게 된다. ★요청 자체의 실패와 «문서별» 되돌림을 둘 다 적는다
|
|
3880
|
-
// (2026-09-23 독립 재리뷰 I5: HTTP 200 으로 온 빈 문서가 조용히 패키지 판이 됐다).
|
|
3881
|
-
notes: [...rootNotes, ...plan.notes, ...fallbackNotes(documents)],
|
|
3882
|
-
// ★「Enforce this on the server too: ainecto auth login …」 줄은 뺐다. init 이 이미 그 역할로
|
|
3883
|
-
// 로그인했으므로 참이 아니고, 남아 있으면 에이전트가 따라 쳐서 사람이 한 번 더 승인하게 된다
|
|
3884
|
-
// (2026-09-29 독립 리뷰 P1-1).
|
|
3885
|
-
// ★재시작 없이 이어간다(2026-09-30 사용자 결정). 셸의 `ainecto` 는 부를 때마다 저장소 역할을 읽어
|
|
3886
|
-
// 역할 헤더로 싣는다 — 서버가 요청마다 강제하므로 MCP 연결과 무관하게 이 세션에서 바로 맞다.
|
|
3887
|
-
// 재시작은 «그 역할의 MCP 도구»를 더할 뿐인 선택이다.
|
|
3642
|
+
// ⚠조용히 기본값으로 떨어지지 않는다 — 서버에서 규칙을 고쳐도 안 바뀌는 이유를 사용자가 영영 못 찾는다.
|
|
3643
|
+
// ★요청 자체의 실패와 «문서별» 되돌림을 둘 다, 어느 «파일»이 그 글을 받는지까지 적는다.
|
|
3644
|
+
notes: [...rootNotes, ...plan.notes, ...fallbackNotes(documents, args.dryRun)],
|
|
3888
3645
|
next: [
|
|
3889
|
-
`
|
|
3890
|
-
|
|
3891
|
-
"Do not use MCP tools this session started with for this work \u2014 they keep the role (or no role) the session started with.",
|
|
3892
|
-
"Optional, to add the role's MCP tools: start a new session.",
|
|
3893
|
-
`Claude Code: exit and run \`claude -c\` in this folder, then approve the "${SERVER_NAME}" server from .mcp.json when asked.`,
|
|
3894
|
-
`Codex: save the profile above as $CODEX_HOME/${role}.config.toml and start with \`codex -p ${role}\`.`,
|
|
3895
|
-
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.`,
|
|
3896
|
-
`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\`).`,
|
|
3646
|
+
`Read ${HARNESS_DOC_PATH} first. Every session sees the same AiNecto tools; your AiNecto permissions decide what they may change.`,
|
|
3647
|
+
mcpWritten ? "Claude Code and Cursor pick up the repository's MCP config in a new session." : "To connect your coding agents on this machine, run `ainecto setup` once (or re-run init with --mcp to also write this repository's .mcp.json and .cursor/mcp.json).",
|
|
3897
3648
|
"Undo everything with: ainecto init --undo"
|
|
3898
3649
|
]
|
|
3899
3650
|
},
|
|
@@ -3901,43 +3652,29 @@ async function runInit(initialOptions) {
|
|
|
3901
3652
|
));
|
|
3902
3653
|
return 0;
|
|
3903
3654
|
}
|
|
3904
|
-
function
|
|
3905
|
-
const
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
}
|
|
3911
|
-
return
|
|
3912
|
-
}
|
|
3913
|
-
function fallbackNotes(documents) {
|
|
3914
|
-
const notes = [];
|
|
3915
|
-
if (documents.unavailableReason) {
|
|
3916
|
-
notes.push(`Harness rules came from this package, not the server (${documents.unavailableReason}).`);
|
|
3917
|
-
}
|
|
3918
|
-
for (const [key, reason] of Object.entries(documents.fallbacks)) {
|
|
3919
|
-
notes.push(`${key} came from this package, not the server (${reason}).`);
|
|
3920
|
-
}
|
|
3921
|
-
return notes;
|
|
3655
|
+
function fallbackNotes(documents, dryRun) {
|
|
3656
|
+
const filesOf = {
|
|
3657
|
+
[DOC_KEY]: HARNESS_DOC_PATH,
|
|
3658
|
+
[AGENT_NOTE_KEY]: "AGENTS.md / CLAUDE.md"
|
|
3659
|
+
};
|
|
3660
|
+
const caveat = dryRun ? " \u2014 the real run may write the server's version instead" : "";
|
|
3661
|
+
const reasons = documents.unavailableReason ? { [DOC_KEY]: documents.unavailableReason, [AGENT_NOTE_KEY]: documents.unavailableReason } : documents.fallbacks;
|
|
3662
|
+
return Object.entries(reasons).map(([key, reason]) => `${filesOf[key] ?? key}: written from this package's default ${key}, not the server's (${reason})${caveat}.`);
|
|
3922
3663
|
}
|
|
3923
|
-
async function ensureSignedIn(options,
|
|
3924
|
-
if (options.login === void 0) {
|
|
3925
|
-
return;
|
|
3926
|
-
}
|
|
3664
|
+
async function ensureSignedIn(options, dryRun) {
|
|
3927
3665
|
if (await options.accessToken?.()) {
|
|
3928
|
-
return;
|
|
3666
|
+
return true;
|
|
3929
3667
|
}
|
|
3930
|
-
if (dryRun) {
|
|
3931
|
-
|
|
3932
|
-
`No usable AiNecto sign-in
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
Then run init again.`
|
|
3937
|
-
);
|
|
3668
|
+
if (dryRun || options.login === void 0) {
|
|
3669
|
+
if (dryRun) {
|
|
3670
|
+
options.io.stderr.write(`No usable AiNecto sign-in, so this plan uses the package's default rules. Sign in with: ${loginCommand(options)}
|
|
3671
|
+
`);
|
|
3672
|
+
}
|
|
3673
|
+
return false;
|
|
3938
3674
|
}
|
|
3939
3675
|
await options.login();
|
|
3940
3676
|
options.io.stderr.write("Signed in.\n");
|
|
3677
|
+
return true;
|
|
3941
3678
|
}
|
|
3942
3679
|
async function repositoryRootNotes(cwd) {
|
|
3943
3680
|
const gitRoot = await findGitRoot(cwd);
|
|
@@ -3955,178 +3692,10 @@ Nothing was changed.`
|
|
|
3955
3692
|
}
|
|
3956
3693
|
return [];
|
|
3957
3694
|
}
|
|
3958
|
-
function connectAs(options, role) {
|
|
3959
|
-
if (options.connect) {
|
|
3960
|
-
const connection = options.connect(role);
|
|
3961
|
-
return { ...options, ...connection };
|
|
3962
|
-
}
|
|
3963
|
-
if (!options.client) {
|
|
3964
|
-
throw new Error("init needs either a client or a way to connect once the role is known.");
|
|
3965
|
-
}
|
|
3966
|
-
return { ...options, client: options.client };
|
|
3967
|
-
}
|
|
3968
|
-
var ROLE_CHOICES = [
|
|
3969
|
-
{ label: "Design requirements, ERD, boundaries, tasks", value: "design" },
|
|
3970
|
-
{ label: "Development product code, following the approved design", value: "development" },
|
|
3971
|
-
{ label: "Test test scenarios and test code", value: "test" },
|
|
3972
|
-
{ label: "Validation judges the result; changes nothing", value: "validation" }
|
|
3973
|
-
];
|
|
3974
|
-
async function askRole(prompter, current) {
|
|
3975
|
-
const index = current === void 0 ? void 0 : ROLE_CHOICES.findIndex((choice) => choice.value === current);
|
|
3976
|
-
return prompter.choose(
|
|
3977
|
-
current === void 0 ? "Which role should AI sessions in this repository have?" : `Which role should AI sessions in this repository have? (now: ${current})`,
|
|
3978
|
-
ROLE_CHOICES,
|
|
3979
|
-
index
|
|
3980
|
-
);
|
|
3981
|
-
}
|
|
3982
|
-
async function askProject(options, args, projects, bound, root, role, prompter) {
|
|
3983
|
-
const canCreate = role === "design" && !args.dryRun;
|
|
3984
|
-
const askNameAndCreate = async () => {
|
|
3985
|
-
const name = await prompter.text("New project name", args.projectName ?? basenameOf(root));
|
|
3986
|
-
return createProject(options, name);
|
|
3987
|
-
};
|
|
3988
|
-
if (projects.length === 0) {
|
|
3989
|
-
if (role !== "design") {
|
|
3990
|
-
throw new Error(noProjectForRole(options, role));
|
|
3991
|
-
}
|
|
3992
|
-
if (args.dryRun) {
|
|
3993
|
-
throw new Error(dryRunCannotCreate(args.projectName ?? basenameOf(root)));
|
|
3994
|
-
}
|
|
3995
|
-
return askNameAndCreate();
|
|
3996
|
-
}
|
|
3997
|
-
const choices = projects.map((project) => ({
|
|
3998
|
-
label: `${project.name} (${project.uuid})`,
|
|
3999
|
-
value: { project }
|
|
4000
|
-
}));
|
|
4001
|
-
if (canCreate) {
|
|
4002
|
-
choices.push({ label: "Create a new project", value: { create: true } });
|
|
4003
|
-
}
|
|
4004
|
-
const boundIndex = bound === void 0 ? -1 : projects.findIndex((project) => project.uuid === bound);
|
|
4005
|
-
const defaultIndex = boundIndex >= 0 ? boundIndex : projects.length === 1 ? 0 : void 0;
|
|
4006
|
-
const picked = await prompter.choose("Which AiNecto project should this repository use?", choices, defaultIndex);
|
|
4007
|
-
return "create" in picked ? askNameAndCreate() : picked.project;
|
|
4008
|
-
}
|
|
4009
|
-
function noProjectForRole(options, role) {
|
|
4010
|
-
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}.`;
|
|
4011
|
-
}
|
|
4012
|
-
function dryRunCannotCreate(name) {
|
|
4013
|
-
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>.`;
|
|
4014
|
-
}
|
|
4015
|
-
async function resolveProject(options, args, files, root, role) {
|
|
4016
|
-
const projects = await listProjects(options);
|
|
4017
|
-
if (args.projectUuid) {
|
|
4018
|
-
const found = projects.find((project) => project.uuid === args.projectUuid);
|
|
4019
|
-
return found ?? { uuid: args.projectUuid, name: args.projectName ?? args.projectUuid };
|
|
4020
|
-
}
|
|
4021
|
-
const bound = boundProjectUuid(files.get(HARNESS_CONFIG_PATH));
|
|
4022
|
-
if (options.prompter) {
|
|
4023
|
-
return askProject(options, args, projects, bound, root, role, options.prompter);
|
|
4024
|
-
}
|
|
4025
|
-
if (bound) {
|
|
4026
|
-
const found = projects.find((project) => project.uuid === bound);
|
|
4027
|
-
if (found) {
|
|
4028
|
-
return found;
|
|
4029
|
-
}
|
|
4030
|
-
}
|
|
4031
|
-
if (projects.length === 1) {
|
|
4032
|
-
return projects[0];
|
|
4033
|
-
}
|
|
4034
|
-
if (projects.length > 1) {
|
|
4035
|
-
return { choices: projects };
|
|
4036
|
-
}
|
|
4037
|
-
if (role !== "design") {
|
|
4038
|
-
throw new Error(noProjectForRole(options, role));
|
|
4039
|
-
}
|
|
4040
|
-
const name = args.projectName ?? basenameOf(root);
|
|
4041
|
-
if (!args.yes) {
|
|
4042
|
-
throw new Error(
|
|
4043
|
-
`No project found in your AiNecto account. Re-run with --yes to create "${name}", or --project-name <name> --yes to pick the name.`
|
|
4044
|
-
);
|
|
4045
|
-
}
|
|
4046
|
-
if (args.dryRun) {
|
|
4047
|
-
throw new Error(dryRunCannotCreate(name));
|
|
4048
|
-
}
|
|
4049
|
-
return createProject(options, name);
|
|
4050
|
-
}
|
|
4051
|
-
async function listProjects(options) {
|
|
4052
|
-
const result = await callOrExplainSignIn(options, () => options.client.toolsCall("list_projects", {}));
|
|
4053
|
-
return extractProjects(unwrapToolJson(result));
|
|
4054
|
-
}
|
|
4055
|
-
async function callOrExplainSignIn(options, run) {
|
|
4056
|
-
try {
|
|
4057
|
-
return await run();
|
|
4058
|
-
} catch (error) {
|
|
4059
|
-
if (!isUnauthorized(error)) {
|
|
4060
|
-
throw error;
|
|
4061
|
-
}
|
|
4062
|
-
throw new Error(
|
|
4063
|
-
`Not signed in, so ${options.endpoint} refused the request (HTTP 401).
|
|
4064
|
-
|
|
4065
|
-
${loginCommand(options)}
|
|
4066
|
-
|
|
4067
|
-
Then run init again. Sign-in is one per machine; the role comes from this repository.`
|
|
4068
|
-
);
|
|
4069
|
-
}
|
|
4070
|
-
}
|
|
4071
3695
|
function loginCommand(options) {
|
|
4072
3696
|
const target = isDefaultEndpointFor(options.env, options.endpoint) ? options.env === "prod" ? "" : ` --env ${options.env}` : ` --endpoint ${options.endpoint}`;
|
|
4073
3697
|
return `ainecto auth login${target}`;
|
|
4074
3698
|
}
|
|
4075
|
-
function isUnauthorized(error) {
|
|
4076
|
-
return error instanceof McpRpcError && error.code === "MCP_HTTP_ERROR" && error.details?.status === 401;
|
|
4077
|
-
}
|
|
4078
|
-
async function createProject(options, name) {
|
|
4079
|
-
const result = await callOrExplainSignIn(options, () => options.client.toolsCall("create_projects", { items: [{ name }] }));
|
|
4080
|
-
const created = extractProjects(unwrapToolJson(result));
|
|
4081
|
-
const first = created[0];
|
|
4082
|
-
if (!first) {
|
|
4083
|
-
throw new Error("Project creation did not return a project uuid.");
|
|
4084
|
-
}
|
|
4085
|
-
options.io.stderr.write(`Created project ${first.name} (${first.uuid}).
|
|
4086
|
-
`);
|
|
4087
|
-
return first;
|
|
4088
|
-
}
|
|
4089
|
-
function unwrapToolJson(result) {
|
|
4090
|
-
if (!isRecord11(result) || !Array.isArray(result.content)) {
|
|
4091
|
-
return result;
|
|
4092
|
-
}
|
|
4093
|
-
const first = result.content.find((item) => isRecord11(item) && item.type === "text");
|
|
4094
|
-
if (!isRecord11(first) || typeof first.text !== "string") {
|
|
4095
|
-
return result;
|
|
4096
|
-
}
|
|
4097
|
-
try {
|
|
4098
|
-
return JSON.parse(first.text);
|
|
4099
|
-
} catch {
|
|
4100
|
-
return first.text;
|
|
4101
|
-
}
|
|
4102
|
-
}
|
|
4103
|
-
function extractProjects(value) {
|
|
4104
|
-
const candidates = Array.isArray(value) ? value : isRecord11(value) ? ["projects", "items", "created", "data", "hits"].flatMap((key) => {
|
|
4105
|
-
const nested = value[key];
|
|
4106
|
-
return Array.isArray(nested) ? nested : [];
|
|
4107
|
-
}) : [];
|
|
4108
|
-
const projects = [];
|
|
4109
|
-
for (const candidate of candidates) {
|
|
4110
|
-
if (isRecord11(candidate) && typeof candidate.uuid === "string") {
|
|
4111
|
-
projects.push({
|
|
4112
|
-
uuid: candidate.uuid,
|
|
4113
|
-
name: typeof candidate.name === "string" ? candidate.name : candidate.uuid
|
|
4114
|
-
});
|
|
4115
|
-
}
|
|
4116
|
-
}
|
|
4117
|
-
return projects;
|
|
4118
|
-
}
|
|
4119
|
-
function boundProjectUuid(config) {
|
|
4120
|
-
if (!config) {
|
|
4121
|
-
return void 0;
|
|
4122
|
-
}
|
|
4123
|
-
try {
|
|
4124
|
-
const parsed = JSON.parse(config);
|
|
4125
|
-
return typeof parsed.project?.uuid === "string" ? parsed.project.uuid : void 0;
|
|
4126
|
-
} catch {
|
|
4127
|
-
return void 0;
|
|
4128
|
-
}
|
|
4129
|
-
}
|
|
4130
3699
|
async function readSnapshot(root) {
|
|
4131
3700
|
const snapshot = /* @__PURE__ */ new Map();
|
|
4132
3701
|
for (const path of MANAGED_PATHS) {
|
|
@@ -4271,22 +3840,26 @@ async function fileMode(absolutePath) {
|
|
|
4271
3840
|
}
|
|
4272
3841
|
}
|
|
4273
3842
|
function parseInitArgs(argv) {
|
|
4274
|
-
const args = { dryRun: false, undo: false,
|
|
3843
|
+
const args = { mcp: false, dryRun: false, undo: false, ignored: [] };
|
|
4275
3844
|
for (let index = 0; index < argv.length; index += 1) {
|
|
4276
3845
|
const arg = argv[index];
|
|
4277
3846
|
if (!arg) {
|
|
4278
3847
|
continue;
|
|
4279
3848
|
}
|
|
4280
|
-
if (arg === "--
|
|
4281
|
-
args.
|
|
4282
|
-
} else if (arg === "--project-name") {
|
|
4283
|
-
args.projectName = requireValue3(arg, argv[++index]);
|
|
3849
|
+
if (arg === "--mcp") {
|
|
3850
|
+
args.mcp = true;
|
|
4284
3851
|
} else if (arg === "--dry-run") {
|
|
4285
3852
|
args.dryRun = true;
|
|
4286
3853
|
} else if (arg === "--undo") {
|
|
4287
3854
|
args.undo = true;
|
|
3855
|
+
} else if (arg === "--project" || arg === "--project-name") {
|
|
3856
|
+
const value = argv[index + 1];
|
|
3857
|
+
if (value !== void 0 && !value.startsWith("--")) {
|
|
3858
|
+
index += 1;
|
|
3859
|
+
}
|
|
3860
|
+
args.ignored.push(arg);
|
|
4288
3861
|
} else if (arg === "--yes" || arg === "-y") {
|
|
4289
|
-
args.
|
|
3862
|
+
args.ignored.push(arg);
|
|
4290
3863
|
} else if (arg === "--json") {
|
|
4291
3864
|
} else {
|
|
4292
3865
|
throw new Error(`Unknown argument: ${arg}`);
|
|
@@ -4294,21 +3867,730 @@ function parseInitArgs(argv) {
|
|
|
4294
3867
|
}
|
|
4295
3868
|
return args;
|
|
4296
3869
|
}
|
|
4297
|
-
function
|
|
4298
|
-
|
|
4299
|
-
|
|
3870
|
+
function isRecord11(value) {
|
|
3871
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3872
|
+
}
|
|
3873
|
+
|
|
3874
|
+
// src/core/setup/accountSummary.ts
|
|
3875
|
+
async function readAccount(client) {
|
|
3876
|
+
const summary = {};
|
|
3877
|
+
try {
|
|
3878
|
+
await client.initialize();
|
|
3879
|
+
const self = unwrapMcpToolPayload(await client.toolsCall("identity_get_self", {}));
|
|
3880
|
+
if (typeof self?.userId === "string") summary.userId = self.userId;
|
|
3881
|
+
if (typeof self?.displayName === "string") summary.displayName = self.displayName;
|
|
3882
|
+
if ("jobProfile" in (self ?? {})) {
|
|
3883
|
+
const profile = self.jobProfile;
|
|
3884
|
+
summary.jobTitle = profile && typeof profile.title === "string" ? profile.title : null;
|
|
3885
|
+
}
|
|
3886
|
+
summary.projectCount = extractProjects(unwrapMcpToolPayload(await client.toolsCall("list_projects", {}))).length;
|
|
3887
|
+
} catch (error) {
|
|
3888
|
+
summary.problem = classify(error);
|
|
4300
3889
|
}
|
|
4301
|
-
return
|
|
3890
|
+
return summary;
|
|
4302
3891
|
}
|
|
4303
|
-
function
|
|
4304
|
-
const
|
|
4305
|
-
|
|
3892
|
+
function classify(error) {
|
|
3893
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3894
|
+
if (error instanceof McpRpcError && error.code === "MCP_HTTP_ERROR") {
|
|
3895
|
+
const status = error.details?.status;
|
|
3896
|
+
if (status === 401) return { kind: "unauthorized", message };
|
|
3897
|
+
if (status === 403) return { kind: "forbidden", message };
|
|
3898
|
+
return { kind: "other", message };
|
|
3899
|
+
}
|
|
3900
|
+
if (error instanceof McpRpcError) {
|
|
3901
|
+
return { kind: "other", message };
|
|
3902
|
+
}
|
|
3903
|
+
return { kind: "network", message };
|
|
4306
3904
|
}
|
|
4307
|
-
|
|
4308
|
-
|
|
3905
|
+
|
|
3906
|
+
// src/core/setup/agentConnections.ts
|
|
3907
|
+
import { chmod as chmod3, mkdir as mkdir4, readFile as readFile6, rename as rename4, stat as stat4, writeFile as writeFile3 } from "fs/promises";
|
|
3908
|
+
import { dirname as dirname5, join as join4 } from "path";
|
|
3909
|
+
|
|
3910
|
+
// src/core/setup/commandRunner.ts
|
|
3911
|
+
import { spawn as spawn2 } from "child_process";
|
|
3912
|
+
var COMMAND_TIMEOUT_MS = 9e4;
|
|
3913
|
+
function systemCommandRunner(options) {
|
|
3914
|
+
return (command, args) => new Promise((resolve3) => {
|
|
3915
|
+
let stdout = "";
|
|
3916
|
+
let stderr = "";
|
|
3917
|
+
let settled = false;
|
|
3918
|
+
const finish = (result) => {
|
|
3919
|
+
if (!settled) {
|
|
3920
|
+
settled = true;
|
|
3921
|
+
clearTimeout(timer);
|
|
3922
|
+
resolve3(result);
|
|
3923
|
+
}
|
|
3924
|
+
};
|
|
3925
|
+
const child = spawn2(command, [...args], {
|
|
3926
|
+
cwd: options.cwd,
|
|
3927
|
+
env: options.env ?? process.env,
|
|
3928
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
3929
|
+
});
|
|
3930
|
+
const timer = setTimeout(() => {
|
|
3931
|
+
child.kill();
|
|
3932
|
+
finish({ exitCode: null, stdout, stderr: `${stderr}
|
|
3933
|
+
${command} timed out after ${COMMAND_TIMEOUT_MS / 1e3}s`, missing: false });
|
|
3934
|
+
}, COMMAND_TIMEOUT_MS);
|
|
3935
|
+
child.stdout.on("data", (chunk) => {
|
|
3936
|
+
stdout += String(chunk);
|
|
3937
|
+
});
|
|
3938
|
+
child.stderr.on("data", (chunk) => {
|
|
3939
|
+
stderr += String(chunk);
|
|
3940
|
+
});
|
|
3941
|
+
child.on("error", (error) => {
|
|
3942
|
+
finish({ exitCode: null, stdout, stderr: error.message, missing: error.code === "ENOENT" });
|
|
3943
|
+
});
|
|
3944
|
+
child.on("close", (code) => finish({ exitCode: code, stdout, stderr, missing: false }));
|
|
3945
|
+
});
|
|
3946
|
+
}
|
|
3947
|
+
function recordingRunner(runner, log) {
|
|
3948
|
+
return async (command, args) => {
|
|
3949
|
+
const result = await runner(command, args);
|
|
3950
|
+
const entry = { command: [command, ...args].join(" "), exitCode: result.exitCode };
|
|
3951
|
+
if (result.missing) {
|
|
3952
|
+
entry.missing = true;
|
|
3953
|
+
} else if (result.exitCode !== 0) {
|
|
3954
|
+
entry.error = lastLine(result.stderr) ?? lastLine(result.stdout);
|
|
3955
|
+
}
|
|
3956
|
+
log.push(entry);
|
|
3957
|
+
return result;
|
|
3958
|
+
};
|
|
3959
|
+
}
|
|
3960
|
+
function lastLine(text) {
|
|
3961
|
+
const lines = text.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
3962
|
+
return lines[lines.length - 1];
|
|
3963
|
+
}
|
|
3964
|
+
|
|
3965
|
+
// src/core/setup/agentConnections.ts
|
|
3966
|
+
var AGENT_CLIENT_LABELS = {
|
|
3967
|
+
"claude-code": "Claude Code",
|
|
3968
|
+
cursor: "Cursor",
|
|
3969
|
+
codex: "Codex"
|
|
3970
|
+
};
|
|
3971
|
+
async function inspectAgents(context) {
|
|
3972
|
+
return [await inspectClaude(context), await inspectCursor(context), await inspectCodex(context)];
|
|
3973
|
+
}
|
|
3974
|
+
var NOT_FOUND = /No MCP server named/i;
|
|
3975
|
+
async function inspectClaude({ runner }) {
|
|
3976
|
+
const base = { client: "claude-code", ours: false, carriesRole: false };
|
|
3977
|
+
const result = await runner("claude", ["mcp", "get", SERVER_NAME]);
|
|
3978
|
+
if (result.missing) {
|
|
3979
|
+
return { ...base, installed: false };
|
|
3980
|
+
}
|
|
3981
|
+
if (result.exitCode !== 0) {
|
|
3982
|
+
return NOT_FOUND.test(result.stdout + result.stderr) ? { ...base, installed: true } : { ...base, installed: true, problem: lastLine(result.stderr) ?? `claude mcp get exited ${result.exitCode}` };
|
|
3983
|
+
}
|
|
3984
|
+
const field = (name) => result.stdout.match(new RegExp(`^\\s*${name}:\\s*(.*)$`, "m"))?.[1]?.trim();
|
|
3985
|
+
const scopeText = field("Scope") ?? "";
|
|
3986
|
+
const scope = /^user/i.test(scopeText) ? "user" : /^local/i.test(scopeText) ? "local" : /^project/i.test(scopeText) ? "project" : scopeText || void 0;
|
|
3987
|
+
const url = field("URL");
|
|
3988
|
+
const command = field("Command");
|
|
3989
|
+
const entry = url ? { url } : command ? { command, args: (field("Args") ?? "").split(/\s+/).filter(Boolean) } : void 0;
|
|
3990
|
+
return { ...base, installed: true, scope, ...describe(entry) };
|
|
3991
|
+
}
|
|
3992
|
+
async function inspectCursor({ home }) {
|
|
3993
|
+
const base = { client: "cursor", ours: false, carriesRole: false, scope: "global" };
|
|
3994
|
+
if (!await exists(join4(home, ".cursor"))) {
|
|
3995
|
+
return { ...base, installed: false };
|
|
3996
|
+
}
|
|
3997
|
+
let content;
|
|
3998
|
+
try {
|
|
3999
|
+
content = await readFile6(cursorConfigPath(home), "utf8");
|
|
4000
|
+
} catch (error) {
|
|
4001
|
+
if (!isNodeError3(error, "ENOENT")) {
|
|
4002
|
+
return { ...base, installed: true, problem: error instanceof Error ? error.message : String(error) };
|
|
4003
|
+
}
|
|
4004
|
+
}
|
|
4005
|
+
if (content !== void 0 && content.trim()) {
|
|
4006
|
+
try {
|
|
4007
|
+
JSON.parse(content);
|
|
4008
|
+
} catch {
|
|
4009
|
+
return { ...base, installed: true, problem: `${cursorConfigPath(home)} is not valid JSON` };
|
|
4010
|
+
}
|
|
4011
|
+
}
|
|
4012
|
+
const raw = readServerEntry(content);
|
|
4013
|
+
return { ...base, installed: true, ...describe(toExisting(raw)) };
|
|
4014
|
+
}
|
|
4015
|
+
async function inspectCodex({ runner }) {
|
|
4016
|
+
const base = { client: "codex", ours: false, carriesRole: false, scope: "global" };
|
|
4017
|
+
const result = await runner("codex", ["mcp", "get", SERVER_NAME, "--json"]);
|
|
4018
|
+
if (result.missing) {
|
|
4019
|
+
return { ...base, installed: false };
|
|
4020
|
+
}
|
|
4021
|
+
if (result.exitCode !== 0) {
|
|
4022
|
+
return NOT_FOUND.test(result.stdout + result.stderr) ? { ...base, installed: true } : { ...base, installed: true, problem: lastLine(result.stderr) ?? `codex mcp get exited ${result.exitCode}` };
|
|
4023
|
+
}
|
|
4024
|
+
try {
|
|
4025
|
+
const parsed = JSON.parse(result.stdout);
|
|
4026
|
+
const transport = parsed.transport ?? {};
|
|
4027
|
+
return { ...base, installed: true, ...describe(toExisting(transport)) };
|
|
4028
|
+
} catch {
|
|
4029
|
+
return { ...base, installed: true, problem: "codex mcp get --json did not return JSON" };
|
|
4030
|
+
}
|
|
4031
|
+
}
|
|
4032
|
+
async function connectAgent(state, desired, context, dryRun) {
|
|
4033
|
+
const outcome = (status, detail) => ({
|
|
4034
|
+
client: state.client,
|
|
4035
|
+
label: AGENT_CLIENT_LABELS[state.client],
|
|
4036
|
+
status,
|
|
4037
|
+
...dryRun && (status === "connected" || status === "updated") ? { planned: true } : {},
|
|
4038
|
+
...detail ? { detail } : {}
|
|
4039
|
+
});
|
|
4040
|
+
if (!state.installed) {
|
|
4041
|
+
return outcome("not installed");
|
|
4042
|
+
}
|
|
4043
|
+
if (state.problem) {
|
|
4044
|
+
return outcome("failed", state.problem);
|
|
4045
|
+
}
|
|
4046
|
+
if (state.entry && (!state.ours || state.client === "claude-code" && state.scope !== "user")) {
|
|
4047
|
+
return outcome("kept", keptDetail(state));
|
|
4048
|
+
}
|
|
4049
|
+
if (state.entry && sameEntry(state.entry, desired)) {
|
|
4050
|
+
return outcome("connected", "already connected");
|
|
4051
|
+
}
|
|
4052
|
+
const updating = state.entry !== void 0;
|
|
4053
|
+
const commands = writeCommands(state.client, desired, updating);
|
|
4054
|
+
if (dryRun) {
|
|
4055
|
+
return outcome(updating ? "updated" : "connected", commands.length > 0 ? `would run: ${commands.map((c) => c.join(" ")).join(" && ")}` : `would write ${cursorConfigPath(context.home)}`);
|
|
4056
|
+
}
|
|
4057
|
+
try {
|
|
4058
|
+
if (state.client === "cursor") {
|
|
4059
|
+
const path = cursorConfigPath(context.home);
|
|
4060
|
+
await writeKeepingMode(path, mergeServerEntry(await readIfExists2(path), desired).content);
|
|
4061
|
+
}
|
|
4062
|
+
for (const [command, ...args] of commands) {
|
|
4063
|
+
const result = await context.runner(command, args);
|
|
4064
|
+
if (result.exitCode !== 0) {
|
|
4065
|
+
return outcome("failed", `${[command, ...args].join(" ")}: ${lastLine(result.stderr) ?? lastLine(result.stdout) ?? `exit ${result.exitCode}`}`);
|
|
4066
|
+
}
|
|
4067
|
+
}
|
|
4068
|
+
} catch (error) {
|
|
4069
|
+
return outcome("failed", error instanceof Error ? error.message : String(error));
|
|
4070
|
+
}
|
|
4071
|
+
return outcome(updating ? "updated" : "connected");
|
|
4072
|
+
}
|
|
4073
|
+
async function disconnectAgent(state, context, dryRun) {
|
|
4074
|
+
const outcome = (status, detail) => ({
|
|
4075
|
+
client: state.client,
|
|
4076
|
+
label: AGENT_CLIENT_LABELS[state.client],
|
|
4077
|
+
status,
|
|
4078
|
+
...dryRun && status === "removed" ? { planned: true } : {},
|
|
4079
|
+
...detail ? { detail } : {}
|
|
4080
|
+
});
|
|
4081
|
+
if (!state.installed) {
|
|
4082
|
+
return outcome("not installed");
|
|
4083
|
+
}
|
|
4084
|
+
if (state.problem) {
|
|
4085
|
+
return outcome("failed", state.problem);
|
|
4086
|
+
}
|
|
4087
|
+
if (!state.entry) {
|
|
4088
|
+
return outcome("not connected");
|
|
4089
|
+
}
|
|
4090
|
+
if (!state.ours || state.client === "claude-code" && state.scope !== "user") {
|
|
4091
|
+
return outcome("kept", keptDetail(state));
|
|
4092
|
+
}
|
|
4093
|
+
if (dryRun) {
|
|
4094
|
+
return outcome("removed");
|
|
4095
|
+
}
|
|
4096
|
+
try {
|
|
4097
|
+
if (state.client === "cursor") {
|
|
4098
|
+
const path = cursorConfigPath(context.home);
|
|
4099
|
+
const removed = removeServerEntry(await readIfExists2(path));
|
|
4100
|
+
if (removed.content !== void 0) {
|
|
4101
|
+
await writeKeepingMode(path, removed.content);
|
|
4102
|
+
}
|
|
4103
|
+
} else {
|
|
4104
|
+
const [command, ...args] = removeCommand(state.client);
|
|
4105
|
+
const result = await context.runner(command, args);
|
|
4106
|
+
if (result.exitCode !== 0) {
|
|
4107
|
+
return outcome("failed", `${[command, ...args].join(" ")}: ${lastLine(result.stderr) ?? `exit ${result.exitCode}`}`);
|
|
4108
|
+
}
|
|
4109
|
+
}
|
|
4110
|
+
} catch (error) {
|
|
4111
|
+
return outcome("failed", error instanceof Error ? error.message : String(error));
|
|
4112
|
+
}
|
|
4113
|
+
return outcome("removed");
|
|
4114
|
+
}
|
|
4115
|
+
function desiredEntry(input) {
|
|
4116
|
+
return buildServerEntry({ env: input.env, endpoint: input.endpoint });
|
|
4117
|
+
}
|
|
4118
|
+
function cursorConfigPath(home) {
|
|
4119
|
+
return join4(home, ".cursor", "mcp.json");
|
|
4120
|
+
}
|
|
4121
|
+
function writeCommands(client, entry, updating) {
|
|
4122
|
+
const bridge = [entry.command, ...entry.args];
|
|
4123
|
+
if (client === "claude-code") {
|
|
4124
|
+
return [
|
|
4125
|
+
...updating ? [["claude", "mcp", "remove", "-s", "user", SERVER_NAME]] : [],
|
|
4126
|
+
["claude", "mcp", "add", "-s", "user", SERVER_NAME, "--", ...bridge]
|
|
4127
|
+
];
|
|
4128
|
+
}
|
|
4129
|
+
if (client === "codex") {
|
|
4130
|
+
return [["codex", "mcp", "add", SERVER_NAME, "--", ...bridge]];
|
|
4131
|
+
}
|
|
4132
|
+
return [];
|
|
4133
|
+
}
|
|
4134
|
+
function removeCommand(client) {
|
|
4135
|
+
return client === "claude-code" ? ["claude", "mcp", "remove", "-s", "user", SERVER_NAME] : ["codex", "mcp", "remove", SERVER_NAME];
|
|
4136
|
+
}
|
|
4137
|
+
function keptDetail(state) {
|
|
4138
|
+
if (state.client === "claude-code" && state.ours && state.scope !== "user") {
|
|
4139
|
+
return `a ${state.scope}-scope "${SERVER_NAME}" entry takes precedence here \u2014 left as it is`;
|
|
4140
|
+
}
|
|
4141
|
+
const where = state.scope ? ` (${state.scope})` : "";
|
|
4142
|
+
return `someone else's "${SERVER_NAME}" entry${where} \u2014 left untouched`;
|
|
4143
|
+
}
|
|
4144
|
+
function describe(entry) {
|
|
4145
|
+
if (!entry) {
|
|
4146
|
+
return { ours: false, carriesRole: false };
|
|
4147
|
+
}
|
|
4148
|
+
if ("url" in entry) {
|
|
4149
|
+
return { entry, ours: false, carriesRole: false };
|
|
4150
|
+
}
|
|
4151
|
+
const pinnedVersion = pinnedBridgeVersion(entry);
|
|
4152
|
+
return {
|
|
4153
|
+
entry,
|
|
4154
|
+
ours: isOurBridge(entry),
|
|
4155
|
+
carriesRole: entryCarriesRole(entry),
|
|
4156
|
+
...pinnedVersion ? { pinnedVersion } : {}
|
|
4157
|
+
};
|
|
4158
|
+
}
|
|
4159
|
+
function toExisting(raw) {
|
|
4160
|
+
if (typeof raw !== "object" || raw === null) {
|
|
4161
|
+
return void 0;
|
|
4162
|
+
}
|
|
4163
|
+
const { command, args, url } = raw;
|
|
4164
|
+
if (typeof url === "string" && url) {
|
|
4165
|
+
return { url };
|
|
4166
|
+
}
|
|
4167
|
+
if (typeof command === "string") {
|
|
4168
|
+
return { command, args: Array.isArray(args) ? args.filter((arg) => typeof arg === "string") : [] };
|
|
4169
|
+
}
|
|
4170
|
+
return void 0;
|
|
4171
|
+
}
|
|
4172
|
+
function sameEntry(entry, desired) {
|
|
4173
|
+
return "command" in entry && entry.command === desired.command && entry.args.length === desired.args.length && entry.args.every((arg, at) => arg === desired.args[at]);
|
|
4174
|
+
}
|
|
4175
|
+
async function writeKeepingMode(path, content) {
|
|
4176
|
+
await mkdir4(dirname5(path), { recursive: true });
|
|
4177
|
+
let mode;
|
|
4178
|
+
try {
|
|
4179
|
+
mode = (await stat4(path)).mode & 4095;
|
|
4180
|
+
} catch (error) {
|
|
4181
|
+
if (!isNodeError3(error, "ENOENT")) {
|
|
4182
|
+
throw error;
|
|
4183
|
+
}
|
|
4184
|
+
}
|
|
4185
|
+
const tmp = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
4186
|
+
await writeFile3(tmp, content, { encoding: "utf8", mode: mode ?? 384 });
|
|
4187
|
+
if (mode !== void 0) {
|
|
4188
|
+
await chmod3(tmp, mode);
|
|
4189
|
+
}
|
|
4190
|
+
await rename4(tmp, path);
|
|
4191
|
+
}
|
|
4192
|
+
async function readIfExists2(path) {
|
|
4193
|
+
try {
|
|
4194
|
+
return await readFile6(path, "utf8");
|
|
4195
|
+
} catch (error) {
|
|
4196
|
+
if (isNodeError3(error, "ENOENT")) {
|
|
4197
|
+
return void 0;
|
|
4198
|
+
}
|
|
4199
|
+
throw error;
|
|
4200
|
+
}
|
|
4201
|
+
}
|
|
4202
|
+
async function exists(path) {
|
|
4203
|
+
try {
|
|
4204
|
+
await stat4(path);
|
|
4205
|
+
return true;
|
|
4206
|
+
} catch {
|
|
4207
|
+
return false;
|
|
4208
|
+
}
|
|
4209
|
+
}
|
|
4210
|
+
function isNodeError3(error, code) {
|
|
4211
|
+
return typeof error === "object" && error !== null && error.code === code;
|
|
4212
|
+
}
|
|
4213
|
+
|
|
4214
|
+
// src/adapters/cli/setupCommand.ts
|
|
4215
|
+
async function executeSetupCommand(options) {
|
|
4216
|
+
const args = parseSetupArgs(options.argv);
|
|
4217
|
+
const commands = [];
|
|
4218
|
+
const context = { runner: recordingRunner(options.runner, commands), home: options.home };
|
|
4219
|
+
if (args.undo) {
|
|
4220
|
+
const states = await inspectAgents(context);
|
|
4221
|
+
const agents2 = [];
|
|
4222
|
+
for (const state of states) {
|
|
4223
|
+
agents2.push(await disconnectAgent(state, context, args.dryRun));
|
|
4224
|
+
}
|
|
4225
|
+
const failed2 = agents2.some((agent) => agent.status === "failed");
|
|
4226
|
+
options.io.stdout.write(renderSuccess({
|
|
4227
|
+
action: args.dryRun ? "setup --undo (dry-run)" : "setup --undo",
|
|
4228
|
+
agents: agents2,
|
|
4229
|
+
commands,
|
|
4230
|
+
notes: ["Your sign-in is kept \u2014 sign out with `ainecto auth logout`."]
|
|
4231
|
+
}, { json: options.json }));
|
|
4232
|
+
return failed2 ? 1 : 0;
|
|
4233
|
+
}
|
|
4234
|
+
let signedIn = Boolean(await usableAccessToken(options.auth));
|
|
4235
|
+
if (!signedIn && !args.dryRun) {
|
|
4236
|
+
await options.auth.login();
|
|
4237
|
+
options.io.stderr.write("Signed in.\n");
|
|
4238
|
+
signedIn = true;
|
|
4239
|
+
}
|
|
4240
|
+
const account = signedIn ? await readAccount(options.client) : void 0;
|
|
4241
|
+
const desired = desiredEntry({ env: options.env, endpoint: options.endpoint });
|
|
4242
|
+
const agents = [];
|
|
4243
|
+
for (const state of await inspectAgents(context)) {
|
|
4244
|
+
agents.push(await connectAgent(state, desired, context, args.dryRun));
|
|
4245
|
+
}
|
|
4246
|
+
const failed = agents.some((agent) => agent.status === "failed") || account?.problem !== void 0;
|
|
4247
|
+
options.io.stdout.write(renderSuccess({
|
|
4248
|
+
action: args.dryRun ? "setup (dry-run)" : "setup",
|
|
4249
|
+
setup: failed ? "partial" : "complete",
|
|
4250
|
+
endpoint: options.endpoint,
|
|
4251
|
+
account: accountView(account, signedIn, options.endpoint),
|
|
4252
|
+
agents,
|
|
4253
|
+
commands,
|
|
4254
|
+
next: nextSteps(account, agents, options.endpoint)
|
|
4255
|
+
}, { json: options.json }));
|
|
4256
|
+
return failed ? 1 : 0;
|
|
4257
|
+
}
|
|
4258
|
+
function accountView(account, signedIn, endpoint) {
|
|
4259
|
+
if (!signedIn) {
|
|
4260
|
+
return { signedIn: false, note: "sign-in required \u2014 `ainecto setup` (without --dry-run) opens the browser once" };
|
|
4261
|
+
}
|
|
4262
|
+
const view = { signedIn: true };
|
|
4263
|
+
if (account?.userId) view.userId = account.userId;
|
|
4264
|
+
if (account?.displayName) view.name = account.displayName;
|
|
4265
|
+
if (account?.jobTitle !== void 0) {
|
|
4266
|
+
view.jobTitle = account.jobTitle ?? `not set \u2014 set it at ${profileSettingsUrl(endpoint)}`;
|
|
4267
|
+
}
|
|
4268
|
+
if (account?.projectCount !== void 0) view.projects = account.projectCount;
|
|
4269
|
+
if (account?.problem) view.problem = `${account.problem.kind}: ${account.problem.message}`;
|
|
4270
|
+
return view;
|
|
4271
|
+
}
|
|
4272
|
+
function nextSteps(account, agents, endpoint) {
|
|
4273
|
+
const next = [];
|
|
4274
|
+
if (agents.some((agent) => agent.status === "connected" || agent.status === "updated")) {
|
|
4275
|
+
next.push("Start a new agent session to load the AiNecto tools. Every session sees the same tools; your AiNecto permissions decide what they may change.");
|
|
4276
|
+
}
|
|
4277
|
+
if (account?.jobTitle === null) {
|
|
4278
|
+
next.push(`Set your AiNecto role (your job) at ${profileSettingsUrl(endpoint)} \u2014 AI uses it to pitch explanations; it does not change permissions.`);
|
|
4279
|
+
}
|
|
4280
|
+
next.push("To add AiNecto's working rules to a repository, run `ainecto init` from its root.");
|
|
4281
|
+
next.push("`ainecto doctor` shows anything still missing.");
|
|
4282
|
+
return next;
|
|
4283
|
+
}
|
|
4284
|
+
function parseSetupArgs(argv) {
|
|
4285
|
+
const args = { dryRun: false, undo: false };
|
|
4286
|
+
for (const arg of argv) {
|
|
4287
|
+
if (arg === "--dry-run") {
|
|
4288
|
+
args.dryRun = true;
|
|
4289
|
+
} else if (arg === "--undo") {
|
|
4290
|
+
args.undo = true;
|
|
4291
|
+
} else if (arg === "--json") {
|
|
4292
|
+
} else {
|
|
4293
|
+
throw new Error(`Unknown argument: ${arg}`);
|
|
4294
|
+
}
|
|
4295
|
+
}
|
|
4296
|
+
return args;
|
|
4297
|
+
}
|
|
4298
|
+
|
|
4299
|
+
// src/core/setup/doctorChecks.ts
|
|
4300
|
+
function doctorChecks(status) {
|
|
4301
|
+
const checks = [];
|
|
4302
|
+
const settings = profileSettingsUrl(status.endpoint);
|
|
4303
|
+
if (status.token.state === "not-signed-in") {
|
|
4304
|
+
checks.push({ item: "sign-in", level: "fail", detail: `not signed in to ${status.endpoint}`, fix: "ainecto setup" });
|
|
4305
|
+
} else if (status.token.state === "refresh-failed") {
|
|
4306
|
+
checks.push({ item: "sign-in", level: "fail", detail: `sign-in expired and could not be renewed (${status.token.reason})`, fix: "ainecto setup" });
|
|
4307
|
+
} else {
|
|
4308
|
+
checks.push({ item: "sign-in", level: "ok", detail: status.token.state === "env" ? "AINECTO_TOKEN" : "signed in" });
|
|
4309
|
+
}
|
|
4310
|
+
const problem = status.account?.problem;
|
|
4311
|
+
if (problem?.kind === "network") {
|
|
4312
|
+
checks.push({ item: "server", level: "fail", detail: `cannot reach ${status.endpoint} (${problem.message})`, fix: "check the network, or the --endpoint you passed" });
|
|
4313
|
+
} else if (problem?.kind === "unauthorized") {
|
|
4314
|
+
checks.push({ item: "server", level: "fail", detail: `${status.endpoint} refused the sign-in (HTTP 401)`, fix: "ainecto auth login" });
|
|
4315
|
+
} else if (problem?.kind === "forbidden") {
|
|
4316
|
+
checks.push({ item: "server", level: "fail", detail: "the account's plan does not include MCP, or the account is not active (HTTP 403)", fix: `check your plan at ${settings}` });
|
|
4317
|
+
} else if (problem) {
|
|
4318
|
+
checks.push({ item: "server", level: "fail", detail: problem.message });
|
|
4319
|
+
} else if (status.account) {
|
|
4320
|
+
checks.push({ item: "server", level: "ok", detail: `${status.endpoint} answered; ${status.account.projectCount ?? 0} project(s) visible` });
|
|
4321
|
+
}
|
|
4322
|
+
for (const agent of status.agents) {
|
|
4323
|
+
checks.push(agentCheck(agent, status.cliVersion));
|
|
4324
|
+
}
|
|
4325
|
+
if (status.claudeHere?.entry && status.claudeHere.scope === "local") {
|
|
4326
|
+
const hereOursUpToDate = status.claudeHere.ours && !status.claudeHere.carriesRole;
|
|
4327
|
+
if (!hereOursUpToDate) {
|
|
4328
|
+
checks.push({
|
|
4329
|
+
item: "Claude Code (this folder)",
|
|
4330
|
+
level: "warn",
|
|
4331
|
+
detail: `a local-scope "${SERVER_NAME}" entry in this folder hides the user-scope connection`,
|
|
4332
|
+
fix: `claude mcp remove ${SERVER_NAME} -s local`
|
|
4333
|
+
});
|
|
4334
|
+
}
|
|
4335
|
+
}
|
|
4336
|
+
const repo = status.repository;
|
|
4337
|
+
if (repo?.problem) {
|
|
4338
|
+
checks.push({ item: "repository", level: "warn", detail: repo.problem });
|
|
4339
|
+
} else if (repo) {
|
|
4340
|
+
const leftovers = [
|
|
4341
|
+
...repo.mcpFiles.filter((file) => file.ours && file.carriesRole).map((file) => `${file.path} passes the removed --role`),
|
|
4342
|
+
...repo.mcpFiles.filter((file) => file.ours && olderThan(file.pinnedVersion, status.cliVersion)).map((file) => `${file.path} pins @ainecto/mcp@${file.pinnedVersion}`),
|
|
4343
|
+
...repo.configHasProject ? [".ainecto/config.json still has the old project field"] : []
|
|
4344
|
+
];
|
|
4345
|
+
if (leftovers.length > 0) {
|
|
4346
|
+
checks.push({ item: "repository", level: "warn", detail: leftovers.join("; "), fix: `ainecto init (in ${repo.root})` });
|
|
4347
|
+
} else {
|
|
4348
|
+
checks.push({
|
|
4349
|
+
item: "repository",
|
|
4350
|
+
level: "ok",
|
|
4351
|
+
detail: repo.harnessDoc ? `AiNecto rules are set up in ${repo.root}` : `no AiNecto rules in ${repo.root} (optional \u2014 \`ainecto init\` adds them)`
|
|
4352
|
+
});
|
|
4353
|
+
}
|
|
4354
|
+
}
|
|
4355
|
+
for (const name of status.legacyEnv) {
|
|
4356
|
+
checks.push({ item: name, level: "warn", detail: `${name} is set; roles were removed and it is ignored`, fix: `unset ${name} (it can be deleted)` });
|
|
4357
|
+
}
|
|
4358
|
+
if (status.legacyCodexProfiles.length > 0) {
|
|
4359
|
+
checks.push({
|
|
4360
|
+
item: "Codex role profiles",
|
|
4361
|
+
level: "warn",
|
|
4362
|
+
detail: `these Codex profiles still pass --role: ${status.legacyCodexProfiles.join(", ")}`,
|
|
4363
|
+
fix: "delete them \u2014 `ainecto setup` connects Codex for every session"
|
|
4364
|
+
});
|
|
4365
|
+
}
|
|
4366
|
+
if (status.globalCliVersion && olderThan(status.globalCliVersion, status.cliVersion)) {
|
|
4367
|
+
checks.push({
|
|
4368
|
+
item: "global ainecto",
|
|
4369
|
+
level: "warn",
|
|
4370
|
+
detail: `the ainecto on your PATH is ${status.globalCliVersion}, older than ${status.cliVersion}`,
|
|
4371
|
+
fix: "npm i -g ainecto@latest"
|
|
4372
|
+
});
|
|
4373
|
+
}
|
|
4374
|
+
if (status.account?.jobTitle === null) {
|
|
4375
|
+
checks.push({ item: "AiNecto job title", level: "warn", detail: "not set \u2014 AI uses it to pitch explanations; it does not change permissions", fix: `set it at ${settings}` });
|
|
4376
|
+
} else if (typeof status.account?.jobTitle === "string") {
|
|
4377
|
+
checks.push({ item: "AiNecto job title", level: "ok", detail: status.account.jobTitle });
|
|
4378
|
+
}
|
|
4379
|
+
return checks;
|
|
4380
|
+
}
|
|
4381
|
+
function agentCheck(agent, cliVersion) {
|
|
4382
|
+
const item = AGENT_CLIENT_LABELS[agent.client];
|
|
4383
|
+
if (!agent.installed) {
|
|
4384
|
+
return { item, level: "ok", detail: "not installed" };
|
|
4385
|
+
}
|
|
4386
|
+
if (agent.problem) {
|
|
4387
|
+
return { item, level: "fail", detail: agent.problem, fix: "ainecto setup" };
|
|
4388
|
+
}
|
|
4389
|
+
if (!agent.entry) {
|
|
4390
|
+
return { item, level: "warn", detail: "not connected to AiNecto", fix: "ainecto setup" };
|
|
4391
|
+
}
|
|
4392
|
+
if (!agent.ours) {
|
|
4393
|
+
return {
|
|
4394
|
+
item,
|
|
4395
|
+
level: "warn",
|
|
4396
|
+
detail: `someone else's "${SERVER_NAME}" entry${agent.scope ? ` (${agent.scope})` : ""} \u2014 AiNecto setup leaves it alone`,
|
|
4397
|
+
fix: agent.client === "claude-code" ? `claude mcp get ${SERVER_NAME}` : "keep it, or remove it and run `ainecto setup`"
|
|
4398
|
+
};
|
|
4399
|
+
}
|
|
4400
|
+
if (agent.client === "claude-code" && agent.scope !== "user") {
|
|
4401
|
+
return { item, level: "warn", detail: `connected in the ${agent.scope} scope, not for every project`, fix: "ainecto setup" };
|
|
4402
|
+
}
|
|
4403
|
+
if (agent.carriesRole) {
|
|
4404
|
+
return { item, level: "warn", detail: "connected, but still passes the removed --role", fix: "ainecto setup" };
|
|
4405
|
+
}
|
|
4406
|
+
if (olderThan(agent.pinnedVersion, cliVersion)) {
|
|
4407
|
+
return { item, level: "warn", detail: `connected with @ainecto/mcp@${agent.pinnedVersion}; ${cliVersion} is available`, fix: "ainecto setup" };
|
|
4408
|
+
}
|
|
4409
|
+
return { item, level: "ok", detail: agent.pinnedVersion ? `connected (@ainecto/mcp@${agent.pinnedVersion})` : "connected" };
|
|
4410
|
+
}
|
|
4411
|
+
function olderThan(a, b) {
|
|
4412
|
+
const left = parseVersion(a);
|
|
4413
|
+
const right = parseVersion(b);
|
|
4414
|
+
if (!left || !right) {
|
|
4415
|
+
return false;
|
|
4416
|
+
}
|
|
4417
|
+
for (let at = 0; at < 3; at += 1) {
|
|
4418
|
+
if (left[at] !== right[at]) {
|
|
4419
|
+
return left[at] < right[at];
|
|
4420
|
+
}
|
|
4421
|
+
}
|
|
4422
|
+
return false;
|
|
4423
|
+
}
|
|
4424
|
+
function parseVersion(value) {
|
|
4425
|
+
const match = value?.match(/^(\d+)\.(\d+)\.(\d+)/);
|
|
4426
|
+
return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : void 0;
|
|
4427
|
+
}
|
|
4428
|
+
|
|
4429
|
+
// src/core/setup/machineStatus.ts
|
|
4430
|
+
import { readdir, readFile as readFile7 } from "fs/promises";
|
|
4431
|
+
import { join as join5 } from "path";
|
|
4432
|
+
async function collectStatus(input) {
|
|
4433
|
+
const token = await tokenStatus(input);
|
|
4434
|
+
const usable = token.state === "env" || token.state === "signed-in";
|
|
4435
|
+
const status = {
|
|
4436
|
+
cliVersion: input.cliVersion,
|
|
4437
|
+
endpoint: input.endpoint,
|
|
4438
|
+
env: input.env,
|
|
4439
|
+
token,
|
|
4440
|
+
agents: await inspectAgents({ runner: input.runner, home: input.home }),
|
|
4441
|
+
legacyEnv: [LEGACY_ROLE_ENV_VAR, "AI_ERD_ROLE"].filter((name) => input.envVars[name]?.trim()),
|
|
4442
|
+
legacyCodexProfiles: await legacyCodexProfiles(input.envVars.CODEX_HOME ?? join5(input.home, ".codex"))
|
|
4443
|
+
};
|
|
4444
|
+
if (usable) {
|
|
4445
|
+
status.account = await readAccount(input.client);
|
|
4446
|
+
}
|
|
4447
|
+
if (input.hereRunner && input.cwd !== input.home) {
|
|
4448
|
+
status.claudeHere = await inspectClaude({ runner: input.hereRunner, home: input.home });
|
|
4449
|
+
}
|
|
4450
|
+
status.repository = await repositoryStatus(input.cwd);
|
|
4451
|
+
const globalVersion = await globalCliVersion(input.runner);
|
|
4452
|
+
if (globalVersion) {
|
|
4453
|
+
status.globalCliVersion = globalVersion;
|
|
4454
|
+
}
|
|
4455
|
+
return status;
|
|
4456
|
+
}
|
|
4457
|
+
async function tokenStatus(input) {
|
|
4458
|
+
if (input.envVars.AINECTO_TOKEN) {
|
|
4459
|
+
return { state: "env" };
|
|
4460
|
+
}
|
|
4461
|
+
const stored = await input.tokenStore.load(input.endpoint);
|
|
4462
|
+
const expiresAt = stored?.expiresAt ? new Date(stored.expiresAt).toISOString() : void 0;
|
|
4463
|
+
try {
|
|
4464
|
+
const token = await input.auth.getAccessToken();
|
|
4465
|
+
if (token) {
|
|
4466
|
+
return { state: "signed-in", ...expiresAt ? { expiresAt } : {} };
|
|
4467
|
+
}
|
|
4468
|
+
return stored ? { state: "refresh-failed", ...expiresAt ? { expiresAt } : {}, reason: "the stored sign-in expired and has no refresh token" } : { state: "not-signed-in" };
|
|
4469
|
+
} catch (error) {
|
|
4470
|
+
return {
|
|
4471
|
+
state: "refresh-failed",
|
|
4472
|
+
...expiresAt ? { expiresAt } : {},
|
|
4473
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
4474
|
+
};
|
|
4475
|
+
}
|
|
4476
|
+
}
|
|
4477
|
+
async function repositoryStatus(cwd) {
|
|
4478
|
+
const gitRoot = await findGitRoot(cwd);
|
|
4479
|
+
const root = gitRoot ?? cwd;
|
|
4480
|
+
const status = { root, git: gitRoot !== void 0, harnessDoc: false, configHasProject: false, mcpFiles: [] };
|
|
4481
|
+
try {
|
|
4482
|
+
status.harnessDoc = await readManagedFile(root, HARNESS_DOC_PATH) !== void 0;
|
|
4483
|
+
const config = parseObject(await readManagedFile(root, HARNESS_CONFIG_PATH));
|
|
4484
|
+
if (config) {
|
|
4485
|
+
if (typeof config.version === "number") status.configVersion = config.version;
|
|
4486
|
+
status.configHasProject = Object.prototype.hasOwnProperty.call(config, "project");
|
|
4487
|
+
const versions = config.promptVersions;
|
|
4488
|
+
if (typeof versions?.[DOC_KEY] === "string") status.harnessDocVersion = versions[DOC_KEY];
|
|
4489
|
+
}
|
|
4490
|
+
for (const target of AGENT_TARGETS) {
|
|
4491
|
+
const entry = readServerEntry(await readManagedFile(root, target.path));
|
|
4492
|
+
if (entry !== void 0) {
|
|
4493
|
+
const pinnedVersion = pinnedBridgeVersion(entry);
|
|
4494
|
+
status.mcpFiles.push({
|
|
4495
|
+
path: target.path,
|
|
4496
|
+
ours: isOurBridge(entry),
|
|
4497
|
+
carriesRole: entryCarriesRole(entry),
|
|
4498
|
+
...pinnedVersion ? { pinnedVersion } : {}
|
|
4499
|
+
});
|
|
4500
|
+
}
|
|
4501
|
+
}
|
|
4502
|
+
} catch (error) {
|
|
4503
|
+
status.problem = error instanceof Error ? error.message : String(error);
|
|
4504
|
+
}
|
|
4505
|
+
return status;
|
|
4506
|
+
}
|
|
4507
|
+
async function legacyCodexProfiles(codexHome) {
|
|
4508
|
+
let names;
|
|
4509
|
+
try {
|
|
4510
|
+
names = await readdir(codexHome);
|
|
4511
|
+
} catch {
|
|
4512
|
+
return [];
|
|
4513
|
+
}
|
|
4514
|
+
const found = [];
|
|
4515
|
+
for (const name of names.filter((file) => file.endsWith(".config.toml"))) {
|
|
4516
|
+
const content = await readFile7(join5(codexHome, name), "utf8").catch(() => "");
|
|
4517
|
+
if (content.includes(PACKAGE_NAME) && content.includes("--role")) {
|
|
4518
|
+
found.push(join5(codexHome, name));
|
|
4519
|
+
}
|
|
4520
|
+
}
|
|
4521
|
+
return found;
|
|
4522
|
+
}
|
|
4523
|
+
async function globalCliVersion(runner) {
|
|
4524
|
+
const result = await runner("ainecto", ["--version", "--json"]);
|
|
4525
|
+
if (result.missing || result.exitCode !== 0) {
|
|
4526
|
+
return void 0;
|
|
4527
|
+
}
|
|
4528
|
+
try {
|
|
4529
|
+
const version = JSON.parse(result.stdout).data?.version;
|
|
4530
|
+
return typeof version === "string" ? version : void 0;
|
|
4531
|
+
} catch {
|
|
4532
|
+
return void 0;
|
|
4533
|
+
}
|
|
4534
|
+
}
|
|
4535
|
+
function parseObject(content) {
|
|
4536
|
+
if (content === void 0) {
|
|
4537
|
+
return void 0;
|
|
4538
|
+
}
|
|
4539
|
+
try {
|
|
4540
|
+
const parsed = JSON.parse(content);
|
|
4541
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
|
|
4542
|
+
} catch {
|
|
4543
|
+
return void 0;
|
|
4544
|
+
}
|
|
4545
|
+
}
|
|
4546
|
+
|
|
4547
|
+
// src/adapters/cli/statusCommand.ts
|
|
4548
|
+
async function executeStatusCommand(options) {
|
|
4549
|
+
assertNoArgs("status", options.argv);
|
|
4550
|
+
const status = await collectStatus(options.collect);
|
|
4551
|
+
options.io.stdout.write(renderSuccess(statusView(status), { json: options.json }));
|
|
4552
|
+
return 0;
|
|
4553
|
+
}
|
|
4554
|
+
async function executeDoctorCommand(options) {
|
|
4555
|
+
assertNoArgs("doctor", options.argv);
|
|
4556
|
+
const status = await collectStatus(options.collect);
|
|
4557
|
+
const checks = doctorChecks(status);
|
|
4558
|
+
const failed = checks.filter((check) => check.level === "fail").length;
|
|
4559
|
+
const warned = checks.filter((check) => check.level === "warn").length;
|
|
4560
|
+
options.io.stdout.write(renderSuccess({
|
|
4561
|
+
summary: failed > 0 ? `${failed} problem(s) to fix` : warned > 0 ? `working \u2014 ${warned} suggestion(s)` : "all good",
|
|
4562
|
+
checks
|
|
4563
|
+
}, { json: options.json }));
|
|
4564
|
+
return failed > 0 ? 1 : 0;
|
|
4565
|
+
}
|
|
4566
|
+
function statusView(status) {
|
|
4567
|
+
const signedIn = status.token.state === "env" || status.token.state === "signed-in";
|
|
4568
|
+
return {
|
|
4569
|
+
cli: status.cliVersion,
|
|
4570
|
+
endpoint: status.endpoint,
|
|
4571
|
+
env: status.env,
|
|
4572
|
+
signIn: status.token,
|
|
4573
|
+
account: accountView(status.account, signedIn, status.endpoint),
|
|
4574
|
+
agents: status.agents.map((agent) => ({
|
|
4575
|
+
client: AGENT_CLIENT_LABELS[agent.client],
|
|
4576
|
+
state: !agent.installed ? "not installed" : agent.problem ? `unknown (${agent.problem})` : !agent.entry ? "not connected" : agent.ours ? `connected${agent.scope ? ` (${agent.scope})` : ""}` : `someone else's entry${agent.scope ? ` (${agent.scope})` : ""}`,
|
|
4577
|
+
...agent.pinnedVersion ? { bridge: `@ainecto/mcp@${agent.pinnedVersion}` } : {},
|
|
4578
|
+
...agent.carriesRole ? { legacyRole: true } : {}
|
|
4579
|
+
})),
|
|
4580
|
+
...status.claudeHere?.entry ? { claudeCodeHere: { scope: status.claudeHere.scope, ours: status.claudeHere.ours } } : {},
|
|
4581
|
+
repository: status.repository,
|
|
4582
|
+
...status.globalCliVersion ? { globalAinecto: status.globalCliVersion } : {}
|
|
4583
|
+
};
|
|
4584
|
+
}
|
|
4585
|
+
function assertNoArgs(command, argv) {
|
|
4586
|
+
const unknown = argv.filter((arg) => arg !== "--json");
|
|
4587
|
+
if (unknown.length > 0) {
|
|
4588
|
+
throw new Error(`Unknown argument for ${command}: ${unknown[0]}`);
|
|
4589
|
+
}
|
|
4309
4590
|
}
|
|
4310
4591
|
|
|
4311
4592
|
// src/adapters/cli/ainectoCli.ts
|
|
4593
|
+
import { homedir as homedir2 } from "os";
|
|
4312
4594
|
async function runAinectoCli(argv, io) {
|
|
4313
4595
|
let parsed = { json: argv.includes("--json"), help: false, version: false, positionals: [] };
|
|
4314
4596
|
try {
|
|
@@ -4322,15 +4604,21 @@ async function runAinectoCli(argv, io) {
|
|
|
4322
4604
|
return 0;
|
|
4323
4605
|
}
|
|
4324
4606
|
const resolved = resolveEndpoint({ env: parsed.env, endpoint: parsed.endpoint });
|
|
4325
|
-
const
|
|
4607
|
+
const [domain, action, ...rest] = parsed.positionals;
|
|
4608
|
+
const legacyRole = legacyRoleInput(parsed.legacyRole);
|
|
4609
|
+
if (legacyRole !== void 0 && domain !== "mcp") {
|
|
4610
|
+
io.stderr.write(parsed.json ? `${JSON.stringify({ event: "legacy_role_ignored", message: LEGACY_ROLE_NOTICE })}
|
|
4611
|
+
` : `${LEGACY_ROLE_NOTICE}
|
|
4612
|
+
`);
|
|
4613
|
+
}
|
|
4326
4614
|
const tokenStore = new FileTokenStore();
|
|
4327
|
-
const connect = (
|
|
4615
|
+
const connect = () => {
|
|
4328
4616
|
const auth = new OAuthClient({
|
|
4329
4617
|
endpoint: resolved.endpoint,
|
|
4330
4618
|
tokenStore,
|
|
4331
|
-
// ★init --dry-run 은 토큰을 갱신하지도 않는다 — 「아무것도 바꾸지 않는다」에 로그인 상태도 든다.
|
|
4332
|
-
// (
|
|
4333
|
-
readOnly:
|
|
4619
|
+
// ★init·setup --dry-run 은 토큰을 갱신하지도 않는다 — 「아무것도 바꾸지 않는다」에 로그인 상태도 든다.
|
|
4620
|
+
// (하위 명령의 플래그는 positional 로 흘러온다.)
|
|
4621
|
+
readOnly: (domain === "init" || domain === "setup") && parsed.positionals.includes("--dry-run"),
|
|
4334
4622
|
// ★URL 을 «브라우저를 열기 전에» 알린다. stdout 은 결과 전용이라 stderr 에 쓴다.
|
|
4335
4623
|
// --json 이면 JSON 한 줄이다(설계 §21-3 (가)). 로그인이 역할과 무관해져 role 칸은 없다(0.4.2 §2-5).
|
|
4336
4624
|
onAuthorizeUrl: (url) => {
|
|
@@ -4340,15 +4628,13 @@ async function runAinectoCli(argv, io) {
|
|
|
4340
4628
|
`);
|
|
4341
4629
|
}
|
|
4342
4630
|
});
|
|
4343
|
-
const client2 = new McpRpcClient({ endpoint: resolved.endpoint, tokenProvider: auth,
|
|
4631
|
+
const client2 = new McpRpcClient({ endpoint: resolved.endpoint, tokenProvider: auth, legacyRole, channel: "cli" });
|
|
4344
4632
|
return { auth, client: client2 };
|
|
4345
4633
|
};
|
|
4346
|
-
const [domain, action, ...rest] = parsed.positionals;
|
|
4347
4634
|
if (domain === "mcp") {
|
|
4348
4635
|
await runConnector({
|
|
4349
4636
|
endpoint: resolved.endpoint,
|
|
4350
|
-
|
|
4351
|
-
role: explicitRole().role ?? null,
|
|
4637
|
+
legacyRoleFlag: parsed.legacyRole,
|
|
4352
4638
|
input: io.stdin,
|
|
4353
4639
|
output: io.stdout,
|
|
4354
4640
|
errorOutput: io.stderr
|
|
@@ -4356,36 +4642,18 @@ async function runAinectoCli(argv, io) {
|
|
|
4356
4642
|
return 0;
|
|
4357
4643
|
}
|
|
4358
4644
|
if (domain === "init") {
|
|
4645
|
+
const { auth } = connect();
|
|
4359
4646
|
return await executeInitCommand({
|
|
4360
4647
|
argv: parsed.positionals.slice(1),
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
// ★갱신 실패는 «토큰 없음»(→ 로그인)이지만, AINECTO_TOKEN 을 못 쓰는 주소라는 거절은 삼키지 않는다 —
|
|
4370
|
-
// 삼키면 그 설정 오류가 브라우저 로그인으로 둔갑한다.
|
|
4371
|
-
accessToken: async () => {
|
|
4372
|
-
try {
|
|
4373
|
-
return await auth.getAccessToken();
|
|
4374
|
-
} catch (error) {
|
|
4375
|
-
if (process.env.AINECTO_TOKEN) {
|
|
4376
|
-
throw error;
|
|
4377
|
-
}
|
|
4378
|
-
return void 0;
|
|
4379
|
-
}
|
|
4380
|
-
},
|
|
4381
|
-
// 로그인한다 — `ainecto auth login` 과 같은 함수다. 서버마다 한 번이다(0.4.2).
|
|
4382
|
-
login: async () => {
|
|
4383
|
-
await auth.login();
|
|
4384
|
-
}
|
|
4385
|
-
};
|
|
4648
|
+
// 문서는 인증 후에 받는다 — 토큰이 없으면 패키지 기본값으로 가고, 그 사실을 보고한다.
|
|
4649
|
+
// ★«필요할 때» 가져온다. --undo 는 로컬 작업이라 토큰을 건드리지 않는다(S1).
|
|
4650
|
+
// ★판정은 setup 과 같은 {@link usableAccessToken} 하나 — 서버가 저장 로그인을 거절했으면 «없음»(→ 로그인),
|
|
4651
|
+
// 네트워크 장애·AINECTO_TOKEN 을 못 쓰는 주소라는 거절은 삼키지 않는다(브라우저 로그인으로 둔갑하지 않게).
|
|
4652
|
+
accessToken: () => usableAccessToken(auth),
|
|
4653
|
+
// 로그인한다 — `ainecto auth login` 과 같은 함수다. 서버마다 한 번이다.
|
|
4654
|
+
login: async () => {
|
|
4655
|
+
await auth.login();
|
|
4386
4656
|
},
|
|
4387
|
-
// ★대화형일 때만 묻는다(판정은 isInteractive 한 곳). 에이전트·CI 는 TTY 가 아니다.
|
|
4388
|
-
prompter: isInteractive(io, parsed.json) ? createReadlinePrompter(io) : void 0,
|
|
4389
4657
|
endpoint: resolved.endpoint,
|
|
4390
4658
|
env: resolved.env,
|
|
4391
4659
|
cliVersion: `${PACKAGE_NAME} ${CLI_VERSION}`,
|
|
@@ -4394,23 +4662,54 @@ async function runAinectoCli(argv, io) {
|
|
|
4394
4662
|
io
|
|
4395
4663
|
});
|
|
4396
4664
|
}
|
|
4665
|
+
if (domain === "setup") {
|
|
4666
|
+
const { auth, client: client2 } = connect();
|
|
4667
|
+
return await executeSetupCommand({
|
|
4668
|
+
argv: parsed.positionals.slice(1),
|
|
4669
|
+
endpoint: resolved.endpoint,
|
|
4670
|
+
env: resolved.env,
|
|
4671
|
+
auth,
|
|
4672
|
+
client: client2,
|
|
4673
|
+
// ★외부 CLI 는 홈 폴더에서 돈다 — 지금 저장소의 local·project 범위 항목이 사용자 범위 판정에 끼지 않게.
|
|
4674
|
+
runner: systemCommandRunner({ cwd: homedir2() }),
|
|
4675
|
+
home: homedir2(),
|
|
4676
|
+
json: parsed.json,
|
|
4677
|
+
io
|
|
4678
|
+
});
|
|
4679
|
+
}
|
|
4680
|
+
if (domain === "status" || domain === "doctor") {
|
|
4681
|
+
const { auth, client: client2 } = connect();
|
|
4682
|
+
const run = domain === "status" ? executeStatusCommand : executeDoctorCommand;
|
|
4683
|
+
return await run({
|
|
4684
|
+
argv: parsed.positionals.slice(1),
|
|
4685
|
+
collect: {
|
|
4686
|
+
endpoint: resolved.endpoint,
|
|
4687
|
+
env: resolved.env,
|
|
4688
|
+
cliVersion: CLI_VERSION,
|
|
4689
|
+
tokenStore,
|
|
4690
|
+
auth,
|
|
4691
|
+
client: client2,
|
|
4692
|
+
runner: systemCommandRunner({ cwd: homedir2() }),
|
|
4693
|
+
hereRunner: systemCommandRunner({ cwd: process.cwd() }),
|
|
4694
|
+
home: homedir2(),
|
|
4695
|
+
cwd: process.cwd(),
|
|
4696
|
+
envVars: process.env
|
|
4697
|
+
},
|
|
4698
|
+
json: parsed.json,
|
|
4699
|
+
io
|
|
4700
|
+
});
|
|
4701
|
+
}
|
|
4397
4702
|
if (domain === "auth") {
|
|
4398
|
-
|
|
4399
|
-
io.stderr.write("Sign-in is one per machine; --role is not used by auth.\n");
|
|
4400
|
-
}
|
|
4401
|
-
return await handleAuth(action, connect(null).auth, resolved.endpoint, parsed.json, io);
|
|
4703
|
+
return await handleAuth(action, connect().auth, resolved.endpoint, parsed.json, io);
|
|
4402
4704
|
}
|
|
4403
4705
|
const env = parsed.env ?? "prod";
|
|
4404
4706
|
if (domain === "tools" && action === "catalog") {
|
|
4405
|
-
return await printCatalog(parsed, io, resolved.endpoint, async () =>
|
|
4406
|
-
const explicit2 = explicitRole();
|
|
4407
|
-
return connect(await commandRole(explicit2, process.cwd())).client;
|
|
4408
|
-
});
|
|
4707
|
+
return await printCatalog(parsed, io, resolved.endpoint, async () => connect().client);
|
|
4409
4708
|
}
|
|
4410
4709
|
let routeTools = getGeneratedTools(env);
|
|
4411
4710
|
let remote = isAttachmentsUploadCommand(domain, action) || isInboxPollCommand(domain, action) || domain === "tools" && (action === "list" || action === "call") || matchGeneratedCommand(routeTools, parsed.positionals) !== void 0;
|
|
4412
4711
|
if (!remote) {
|
|
4413
|
-
const live = await loadLiveTools(env, connect(
|
|
4712
|
+
const live = await loadLiveTools(env, connect().client).catch(() => void 0);
|
|
4414
4713
|
if (live && matchGeneratedCommand(live, parsed.positionals)) {
|
|
4415
4714
|
routeTools = live;
|
|
4416
4715
|
remote = true;
|
|
@@ -4419,8 +4718,7 @@ async function runAinectoCli(argv, io) {
|
|
|
4419
4718
|
if (!remote) {
|
|
4420
4719
|
throw new Error(domain === "tools" ? "Expected tools list, tools call <mcpName>, or tools catalog." : `Unknown command "${domain}".`);
|
|
4421
4720
|
}
|
|
4422
|
-
const
|
|
4423
|
-
const { client } = connect(await commandRole(explicit, process.cwd()));
|
|
4721
|
+
const { client } = connect();
|
|
4424
4722
|
if (isAttachmentsUploadCommand(domain, action)) {
|
|
4425
4723
|
return await executeAttachmentsUploadCommand({
|
|
4426
4724
|
argv: rest,
|
|
@@ -4451,23 +4749,6 @@ async function runAinectoCli(argv, io) {
|
|
|
4451
4749
|
return 1;
|
|
4452
4750
|
}
|
|
4453
4751
|
}
|
|
4454
|
-
async function commandRole(explicit, cwd) {
|
|
4455
|
-
const explicitRole = explicit.role ?? null;
|
|
4456
|
-
const found = await findRepositoryRole(cwd);
|
|
4457
|
-
if (found.kind === "conflict") {
|
|
4458
|
-
throw new Error(await askTheUserToFixRole(found.dir, found.reason));
|
|
4459
|
-
}
|
|
4460
|
-
if (found.kind === "none") {
|
|
4461
|
-
return explicitRole;
|
|
4462
|
-
}
|
|
4463
|
-
if (explicitRole !== null && explicitRole !== found.role) {
|
|
4464
|
-
const given = explicit.source === "env" ? `${ROLE_ENV_VAR}=${explicitRole}` : `--role ${explicitRole}`;
|
|
4465
|
-
throw new Error(
|
|
4466
|
-
`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.`
|
|
4467
|
-
);
|
|
4468
|
-
}
|
|
4469
|
-
return found.role;
|
|
4470
|
-
}
|
|
4471
4752
|
async function handleAuth(action, auth, endpoint, json, io) {
|
|
4472
4753
|
if (action === "login") {
|
|
4473
4754
|
await auth.login();
|
|
@@ -4532,11 +4813,15 @@ function parseGlobalArgs(argv) {
|
|
|
4532
4813
|
continue;
|
|
4533
4814
|
}
|
|
4534
4815
|
if (arg === "--env") {
|
|
4535
|
-
result.env = assertEnv(
|
|
4816
|
+
result.env = assertEnv(requireValue3(arg, argv[++index]));
|
|
4536
4817
|
} else if (arg === "--endpoint") {
|
|
4537
|
-
result.endpoint =
|
|
4818
|
+
result.endpoint = requireValue3(arg, argv[++index]);
|
|
4538
4819
|
} else if (arg === "--role") {
|
|
4539
|
-
|
|
4820
|
+
const value = argv[index + 1];
|
|
4821
|
+
if (value !== void 0 && !value.startsWith("--")) {
|
|
4822
|
+
result.legacyRole = value;
|
|
4823
|
+
index += 1;
|
|
4824
|
+
}
|
|
4540
4825
|
} else if (arg === "--json") {
|
|
4541
4826
|
result.json = true;
|
|
4542
4827
|
} else if (arg === "--help" || arg === "-h") {
|
|
@@ -4558,7 +4843,7 @@ function parsePayloadArgs(argv) {
|
|
|
4558
4843
|
continue;
|
|
4559
4844
|
}
|
|
4560
4845
|
if (arg === "--file" || arg === "-f") {
|
|
4561
|
-
result.file =
|
|
4846
|
+
result.file = requireValue3(arg, argv[++index]);
|
|
4562
4847
|
} else {
|
|
4563
4848
|
inline.push(arg);
|
|
4564
4849
|
}
|
|
@@ -4568,28 +4853,26 @@ function parsePayloadArgs(argv) {
|
|
|
4568
4853
|
}
|
|
4569
4854
|
return result;
|
|
4570
4855
|
}
|
|
4571
|
-
function
|
|
4856
|
+
function requireValue3(flag, value) {
|
|
4572
4857
|
if (!value || value.startsWith("--")) {
|
|
4573
4858
|
throw new Error(`${flag} requires a value.`);
|
|
4574
4859
|
}
|
|
4575
4860
|
return value;
|
|
4576
4861
|
}
|
|
4577
4862
|
async function nextStep() {
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
"
|
|
4583
|
-
"
|
|
4584
|
-
"
|
|
4585
|
-
"never pick one yourself, and run `npx -y ainecto@latest init --role <role>` from the repository root.",
|
|
4586
|
-
"In a terminal: `npx -y ainecto@latest init` asks for the role and the project."
|
|
4863
|
+
return { next: NEXT_STEP };
|
|
4864
|
+
}
|
|
4865
|
+
var NEXT_STEP = [
|
|
4866
|
+
"Connect your coding agents to AiNecto once per machine: `npx -y ainecto@latest setup`.",
|
|
4867
|
+
"AI agents: tell the user this now, even if they only asked to install, update, or sign in.",
|
|
4868
|
+
"To add AiNecto's working rules to a repository, run `npx -y ainecto@latest init` from its root.",
|
|
4869
|
+
"`npx -y ainecto@latest doctor` shows what is still missing and the command that fixes it."
|
|
4587
4870
|
].join("\n");
|
|
4588
4871
|
function helpText() {
|
|
4589
4872
|
return [
|
|
4590
4873
|
"AiNecto CLI",
|
|
4591
4874
|
"",
|
|
4592
|
-
|
|
4875
|
+
NEXT_STEP,
|
|
4593
4876
|
"",
|
|
4594
4877
|
"Usage:",
|
|
4595
4878
|
" ainecto auth login|status|logout [--env prod|dev] [--json] (one sign-in per machine)",
|
|
@@ -4602,11 +4885,16 @@ function helpText() {
|
|
|
4602
4885
|
" --workspace and --project may repeat: any of the given workspaces AND any of the given projects",
|
|
4603
4886
|
" poll exit codes: 0 new items \xB7 3 nothing new \xB7 1 error \xB7 2 usage error",
|
|
4604
4887
|
" ainecto <generated-command> [flags] [-f payload.json] [--yes] [--json]",
|
|
4605
|
-
" ainecto mcp [--env prod|dev] [--endpoint URL]
|
|
4888
|
+
" ainecto mcp [--env prod|dev] [--endpoint URL]",
|
|
4889
|
+
"",
|
|
4890
|
+
"Setup (once per machine):",
|
|
4891
|
+
" ainecto setup [--env prod|dev] [--endpoint URL] [--dry-run] [--json] sign in and connect Claude Code, Cursor, and Codex",
|
|
4892
|
+
" ainecto setup --undo remove those connections (sign-in stays)",
|
|
4893
|
+
" ainecto status [--json] who is signed in, and what is connected (read-only)",
|
|
4894
|
+
" ainecto doctor [--json] status + what is missing and the command that fixes it",
|
|
4606
4895
|
"",
|
|
4607
4896
|
"Harness:",
|
|
4608
|
-
" ainecto init [--
|
|
4609
|
-
" In a terminal, asks for the role and project when they are not given.",
|
|
4897
|
+
" ainecto init [--mcp] [--dry-run] [--json] AiNecto rules for this repository (--mcp also writes .mcp.json and .cursor/mcp.json)",
|
|
4610
4898
|
" ainecto init --undo",
|
|
4611
4899
|
""
|
|
4612
4900
|
].join("\n");
|