@ainecto/mcp 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -83
- package/dist/bin/ainecto.js +1136 -870
- package/dist/bin/ainecto.js.map +1 -1
- package/dist/bin/mcp.js +59 -47
- 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) {
|
|
@@ -614,12 +581,28 @@ async function readAllStdin(stdin) {
|
|
|
614
581
|
return text;
|
|
615
582
|
}
|
|
616
583
|
|
|
584
|
+
// src/core/harness/legacyRole.ts
|
|
585
|
+
var LEGACY_ROLE_HEADER = "X-AiNecto-Role";
|
|
586
|
+
var LEGACY_ROLE_ENV_VAR = "AINECTO_ROLE";
|
|
587
|
+
var LEGACY_ROLE_NOTICE = "AiNecto session roles were removed; access follows your AiNecto permissions. Re-run `ainecto init` to tidy this repository.";
|
|
588
|
+
function legacyRoleInput(flag, envVars = process.env) {
|
|
589
|
+
const value = flag ?? envVars[LEGACY_ROLE_ENV_VAR];
|
|
590
|
+
return value !== void 0 && value.trim() ? value.trim() : void 0;
|
|
591
|
+
}
|
|
592
|
+
|
|
617
593
|
// src/core/version.ts
|
|
618
|
-
var
|
|
594
|
+
var UNBUNDLED_VERSION = "dev";
|
|
595
|
+
var CLI_VERSION = true ? "0.8.0" : UNBUNDLED_VERSION;
|
|
619
596
|
var PACKAGE_NAME = "@ainecto/mcp";
|
|
620
597
|
var CLI_USER_AGENT = `${PACKAGE_NAME} ${CLI_VERSION}`;
|
|
598
|
+
function bridgePackageSpec(version = CLI_VERSION) {
|
|
599
|
+
return version === UNBUNDLED_VERSION ? PACKAGE_NAME : `${PACKAGE_NAME}@${version}`;
|
|
600
|
+
}
|
|
621
601
|
|
|
622
602
|
// src/core/mcp/rpcClient.ts
|
|
603
|
+
var CHANNEL_HEADER = "X-AiNecto-Channel";
|
|
604
|
+
var AGENT_HEADER = "X-AiNecto-Agent";
|
|
605
|
+
var MAX_AGENT_NAME_LENGTH = 100;
|
|
623
606
|
var McpRpcError = class extends Error {
|
|
624
607
|
constructor(message, code, details) {
|
|
625
608
|
super(message);
|
|
@@ -637,6 +620,14 @@ var McpRpcClient = class {
|
|
|
637
620
|
options;
|
|
638
621
|
fetchImpl;
|
|
639
622
|
nextId = 1;
|
|
623
|
+
agent;
|
|
624
|
+
/**
|
|
625
|
+
* 브리지 전용 — 호스트 에이전트가 보낸 {@code initialize} 의 {@code clientInfo.name}(프로그램 고정 이름).
|
|
626
|
+
* 이후 모든 요청의 {@code X-AiNecto-Agent} 헤더로 싣는다. 직접 명령은 부르지 않는다(누가 셸을 돌렸는지 모른다).
|
|
627
|
+
*/
|
|
628
|
+
useAgent(clientName) {
|
|
629
|
+
this.agent = agentHeaderValue(clientName);
|
|
630
|
+
}
|
|
640
631
|
initialize() {
|
|
641
632
|
return this.request("initialize", {
|
|
642
633
|
protocolVersion: "2024-11-05",
|
|
@@ -706,8 +697,13 @@ var McpRpcClient = class {
|
|
|
706
697
|
if (token) {
|
|
707
698
|
headers.authorization = `Bearer ${token}`;
|
|
708
699
|
}
|
|
709
|
-
|
|
710
|
-
|
|
700
|
+
const legacyRole = printableHeaderValue(this.options.legacyRole);
|
|
701
|
+
if (legacyRole) {
|
|
702
|
+
headers[LEGACY_ROLE_HEADER.toLowerCase()] = legacyRole;
|
|
703
|
+
}
|
|
704
|
+
headers[CHANNEL_HEADER.toLowerCase()] = this.options.channel;
|
|
705
|
+
if (this.agent) {
|
|
706
|
+
headers[AGENT_HEADER.toLowerCase()] = this.agent;
|
|
711
707
|
}
|
|
712
708
|
return this.fetchImpl(this.options.endpoint, {
|
|
713
709
|
method: "POST",
|
|
@@ -719,6 +715,16 @@ var McpRpcClient = class {
|
|
|
719
715
|
function isToolError(result) {
|
|
720
716
|
return isRecord2(result) && result.isError === true;
|
|
721
717
|
}
|
|
718
|
+
function agentHeaderValue(clientName) {
|
|
719
|
+
return printableHeaderValue(clientName);
|
|
720
|
+
}
|
|
721
|
+
function printableHeaderValue(value) {
|
|
722
|
+
if (typeof value !== "string") {
|
|
723
|
+
return void 0;
|
|
724
|
+
}
|
|
725
|
+
const printable = value.replace(/[^\x20-\x7E]/g, "").trim().slice(0, MAX_AGENT_NAME_LENGTH).trim();
|
|
726
|
+
return printable || void 0;
|
|
727
|
+
}
|
|
722
728
|
function isJsonRpcResponse(value) {
|
|
723
729
|
return isRecord2(value) && value.jsonrpc === "2.0" && ("result" in value || "error" in value);
|
|
724
730
|
}
|
|
@@ -892,92 +898,81 @@ var generatedTools = [
|
|
|
892
898
|
|
|
893
899
|
// src/core/catalog/generated.dev.ts
|
|
894
900
|
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" }
|
|
901
|
+
{ "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" },
|
|
902
|
+
{ "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" },
|
|
903
|
+
{ "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" },
|
|
904
|
+
{ "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" },
|
|
905
|
+
{ "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" },
|
|
906
|
+
{ "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" },
|
|
907
|
+
{ "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" },
|
|
908
|
+
{ "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" },
|
|
909
|
+
{ "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" },
|
|
910
|
+
{ "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" },
|
|
911
|
+
{ "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" },
|
|
912
|
+
{ "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" },
|
|
913
|
+
{ "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" },
|
|
914
|
+
{ "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" },
|
|
915
|
+
{ "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" },
|
|
916
|
+
{ "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" },
|
|
917
|
+
{ "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" },
|
|
918
|
+
{ "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" },
|
|
919
|
+
{ "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" },
|
|
920
|
+
{ "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" },
|
|
921
|
+
{ "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" },
|
|
922
|
+
{ "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" },
|
|
923
|
+
{ "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" },
|
|
924
|
+
{ "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" },
|
|
925
|
+
{ "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" },
|
|
926
|
+
{ "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" },
|
|
927
|
+
{ "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" },
|
|
928
|
+
{ "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" },
|
|
929
|
+
{ "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" },
|
|
930
|
+
{ "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" },
|
|
931
|
+
{ "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" },
|
|
932
|
+
{ "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" },
|
|
933
|
+
{ "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" },
|
|
934
|
+
{ "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" },
|
|
935
|
+
{ "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" },
|
|
936
|
+
{ "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" },
|
|
937
|
+
{ "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" },
|
|
938
|
+
{ "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" },
|
|
939
|
+
{ "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" },
|
|
940
|
+
{ "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" },
|
|
941
|
+
{ "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" },
|
|
942
|
+
{ "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" },
|
|
943
|
+
{ "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" },
|
|
944
|
+
{ "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" },
|
|
945
|
+
{ "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" },
|
|
946
|
+
{ "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" },
|
|
947
|
+
{ "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" },
|
|
948
|
+
{ "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" },
|
|
949
|
+
{ "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" },
|
|
950
|
+
{ "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" },
|
|
951
|
+
{ "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" },
|
|
952
|
+
{ "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" },
|
|
953
|
+
{ "mcpName": "list_trash", "commandPath": ["trash", "list"], "group": "generic", "description": "", "inputSchema": { "properties": {}, "type": "object" }, "required": [], "payloadMode": "flags", "destructive": false, "sourceCatalog": "dev", "schemaHash": "329250aecc06a298" },
|
|
954
|
+
{ "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" },
|
|
955
|
+
{ "mcpName": "list_workspaces", "commandPath": ["workspaces", "list"], "group": "workspace", "description": "", "inputSchema": { "properties": {}, "type": "object" }, "required": [], "payloadMode": "flags", "destructive": false, "sourceCatalog": "dev", "schemaHash": "39dba0089fdbdf6a" },
|
|
956
|
+
{ "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" },
|
|
957
|
+
{ "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" },
|
|
958
|
+
{ "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" },
|
|
959
|
+
{ "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" },
|
|
960
|
+
{ "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" },
|
|
961
|
+
{ "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" },
|
|
962
|
+
{ "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" },
|
|
963
|
+
{ "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" },
|
|
964
|
+
{ "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" },
|
|
965
|
+
{ "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" },
|
|
966
|
+
{ "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" },
|
|
967
|
+
{ "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" },
|
|
968
|
+
{ "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" },
|
|
969
|
+
{ "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" },
|
|
970
|
+
{ "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" },
|
|
971
|
+
{ "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" },
|
|
972
|
+
{ "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" },
|
|
973
|
+
{ "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" },
|
|
974
|
+
{ "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" },
|
|
975
|
+
{ "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
976
|
];
|
|
982
977
|
|
|
983
978
|
// src/core/catalog/generator.ts
|
|
@@ -995,7 +990,6 @@ var DESTRUCTIVE_PREFIXES = [
|
|
|
995
990
|
"move_",
|
|
996
991
|
"erd_delete_",
|
|
997
992
|
"erd_remove_",
|
|
998
|
-
"task_delete_",
|
|
999
993
|
"testcase_delete_"
|
|
1000
994
|
];
|
|
1001
995
|
function generateCatalog(tools, sourceCatalog) {
|
|
@@ -1029,7 +1023,7 @@ function inferGroup(strippedName) {
|
|
|
1029
1023
|
if (strippedName.startsWith("testcase_")) return "testcase";
|
|
1030
1024
|
if (strippedName.startsWith("markdown_")) return "markdown";
|
|
1031
1025
|
if (strippedName.includes("attachment") || strippedName.includes("upload")) return "attachment";
|
|
1032
|
-
if (strippedName.startsWith("
|
|
1026
|
+
if (strippedName.startsWith("kanban_")) return "kanban";
|
|
1033
1027
|
if (strippedName.includes("workspace")) return "workspace";
|
|
1034
1028
|
if (strippedName.includes("document")) return "document";
|
|
1035
1029
|
return "generic";
|
|
@@ -1132,6 +1126,9 @@ async function runStdioBridge(options) {
|
|
|
1132
1126
|
}
|
|
1133
1127
|
try {
|
|
1134
1128
|
const request = parseJsonRpcRequest(line);
|
|
1129
|
+
if (request.method === "initialize") {
|
|
1130
|
+
options.client.useAgent(clientInfoName(request.params));
|
|
1131
|
+
}
|
|
1135
1132
|
const response = await options.client.sendRaw(request);
|
|
1136
1133
|
if (response && request.id !== void 0) {
|
|
1137
1134
|
options.output.write(`${JSON.stringify(response)}
|
|
@@ -1160,17 +1157,27 @@ function parseJsonRpcRequest(line) {
|
|
|
1160
1157
|
params: parsed.params
|
|
1161
1158
|
};
|
|
1162
1159
|
}
|
|
1160
|
+
function clientInfoName(params) {
|
|
1161
|
+
return isRecord4(params) && isRecord4(params.clientInfo) ? params.clientInfo.name : void 0;
|
|
1162
|
+
}
|
|
1163
1163
|
function isRecord4(value) {
|
|
1164
1164
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1165
1165
|
}
|
|
1166
1166
|
|
|
1167
1167
|
// src/adapters/mcp/connector.ts
|
|
1168
1168
|
async function runConnector(options) {
|
|
1169
|
+
const legacyRole = legacyRoleInput(options.legacyRoleFlag, options.envVars);
|
|
1170
|
+
if (legacyRole !== void 0) {
|
|
1171
|
+
options.errorOutput.write(`${LEGACY_ROLE_NOTICE}
|
|
1172
|
+
`);
|
|
1173
|
+
}
|
|
1169
1174
|
const auth = new OAuthClient({ endpoint: options.endpoint, tokenStore: new FileTokenStore() });
|
|
1170
1175
|
const client = new McpRpcClient({
|
|
1171
1176
|
endpoint: options.endpoint,
|
|
1172
1177
|
tokenProvider: auth,
|
|
1173
|
-
|
|
1178
|
+
legacyRole,
|
|
1179
|
+
// 에이전트의 MCP 연결 — 에이전트 이름은 브리지가 호스트의 initialize 에서 읽어 싣는다.
|
|
1180
|
+
channel: "mcp"
|
|
1174
1181
|
});
|
|
1175
1182
|
await runStdioBridge({
|
|
1176
1183
|
input: options.input,
|
|
@@ -1243,18 +1250,6 @@ var enrichments = [
|
|
|
1243
1250
|
tableColumns: ["uuid", "name", "display_order"],
|
|
1244
1251
|
displayName: "List ERD table groups"
|
|
1245
1252
|
},
|
|
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
1253
|
{
|
|
1259
1254
|
mcpName: "enable_shares",
|
|
1260
1255
|
aliases: ["shares enable"],
|
|
@@ -1646,6 +1641,22 @@ function toolErrorText(value) {
|
|
|
1646
1641
|
function isRecord7(value) {
|
|
1647
1642
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1648
1643
|
}
|
|
1644
|
+
function extractProjects(value) {
|
|
1645
|
+
const candidates = Array.isArray(value) ? value : isRecord7(value) ? ["projects", "items", "created", "data", "hits"].flatMap((key) => {
|
|
1646
|
+
const nested = value[key];
|
|
1647
|
+
return Array.isArray(nested) ? nested : [];
|
|
1648
|
+
}) : [];
|
|
1649
|
+
const projects = [];
|
|
1650
|
+
for (const candidate of candidates) {
|
|
1651
|
+
if (isRecord7(candidate) && typeof candidate.uuid === "string") {
|
|
1652
|
+
projects.push({
|
|
1653
|
+
uuid: candidate.uuid,
|
|
1654
|
+
name: typeof candidate.name === "string" ? candidate.name : candidate.uuid
|
|
1655
|
+
});
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
return projects;
|
|
1659
|
+
}
|
|
1649
1660
|
|
|
1650
1661
|
// src/core/upload/attachmentUpload.ts
|
|
1651
1662
|
var AttachmentUploadError = class extends Error {
|
|
@@ -2261,8 +2272,8 @@ var AGENT_TARGETS = [
|
|
|
2261
2272
|
{ id: "claude-code", label: "Claude Code", path: ".mcp.json" },
|
|
2262
2273
|
{ id: "cursor", label: "Cursor", path: ".cursor/mcp.json" }
|
|
2263
2274
|
];
|
|
2264
|
-
function buildServerEntry(input) {
|
|
2265
|
-
const args = ["-y",
|
|
2275
|
+
function buildServerEntry(input = {}) {
|
|
2276
|
+
const args = ["-y", input.packageSpec ?? bridgePackageSpec()];
|
|
2266
2277
|
if (input.env && input.env !== "prod") {
|
|
2267
2278
|
args.push("--env", input.env);
|
|
2268
2279
|
}
|
|
@@ -2271,13 +2282,15 @@ function buildServerEntry(input) {
|
|
|
2271
2282
|
}
|
|
2272
2283
|
return { command: "npx", args };
|
|
2273
2284
|
}
|
|
2274
|
-
function
|
|
2285
|
+
function entryCarriesRole(entry) {
|
|
2286
|
+
return isRecord10(entry) && Array.isArray(entry.args) && entry.args.includes("--role");
|
|
2287
|
+
}
|
|
2288
|
+
function pinnedBridgeVersion(entry) {
|
|
2275
2289
|
if (!isRecord10(entry) || !Array.isArray(entry.args)) {
|
|
2276
2290
|
return void 0;
|
|
2277
2291
|
}
|
|
2278
|
-
const
|
|
2279
|
-
|
|
2280
|
-
return typeof value === "string" && HARNESS_ROLES.includes(value) ? value : void 0;
|
|
2292
|
+
const pinned = entry.args.find((arg) => typeof arg === "string" && arg.startsWith(`${PACKAGE_NAME}@`));
|
|
2293
|
+
return pinned?.slice(PACKAGE_NAME.length + 1);
|
|
2281
2294
|
}
|
|
2282
2295
|
function readServerEntry(existing) {
|
|
2283
2296
|
if (existing === void 0) {
|
|
@@ -2331,26 +2344,6 @@ function removeServerEntry(existing, restore) {
|
|
|
2331
2344
|
const emptied = Object.keys(servers).length === 0 && otherKeys.length === 0;
|
|
2332
2345
|
return { content: stringify({ ...root, mcpServers: servers }), removed, emptied };
|
|
2333
2346
|
}
|
|
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
2347
|
function readServers(root) {
|
|
2355
2348
|
const servers = root.mcpServers;
|
|
2356
2349
|
if (servers === void 0) {
|
|
@@ -2397,11 +2390,7 @@ var HARNESS_DOC_PATH = `${HARNESS_DIR}/HARNESS.md`;
|
|
|
2397
2390
|
var HARNESS_CONFIG_PATH = `${HARNESS_DIR}/config.json`;
|
|
2398
2391
|
var HARNESS_RECORD_PATH = `${HARNESS_DIR}/init-record.json`;
|
|
2399
2392
|
function renderHarnessDoc(input, template) {
|
|
2400
|
-
return fillTemplate(template ?? DEFAULT_HARNESS_DOC, {
|
|
2401
|
-
projectName: input.projectName,
|
|
2402
|
-
projectUuid: input.projectUuid,
|
|
2403
|
-
endpoint: input.endpoint
|
|
2404
|
-
});
|
|
2393
|
+
return fillTemplate(template ?? DEFAULT_HARNESS_DOC, { endpoint: input.endpoint });
|
|
2405
2394
|
}
|
|
2406
2395
|
function fillTemplate(template, values) {
|
|
2407
2396
|
return template.replace(/\{\{([a-zA-Z0-9_]+)}}/g, (whole, name) => Object.prototype.hasOwnProperty.call(values, name) ? values[name] : whole);
|
|
@@ -2409,15 +2398,21 @@ function fillTemplate(template, values) {
|
|
|
2409
2398
|
var DOC_KEY = "HARNESS.DOC";
|
|
2410
2399
|
var AGENT_NOTE_KEY = "HARNESS.AGENT_NOTE";
|
|
2411
2400
|
var REQUIRED_VARIABLES = {
|
|
2412
|
-
[DOC_KEY]: ["
|
|
2401
|
+
[DOC_KEY]: ["endpoint"],
|
|
2413
2402
|
[AGENT_NOTE_KEY]: ["harnessDocPath", "serverName"]
|
|
2414
2403
|
};
|
|
2415
2404
|
function documentContractProblem(key, body) {
|
|
2416
|
-
|
|
2405
|
+
const known = REQUIRED_VARIABLES[key] ?? [];
|
|
2406
|
+
for (const name of known) {
|
|
2417
2407
|
if (!body.includes(`{{${name}}}`)) {
|
|
2418
2408
|
return `${key} is missing the required {{${name}}} placeholder`;
|
|
2419
2409
|
}
|
|
2420
2410
|
}
|
|
2411
|
+
for (const [, name] of body.matchAll(/\{\{([a-zA-Z0-9_]+)}}/g)) {
|
|
2412
|
+
if (!known.includes(name)) {
|
|
2413
|
+
return `${key} has a {{${name}}} placeholder this CLI does not fill`;
|
|
2414
|
+
}
|
|
2415
|
+
}
|
|
2421
2416
|
if (key === AGENT_NOTE_KEY) {
|
|
2422
2417
|
return managedBlockProblem(key, body);
|
|
2423
2418
|
}
|
|
@@ -2425,58 +2420,32 @@ function documentContractProblem(key, body) {
|
|
|
2425
2420
|
}
|
|
2426
2421
|
var DEFAULT_HARNESS_DOC = `# AiNecto Harness
|
|
2427
2422
|
|
|
2428
|
-
This repository
|
|
2429
|
-
not in this repository's files.
|
|
2423
|
+
This repository works with AiNecto. The design source of truth \u2014 requirements, the ERD,
|
|
2424
|
+
diagrams, documents, and task boards \u2014 lives in AiNecto, not in this repository's files.
|
|
2430
2425
|
|
|
2431
|
-
- Project: **{{projectName}}** (\`{{projectUuid}}\`)
|
|
2432
2426
|
- MCP endpoint: {{endpoint}}
|
|
2433
2427
|
|
|
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
|
|
2428
|
+
## What you can do
|
|
2454
2429
|
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2430
|
+
Every AI session sees the same AiNecto tools. What a tool may change is decided by the signed-in
|
|
2431
|
+
user's AiNecto permissions on the workspace, project, or document it touches \u2014 the server refuses
|
|
2432
|
+
what the user may not do. A refusal is an answer: report it to the user instead of looking for a
|
|
2433
|
+
way around it.
|
|
2458
2434
|
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
Settle it in a Design Session, then continue here.
|
|
2435
|
+
Find the project and document for the task with the tools (\`list_projects\`, \`search_documents\`,
|
|
2436
|
+
\`get_document\`) \u2014 this repository is not bound to one project.
|
|
2462
2437
|
|
|
2463
|
-
|
|
2464
|
-
and which session should pick it up next.
|
|
2438
|
+
## When a tool is missing
|
|
2465
2439
|
|
|
2466
|
-
|
|
2440
|
+
If a result names a tool that is not in your tool list, this session started before that tool
|
|
2441
|
+
was added. Start a new session, or call it from the repository with
|
|
2442
|
+
\`npx -y ainecto@latest tools call <tool> '<json>'\`.
|
|
2467
2443
|
|
|
2468
|
-
|
|
2469
|
-
is not "always run four steps" \u2014 it is that when a step is needed, it does not happen inside
|
|
2470
|
-
another role's session.
|
|
2444
|
+
## Commands
|
|
2471
2445
|
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
${AGENT_TARGETS.map((target) => ` ${target.label.padEnd(12)} ${target.path}`).join("\n")}
|
|
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.
|
|
2446
|
+
npx -y ainecto@latest setup connect this machine's coding agents (once per machine)
|
|
2447
|
+
npx -y ainecto@latest status who is signed in, and what is connected
|
|
2448
|
+
npx -y ainecto@latest doctor what is missing, and the command that fixes it
|
|
2480
2449
|
|
|
2481
2450
|
---
|
|
2482
2451
|
Generated by \`ainecto init\`. Remove everything it wrote with \`ainecto init --undo\`.
|
|
@@ -2490,14 +2459,11 @@ function renderAgentNote(template) {
|
|
|
2490
2459
|
var DEFAULT_AGENT_NOTE = `${MARKER_BEGIN}
|
|
2491
2460
|
## AiNecto Harness
|
|
2492
2461
|
|
|
2493
|
-
This repository uses AiNecto as its design source of truth
|
|
2494
|
-
|
|
2495
|
-
touches requirements, the ERD, or architecture.
|
|
2462
|
+
This repository uses AiNecto as its design source of truth. Read \`{{harnessDocPath}}\` before doing
|
|
2463
|
+
work that touches requirements, the ERD, or architecture.
|
|
2496
2464
|
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
that role's tools. If the work needs something outside the role,
|
|
2500
|
-
stop and report which session is required.
|
|
2465
|
+
AiNecto tools come from the \`{{serverName}}\` MCP server. Every session sees the same tools; the
|
|
2466
|
+
signed-in user's AiNecto permissions decide what they may change, and the server refuses the rest.
|
|
2501
2467
|
${MARKER_END}`;
|
|
2502
2468
|
function managedBlockProblem(key, body) {
|
|
2503
2469
|
const trimmed = body.trim();
|
|
@@ -2934,175 +2900,6 @@ function isStringArray(value) {
|
|
|
2934
2900
|
|
|
2935
2901
|
// src/core/harness/initPlan.ts
|
|
2936
2902
|
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
2903
|
var NOTE_TARGETS = [
|
|
3107
2904
|
{ path: "AGENTS.md", createIfMissing: true },
|
|
3108
2905
|
{ path: "CLAUDE.md", createIfMissing: false }
|
|
@@ -3159,20 +2956,22 @@ function parseJsonObject(content) {
|
|
|
3159
2956
|
}
|
|
3160
2957
|
function configBodyOf(values) {
|
|
3161
2958
|
return {
|
|
3162
|
-
version:
|
|
3163
|
-
project: { uuid: values.projectUuid, name: values.projectName },
|
|
2959
|
+
version: 2,
|
|
3164
2960
|
endpoint: values.endpoint,
|
|
3165
2961
|
generatedBy: values.cliVersion,
|
|
3166
2962
|
promptVersions: values.versions
|
|
3167
2963
|
};
|
|
3168
2964
|
}
|
|
3169
2965
|
var OUR_CONFIG_FIELDS = Object.keys(configBodyOf({
|
|
3170
|
-
projectUuid: "",
|
|
3171
|
-
projectName: "",
|
|
3172
2966
|
endpoint: "",
|
|
3173
2967
|
cliVersion: "",
|
|
3174
2968
|
versions: {}
|
|
3175
2969
|
}));
|
|
2970
|
+
var RETIRED_CONFIG_FIELDS = ["project"];
|
|
2971
|
+
function ownedConfigFields(contract) {
|
|
2972
|
+
const written = contract?.writtenFields ?? {};
|
|
2973
|
+
return [...OUR_CONFIG_FIELDS, ...RETIRED_CONFIG_FIELDS.filter((field) => hasField(written, field))];
|
|
2974
|
+
}
|
|
3176
2975
|
var hasField = (object, field) => Object.prototype.hasOwnProperty.call(object, field);
|
|
3177
2976
|
function initRefusal(files, previous) {
|
|
3178
2977
|
const state = previous ?? readRecord(files.get(HARNESS_RECORD_PATH));
|
|
@@ -3195,16 +2994,10 @@ function planInit(input) {
|
|
|
3195
2994
|
const previous = input.previous ?? readRecord(input.files.get(HARNESS_RECORD_PATH));
|
|
3196
2995
|
const refusal = initRefusal(input.files, previous);
|
|
3197
2996
|
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
|
-
};
|
|
2997
|
+
return { writes: [], deletes: [], notes: [], refusal };
|
|
3205
2998
|
}
|
|
3206
2999
|
const carriedRecord = previous.state === "valid" ? previous.record : emptyRecord();
|
|
3207
|
-
const entry = buildServerEntry({
|
|
3000
|
+
const entry = buildServerEntry({ env: input.env, endpoint: input.endpoint, packageSpec: input.packageSpec });
|
|
3208
3001
|
const writes = [];
|
|
3209
3002
|
const notes = [];
|
|
3210
3003
|
const record = {
|
|
@@ -3227,15 +3020,14 @@ function planInit(input) {
|
|
|
3227
3020
|
for (const target of AGENT_TARGETS) {
|
|
3228
3021
|
const existing = input.files.get(target.path);
|
|
3229
3022
|
const currentEntry = existing === void 0 ? void 0 : readServerEntry(existing);
|
|
3023
|
+
if (!input.mcp && !isOurBridge(currentEntry)) {
|
|
3024
|
+
continue;
|
|
3025
|
+
}
|
|
3230
3026
|
const prior = priorOf(target.path, ownedJson(currentEntry, currentEntry !== void 0));
|
|
3231
3027
|
const merged = mergeServerEntry(existing, entry);
|
|
3232
3028
|
if (existing === merged.content) {
|
|
3233
3029
|
continue;
|
|
3234
3030
|
}
|
|
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
3031
|
const original = prior?.original ?? ownedJson(merged.replacedEntry, merged.replaced);
|
|
3240
3032
|
if (prior === void 0 && original.at !== "absent") {
|
|
3241
3033
|
notes.push(`${target.path}: your existing "${SERVER_NAME}" entry was saved and will be restored by --undo`);
|
|
@@ -3251,11 +3043,7 @@ function planInit(input) {
|
|
|
3251
3043
|
}
|
|
3252
3044
|
const existingDoc = input.files.get(HARNESS_DOC_PATH);
|
|
3253
3045
|
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);
|
|
3046
|
+
const docContent = renderHarnessDoc({ endpoint: input.endpoint }, input.documents?.doc);
|
|
3259
3047
|
const docOriginal = priorDoc?.original ?? ownedText(existingDoc);
|
|
3260
3048
|
if (priorDoc === void 0 && docOriginal.at !== "absent") {
|
|
3261
3049
|
notes.push(`${HARNESS_DOC_PATH} already existed; its original was saved and --undo will restore it.`);
|
|
@@ -3317,8 +3105,6 @@ function planInit(input) {
|
|
|
3317
3105
|
}
|
|
3318
3106
|
const existingConfig = input.files.get(HARNESS_CONFIG_PATH);
|
|
3319
3107
|
const ourFields = configBodyOf({
|
|
3320
|
-
projectUuid: input.projectUuid,
|
|
3321
|
-
projectName: input.projectName,
|
|
3322
3108
|
endpoint: input.endpoint,
|
|
3323
3109
|
cliVersion: input.cliVersion,
|
|
3324
3110
|
// ★어느 판의 규칙으로 썼나. 이게 없으면 「무엇이 적혀 있었나」를 나중에 못 되짚는다.
|
|
@@ -3342,7 +3128,24 @@ function planInit(input) {
|
|
|
3342
3128
|
notes.push(`${HARNESS_CONFIG_PATH}: you changed ${field} \u2014 ainecto now treats your value as the one to put back.`);
|
|
3343
3129
|
}
|
|
3344
3130
|
}
|
|
3345
|
-
const
|
|
3131
|
+
const retained = { ...theirConfig ?? {} };
|
|
3132
|
+
for (const field of RETIRED_CONFIG_FIELDS) {
|
|
3133
|
+
const priorWritten = priorConfig?.writtenFields;
|
|
3134
|
+
if (priorWritten === void 0 || !hasField(priorWritten, field) || !hasField(retained, field)) {
|
|
3135
|
+
continue;
|
|
3136
|
+
}
|
|
3137
|
+
if (stableStringify2(retained[field]) !== stableStringify2(priorWritten[field])) {
|
|
3138
|
+
notes.push(`${HARNESS_CONFIG_PATH}: you changed ${field} \u2014 left as you set it (ainecto no longer writes it).`);
|
|
3139
|
+
continue;
|
|
3140
|
+
}
|
|
3141
|
+
const original = priorConfig?.originalFields?.[field];
|
|
3142
|
+
if (original?.at === "json") {
|
|
3143
|
+
retained[field] = original.value;
|
|
3144
|
+
} else {
|
|
3145
|
+
delete retained[field];
|
|
3146
|
+
}
|
|
3147
|
+
}
|
|
3148
|
+
const mergedConfig = serialize({ ...retained, ...ourFields });
|
|
3346
3149
|
record.files[HARNESS_CONFIG_PATH] = {
|
|
3347
3150
|
scope: "fields",
|
|
3348
3151
|
createdByUs: priorConfig?.createdByUs ?? existingConfig === void 0,
|
|
@@ -3369,14 +3172,7 @@ function planInit(input) {
|
|
|
3369
3172
|
existed: existingRecord !== void 0
|
|
3370
3173
|
});
|
|
3371
3174
|
}
|
|
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
|
-
};
|
|
3175
|
+
return { writes, deletes: [], notes, record };
|
|
3380
3176
|
}
|
|
3381
3177
|
function planUndo(input) {
|
|
3382
3178
|
const state = input.record ?? readRecord(input.files.get(HARNESS_RECORD_PATH));
|
|
@@ -3384,20 +3180,19 @@ function planUndo(input) {
|
|
|
3384
3180
|
const deletes = [];
|
|
3385
3181
|
const notes = [];
|
|
3386
3182
|
const tail = () => {
|
|
3387
|
-
notes.push(`
|
|
3388
|
-
notes.push(`If you set ${ROLE_ENV_VAR} anywhere, unset it too.`);
|
|
3183
|
+
notes.push("Agent connections that `ainecto setup` made on this machine are removed with `ainecto setup --undo`.");
|
|
3389
3184
|
};
|
|
3390
3185
|
if (state.state === "absent") {
|
|
3391
3186
|
notes.push(`No record of a previous init in ${HARNESS_RECORD_PATH} \u2014 no local file was changed.`);
|
|
3392
3187
|
tail();
|
|
3393
|
-
return { writes, deletes,
|
|
3188
|
+
return { writes, deletes, notes };
|
|
3394
3189
|
}
|
|
3395
3190
|
if (state.state !== "valid") {
|
|
3396
3191
|
notes.push(
|
|
3397
3192
|
`${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
3193
|
);
|
|
3399
3194
|
tail();
|
|
3400
|
-
return { writes, deletes,
|
|
3195
|
+
return { writes, deletes, notes };
|
|
3401
3196
|
}
|
|
3402
3197
|
const record = state.record;
|
|
3403
3198
|
const remaining = { ...record.files };
|
|
@@ -3431,7 +3226,7 @@ function planUndo(input) {
|
|
|
3431
3226
|
}
|
|
3432
3227
|
}
|
|
3433
3228
|
const removableDirectories = parentsOf(deletes).filter((directory) => record.createdDirectories.includes(directory));
|
|
3434
|
-
return { writes, deletes,
|
|
3229
|
+
return { writes, deletes, notes, removableDirectories };
|
|
3435
3230
|
}
|
|
3436
3231
|
function unexplainedMarkers(existing, contract) {
|
|
3437
3232
|
const recorded = [...contract.foreignBlocks ?? []];
|
|
@@ -3577,7 +3372,8 @@ function undoFields(path, contract, existing) {
|
|
|
3577
3372
|
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
3373
|
};
|
|
3579
3374
|
}
|
|
3580
|
-
const
|
|
3375
|
+
const owned = ownedConfigFields(contract);
|
|
3376
|
+
const contractProblem = fieldsContractProblem(contract, owned);
|
|
3581
3377
|
if (contractProblem !== void 0) {
|
|
3582
3378
|
return {
|
|
3583
3379
|
kind: "unresolved",
|
|
@@ -3589,7 +3385,7 @@ function undoFields(path, contract, existing) {
|
|
|
3589
3385
|
const next = {};
|
|
3590
3386
|
const kept = [];
|
|
3591
3387
|
for (const [field, value] of Object.entries(parsed)) {
|
|
3592
|
-
if (!
|
|
3388
|
+
if (!owned.includes(field)) {
|
|
3593
3389
|
next[field] = value;
|
|
3594
3390
|
continue;
|
|
3595
3391
|
}
|
|
@@ -3644,14 +3440,8 @@ function parentsOf(paths) {
|
|
|
3644
3440
|
}
|
|
3645
3441
|
return [...directories].sort((left, right) => right.length - left.length);
|
|
3646
3442
|
}
|
|
3647
|
-
function previousRole(path, existing) {
|
|
3648
|
-
const read = readConfigRole(path, existing);
|
|
3649
|
-
return read.kind === "role" ? read.role : void 0;
|
|
3650
|
-
}
|
|
3651
3443
|
|
|
3652
3444
|
// src/core/harness/documentFetch.ts
|
|
3653
|
-
var DOC_KEY2 = "HARNESS.DOC";
|
|
3654
|
-
var AGENT_NOTE_KEY2 = "HARNESS.AGENT_NOTE";
|
|
3655
3445
|
async function fetchHarnessDocuments(input) {
|
|
3656
3446
|
const url = new URL(`${input.endpoint.replace(/\/$/, "")}/harness/documents`);
|
|
3657
3447
|
assertTrustedEndpointUrl(url, "harness documents URL");
|
|
@@ -3674,7 +3464,7 @@ async function fetchHarnessDocuments(input) {
|
|
|
3674
3464
|
const versions = {};
|
|
3675
3465
|
const fallbacks = {};
|
|
3676
3466
|
const bodies = {};
|
|
3677
|
-
for (const key of [
|
|
3467
|
+
for (const key of [DOC_KEY, AGENT_NOTE_KEY]) {
|
|
3678
3468
|
const entry = documents[key];
|
|
3679
3469
|
const problem = usableBodyProblem(key, entry);
|
|
3680
3470
|
if (problem) {
|
|
@@ -3686,7 +3476,7 @@ async function fetchHarnessDocuments(input) {
|
|
|
3686
3476
|
versions[key] = entry.version;
|
|
3687
3477
|
}
|
|
3688
3478
|
}
|
|
3689
|
-
return { doc: bodies[
|
|
3479
|
+
return { doc: bodies[DOC_KEY], agentNote: bodies[AGENT_NOTE_KEY], versions, fallbacks };
|
|
3690
3480
|
} catch (error) {
|
|
3691
3481
|
return {
|
|
3692
3482
|
versions: {},
|
|
@@ -3702,95 +3492,75 @@ function usableBodyProblem(key, entry) {
|
|
|
3702
3492
|
return documentContractProblem(key, entry.body);
|
|
3703
3493
|
}
|
|
3704
3494
|
|
|
3705
|
-
// src/
|
|
3706
|
-
import {
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3495
|
+
// src/core/harness/repositoryFiles.ts
|
|
3496
|
+
import { lstat, readFile as readFile5, realpath } from "fs/promises";
|
|
3497
|
+
import { dirname as dirname3, join as join2, resolve, sep } from "path";
|
|
3498
|
+
async function findGitRoot(cwd) {
|
|
3499
|
+
for (let at = resolve(cwd); ; at = dirname3(at)) {
|
|
3500
|
+
try {
|
|
3501
|
+
await lstat(join2(at, ".git"));
|
|
3502
|
+
return at;
|
|
3503
|
+
} catch {
|
|
3504
|
+
}
|
|
3505
|
+
if (dirname3(at) === at) {
|
|
3506
|
+
return void 0;
|
|
3507
|
+
}
|
|
3711
3508
|
}
|
|
3712
|
-
};
|
|
3713
|
-
var AGENT_ENV_MARKERS = ["CLAUDECODE", "CI", "GEMINI_CLI", "CODEX_SANDBOX", "CURSOR_AGENT"];
|
|
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
3509
|
}
|
|
3720
|
-
function
|
|
3721
|
-
|
|
3510
|
+
async function readManagedFile(root, path) {
|
|
3511
|
+
await assertInsideRepository(root, path);
|
|
3512
|
+
return readIfExists(join2(root, path));
|
|
3722
3513
|
}
|
|
3723
|
-
function
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
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();
|
|
3736
|
-
}
|
|
3737
|
-
return next.value;
|
|
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();
|
|
3514
|
+
async function readIfExists(absolutePath) {
|
|
3515
|
+
try {
|
|
3516
|
+
return await readFile5(absolutePath, "utf8");
|
|
3517
|
+
} catch (error) {
|
|
3518
|
+
if (isNodeError2(error, "ENOENT")) {
|
|
3519
|
+
return void 0;
|
|
3747
3520
|
}
|
|
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
|
-
`);
|
|
3521
|
+
throw error;
|
|
3770
3522
|
}
|
|
3771
3523
|
}
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
}
|
|
3777
|
-
async function executeInitCommand(options) {
|
|
3524
|
+
async function assertInsideRepository(root, relativePath) {
|
|
3525
|
+
const absolutePath = join2(root, relativePath);
|
|
3526
|
+
const realRoot = await realpath(root);
|
|
3527
|
+
let realParent;
|
|
3778
3528
|
try {
|
|
3779
|
-
|
|
3529
|
+
realParent = await realpath(dirname3(absolutePath));
|
|
3780
3530
|
} catch (error) {
|
|
3781
|
-
if (error
|
|
3782
|
-
|
|
3783
|
-
`);
|
|
3784
|
-
return 130;
|
|
3531
|
+
if (isNodeError2(error, "ENOENT")) {
|
|
3532
|
+
return;
|
|
3785
3533
|
}
|
|
3786
3534
|
throw error;
|
|
3787
|
-
} finally {
|
|
3788
|
-
options.prompter?.close();
|
|
3789
3535
|
}
|
|
3536
|
+
if (realParent !== realRoot && !realParent.startsWith(realRoot + sep)) {
|
|
3537
|
+
throw new Error(
|
|
3538
|
+
`${relativePath} resolves outside the repository (${realParent}); refusing to touch it.`
|
|
3539
|
+
);
|
|
3540
|
+
}
|
|
3541
|
+
try {
|
|
3542
|
+
if ((await lstat(absolutePath)).isSymbolicLink()) {
|
|
3543
|
+
throw new Error(`${relativePath} is a symbolic link; refusing to write through it.`);
|
|
3544
|
+
}
|
|
3545
|
+
} catch (error) {
|
|
3546
|
+
if (!isNodeError2(error, "ENOENT")) {
|
|
3547
|
+
throw error;
|
|
3548
|
+
}
|
|
3549
|
+
}
|
|
3550
|
+
}
|
|
3551
|
+
function isNodeError2(error, code) {
|
|
3552
|
+
return typeof error === "object" && error !== null && error.code === code;
|
|
3790
3553
|
}
|
|
3791
|
-
|
|
3792
|
-
|
|
3554
|
+
|
|
3555
|
+
// src/adapters/cli/initCommand.ts
|
|
3556
|
+
async function executeInitCommand(options) {
|
|
3793
3557
|
const args = parseInitArgs(options.argv);
|
|
3558
|
+
if (args.ignored.length > 0) {
|
|
3559
|
+
options.io.stderr.write(
|
|
3560
|
+
`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.
|
|
3561
|
+
`
|
|
3562
|
+
);
|
|
3563
|
+
}
|
|
3794
3564
|
const root = resolve2(options.cwd);
|
|
3795
3565
|
const rootNotes = await repositoryRootNotes(root);
|
|
3796
3566
|
const files = await readSnapshot(root);
|
|
@@ -3812,47 +3582,18 @@ async function runInit(initialOptions) {
|
|
|
3812
3582
|
if (refusal !== void 0) {
|
|
3813
3583
|
throw new Error(refusal);
|
|
3814
3584
|
}
|
|
3815
|
-
const
|
|
3816
|
-
const
|
|
3817
|
-
const previousRole2 = (() => {
|
|
3818
|
-
try {
|
|
3819
|
-
return currentRole(files);
|
|
3820
|
-
} catch {
|
|
3821
|
-
return void 0;
|
|
3822
|
-
}
|
|
3823
|
-
})();
|
|
3824
|
-
if (!role) {
|
|
3825
|
-
throw new Error(
|
|
3826
|
-
`No role given. Ask the user which role this repository's AI sessions should have (${HARNESS_ROLES.join(", ")}), then re-run with --role <role>.`
|
|
3827
|
-
);
|
|
3828
|
-
}
|
|
3829
|
-
const connected = connectAs(options, role);
|
|
3830
|
-
options = connected;
|
|
3831
|
-
await ensureSignedIn(connected, role, args.dryRun);
|
|
3832
|
-
const project = await resolveProject(connected, args, files, root, role);
|
|
3833
|
-
if ("choices" in project) {
|
|
3834
|
-
options.io.stdout.write(renderSuccess(
|
|
3835
|
-
{
|
|
3836
|
-
message: "More than one project found. Re-run with --project <uuid>.",
|
|
3837
|
-
projects: project.choices
|
|
3838
|
-
},
|
|
3839
|
-
{ json: options.json }
|
|
3840
|
-
));
|
|
3841
|
-
return 1;
|
|
3842
|
-
}
|
|
3843
|
-
const documents = args.dryRun ? { versions: {}, fallbacks: {}, unavailableReason: "skipped in --dry-run" } : await fetchHarnessDocuments({
|
|
3585
|
+
const signedIn = await ensureSignedIn(options, args.dryRun);
|
|
3586
|
+
const documents = signedIn ? await fetchHarnessDocuments({
|
|
3844
3587
|
endpoint: options.endpoint,
|
|
3845
3588
|
accessToken: await options.accessToken?.(),
|
|
3846
3589
|
// 서버가 문서의 클라이언트별 변형 키를 이 이름으로 찾는다.
|
|
3847
3590
|
client: "ainecto-cli",
|
|
3848
3591
|
fetchImpl: options.fetchImpl
|
|
3849
|
-
});
|
|
3592
|
+
}) : { versions: {}, fallbacks: {}, unavailableReason: args.dryRun ? "not signed in, and --dry-run does not sign in" : "not signed in" };
|
|
3850
3593
|
const plan = planInit({
|
|
3851
|
-
role,
|
|
3852
3594
|
env: options.env,
|
|
3853
|
-
projectName: project.name,
|
|
3854
|
-
projectUuid: project.uuid,
|
|
3855
3595
|
endpoint: options.endpoint,
|
|
3596
|
+
mcp: args.mcp,
|
|
3856
3597
|
cliVersion: options.cliVersion,
|
|
3857
3598
|
files,
|
|
3858
3599
|
documents
|
|
@@ -3861,39 +3602,18 @@ async function runInit(initialOptions) {
|
|
|
3861
3602
|
throw new Error(plan.refusal);
|
|
3862
3603
|
}
|
|
3863
3604
|
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
|
-
}
|
|
3605
|
+
const mcpWritten = plan.writes.some((write) => AGENT_TARGETS.some((target) => target.path === write.path));
|
|
3868
3606
|
options.io.stdout.write(renderSuccess(
|
|
3869
3607
|
{
|
|
3870
3608
|
action: args.dryRun ? "init (dry-run)" : "init",
|
|
3871
|
-
role,
|
|
3872
|
-
project: { uuid: project.uuid, name: project.name },
|
|
3873
3609
|
endpoint: options.endpoint,
|
|
3874
3610
|
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 도구»를 더할 뿐인 선택이다.
|
|
3611
|
+
// ⚠조용히 기본값으로 떨어지지 않는다 — 서버에서 규칙을 고쳐도 안 바뀌는 이유를 사용자가 영영 못 찾는다.
|
|
3612
|
+
// ★요청 자체의 실패와 «문서별» 되돌림을 둘 다, 어느 «파일»이 그 글을 받는지까지 적는다.
|
|
3613
|
+
notes: [...rootNotes, ...plan.notes, ...fallbackNotes(documents, args.dryRun)],
|
|
3888
3614
|
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\`).`,
|
|
3615
|
+
`Read ${HARNESS_DOC_PATH} first. Every session sees the same AiNecto tools; your AiNecto permissions decide what they may change.`,
|
|
3616
|
+
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
3617
|
"Undo everything with: ainecto init --undo"
|
|
3898
3618
|
]
|
|
3899
3619
|
},
|
|
@@ -3901,43 +3621,29 @@ async function runInit(initialOptions) {
|
|
|
3901
3621
|
));
|
|
3902
3622
|
return 0;
|
|
3903
3623
|
}
|
|
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;
|
|
3624
|
+
function fallbackNotes(documents, dryRun) {
|
|
3625
|
+
const filesOf = {
|
|
3626
|
+
[DOC_KEY]: HARNESS_DOC_PATH,
|
|
3627
|
+
[AGENT_NOTE_KEY]: "AGENTS.md / CLAUDE.md"
|
|
3628
|
+
};
|
|
3629
|
+
const caveat = dryRun ? " \u2014 the real run may write the server's version instead" : "";
|
|
3630
|
+
const reasons = documents.unavailableReason ? { [DOC_KEY]: documents.unavailableReason, [AGENT_NOTE_KEY]: documents.unavailableReason } : documents.fallbacks;
|
|
3631
|
+
return Object.entries(reasons).map(([key, reason]) => `${filesOf[key] ?? key}: written from this package's default ${key}, not the server's (${reason})${caveat}.`);
|
|
3922
3632
|
}
|
|
3923
|
-
async function ensureSignedIn(options,
|
|
3924
|
-
if (options.login === void 0) {
|
|
3925
|
-
return;
|
|
3926
|
-
}
|
|
3633
|
+
async function ensureSignedIn(options, dryRun) {
|
|
3927
3634
|
if (await options.accessToken?.()) {
|
|
3928
|
-
return;
|
|
3635
|
+
return true;
|
|
3929
3636
|
}
|
|
3930
|
-
if (dryRun) {
|
|
3931
|
-
|
|
3932
|
-
`No usable AiNecto sign-in
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
Then run init again.`
|
|
3937
|
-
);
|
|
3637
|
+
if (dryRun || options.login === void 0) {
|
|
3638
|
+
if (dryRun) {
|
|
3639
|
+
options.io.stderr.write(`No usable AiNecto sign-in, so this plan uses the package's default rules. Sign in with: ${loginCommand(options)}
|
|
3640
|
+
`);
|
|
3641
|
+
}
|
|
3642
|
+
return false;
|
|
3938
3643
|
}
|
|
3939
3644
|
await options.login();
|
|
3940
3645
|
options.io.stderr.write("Signed in.\n");
|
|
3646
|
+
return true;
|
|
3941
3647
|
}
|
|
3942
3648
|
async function repositoryRootNotes(cwd) {
|
|
3943
3649
|
const gitRoot = await findGitRoot(cwd);
|
|
@@ -3955,178 +3661,10 @@ Nothing was changed.`
|
|
|
3955
3661
|
}
|
|
3956
3662
|
return [];
|
|
3957
3663
|
}
|
|
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
3664
|
function loginCommand(options) {
|
|
4072
3665
|
const target = isDefaultEndpointFor(options.env, options.endpoint) ? options.env === "prod" ? "" : ` --env ${options.env}` : ` --endpoint ${options.endpoint}`;
|
|
4073
3666
|
return `ainecto auth login${target}`;
|
|
4074
3667
|
}
|
|
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
3668
|
async function readSnapshot(root) {
|
|
4131
3669
|
const snapshot = /* @__PURE__ */ new Map();
|
|
4132
3670
|
for (const path of MANAGED_PATHS) {
|
|
@@ -4271,22 +3809,26 @@ async function fileMode(absolutePath) {
|
|
|
4271
3809
|
}
|
|
4272
3810
|
}
|
|
4273
3811
|
function parseInitArgs(argv) {
|
|
4274
|
-
const args = { dryRun: false, undo: false,
|
|
3812
|
+
const args = { mcp: false, dryRun: false, undo: false, ignored: [] };
|
|
4275
3813
|
for (let index = 0; index < argv.length; index += 1) {
|
|
4276
3814
|
const arg = argv[index];
|
|
4277
3815
|
if (!arg) {
|
|
4278
3816
|
continue;
|
|
4279
3817
|
}
|
|
4280
|
-
if (arg === "--
|
|
4281
|
-
args.
|
|
4282
|
-
} else if (arg === "--project-name") {
|
|
4283
|
-
args.projectName = requireValue3(arg, argv[++index]);
|
|
3818
|
+
if (arg === "--mcp") {
|
|
3819
|
+
args.mcp = true;
|
|
4284
3820
|
} else if (arg === "--dry-run") {
|
|
4285
3821
|
args.dryRun = true;
|
|
4286
3822
|
} else if (arg === "--undo") {
|
|
4287
3823
|
args.undo = true;
|
|
3824
|
+
} else if (arg === "--project" || arg === "--project-name") {
|
|
3825
|
+
const value = argv[index + 1];
|
|
3826
|
+
if (value !== void 0 && !value.startsWith("--")) {
|
|
3827
|
+
index += 1;
|
|
3828
|
+
}
|
|
3829
|
+
args.ignored.push(arg);
|
|
4288
3830
|
} else if (arg === "--yes" || arg === "-y") {
|
|
4289
|
-
args.
|
|
3831
|
+
args.ignored.push(arg);
|
|
4290
3832
|
} else if (arg === "--json") {
|
|
4291
3833
|
} else {
|
|
4292
3834
|
throw new Error(`Unknown argument: ${arg}`);
|
|
@@ -4294,21 +3836,730 @@ function parseInitArgs(argv) {
|
|
|
4294
3836
|
}
|
|
4295
3837
|
return args;
|
|
4296
3838
|
}
|
|
4297
|
-
function
|
|
4298
|
-
|
|
4299
|
-
|
|
3839
|
+
function isRecord11(value) {
|
|
3840
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3841
|
+
}
|
|
3842
|
+
|
|
3843
|
+
// src/core/setup/accountSummary.ts
|
|
3844
|
+
async function readAccount(client) {
|
|
3845
|
+
const summary = {};
|
|
3846
|
+
try {
|
|
3847
|
+
await client.initialize();
|
|
3848
|
+
const self = unwrapMcpToolPayload(await client.toolsCall("identity_get_self", {}));
|
|
3849
|
+
if (typeof self?.userId === "string") summary.userId = self.userId;
|
|
3850
|
+
if (typeof self?.displayName === "string") summary.displayName = self.displayName;
|
|
3851
|
+
if ("jobProfile" in (self ?? {})) {
|
|
3852
|
+
const profile = self.jobProfile;
|
|
3853
|
+
summary.jobTitle = profile && typeof profile.title === "string" ? profile.title : null;
|
|
3854
|
+
}
|
|
3855
|
+
summary.projectCount = extractProjects(unwrapMcpToolPayload(await client.toolsCall("list_projects", {}))).length;
|
|
3856
|
+
} catch (error) {
|
|
3857
|
+
summary.problem = classify(error);
|
|
4300
3858
|
}
|
|
4301
|
-
return
|
|
3859
|
+
return summary;
|
|
4302
3860
|
}
|
|
4303
|
-
function
|
|
4304
|
-
const
|
|
4305
|
-
|
|
3861
|
+
function classify(error) {
|
|
3862
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3863
|
+
if (error instanceof McpRpcError && error.code === "MCP_HTTP_ERROR") {
|
|
3864
|
+
const status = error.details?.status;
|
|
3865
|
+
if (status === 401) return { kind: "unauthorized", message };
|
|
3866
|
+
if (status === 403) return { kind: "forbidden", message };
|
|
3867
|
+
return { kind: "other", message };
|
|
3868
|
+
}
|
|
3869
|
+
if (error instanceof McpRpcError) {
|
|
3870
|
+
return { kind: "other", message };
|
|
3871
|
+
}
|
|
3872
|
+
return { kind: "network", message };
|
|
4306
3873
|
}
|
|
4307
|
-
|
|
4308
|
-
|
|
3874
|
+
|
|
3875
|
+
// src/core/setup/agentConnections.ts
|
|
3876
|
+
import { chmod as chmod3, mkdir as mkdir4, readFile as readFile6, rename as rename4, stat as stat4, writeFile as writeFile3 } from "fs/promises";
|
|
3877
|
+
import { dirname as dirname5, join as join4 } from "path";
|
|
3878
|
+
|
|
3879
|
+
// src/core/setup/commandRunner.ts
|
|
3880
|
+
import { spawn as spawn2 } from "child_process";
|
|
3881
|
+
var COMMAND_TIMEOUT_MS = 9e4;
|
|
3882
|
+
function systemCommandRunner(options) {
|
|
3883
|
+
return (command, args) => new Promise((resolve3) => {
|
|
3884
|
+
let stdout = "";
|
|
3885
|
+
let stderr = "";
|
|
3886
|
+
let settled = false;
|
|
3887
|
+
const finish = (result) => {
|
|
3888
|
+
if (!settled) {
|
|
3889
|
+
settled = true;
|
|
3890
|
+
clearTimeout(timer);
|
|
3891
|
+
resolve3(result);
|
|
3892
|
+
}
|
|
3893
|
+
};
|
|
3894
|
+
const child = spawn2(command, [...args], {
|
|
3895
|
+
cwd: options.cwd,
|
|
3896
|
+
env: options.env ?? process.env,
|
|
3897
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
3898
|
+
});
|
|
3899
|
+
const timer = setTimeout(() => {
|
|
3900
|
+
child.kill();
|
|
3901
|
+
finish({ exitCode: null, stdout, stderr: `${stderr}
|
|
3902
|
+
${command} timed out after ${COMMAND_TIMEOUT_MS / 1e3}s`, missing: false });
|
|
3903
|
+
}, COMMAND_TIMEOUT_MS);
|
|
3904
|
+
child.stdout.on("data", (chunk) => {
|
|
3905
|
+
stdout += String(chunk);
|
|
3906
|
+
});
|
|
3907
|
+
child.stderr.on("data", (chunk) => {
|
|
3908
|
+
stderr += String(chunk);
|
|
3909
|
+
});
|
|
3910
|
+
child.on("error", (error) => {
|
|
3911
|
+
finish({ exitCode: null, stdout, stderr: error.message, missing: error.code === "ENOENT" });
|
|
3912
|
+
});
|
|
3913
|
+
child.on("close", (code) => finish({ exitCode: code, stdout, stderr, missing: false }));
|
|
3914
|
+
});
|
|
3915
|
+
}
|
|
3916
|
+
function recordingRunner(runner, log) {
|
|
3917
|
+
return async (command, args) => {
|
|
3918
|
+
const result = await runner(command, args);
|
|
3919
|
+
const entry = { command: [command, ...args].join(" "), exitCode: result.exitCode };
|
|
3920
|
+
if (result.missing) {
|
|
3921
|
+
entry.missing = true;
|
|
3922
|
+
} else if (result.exitCode !== 0) {
|
|
3923
|
+
entry.error = lastLine(result.stderr) ?? lastLine(result.stdout);
|
|
3924
|
+
}
|
|
3925
|
+
log.push(entry);
|
|
3926
|
+
return result;
|
|
3927
|
+
};
|
|
3928
|
+
}
|
|
3929
|
+
function lastLine(text) {
|
|
3930
|
+
const lines = text.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
3931
|
+
return lines[lines.length - 1];
|
|
3932
|
+
}
|
|
3933
|
+
|
|
3934
|
+
// src/core/setup/agentConnections.ts
|
|
3935
|
+
var AGENT_CLIENT_LABELS = {
|
|
3936
|
+
"claude-code": "Claude Code",
|
|
3937
|
+
cursor: "Cursor",
|
|
3938
|
+
codex: "Codex"
|
|
3939
|
+
};
|
|
3940
|
+
async function inspectAgents(context) {
|
|
3941
|
+
return [await inspectClaude(context), await inspectCursor(context), await inspectCodex(context)];
|
|
3942
|
+
}
|
|
3943
|
+
var NOT_FOUND = /No MCP server named/i;
|
|
3944
|
+
async function inspectClaude({ runner }) {
|
|
3945
|
+
const base = { client: "claude-code", ours: false, carriesRole: false };
|
|
3946
|
+
const result = await runner("claude", ["mcp", "get", SERVER_NAME]);
|
|
3947
|
+
if (result.missing) {
|
|
3948
|
+
return { ...base, installed: false };
|
|
3949
|
+
}
|
|
3950
|
+
if (result.exitCode !== 0) {
|
|
3951
|
+
return NOT_FOUND.test(result.stdout + result.stderr) ? { ...base, installed: true } : { ...base, installed: true, problem: lastLine(result.stderr) ?? `claude mcp get exited ${result.exitCode}` };
|
|
3952
|
+
}
|
|
3953
|
+
const field = (name) => result.stdout.match(new RegExp(`^\\s*${name}:\\s*(.*)$`, "m"))?.[1]?.trim();
|
|
3954
|
+
const scopeText = field("Scope") ?? "";
|
|
3955
|
+
const scope = /^user/i.test(scopeText) ? "user" : /^local/i.test(scopeText) ? "local" : /^project/i.test(scopeText) ? "project" : scopeText || void 0;
|
|
3956
|
+
const url = field("URL");
|
|
3957
|
+
const command = field("Command");
|
|
3958
|
+
const entry = url ? { url } : command ? { command, args: (field("Args") ?? "").split(/\s+/).filter(Boolean) } : void 0;
|
|
3959
|
+
return { ...base, installed: true, scope, ...describe(entry) };
|
|
3960
|
+
}
|
|
3961
|
+
async function inspectCursor({ home }) {
|
|
3962
|
+
const base = { client: "cursor", ours: false, carriesRole: false, scope: "global" };
|
|
3963
|
+
if (!await exists(join4(home, ".cursor"))) {
|
|
3964
|
+
return { ...base, installed: false };
|
|
3965
|
+
}
|
|
3966
|
+
let content;
|
|
3967
|
+
try {
|
|
3968
|
+
content = await readFile6(cursorConfigPath(home), "utf8");
|
|
3969
|
+
} catch (error) {
|
|
3970
|
+
if (!isNodeError3(error, "ENOENT")) {
|
|
3971
|
+
return { ...base, installed: true, problem: error instanceof Error ? error.message : String(error) };
|
|
3972
|
+
}
|
|
3973
|
+
}
|
|
3974
|
+
if (content !== void 0 && content.trim()) {
|
|
3975
|
+
try {
|
|
3976
|
+
JSON.parse(content);
|
|
3977
|
+
} catch {
|
|
3978
|
+
return { ...base, installed: true, problem: `${cursorConfigPath(home)} is not valid JSON` };
|
|
3979
|
+
}
|
|
3980
|
+
}
|
|
3981
|
+
const raw = readServerEntry(content);
|
|
3982
|
+
return { ...base, installed: true, ...describe(toExisting(raw)) };
|
|
3983
|
+
}
|
|
3984
|
+
async function inspectCodex({ runner }) {
|
|
3985
|
+
const base = { client: "codex", ours: false, carriesRole: false, scope: "global" };
|
|
3986
|
+
const result = await runner("codex", ["mcp", "get", SERVER_NAME, "--json"]);
|
|
3987
|
+
if (result.missing) {
|
|
3988
|
+
return { ...base, installed: false };
|
|
3989
|
+
}
|
|
3990
|
+
if (result.exitCode !== 0) {
|
|
3991
|
+
return NOT_FOUND.test(result.stdout + result.stderr) ? { ...base, installed: true } : { ...base, installed: true, problem: lastLine(result.stderr) ?? `codex mcp get exited ${result.exitCode}` };
|
|
3992
|
+
}
|
|
3993
|
+
try {
|
|
3994
|
+
const parsed = JSON.parse(result.stdout);
|
|
3995
|
+
const transport = parsed.transport ?? {};
|
|
3996
|
+
return { ...base, installed: true, ...describe(toExisting(transport)) };
|
|
3997
|
+
} catch {
|
|
3998
|
+
return { ...base, installed: true, problem: "codex mcp get --json did not return JSON" };
|
|
3999
|
+
}
|
|
4000
|
+
}
|
|
4001
|
+
async function connectAgent(state, desired, context, dryRun) {
|
|
4002
|
+
const outcome = (status, detail) => ({
|
|
4003
|
+
client: state.client,
|
|
4004
|
+
label: AGENT_CLIENT_LABELS[state.client],
|
|
4005
|
+
status,
|
|
4006
|
+
...dryRun && (status === "connected" || status === "updated") ? { planned: true } : {},
|
|
4007
|
+
...detail ? { detail } : {}
|
|
4008
|
+
});
|
|
4009
|
+
if (!state.installed) {
|
|
4010
|
+
return outcome("not installed");
|
|
4011
|
+
}
|
|
4012
|
+
if (state.problem) {
|
|
4013
|
+
return outcome("failed", state.problem);
|
|
4014
|
+
}
|
|
4015
|
+
if (state.entry && (!state.ours || state.client === "claude-code" && state.scope !== "user")) {
|
|
4016
|
+
return outcome("kept", keptDetail(state));
|
|
4017
|
+
}
|
|
4018
|
+
if (state.entry && sameEntry(state.entry, desired)) {
|
|
4019
|
+
return outcome("connected", "already connected");
|
|
4020
|
+
}
|
|
4021
|
+
const updating = state.entry !== void 0;
|
|
4022
|
+
const commands = writeCommands(state.client, desired, updating);
|
|
4023
|
+
if (dryRun) {
|
|
4024
|
+
return outcome(updating ? "updated" : "connected", commands.length > 0 ? `would run: ${commands.map((c) => c.join(" ")).join(" && ")}` : `would write ${cursorConfigPath(context.home)}`);
|
|
4025
|
+
}
|
|
4026
|
+
try {
|
|
4027
|
+
if (state.client === "cursor") {
|
|
4028
|
+
const path = cursorConfigPath(context.home);
|
|
4029
|
+
await writeKeepingMode(path, mergeServerEntry(await readIfExists2(path), desired).content);
|
|
4030
|
+
}
|
|
4031
|
+
for (const [command, ...args] of commands) {
|
|
4032
|
+
const result = await context.runner(command, args);
|
|
4033
|
+
if (result.exitCode !== 0) {
|
|
4034
|
+
return outcome("failed", `${[command, ...args].join(" ")}: ${lastLine(result.stderr) ?? lastLine(result.stdout) ?? `exit ${result.exitCode}`}`);
|
|
4035
|
+
}
|
|
4036
|
+
}
|
|
4037
|
+
} catch (error) {
|
|
4038
|
+
return outcome("failed", error instanceof Error ? error.message : String(error));
|
|
4039
|
+
}
|
|
4040
|
+
return outcome(updating ? "updated" : "connected");
|
|
4041
|
+
}
|
|
4042
|
+
async function disconnectAgent(state, context, dryRun) {
|
|
4043
|
+
const outcome = (status, detail) => ({
|
|
4044
|
+
client: state.client,
|
|
4045
|
+
label: AGENT_CLIENT_LABELS[state.client],
|
|
4046
|
+
status,
|
|
4047
|
+
...dryRun && status === "removed" ? { planned: true } : {},
|
|
4048
|
+
...detail ? { detail } : {}
|
|
4049
|
+
});
|
|
4050
|
+
if (!state.installed) {
|
|
4051
|
+
return outcome("not installed");
|
|
4052
|
+
}
|
|
4053
|
+
if (state.problem) {
|
|
4054
|
+
return outcome("failed", state.problem);
|
|
4055
|
+
}
|
|
4056
|
+
if (!state.entry) {
|
|
4057
|
+
return outcome("not connected");
|
|
4058
|
+
}
|
|
4059
|
+
if (!state.ours || state.client === "claude-code" && state.scope !== "user") {
|
|
4060
|
+
return outcome("kept", keptDetail(state));
|
|
4061
|
+
}
|
|
4062
|
+
if (dryRun) {
|
|
4063
|
+
return outcome("removed");
|
|
4064
|
+
}
|
|
4065
|
+
try {
|
|
4066
|
+
if (state.client === "cursor") {
|
|
4067
|
+
const path = cursorConfigPath(context.home);
|
|
4068
|
+
const removed = removeServerEntry(await readIfExists2(path));
|
|
4069
|
+
if (removed.content !== void 0) {
|
|
4070
|
+
await writeKeepingMode(path, removed.content);
|
|
4071
|
+
}
|
|
4072
|
+
} else {
|
|
4073
|
+
const [command, ...args] = removeCommand(state.client);
|
|
4074
|
+
const result = await context.runner(command, args);
|
|
4075
|
+
if (result.exitCode !== 0) {
|
|
4076
|
+
return outcome("failed", `${[command, ...args].join(" ")}: ${lastLine(result.stderr) ?? `exit ${result.exitCode}`}`);
|
|
4077
|
+
}
|
|
4078
|
+
}
|
|
4079
|
+
} catch (error) {
|
|
4080
|
+
return outcome("failed", error instanceof Error ? error.message : String(error));
|
|
4081
|
+
}
|
|
4082
|
+
return outcome("removed");
|
|
4083
|
+
}
|
|
4084
|
+
function desiredEntry(input) {
|
|
4085
|
+
return buildServerEntry({ env: input.env, endpoint: input.endpoint });
|
|
4086
|
+
}
|
|
4087
|
+
function cursorConfigPath(home) {
|
|
4088
|
+
return join4(home, ".cursor", "mcp.json");
|
|
4089
|
+
}
|
|
4090
|
+
function writeCommands(client, entry, updating) {
|
|
4091
|
+
const bridge = [entry.command, ...entry.args];
|
|
4092
|
+
if (client === "claude-code") {
|
|
4093
|
+
return [
|
|
4094
|
+
...updating ? [["claude", "mcp", "remove", "-s", "user", SERVER_NAME]] : [],
|
|
4095
|
+
["claude", "mcp", "add", "-s", "user", SERVER_NAME, "--", ...bridge]
|
|
4096
|
+
];
|
|
4097
|
+
}
|
|
4098
|
+
if (client === "codex") {
|
|
4099
|
+
return [["codex", "mcp", "add", SERVER_NAME, "--", ...bridge]];
|
|
4100
|
+
}
|
|
4101
|
+
return [];
|
|
4102
|
+
}
|
|
4103
|
+
function removeCommand(client) {
|
|
4104
|
+
return client === "claude-code" ? ["claude", "mcp", "remove", "-s", "user", SERVER_NAME] : ["codex", "mcp", "remove", SERVER_NAME];
|
|
4105
|
+
}
|
|
4106
|
+
function keptDetail(state) {
|
|
4107
|
+
if (state.client === "claude-code" && state.ours && state.scope !== "user") {
|
|
4108
|
+
return `a ${state.scope}-scope "${SERVER_NAME}" entry takes precedence here \u2014 left as it is`;
|
|
4109
|
+
}
|
|
4110
|
+
const where = state.scope ? ` (${state.scope})` : "";
|
|
4111
|
+
return `someone else's "${SERVER_NAME}" entry${where} \u2014 left untouched`;
|
|
4112
|
+
}
|
|
4113
|
+
function describe(entry) {
|
|
4114
|
+
if (!entry) {
|
|
4115
|
+
return { ours: false, carriesRole: false };
|
|
4116
|
+
}
|
|
4117
|
+
if ("url" in entry) {
|
|
4118
|
+
return { entry, ours: false, carriesRole: false };
|
|
4119
|
+
}
|
|
4120
|
+
const pinnedVersion = pinnedBridgeVersion(entry);
|
|
4121
|
+
return {
|
|
4122
|
+
entry,
|
|
4123
|
+
ours: isOurBridge(entry),
|
|
4124
|
+
carriesRole: entryCarriesRole(entry),
|
|
4125
|
+
...pinnedVersion ? { pinnedVersion } : {}
|
|
4126
|
+
};
|
|
4127
|
+
}
|
|
4128
|
+
function toExisting(raw) {
|
|
4129
|
+
if (typeof raw !== "object" || raw === null) {
|
|
4130
|
+
return void 0;
|
|
4131
|
+
}
|
|
4132
|
+
const { command, args, url } = raw;
|
|
4133
|
+
if (typeof url === "string" && url) {
|
|
4134
|
+
return { url };
|
|
4135
|
+
}
|
|
4136
|
+
if (typeof command === "string") {
|
|
4137
|
+
return { command, args: Array.isArray(args) ? args.filter((arg) => typeof arg === "string") : [] };
|
|
4138
|
+
}
|
|
4139
|
+
return void 0;
|
|
4140
|
+
}
|
|
4141
|
+
function sameEntry(entry, desired) {
|
|
4142
|
+
return "command" in entry && entry.command === desired.command && entry.args.length === desired.args.length && entry.args.every((arg, at) => arg === desired.args[at]);
|
|
4143
|
+
}
|
|
4144
|
+
async function writeKeepingMode(path, content) {
|
|
4145
|
+
await mkdir4(dirname5(path), { recursive: true });
|
|
4146
|
+
let mode;
|
|
4147
|
+
try {
|
|
4148
|
+
mode = (await stat4(path)).mode & 4095;
|
|
4149
|
+
} catch (error) {
|
|
4150
|
+
if (!isNodeError3(error, "ENOENT")) {
|
|
4151
|
+
throw error;
|
|
4152
|
+
}
|
|
4153
|
+
}
|
|
4154
|
+
const tmp = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
4155
|
+
await writeFile3(tmp, content, { encoding: "utf8", mode: mode ?? 384 });
|
|
4156
|
+
if (mode !== void 0) {
|
|
4157
|
+
await chmod3(tmp, mode);
|
|
4158
|
+
}
|
|
4159
|
+
await rename4(tmp, path);
|
|
4160
|
+
}
|
|
4161
|
+
async function readIfExists2(path) {
|
|
4162
|
+
try {
|
|
4163
|
+
return await readFile6(path, "utf8");
|
|
4164
|
+
} catch (error) {
|
|
4165
|
+
if (isNodeError3(error, "ENOENT")) {
|
|
4166
|
+
return void 0;
|
|
4167
|
+
}
|
|
4168
|
+
throw error;
|
|
4169
|
+
}
|
|
4170
|
+
}
|
|
4171
|
+
async function exists(path) {
|
|
4172
|
+
try {
|
|
4173
|
+
await stat4(path);
|
|
4174
|
+
return true;
|
|
4175
|
+
} catch {
|
|
4176
|
+
return false;
|
|
4177
|
+
}
|
|
4178
|
+
}
|
|
4179
|
+
function isNodeError3(error, code) {
|
|
4180
|
+
return typeof error === "object" && error !== null && error.code === code;
|
|
4181
|
+
}
|
|
4182
|
+
|
|
4183
|
+
// src/adapters/cli/setupCommand.ts
|
|
4184
|
+
async function executeSetupCommand(options) {
|
|
4185
|
+
const args = parseSetupArgs(options.argv);
|
|
4186
|
+
const commands = [];
|
|
4187
|
+
const context = { runner: recordingRunner(options.runner, commands), home: options.home };
|
|
4188
|
+
if (args.undo) {
|
|
4189
|
+
const states = await inspectAgents(context);
|
|
4190
|
+
const agents2 = [];
|
|
4191
|
+
for (const state of states) {
|
|
4192
|
+
agents2.push(await disconnectAgent(state, context, args.dryRun));
|
|
4193
|
+
}
|
|
4194
|
+
const failed2 = agents2.some((agent) => agent.status === "failed");
|
|
4195
|
+
options.io.stdout.write(renderSuccess({
|
|
4196
|
+
action: args.dryRun ? "setup --undo (dry-run)" : "setup --undo",
|
|
4197
|
+
agents: agents2,
|
|
4198
|
+
commands,
|
|
4199
|
+
notes: ["Your sign-in is kept \u2014 sign out with `ainecto auth logout`."]
|
|
4200
|
+
}, { json: options.json }));
|
|
4201
|
+
return failed2 ? 1 : 0;
|
|
4202
|
+
}
|
|
4203
|
+
let signedIn = Boolean(await options.auth.getAccessToken());
|
|
4204
|
+
if (!signedIn && !args.dryRun) {
|
|
4205
|
+
await options.auth.login();
|
|
4206
|
+
options.io.stderr.write("Signed in.\n");
|
|
4207
|
+
signedIn = true;
|
|
4208
|
+
}
|
|
4209
|
+
const account = signedIn ? await readAccount(options.client) : void 0;
|
|
4210
|
+
const desired = desiredEntry({ env: options.env, endpoint: options.endpoint });
|
|
4211
|
+
const agents = [];
|
|
4212
|
+
for (const state of await inspectAgents(context)) {
|
|
4213
|
+
agents.push(await connectAgent(state, desired, context, args.dryRun));
|
|
4214
|
+
}
|
|
4215
|
+
const failed = agents.some((agent) => agent.status === "failed") || account?.problem !== void 0;
|
|
4216
|
+
options.io.stdout.write(renderSuccess({
|
|
4217
|
+
action: args.dryRun ? "setup (dry-run)" : "setup",
|
|
4218
|
+
setup: failed ? "partial" : "complete",
|
|
4219
|
+
endpoint: options.endpoint,
|
|
4220
|
+
account: accountView(account, signedIn, options.endpoint),
|
|
4221
|
+
agents,
|
|
4222
|
+
commands,
|
|
4223
|
+
next: nextSteps(account, agents, options.endpoint)
|
|
4224
|
+
}, { json: options.json }));
|
|
4225
|
+
return failed ? 1 : 0;
|
|
4226
|
+
}
|
|
4227
|
+
function accountView(account, signedIn, endpoint) {
|
|
4228
|
+
if (!signedIn) {
|
|
4229
|
+
return { signedIn: false, note: "sign-in required \u2014 `ainecto setup` (without --dry-run) opens the browser once" };
|
|
4230
|
+
}
|
|
4231
|
+
const view = { signedIn: true };
|
|
4232
|
+
if (account?.userId) view.userId = account.userId;
|
|
4233
|
+
if (account?.displayName) view.name = account.displayName;
|
|
4234
|
+
if (account?.jobTitle !== void 0) {
|
|
4235
|
+
view.jobTitle = account.jobTitle ?? `not set \u2014 set it at ${profileSettingsUrl(endpoint)}`;
|
|
4236
|
+
}
|
|
4237
|
+
if (account?.projectCount !== void 0) view.projects = account.projectCount;
|
|
4238
|
+
if (account?.problem) view.problem = `${account.problem.kind}: ${account.problem.message}`;
|
|
4239
|
+
return view;
|
|
4240
|
+
}
|
|
4241
|
+
function nextSteps(account, agents, endpoint) {
|
|
4242
|
+
const next = [];
|
|
4243
|
+
if (agents.some((agent) => agent.status === "connected" || agent.status === "updated")) {
|
|
4244
|
+
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.");
|
|
4245
|
+
}
|
|
4246
|
+
if (account?.jobTitle === null) {
|
|
4247
|
+
next.push(`Set your AiNecto role (your job) at ${profileSettingsUrl(endpoint)} \u2014 AI uses it to pitch explanations; it does not change permissions.`);
|
|
4248
|
+
}
|
|
4249
|
+
next.push("To add AiNecto's working rules to a repository, run `ainecto init` from its root.");
|
|
4250
|
+
next.push("`ainecto doctor` shows anything still missing.");
|
|
4251
|
+
return next;
|
|
4252
|
+
}
|
|
4253
|
+
function parseSetupArgs(argv) {
|
|
4254
|
+
const args = { dryRun: false, undo: false };
|
|
4255
|
+
for (const arg of argv) {
|
|
4256
|
+
if (arg === "--dry-run") {
|
|
4257
|
+
args.dryRun = true;
|
|
4258
|
+
} else if (arg === "--undo") {
|
|
4259
|
+
args.undo = true;
|
|
4260
|
+
} else if (arg === "--json") {
|
|
4261
|
+
} else {
|
|
4262
|
+
throw new Error(`Unknown argument: ${arg}`);
|
|
4263
|
+
}
|
|
4264
|
+
}
|
|
4265
|
+
return args;
|
|
4266
|
+
}
|
|
4267
|
+
|
|
4268
|
+
// src/core/setup/doctorChecks.ts
|
|
4269
|
+
function doctorChecks(status) {
|
|
4270
|
+
const checks = [];
|
|
4271
|
+
const settings = profileSettingsUrl(status.endpoint);
|
|
4272
|
+
if (status.token.state === "not-signed-in") {
|
|
4273
|
+
checks.push({ item: "sign-in", level: "fail", detail: `not signed in to ${status.endpoint}`, fix: "ainecto setup" });
|
|
4274
|
+
} else if (status.token.state === "refresh-failed") {
|
|
4275
|
+
checks.push({ item: "sign-in", level: "fail", detail: `sign-in expired and could not be renewed (${status.token.reason})`, fix: "ainecto setup" });
|
|
4276
|
+
} else {
|
|
4277
|
+
checks.push({ item: "sign-in", level: "ok", detail: status.token.state === "env" ? "AINECTO_TOKEN" : "signed in" });
|
|
4278
|
+
}
|
|
4279
|
+
const problem = status.account?.problem;
|
|
4280
|
+
if (problem?.kind === "network") {
|
|
4281
|
+
checks.push({ item: "server", level: "fail", detail: `cannot reach ${status.endpoint} (${problem.message})`, fix: "check the network, or the --endpoint you passed" });
|
|
4282
|
+
} else if (problem?.kind === "unauthorized") {
|
|
4283
|
+
checks.push({ item: "server", level: "fail", detail: `${status.endpoint} refused the sign-in (HTTP 401)`, fix: "ainecto auth login" });
|
|
4284
|
+
} else if (problem?.kind === "forbidden") {
|
|
4285
|
+
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}` });
|
|
4286
|
+
} else if (problem) {
|
|
4287
|
+
checks.push({ item: "server", level: "fail", detail: problem.message });
|
|
4288
|
+
} else if (status.account) {
|
|
4289
|
+
checks.push({ item: "server", level: "ok", detail: `${status.endpoint} answered; ${status.account.projectCount ?? 0} project(s) visible` });
|
|
4290
|
+
}
|
|
4291
|
+
for (const agent of status.agents) {
|
|
4292
|
+
checks.push(agentCheck(agent, status.cliVersion));
|
|
4293
|
+
}
|
|
4294
|
+
if (status.claudeHere?.entry && status.claudeHere.scope === "local") {
|
|
4295
|
+
const hereOursUpToDate = status.claudeHere.ours && !status.claudeHere.carriesRole;
|
|
4296
|
+
if (!hereOursUpToDate) {
|
|
4297
|
+
checks.push({
|
|
4298
|
+
item: "Claude Code (this folder)",
|
|
4299
|
+
level: "warn",
|
|
4300
|
+
detail: `a local-scope "${SERVER_NAME}" entry in this folder hides the user-scope connection`,
|
|
4301
|
+
fix: `claude mcp remove ${SERVER_NAME} -s local`
|
|
4302
|
+
});
|
|
4303
|
+
}
|
|
4304
|
+
}
|
|
4305
|
+
const repo = status.repository;
|
|
4306
|
+
if (repo?.problem) {
|
|
4307
|
+
checks.push({ item: "repository", level: "warn", detail: repo.problem });
|
|
4308
|
+
} else if (repo) {
|
|
4309
|
+
const leftovers = [
|
|
4310
|
+
...repo.mcpFiles.filter((file) => file.ours && file.carriesRole).map((file) => `${file.path} passes the removed --role`),
|
|
4311
|
+
...repo.mcpFiles.filter((file) => file.ours && olderThan(file.pinnedVersion, status.cliVersion)).map((file) => `${file.path} pins @ainecto/mcp@${file.pinnedVersion}`),
|
|
4312
|
+
...repo.configHasProject ? [".ainecto/config.json still has the old project field"] : []
|
|
4313
|
+
];
|
|
4314
|
+
if (leftovers.length > 0) {
|
|
4315
|
+
checks.push({ item: "repository", level: "warn", detail: leftovers.join("; "), fix: `ainecto init (in ${repo.root})` });
|
|
4316
|
+
} else {
|
|
4317
|
+
checks.push({
|
|
4318
|
+
item: "repository",
|
|
4319
|
+
level: "ok",
|
|
4320
|
+
detail: repo.harnessDoc ? `AiNecto rules are set up in ${repo.root}` : `no AiNecto rules in ${repo.root} (optional \u2014 \`ainecto init\` adds them)`
|
|
4321
|
+
});
|
|
4322
|
+
}
|
|
4323
|
+
}
|
|
4324
|
+
for (const name of status.legacyEnv) {
|
|
4325
|
+
checks.push({ item: name, level: "warn", detail: `${name} is set; roles were removed and it is ignored`, fix: `unset ${name} (it can be deleted)` });
|
|
4326
|
+
}
|
|
4327
|
+
if (status.legacyCodexProfiles.length > 0) {
|
|
4328
|
+
checks.push({
|
|
4329
|
+
item: "Codex role profiles",
|
|
4330
|
+
level: "warn",
|
|
4331
|
+
detail: `these Codex profiles still pass --role: ${status.legacyCodexProfiles.join(", ")}`,
|
|
4332
|
+
fix: "delete them \u2014 `ainecto setup` connects Codex for every session"
|
|
4333
|
+
});
|
|
4334
|
+
}
|
|
4335
|
+
if (status.globalCliVersion && olderThan(status.globalCliVersion, status.cliVersion)) {
|
|
4336
|
+
checks.push({
|
|
4337
|
+
item: "global ainecto",
|
|
4338
|
+
level: "warn",
|
|
4339
|
+
detail: `the ainecto on your PATH is ${status.globalCliVersion}, older than ${status.cliVersion}`,
|
|
4340
|
+
fix: "npm i -g ainecto@latest"
|
|
4341
|
+
});
|
|
4342
|
+
}
|
|
4343
|
+
if (status.account?.jobTitle === null) {
|
|
4344
|
+
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}` });
|
|
4345
|
+
} else if (typeof status.account?.jobTitle === "string") {
|
|
4346
|
+
checks.push({ item: "AiNecto job title", level: "ok", detail: status.account.jobTitle });
|
|
4347
|
+
}
|
|
4348
|
+
return checks;
|
|
4349
|
+
}
|
|
4350
|
+
function agentCheck(agent, cliVersion) {
|
|
4351
|
+
const item = AGENT_CLIENT_LABELS[agent.client];
|
|
4352
|
+
if (!agent.installed) {
|
|
4353
|
+
return { item, level: "ok", detail: "not installed" };
|
|
4354
|
+
}
|
|
4355
|
+
if (agent.problem) {
|
|
4356
|
+
return { item, level: "fail", detail: agent.problem, fix: "ainecto setup" };
|
|
4357
|
+
}
|
|
4358
|
+
if (!agent.entry) {
|
|
4359
|
+
return { item, level: "warn", detail: "not connected to AiNecto", fix: "ainecto setup" };
|
|
4360
|
+
}
|
|
4361
|
+
if (!agent.ours) {
|
|
4362
|
+
return {
|
|
4363
|
+
item,
|
|
4364
|
+
level: "warn",
|
|
4365
|
+
detail: `someone else's "${SERVER_NAME}" entry${agent.scope ? ` (${agent.scope})` : ""} \u2014 AiNecto setup leaves it alone`,
|
|
4366
|
+
fix: agent.client === "claude-code" ? `claude mcp get ${SERVER_NAME}` : "keep it, or remove it and run `ainecto setup`"
|
|
4367
|
+
};
|
|
4368
|
+
}
|
|
4369
|
+
if (agent.client === "claude-code" && agent.scope !== "user") {
|
|
4370
|
+
return { item, level: "warn", detail: `connected in the ${agent.scope} scope, not for every project`, fix: "ainecto setup" };
|
|
4371
|
+
}
|
|
4372
|
+
if (agent.carriesRole) {
|
|
4373
|
+
return { item, level: "warn", detail: "connected, but still passes the removed --role", fix: "ainecto setup" };
|
|
4374
|
+
}
|
|
4375
|
+
if (olderThan(agent.pinnedVersion, cliVersion)) {
|
|
4376
|
+
return { item, level: "warn", detail: `connected with @ainecto/mcp@${agent.pinnedVersion}; ${cliVersion} is available`, fix: "ainecto setup" };
|
|
4377
|
+
}
|
|
4378
|
+
return { item, level: "ok", detail: agent.pinnedVersion ? `connected (@ainecto/mcp@${agent.pinnedVersion})` : "connected" };
|
|
4379
|
+
}
|
|
4380
|
+
function olderThan(a, b) {
|
|
4381
|
+
const left = parseVersion(a);
|
|
4382
|
+
const right = parseVersion(b);
|
|
4383
|
+
if (!left || !right) {
|
|
4384
|
+
return false;
|
|
4385
|
+
}
|
|
4386
|
+
for (let at = 0; at < 3; at += 1) {
|
|
4387
|
+
if (left[at] !== right[at]) {
|
|
4388
|
+
return left[at] < right[at];
|
|
4389
|
+
}
|
|
4390
|
+
}
|
|
4391
|
+
return false;
|
|
4392
|
+
}
|
|
4393
|
+
function parseVersion(value) {
|
|
4394
|
+
const match = value?.match(/^(\d+)\.(\d+)\.(\d+)/);
|
|
4395
|
+
return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : void 0;
|
|
4396
|
+
}
|
|
4397
|
+
|
|
4398
|
+
// src/core/setup/machineStatus.ts
|
|
4399
|
+
import { readdir, readFile as readFile7 } from "fs/promises";
|
|
4400
|
+
import { join as join5 } from "path";
|
|
4401
|
+
async function collectStatus(input) {
|
|
4402
|
+
const token = await tokenStatus(input);
|
|
4403
|
+
const usable = token.state === "env" || token.state === "signed-in";
|
|
4404
|
+
const status = {
|
|
4405
|
+
cliVersion: input.cliVersion,
|
|
4406
|
+
endpoint: input.endpoint,
|
|
4407
|
+
env: input.env,
|
|
4408
|
+
token,
|
|
4409
|
+
agents: await inspectAgents({ runner: input.runner, home: input.home }),
|
|
4410
|
+
legacyEnv: [LEGACY_ROLE_ENV_VAR, "AI_ERD_ROLE"].filter((name) => input.envVars[name]?.trim()),
|
|
4411
|
+
legacyCodexProfiles: await legacyCodexProfiles(input.envVars.CODEX_HOME ?? join5(input.home, ".codex"))
|
|
4412
|
+
};
|
|
4413
|
+
if (usable) {
|
|
4414
|
+
status.account = await readAccount(input.client);
|
|
4415
|
+
}
|
|
4416
|
+
if (input.hereRunner && input.cwd !== input.home) {
|
|
4417
|
+
status.claudeHere = await inspectClaude({ runner: input.hereRunner, home: input.home });
|
|
4418
|
+
}
|
|
4419
|
+
status.repository = await repositoryStatus(input.cwd);
|
|
4420
|
+
const globalVersion = await globalCliVersion(input.runner);
|
|
4421
|
+
if (globalVersion) {
|
|
4422
|
+
status.globalCliVersion = globalVersion;
|
|
4423
|
+
}
|
|
4424
|
+
return status;
|
|
4425
|
+
}
|
|
4426
|
+
async function tokenStatus(input) {
|
|
4427
|
+
if (input.envVars.AINECTO_TOKEN) {
|
|
4428
|
+
return { state: "env" };
|
|
4429
|
+
}
|
|
4430
|
+
const stored = await input.tokenStore.load(input.endpoint);
|
|
4431
|
+
const expiresAt = stored?.expiresAt ? new Date(stored.expiresAt).toISOString() : void 0;
|
|
4432
|
+
try {
|
|
4433
|
+
const token = await input.auth.getAccessToken();
|
|
4434
|
+
if (token) {
|
|
4435
|
+
return { state: "signed-in", ...expiresAt ? { expiresAt } : {} };
|
|
4436
|
+
}
|
|
4437
|
+
return stored ? { state: "refresh-failed", ...expiresAt ? { expiresAt } : {}, reason: "the stored sign-in expired and has no refresh token" } : { state: "not-signed-in" };
|
|
4438
|
+
} catch (error) {
|
|
4439
|
+
return {
|
|
4440
|
+
state: "refresh-failed",
|
|
4441
|
+
...expiresAt ? { expiresAt } : {},
|
|
4442
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
4443
|
+
};
|
|
4444
|
+
}
|
|
4445
|
+
}
|
|
4446
|
+
async function repositoryStatus(cwd) {
|
|
4447
|
+
const gitRoot = await findGitRoot(cwd);
|
|
4448
|
+
const root = gitRoot ?? cwd;
|
|
4449
|
+
const status = { root, git: gitRoot !== void 0, harnessDoc: false, configHasProject: false, mcpFiles: [] };
|
|
4450
|
+
try {
|
|
4451
|
+
status.harnessDoc = await readManagedFile(root, HARNESS_DOC_PATH) !== void 0;
|
|
4452
|
+
const config = parseObject(await readManagedFile(root, HARNESS_CONFIG_PATH));
|
|
4453
|
+
if (config) {
|
|
4454
|
+
if (typeof config.version === "number") status.configVersion = config.version;
|
|
4455
|
+
status.configHasProject = Object.prototype.hasOwnProperty.call(config, "project");
|
|
4456
|
+
const versions = config.promptVersions;
|
|
4457
|
+
if (typeof versions?.[DOC_KEY] === "string") status.harnessDocVersion = versions[DOC_KEY];
|
|
4458
|
+
}
|
|
4459
|
+
for (const target of AGENT_TARGETS) {
|
|
4460
|
+
const entry = readServerEntry(await readManagedFile(root, target.path));
|
|
4461
|
+
if (entry !== void 0) {
|
|
4462
|
+
const pinnedVersion = pinnedBridgeVersion(entry);
|
|
4463
|
+
status.mcpFiles.push({
|
|
4464
|
+
path: target.path,
|
|
4465
|
+
ours: isOurBridge(entry),
|
|
4466
|
+
carriesRole: entryCarriesRole(entry),
|
|
4467
|
+
...pinnedVersion ? { pinnedVersion } : {}
|
|
4468
|
+
});
|
|
4469
|
+
}
|
|
4470
|
+
}
|
|
4471
|
+
} catch (error) {
|
|
4472
|
+
status.problem = error instanceof Error ? error.message : String(error);
|
|
4473
|
+
}
|
|
4474
|
+
return status;
|
|
4475
|
+
}
|
|
4476
|
+
async function legacyCodexProfiles(codexHome) {
|
|
4477
|
+
let names;
|
|
4478
|
+
try {
|
|
4479
|
+
names = await readdir(codexHome);
|
|
4480
|
+
} catch {
|
|
4481
|
+
return [];
|
|
4482
|
+
}
|
|
4483
|
+
const found = [];
|
|
4484
|
+
for (const name of names.filter((file) => file.endsWith(".config.toml"))) {
|
|
4485
|
+
const content = await readFile7(join5(codexHome, name), "utf8").catch(() => "");
|
|
4486
|
+
if (content.includes(PACKAGE_NAME) && content.includes("--role")) {
|
|
4487
|
+
found.push(join5(codexHome, name));
|
|
4488
|
+
}
|
|
4489
|
+
}
|
|
4490
|
+
return found;
|
|
4491
|
+
}
|
|
4492
|
+
async function globalCliVersion(runner) {
|
|
4493
|
+
const result = await runner("ainecto", ["--version", "--json"]);
|
|
4494
|
+
if (result.missing || result.exitCode !== 0) {
|
|
4495
|
+
return void 0;
|
|
4496
|
+
}
|
|
4497
|
+
try {
|
|
4498
|
+
const version = JSON.parse(result.stdout).data?.version;
|
|
4499
|
+
return typeof version === "string" ? version : void 0;
|
|
4500
|
+
} catch {
|
|
4501
|
+
return void 0;
|
|
4502
|
+
}
|
|
4503
|
+
}
|
|
4504
|
+
function parseObject(content) {
|
|
4505
|
+
if (content === void 0) {
|
|
4506
|
+
return void 0;
|
|
4507
|
+
}
|
|
4508
|
+
try {
|
|
4509
|
+
const parsed = JSON.parse(content);
|
|
4510
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
|
|
4511
|
+
} catch {
|
|
4512
|
+
return void 0;
|
|
4513
|
+
}
|
|
4514
|
+
}
|
|
4515
|
+
|
|
4516
|
+
// src/adapters/cli/statusCommand.ts
|
|
4517
|
+
async function executeStatusCommand(options) {
|
|
4518
|
+
assertNoArgs("status", options.argv);
|
|
4519
|
+
const status = await collectStatus(options.collect);
|
|
4520
|
+
options.io.stdout.write(renderSuccess(statusView(status), { json: options.json }));
|
|
4521
|
+
return 0;
|
|
4522
|
+
}
|
|
4523
|
+
async function executeDoctorCommand(options) {
|
|
4524
|
+
assertNoArgs("doctor", options.argv);
|
|
4525
|
+
const status = await collectStatus(options.collect);
|
|
4526
|
+
const checks = doctorChecks(status);
|
|
4527
|
+
const failed = checks.filter((check) => check.level === "fail").length;
|
|
4528
|
+
const warned = checks.filter((check) => check.level === "warn").length;
|
|
4529
|
+
options.io.stdout.write(renderSuccess({
|
|
4530
|
+
summary: failed > 0 ? `${failed} problem(s) to fix` : warned > 0 ? `working \u2014 ${warned} suggestion(s)` : "all good",
|
|
4531
|
+
checks
|
|
4532
|
+
}, { json: options.json }));
|
|
4533
|
+
return failed > 0 ? 1 : 0;
|
|
4534
|
+
}
|
|
4535
|
+
function statusView(status) {
|
|
4536
|
+
const signedIn = status.token.state === "env" || status.token.state === "signed-in";
|
|
4537
|
+
return {
|
|
4538
|
+
cli: status.cliVersion,
|
|
4539
|
+
endpoint: status.endpoint,
|
|
4540
|
+
env: status.env,
|
|
4541
|
+
signIn: status.token,
|
|
4542
|
+
account: accountView(status.account, signedIn, status.endpoint),
|
|
4543
|
+
agents: status.agents.map((agent) => ({
|
|
4544
|
+
client: AGENT_CLIENT_LABELS[agent.client],
|
|
4545
|
+
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})` : ""}`,
|
|
4546
|
+
...agent.pinnedVersion ? { bridge: `@ainecto/mcp@${agent.pinnedVersion}` } : {},
|
|
4547
|
+
...agent.carriesRole ? { legacyRole: true } : {}
|
|
4548
|
+
})),
|
|
4549
|
+
...status.claudeHere?.entry ? { claudeCodeHere: { scope: status.claudeHere.scope, ours: status.claudeHere.ours } } : {},
|
|
4550
|
+
repository: status.repository,
|
|
4551
|
+
...status.globalCliVersion ? { globalAinecto: status.globalCliVersion } : {}
|
|
4552
|
+
};
|
|
4553
|
+
}
|
|
4554
|
+
function assertNoArgs(command, argv) {
|
|
4555
|
+
const unknown = argv.filter((arg) => arg !== "--json");
|
|
4556
|
+
if (unknown.length > 0) {
|
|
4557
|
+
throw new Error(`Unknown argument for ${command}: ${unknown[0]}`);
|
|
4558
|
+
}
|
|
4309
4559
|
}
|
|
4310
4560
|
|
|
4311
4561
|
// src/adapters/cli/ainectoCli.ts
|
|
4562
|
+
import { homedir as homedir2 } from "os";
|
|
4312
4563
|
async function runAinectoCli(argv, io) {
|
|
4313
4564
|
let parsed = { json: argv.includes("--json"), help: false, version: false, positionals: [] };
|
|
4314
4565
|
try {
|
|
@@ -4322,15 +4573,21 @@ async function runAinectoCli(argv, io) {
|
|
|
4322
4573
|
return 0;
|
|
4323
4574
|
}
|
|
4324
4575
|
const resolved = resolveEndpoint({ env: parsed.env, endpoint: parsed.endpoint });
|
|
4325
|
-
const
|
|
4576
|
+
const [domain, action, ...rest] = parsed.positionals;
|
|
4577
|
+
const legacyRole = legacyRoleInput(parsed.legacyRole);
|
|
4578
|
+
if (legacyRole !== void 0 && domain !== "mcp") {
|
|
4579
|
+
io.stderr.write(parsed.json ? `${JSON.stringify({ event: "legacy_role_ignored", message: LEGACY_ROLE_NOTICE })}
|
|
4580
|
+
` : `${LEGACY_ROLE_NOTICE}
|
|
4581
|
+
`);
|
|
4582
|
+
}
|
|
4326
4583
|
const tokenStore = new FileTokenStore();
|
|
4327
|
-
const connect = (
|
|
4584
|
+
const connect = () => {
|
|
4328
4585
|
const auth = new OAuthClient({
|
|
4329
4586
|
endpoint: resolved.endpoint,
|
|
4330
4587
|
tokenStore,
|
|
4331
|
-
// ★init --dry-run 은 토큰을 갱신하지도 않는다 — 「아무것도 바꾸지 않는다」에 로그인 상태도 든다.
|
|
4332
|
-
// (
|
|
4333
|
-
readOnly:
|
|
4588
|
+
// ★init·setup --dry-run 은 토큰을 갱신하지도 않는다 — 「아무것도 바꾸지 않는다」에 로그인 상태도 든다.
|
|
4589
|
+
// (하위 명령의 플래그는 positional 로 흘러온다.)
|
|
4590
|
+
readOnly: (domain === "init" || domain === "setup") && parsed.positionals.includes("--dry-run"),
|
|
4334
4591
|
// ★URL 을 «브라우저를 열기 전에» 알린다. stdout 은 결과 전용이라 stderr 에 쓴다.
|
|
4335
4592
|
// --json 이면 JSON 한 줄이다(설계 §21-3 (가)). 로그인이 역할과 무관해져 role 칸은 없다(0.4.2 §2-5).
|
|
4336
4593
|
onAuthorizeUrl: (url) => {
|
|
@@ -4340,15 +4597,13 @@ async function runAinectoCli(argv, io) {
|
|
|
4340
4597
|
`);
|
|
4341
4598
|
}
|
|
4342
4599
|
});
|
|
4343
|
-
const client2 = new McpRpcClient({ endpoint: resolved.endpoint, tokenProvider: auth,
|
|
4600
|
+
const client2 = new McpRpcClient({ endpoint: resolved.endpoint, tokenProvider: auth, legacyRole, channel: "cli" });
|
|
4344
4601
|
return { auth, client: client2 };
|
|
4345
4602
|
};
|
|
4346
|
-
const [domain, action, ...rest] = parsed.positionals;
|
|
4347
4603
|
if (domain === "mcp") {
|
|
4348
4604
|
await runConnector({
|
|
4349
4605
|
endpoint: resolved.endpoint,
|
|
4350
|
-
|
|
4351
|
-
role: explicitRole().role ?? null,
|
|
4606
|
+
legacyRoleFlag: parsed.legacyRole,
|
|
4352
4607
|
input: io.stdin,
|
|
4353
4608
|
output: io.stdout,
|
|
4354
4609
|
errorOutput: io.stderr
|
|
@@ -4356,36 +4611,27 @@ async function runAinectoCli(argv, io) {
|
|
|
4356
4611
|
return 0;
|
|
4357
4612
|
}
|
|
4358
4613
|
if (domain === "init") {
|
|
4614
|
+
const { auth } = connect();
|
|
4359
4615
|
return await executeInitCommand({
|
|
4360
4616
|
argv: parsed.positionals.slice(1),
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
|
|
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();
|
|
4617
|
+
// 문서는 인증 후에 받는다 — 토큰이 없으면 패키지 기본값으로 가고, 그 사실을 보고한다.
|
|
4618
|
+
// ★«필요할 때» 가져온다. --undo 는 로컬 작업이라 토큰을 건드리지 않는다(S1).
|
|
4619
|
+
// ★갱신 실패는 «토큰 없음»(→ 로그인)이지만, AINECTO_TOKEN 을 못 쓰는 주소라는 거절은 삼키지 않는다 —
|
|
4620
|
+
// 삼키면 그 설정 오류가 브라우저 로그인으로 둔갑한다.
|
|
4621
|
+
accessToken: async () => {
|
|
4622
|
+
try {
|
|
4623
|
+
return await auth.getAccessToken();
|
|
4624
|
+
} catch (error) {
|
|
4625
|
+
if (process.env.AINECTO_TOKEN) {
|
|
4626
|
+
throw error;
|
|
4384
4627
|
}
|
|
4385
|
-
|
|
4628
|
+
return void 0;
|
|
4629
|
+
}
|
|
4630
|
+
},
|
|
4631
|
+
// 로그인한다 — `ainecto auth login` 과 같은 함수다. 서버마다 한 번이다.
|
|
4632
|
+
login: async () => {
|
|
4633
|
+
await auth.login();
|
|
4386
4634
|
},
|
|
4387
|
-
// ★대화형일 때만 묻는다(판정은 isInteractive 한 곳). 에이전트·CI 는 TTY 가 아니다.
|
|
4388
|
-
prompter: isInteractive(io, parsed.json) ? createReadlinePrompter(io) : void 0,
|
|
4389
4635
|
endpoint: resolved.endpoint,
|
|
4390
4636
|
env: resolved.env,
|
|
4391
4637
|
cliVersion: `${PACKAGE_NAME} ${CLI_VERSION}`,
|
|
@@ -4394,23 +4640,54 @@ async function runAinectoCli(argv, io) {
|
|
|
4394
4640
|
io
|
|
4395
4641
|
});
|
|
4396
4642
|
}
|
|
4643
|
+
if (domain === "setup") {
|
|
4644
|
+
const { auth, client: client2 } = connect();
|
|
4645
|
+
return await executeSetupCommand({
|
|
4646
|
+
argv: parsed.positionals.slice(1),
|
|
4647
|
+
endpoint: resolved.endpoint,
|
|
4648
|
+
env: resolved.env,
|
|
4649
|
+
auth,
|
|
4650
|
+
client: client2,
|
|
4651
|
+
// ★외부 CLI 는 홈 폴더에서 돈다 — 지금 저장소의 local·project 범위 항목이 사용자 범위 판정에 끼지 않게.
|
|
4652
|
+
runner: systemCommandRunner({ cwd: homedir2() }),
|
|
4653
|
+
home: homedir2(),
|
|
4654
|
+
json: parsed.json,
|
|
4655
|
+
io
|
|
4656
|
+
});
|
|
4657
|
+
}
|
|
4658
|
+
if (domain === "status" || domain === "doctor") {
|
|
4659
|
+
const { auth, client: client2 } = connect();
|
|
4660
|
+
const run = domain === "status" ? executeStatusCommand : executeDoctorCommand;
|
|
4661
|
+
return await run({
|
|
4662
|
+
argv: parsed.positionals.slice(1),
|
|
4663
|
+
collect: {
|
|
4664
|
+
endpoint: resolved.endpoint,
|
|
4665
|
+
env: resolved.env,
|
|
4666
|
+
cliVersion: CLI_VERSION,
|
|
4667
|
+
tokenStore,
|
|
4668
|
+
auth,
|
|
4669
|
+
client: client2,
|
|
4670
|
+
runner: systemCommandRunner({ cwd: homedir2() }),
|
|
4671
|
+
hereRunner: systemCommandRunner({ cwd: process.cwd() }),
|
|
4672
|
+
home: homedir2(),
|
|
4673
|
+
cwd: process.cwd(),
|
|
4674
|
+
envVars: process.env
|
|
4675
|
+
},
|
|
4676
|
+
json: parsed.json,
|
|
4677
|
+
io
|
|
4678
|
+
});
|
|
4679
|
+
}
|
|
4397
4680
|
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);
|
|
4681
|
+
return await handleAuth(action, connect().auth, resolved.endpoint, parsed.json, io);
|
|
4402
4682
|
}
|
|
4403
4683
|
const env = parsed.env ?? "prod";
|
|
4404
4684
|
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
|
-
});
|
|
4685
|
+
return await printCatalog(parsed, io, resolved.endpoint, async () => connect().client);
|
|
4409
4686
|
}
|
|
4410
4687
|
let routeTools = getGeneratedTools(env);
|
|
4411
4688
|
let remote = isAttachmentsUploadCommand(domain, action) || isInboxPollCommand(domain, action) || domain === "tools" && (action === "list" || action === "call") || matchGeneratedCommand(routeTools, parsed.positionals) !== void 0;
|
|
4412
4689
|
if (!remote) {
|
|
4413
|
-
const live = await loadLiveTools(env, connect(
|
|
4690
|
+
const live = await loadLiveTools(env, connect().client).catch(() => void 0);
|
|
4414
4691
|
if (live && matchGeneratedCommand(live, parsed.positionals)) {
|
|
4415
4692
|
routeTools = live;
|
|
4416
4693
|
remote = true;
|
|
@@ -4419,8 +4696,7 @@ async function runAinectoCli(argv, io) {
|
|
|
4419
4696
|
if (!remote) {
|
|
4420
4697
|
throw new Error(domain === "tools" ? "Expected tools list, tools call <mcpName>, or tools catalog." : `Unknown command "${domain}".`);
|
|
4421
4698
|
}
|
|
4422
|
-
const
|
|
4423
|
-
const { client } = connect(await commandRole(explicit, process.cwd()));
|
|
4699
|
+
const { client } = connect();
|
|
4424
4700
|
if (isAttachmentsUploadCommand(domain, action)) {
|
|
4425
4701
|
return await executeAttachmentsUploadCommand({
|
|
4426
4702
|
argv: rest,
|
|
@@ -4451,23 +4727,6 @@ async function runAinectoCli(argv, io) {
|
|
|
4451
4727
|
return 1;
|
|
4452
4728
|
}
|
|
4453
4729
|
}
|
|
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
4730
|
async function handleAuth(action, auth, endpoint, json, io) {
|
|
4472
4731
|
if (action === "login") {
|
|
4473
4732
|
await auth.login();
|
|
@@ -4532,11 +4791,15 @@ function parseGlobalArgs(argv) {
|
|
|
4532
4791
|
continue;
|
|
4533
4792
|
}
|
|
4534
4793
|
if (arg === "--env") {
|
|
4535
|
-
result.env = assertEnv(
|
|
4794
|
+
result.env = assertEnv(requireValue3(arg, argv[++index]));
|
|
4536
4795
|
} else if (arg === "--endpoint") {
|
|
4537
|
-
result.endpoint =
|
|
4796
|
+
result.endpoint = requireValue3(arg, argv[++index]);
|
|
4538
4797
|
} else if (arg === "--role") {
|
|
4539
|
-
|
|
4798
|
+
const value = argv[index + 1];
|
|
4799
|
+
if (value !== void 0 && !value.startsWith("--")) {
|
|
4800
|
+
result.legacyRole = value;
|
|
4801
|
+
index += 1;
|
|
4802
|
+
}
|
|
4540
4803
|
} else if (arg === "--json") {
|
|
4541
4804
|
result.json = true;
|
|
4542
4805
|
} else if (arg === "--help" || arg === "-h") {
|
|
@@ -4558,7 +4821,7 @@ function parsePayloadArgs(argv) {
|
|
|
4558
4821
|
continue;
|
|
4559
4822
|
}
|
|
4560
4823
|
if (arg === "--file" || arg === "-f") {
|
|
4561
|
-
result.file =
|
|
4824
|
+
result.file = requireValue3(arg, argv[++index]);
|
|
4562
4825
|
} else {
|
|
4563
4826
|
inline.push(arg);
|
|
4564
4827
|
}
|
|
@@ -4568,28 +4831,26 @@ function parsePayloadArgs(argv) {
|
|
|
4568
4831
|
}
|
|
4569
4832
|
return result;
|
|
4570
4833
|
}
|
|
4571
|
-
function
|
|
4834
|
+
function requireValue3(flag, value) {
|
|
4572
4835
|
if (!value || value.startsWith("--")) {
|
|
4573
4836
|
throw new Error(`${flag} requires a value.`);
|
|
4574
4837
|
}
|
|
4575
4838
|
return value;
|
|
4576
4839
|
}
|
|
4577
4840
|
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."
|
|
4841
|
+
return { next: NEXT_STEP };
|
|
4842
|
+
}
|
|
4843
|
+
var NEXT_STEP = [
|
|
4844
|
+
"Connect your coding agents to AiNecto once per machine: `npx -y ainecto@latest setup`.",
|
|
4845
|
+
"AI agents: tell the user this now, even if they only asked to install, update, or sign in.",
|
|
4846
|
+
"To add AiNecto's working rules to a repository, run `npx -y ainecto@latest init` from its root.",
|
|
4847
|
+
"`npx -y ainecto@latest doctor` shows what is still missing and the command that fixes it."
|
|
4587
4848
|
].join("\n");
|
|
4588
4849
|
function helpText() {
|
|
4589
4850
|
return [
|
|
4590
4851
|
"AiNecto CLI",
|
|
4591
4852
|
"",
|
|
4592
|
-
|
|
4853
|
+
NEXT_STEP,
|
|
4593
4854
|
"",
|
|
4594
4855
|
"Usage:",
|
|
4595
4856
|
" ainecto auth login|status|logout [--env prod|dev] [--json] (one sign-in per machine)",
|
|
@@ -4602,11 +4863,16 @@ function helpText() {
|
|
|
4602
4863
|
" --workspace and --project may repeat: any of the given workspaces AND any of the given projects",
|
|
4603
4864
|
" poll exit codes: 0 new items \xB7 3 nothing new \xB7 1 error \xB7 2 usage error",
|
|
4604
4865
|
" ainecto <generated-command> [flags] [-f payload.json] [--yes] [--json]",
|
|
4605
|
-
" ainecto mcp [--env prod|dev] [--endpoint URL]
|
|
4866
|
+
" ainecto mcp [--env prod|dev] [--endpoint URL]",
|
|
4867
|
+
"",
|
|
4868
|
+
"Setup (once per machine):",
|
|
4869
|
+
" ainecto setup [--env prod|dev] [--endpoint URL] [--dry-run] [--json] sign in and connect Claude Code, Cursor, and Codex",
|
|
4870
|
+
" ainecto setup --undo remove those connections (sign-in stays)",
|
|
4871
|
+
" ainecto status [--json] who is signed in, and what is connected (read-only)",
|
|
4872
|
+
" ainecto doctor [--json] status + what is missing and the command that fixes it",
|
|
4606
4873
|
"",
|
|
4607
4874
|
"Harness:",
|
|
4608
|
-
" ainecto init [--
|
|
4609
|
-
" In a terminal, asks for the role and project when they are not given.",
|
|
4875
|
+
" ainecto init [--mcp] [--dry-run] [--json] AiNecto rules for this repository (--mcp also writes .mcp.json and .cursor/mcp.json)",
|
|
4610
4876
|
" ainecto init --undo",
|
|
4611
4877
|
""
|
|
4612
4878
|
].join("\n");
|