ainecto 0.5.0 → 0.7.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 +46 -70
- package/dist/bin/ainecto.js +410 -361
- package/dist/bin/ainecto.js.map +1 -1
- package/dist/bin/mcp.js +10 -45
- package/dist/bin/mcp.js.map +1 -1
- package/package.json +2 -4
package/dist/bin/ainecto.js
CHANGED
|
@@ -8,20 +8,17 @@ import { dirname, join } from "path";
|
|
|
8
8
|
|
|
9
9
|
// src/core/harness/role.ts
|
|
10
10
|
var HARNESS_ROLES = ["design", "development", "test", "validation"];
|
|
11
|
-
var ROLE_SCOPE_PREFIX = "
|
|
12
|
-
var ROLE_HEADER = "X-
|
|
11
|
+
var ROLE_SCOPE_PREFIX = "ainecto:role:";
|
|
12
|
+
var ROLE_HEADER = "X-AiNecto-Role";
|
|
13
13
|
var ROLE_ENV_VAR = "AINECTO_ROLE";
|
|
14
|
-
var LEGACY_ROLE_ENV_VAR = "AI_ERD_ROLE";
|
|
15
14
|
function resolveRole(input = {}) {
|
|
16
15
|
if (input.role !== void 0) {
|
|
17
16
|
return { role: assertRole(input.role), source: "flag" };
|
|
18
17
|
}
|
|
19
18
|
const envVars = input.envVars ?? process.env;
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
return { role: assertRole(fromEnv), source: "env", envVar };
|
|
24
|
-
}
|
|
19
|
+
const fromEnv = envVars[ROLE_ENV_VAR];
|
|
20
|
+
if (fromEnv && fromEnv.trim()) {
|
|
21
|
+
return { role: assertRole(fromEnv), source: "env" };
|
|
25
22
|
}
|
|
26
23
|
return { source: "none" };
|
|
27
24
|
}
|
|
@@ -124,19 +121,8 @@ import { URLSearchParams } from "url";
|
|
|
124
121
|
// src/core/config/endpoints.ts
|
|
125
122
|
var DEFAULT_ENDPOINTS = {
|
|
126
123
|
prod: "https://ainecto.com/mcp",
|
|
127
|
-
dev: "https://dev.
|
|
128
|
-
};
|
|
129
|
-
var LEGACY_PROD_ENDPOINT = "https://ai-erd.com/mcp";
|
|
130
|
-
var LEGACY_DEFAULT_ENDPOINTS = [
|
|
131
|
-
LEGACY_PROD_ENDPOINT,
|
|
132
|
-
"https://dev.ainecto.com/mcp"
|
|
133
|
-
];
|
|
134
|
-
var TOKEN_SLOT_FALLBACKS = {
|
|
135
|
-
[DEFAULT_ENDPOINTS.prod]: [LEGACY_PROD_ENDPOINT]
|
|
124
|
+
dev: "https://dev.ainecto.com/mcp"
|
|
136
125
|
};
|
|
137
|
-
function fallbackTokenEndpointsFor(endpoint) {
|
|
138
|
-
return TOKEN_SLOT_FALLBACKS[normalizeEndpoint(endpoint)] ?? [];
|
|
139
|
-
}
|
|
140
126
|
function resolveEndpoint(input = {}) {
|
|
141
127
|
const envVars = input.envVars ?? process.env;
|
|
142
128
|
const env = input.env ?? "prod";
|
|
@@ -159,7 +145,7 @@ function isDefaultEndpointFor(env, endpoint) {
|
|
|
159
145
|
}
|
|
160
146
|
function isDefaultEndpoint(endpoint) {
|
|
161
147
|
const normalized = normalizeEndpoint(endpoint);
|
|
162
|
-
return Object.values(DEFAULT_ENDPOINTS).includes(normalized)
|
|
148
|
+
return Object.values(DEFAULT_ENDPOINTS).includes(normalized);
|
|
163
149
|
}
|
|
164
150
|
function assertTrustedEndpointUrl(value, label = "URL") {
|
|
165
151
|
const url = typeof value === "string" ? new URL(value) : value;
|
|
@@ -249,7 +235,7 @@ var OAuthClient = class {
|
|
|
249
235
|
assertEnvTokenEndpointAllowed(this.options.endpoint, this.envVars);
|
|
250
236
|
return envToken;
|
|
251
237
|
}
|
|
252
|
-
const stored = await this.
|
|
238
|
+
const stored = await this.options.tokenStore.load(this.options.endpoint);
|
|
253
239
|
if (!stored) {
|
|
254
240
|
return void 0;
|
|
255
241
|
}
|
|
@@ -265,7 +251,7 @@ var OAuthClient = class {
|
|
|
265
251
|
return this.refreshStoredToken({ ...stored, refreshToken: stored.refreshToken });
|
|
266
252
|
}
|
|
267
253
|
async refreshAfterUnauthorized() {
|
|
268
|
-
const stored = await this.
|
|
254
|
+
const stored = await this.options.tokenStore.load(this.options.endpoint);
|
|
269
255
|
if (this.options.readOnly || !stored?.refreshToken) {
|
|
270
256
|
return void 0;
|
|
271
257
|
}
|
|
@@ -310,29 +296,8 @@ var OAuthClient = class {
|
|
|
310
296
|
await loopback.close();
|
|
311
297
|
}
|
|
312
298
|
}
|
|
313
|
-
/**
|
|
314
|
-
* ★대체로 읽는 옛 칸도 함께 지운다. 새 칸만 지우면 다음 읽기가 곧바로 옛 칸으로 대체돼
|
|
315
|
-
* «로그아웃했는데 여전히 로그인됨»이 된다. 로그인은 기계당 한 번이므로 옛 CLI 의 칸도 같이 끝난다.
|
|
316
|
-
*/
|
|
317
299
|
async logout() {
|
|
318
300
|
await this.options.tokenStore.delete(this.options.endpoint);
|
|
319
|
-
for (const fallback of fallbackTokenEndpointsFor(this.options.endpoint)) {
|
|
320
|
-
await this.options.tokenStore.delete(fallback);
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
/** 이 주소의 칸, 비었으면 옛 기본 주소의 칸({@link fallbackTokenEndpointsFor}). 쓰기는 늘 이 주소의 칸이다. */
|
|
324
|
-
async loadStored() {
|
|
325
|
-
const own = await this.options.tokenStore.load(this.options.endpoint);
|
|
326
|
-
if (own) {
|
|
327
|
-
return own;
|
|
328
|
-
}
|
|
329
|
-
for (const fallback of fallbackTokenEndpointsFor(this.options.endpoint)) {
|
|
330
|
-
const stored = await this.options.tokenStore.load(fallback);
|
|
331
|
-
if (stored) {
|
|
332
|
-
return stored;
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
return void 0;
|
|
336
301
|
}
|
|
337
302
|
/** 부르는 쪽이 refresh token 이 있음을 이미 확인했다 — 타입이 그것을 요구한다. */
|
|
338
303
|
async refreshStoredToken(stored) {
|
|
@@ -650,9 +615,8 @@ async function readAllStdin(stdin) {
|
|
|
650
615
|
}
|
|
651
616
|
|
|
652
617
|
// src/core/version.ts
|
|
653
|
-
var CLI_VERSION = true ? "0.
|
|
618
|
+
var CLI_VERSION = true ? "0.7.0" : "dev";
|
|
654
619
|
var PACKAGE_NAME = "@ainecto/mcp";
|
|
655
|
-
var LEGACY_PACKAGE_NAME = "@ai-erd/mcp";
|
|
656
620
|
var CLI_USER_AGENT = `${PACKAGE_NAME} ${CLI_VERSION}`;
|
|
657
621
|
|
|
658
622
|
// src/core/mcp/rpcClient.ts
|
|
@@ -852,16 +816,18 @@ function redactSecretString(value) {
|
|
|
852
816
|
|
|
853
817
|
// src/core/catalog/generated.prod.ts
|
|
854
818
|
var generatedTools = [
|
|
855
|
-
{ "mcpName": "
|
|
819
|
+
{ "mcpName": "create_comments", "commandPath": ["comments", "create"], "group": "generic", "description": "Add comments, questions, or replies to a document. Anyone who can read the document 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": { "documentUuid": { "description": "Document UUID the comments belong to", "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": ["documentUuid", "items"], "type": "object" }, "required": ["documentUuid", "items"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "b3ad621ef51c3003" },
|
|
820
|
+
{ "mcpName": "create_documents", "commandPath": ["documents", "create"], "group": "document", "description": "Create documents. If a document body is tens of KB or more, prefer request_upload_token (purpose=document.create) and upload the body with curl.", "inputSchema": { "properties": { "items": { "description": "Documents", "items": { "properties": { "body": { "description": "Deprecated compatibility alias for typePayload.body when document type is markdown. \uBCF8\uBB38\uC774 \uC218\uC2ED KB \uC774\uC0C1\uC73C\uB85C \uD06C\uBA74 LLM \uD1A0\uD070 \uC808\uC57D\uC744 \uC704\uD574 `request_upload_token (purpose=document.create)` \uB97C \uD638\uCD9C\uD574 uploadUrl \uC744 \uBC1B\uACE0 curl \uB85C \uC9C1\uC811 PUT \uC73C\uB85C \uBCF4\uB0BC \uAC83.", "type": "string" }, "folderUuid": { "description": "Optional folder UUID", "type": "string" }, "projectUuid": { "description": "Project UUID", "type": "string" }, "title": { "description": "Document title", "type": "string" }, "type": { "description": "Document type. Creatable values: erd, markdown, diagram, openapi, html, file.", "enum": ["erd", "markdown", "diagram", "openapi", "html", "file"], "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).', "type": "object" } }, "required": ["projectUuid", "type", "title"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "aa868b0e2821006a" },
|
|
856
821
|
{ "mcpName": "create_folders", "commandPath": ["folders", "create"], "group": "generic", "description": "", "inputSchema": { "properties": { "items": { "description": "Folders", "items": { "properties": { "name": { "description": "Folder name", "type": "string" }, "parentFolderUuid": { "description": "Parent folder UUID", "type": "string" }, "projectUuid": { "description": "Project UUID", "type": "string" } }, "required": ["projectUuid", "name"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "30f45b887259609f" },
|
|
857
822
|
{ "mcpName": "create_projects", "commandPath": ["projects", "create"], "group": "generic", "description": "", "inputSchema": { "properties": { "items": { "description": "Projects", "items": { "properties": { "description": { "description": "Description", "type": "string" }, "name": { "description": "Project name", "type": "string" }, "projectKey": { "description": "Unique project key", "type": "string" }, "workspaceUuid": { "description": "Workspace UUID \u2014 null uses user's default workspace", "type": "string" } }, "required": ["name"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "edf5cf688d7c039a" },
|
|
858
823
|
{ "mcpName": "create_workspaces", "commandPath": ["workspaces", "create"], "group": "workspace", "description": "", "inputSchema": { "properties": { "items": { "description": "Workspaces", "items": { "properties": { "description": { "description": "Description", "type": "string" }, "name": { "description": "Workspace name", "type": "string" } }, "required": ["name"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "d9d1b0d561828b8e" },
|
|
859
824
|
{ "mcpName": "delete_attachments", "commandPath": ["attachments", "delete"], "group": "attachment", "description": "", "inputSchema": { "properties": { "uuids": { "description": "Attachment UUIDs", "items": { "type": "string" }, "type": "array" } }, "required": ["uuids"], "type": "object" }, "required": ["uuids"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "prod", "schemaHash": "b22c24bffdfe3d7d" },
|
|
825
|
+
{ "mcpName": "delete_comments", "commandPath": ["comments", "delete"], "group": "generic", "description": "Delete your own comments on a document. 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": { "documentUuid": { "description": "Document UUID the comments belong to", "type": "string" }, "ids": { "description": "Comment ids to delete (from mcp__ainecto__list_comments)", "items": { "minimum": 1, "type": "integer" }, "minItems": 1, "type": "array" } }, "required": ["documentUuid", "ids"], "type": "object" }, "required": ["documentUuid", "ids"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "prod", "schemaHash": "17b9658b5a47f866" },
|
|
860
826
|
{ "mcpName": "delete_documents", "commandPath": ["documents", "delete"], "group": "document", "description": "", "inputSchema": { "properties": { "uuids": { "description": "Document UUIDs", "items": { "type": "string" }, "type": "array" } }, "required": ["uuids"], "type": "object" }, "required": ["uuids"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "prod", "schemaHash": "3a725ae70956c270" },
|
|
861
827
|
{ "mcpName": "delete_folders", "commandPath": ["folders", "delete"], "group": "generic", "description": "", "inputSchema": { "properties": { "uuids": { "description": "Folder UUIDs", "items": { "type": "string" }, "type": "array" } }, "required": ["uuids"], "type": "object" }, "required": ["uuids"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "prod", "schemaHash": "2254cde89c9e8f06" },
|
|
862
828
|
{ "mcpName": "delete_projects", "commandPath": ["projects", "delete"], "group": "generic", "description": "", "inputSchema": { "properties": { "uuids": { "description": "Project UUIDs", "items": { "type": "string" }, "type": "array" } }, "required": ["uuids"], "type": "object" }, "required": ["uuids"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "prod", "schemaHash": "6521c33a28cb5465" },
|
|
863
829
|
{ "mcpName": "delete_workspaces", "commandPath": ["workspaces", "delete"], "group": "workspace", "description": "", "inputSchema": { "properties": { "uuids": { "description": "Workspace UUIDs", "items": { "type": "string" }, "type": "array" } }, "required": ["uuids"], "type": "object" }, "required": ["uuids"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "prod", "schemaHash": "3177868221137066" },
|
|
864
|
-
{ "mcpName": "diagram_apply_changes", "commandPath": ["tools", "run", "diagram-apply-changes"], "group": "generic", "description": "Required single-call mutation path for diagram node/edge/group create/update/delete changes. Put nodes, edges, and groups in one request and call this once. Do not look for type-specific diagram CUD tools; this tool is the only diagram CUD entry point.", "inputSchema": { "properties": { "description": { "description": "Reason for batch (audit)", "type": "string" }, "diagramUuid": { "description": "Diagram UUID", "type": "string" }, "edges": { "description": "Edge operations", "items": { "properties": { "action": { "description": "Operation kind", "enum": ["create", "update", "delete"], "type": "string" }, "bridgeType": { "description": "
|
|
830
|
+
{ "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": "prod", "schemaHash": "2c829103f86f4168" },
|
|
865
831
|
{ "mcpName": "diagram_get_edge", "commandPath": ["tools", "run", "diagram-get-edge"], "group": "generic", "description": "", "inputSchema": { "properties": { "edgeUuid": { "description": "Edge UUID", "type": "string" } }, "required": ["edgeUuid"], "type": "object" }, "required": ["edgeUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "2c03c4f7daa69898" },
|
|
866
832
|
{ "mcpName": "diagram_get_group", "commandPath": ["tools", "run", "diagram-get-group"], "group": "generic", "description": "", "inputSchema": { "properties": { "groupUuid": { "description": "Group UUID", "type": "string" } }, "required": ["groupUuid"], "type": "object" }, "required": ["groupUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "55c5aa27b8cf489d" },
|
|
867
833
|
{ "mcpName": "diagram_get_node", "commandPath": ["tools", "run", "diagram-get-node"], "group": "generic", "description": "", "inputSchema": { "properties": { "nodeUuid": { "description": "Node UUID", "type": "string" } }, "required": ["nodeUuid"], "type": "object" }, "required": ["nodeUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "646d553238328a9e" },
|
|
@@ -884,14 +850,20 @@ var generatedTools = [
|
|
|
884
850
|
{ "mcpName": "erd_list_table_groups", "commandPath": ["erd", "list-table-groups"], "group": "erd", "description": "", "inputSchema": { "properties": { "documentUuid": { "description": "ERD document UUID", "type": "string" } }, "required": ["documentUuid"], "type": "object" }, "required": ["documentUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "775bff53bbc21c9e" },
|
|
885
851
|
{ "mcpName": "erd_list_tables", "commandPath": ["erd", "list-tables"], "group": "erd", "description": "", "inputSchema": { "properties": { "documentUuid": { "description": "ERD document UUID", "type": "string" } }, "required": ["documentUuid"], "type": "object" }, "required": ["documentUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "34324dfc7ef461c8" },
|
|
886
852
|
{ "mcpName": "get_attachment", "commandPath": ["attachment", "get"], "group": "attachment", "description": "", "inputSchema": { "properties": { "uuid": { "description": "Attachment UUID", "type": "string" } }, "required": ["uuid"], "type": "object" }, "required": ["uuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "1e0e7f1bf33b669c" },
|
|
887
|
-
{ "mcpName": "get_document", "commandPath": ["document", "get"], "group": "document", "description": "Read a document. The body comes back for the types whose create/update take one: markdown \u2192 content; openapi \u2192 content (the spec text) + format/specVersion/pathCount/operationCount; html \u2192 content (the HTML text); diagram \u2192 diagramSource/sourceLanguage/edgeStyle/isModule/metadata; erd \u2192 metadata only (read tables/refs via erd_list_*).", "inputSchema": { "properties": { "uuid": { "description": "Document UUID", "type": "string" } }, "required": ["uuid"], "type": "object" }, "required": ["uuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "
|
|
853
|
+
{ "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; erd \u2192 metadata only (read tables/refs via erd_list_*).", "inputSchema": { "properties": { "uuid": { "description": "Document UUID", "type": "string" } }, "required": ["uuid"], "type": "object" }, "required": ["uuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "2efef592c2d6dd7a" },
|
|
854
|
+
{ "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}. For details use list_notifications and list_agent_todos; 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": "prod", "schemaHash": "bc63904d51058f42" },
|
|
888
855
|
{ "mcpName": "get_project", "commandPath": ["project", "get"], "group": "generic", "description": "", "inputSchema": { "properties": { "uuid": { "description": "Project UUID", "type": "string" } }, "required": ["uuid"], "type": "object" }, "required": ["uuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "192403c3484ac774" },
|
|
889
856
|
{ "mcpName": "get_version", "commandPath": ["version", "get"], "group": "generic", "description": "", "inputSchema": { "properties": { "documentUuid": { "description": "Document UUID", "type": "string" }, "versionNumber": { "description": "Version number", "type": "integer" } }, "required": ["documentUuid", "versionNumber"], "type": "object" }, "required": ["documentUuid", "versionNumber"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "4e308c1a8c7203d1" },
|
|
890
857
|
{ "mcpName": "get_workspace", "commandPath": ["workspace", "get"], "group": "workspace", "description": "", "inputSchema": { "properties": { "uuid": { "description": "Workspace UUID", "type": "string" } }, "required": ["uuid"], "type": "object" }, "required": ["uuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "db6d1b4879d57c56" },
|
|
891
858
|
{ "mcpName": "identity_get_self", "commandPath": ["tools", "run", "identity-get-self"], "group": "generic", "description": "Return the authenticated MCP caller identity summary; never returns email or token claims.", "inputSchema": { "additionalProperties": false, "properties": {}, "type": "object" }, "required": [], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "f29c86804eb91700" },
|
|
859
|
+
{ "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"], "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": "prod", "schemaHash": "9c228a3044a546cf" },
|
|
892
860
|
{ "mcpName": "list_attachments", "commandPath": ["attachments", "list"], "group": "attachment", "description": "", "inputSchema": { "properties": { "projectUuid": { "description": "Project UUID", "type": "string" } }, "required": ["projectUuid"], "type": "object" }, "required": ["projectUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "fae7152d1e4a930b" },
|
|
893
|
-
{ "mcpName": "
|
|
861
|
+
{ "mcpName": "list_comments", "commandPath": ["comments", "list"], "group": "generic", "description": "List the active comments on a document, 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 the document can list its comments.", "inputSchema": { "additionalProperties": false, "properties": { "documentUuid": { "description": "Document UUID the comments belong to", "type": "string" } }, "required": ["documentUuid"], "type": "object" }, "required": ["documentUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "4c60115a00f61404" },
|
|
862
|
+
{ "mcpName": "list_documents", "commandPath": ["documents", "list"], "group": "document", "description": "", "inputSchema": { "properties": { "folderUuid": { "description": "Optional folder UUID", "type": "string" }, "limit": { "description": "Result cap when q is set (default 50)", "type": "integer" }, "projectUuid": { "description": "Project UUID", "type": "string" }, "q": { "description": "Optional full-text query. When provided, results come from the search index instead of folder listing.", "type": "string" }, "type": { "description": "Optional document type filter. Readable values: erd, markdown, diagram, openapi, html, file.", "enum": ["erd", "markdown", "diagram", "openapi", "html", "file"], "type": "string" } }, "type": "object" }, "required": [], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "2171496b44dc2abf" },
|
|
894
863
|
{ "mcpName": "list_folders", "commandPath": ["folders", "list"], "group": "generic", "description": "", "inputSchema": { "properties": { "projectUuid": { "description": "Project UUID", "type": "string" } }, "required": ["projectUuid"], "type": "object" }, "required": ["projectUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "461dc6735ac92c3b" },
|
|
864
|
+
{ "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"], "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"], "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": "prod", "schemaHash": "235832683071654b" },
|
|
865
|
+
{ "mcpName": "list_mention_candidates", "commandPath": ["mention-candidates", "list"], "group": "generic", "description": "List the people you can mention in comments on a document: userId and displayName. Only people who can read the document are listed. 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": { "cursor": { "description": "nextCursor from the previous page (same documentUuid and query).", "type": "string" }, "documentUuid": { "description": "Document UUID the comments belong to", "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" } }, "required": ["documentUuid"], "type": "object" }, "required": ["documentUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "896a5a6ff357268b" },
|
|
866
|
+
{ "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"], "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": "prod", "schemaHash": "0c2bb3fdc8291b03" },
|
|
895
867
|
{ "mcpName": "list_projects", "commandPath": ["projects", "list"], "group": "generic", "description": "", "inputSchema": { "properties": { "limit": { "description": "Result cap when q is set (default 50)", "type": "integer" }, "q": { "description": "Optional full-text query", "type": "string" }, "workspaceUuid": { "description": "Optional Workspace UUID", "type": "string" } }, "type": "object" }, "required": [], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "939bfcc088425771" },
|
|
896
868
|
{ "mcpName": "list_trash", "commandPath": ["trash", "list"], "group": "generic", "description": "", "inputSchema": { "properties": {}, "type": "object" }, "required": [], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "329250aecc06a298" },
|
|
897
869
|
{ "mcpName": "list_versions", "commandPath": ["versions", "list"], "group": "generic", "description": "", "inputSchema": { "properties": { "documentUuid": { "description": "Document UUID", "type": "string" } }, "required": ["documentUuid"], "type": "object" }, "required": ["documentUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "f08f9faee3ba9107" },
|
|
@@ -901,14 +873,17 @@ var generatedTools = [
|
|
|
901
873
|
{ "mcpName": "move_documents", "commandPath": ["documents", "move"], "group": "document", "description": "", "inputSchema": { "properties": { "clientMutationId": { "description": "Optional mutation identifier for client retry tracing", "maxLength": 128, "minLength": 1, "pattern": "^[\\x21-\\x7E]{1,128}$", "type": "string" }, "items": { "description": "Moves", "items": { "properties": { "newFolderUuid": { "description": "New folder UUID \u2014 null/empty for root", "type": "string" }, "targetProjectUuid": { "description": "Target project UUID \u2014 omit to stay in current project", "type": "string" }, "targetWorkspaceUuid": { "description": "Guard only: must match targetProjectUuid workspace", "type": "string" }, "uuid": { "description": "Document UUID", "type": "string" } }, "required": ["uuid"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "prod", "schemaHash": "45899053743a0c1e" },
|
|
902
874
|
{ "mcpName": "move_folders", "commandPath": ["folders", "move"], "group": "generic", "description": "", "inputSchema": { "properties": { "items": { "description": "Moves", "items": { "properties": { "newParentFolderUuid": { "description": "New parent UUID \u2014 null/empty for root", "type": "string" }, "uuid": { "description": "Folder UUID", "type": "string" } }, "required": ["uuid"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "prod", "schemaHash": "eff4458d2f35587a" },
|
|
903
875
|
{ "mcpName": "move_projects", "commandPath": ["projects", "move"], "group": "generic", "description": "", "inputSchema": { "properties": { "items": { "description": "Moves", "items": { "properties": { "targetWorkspaceUuid": { "description": "Destination workspace UUID (you need write access there)", "type": "string" }, "uuid": { "description": "Project UUID", "type": "string" } }, "required": ["uuid", "targetWorkspaceUuid"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "prod", "schemaHash": "abe0196da3c48ad5" },
|
|
904
|
-
{ "mcpName": "request_upload_token", "commandPath": ["tools", "run", "request-upload-token"], "group": "attachment", "description": "Use this for large payloads of tens of KB or more to save LLM tokens. purpose=mcp.call returns a short-lived bearer token that can call any MCP tool, including erd_apply_changes, via curl. purpose=document.create/update/append uploads document bodies directly to the upload endpoint with PUT/curl. Tokens are reusable for 5 minutes by default.", "inputSchema": { "properties": { "maxBytes": { "description": "\uCD5C\uB300 \uC5C5\uB85C\uB4DC byte (\uC11C\uBC84 \uCE21 \uD55C\uB3C4)", "type": "integer" }, "purpose": { "description": "Token \uC6A9\uB3C4. `mcp.call` \uC740 curl \uB85C /mcp \uD638\uCD9C, `document.create/update/append` \uB294 PUT body \uB85C \uBCF8\uBB38 \uC5C5\uB85C\uB4DC, `attachment.upload` \uB294 \uD30C\uC77C bytes \uC5C5\uB85C\uB4DC. \uCF58\uD150\uCE20\uAC00 \uD06C\uBA74 (\uC218\uC2ED KB \uC774\uC0C1) LLM \uD1A0\uD070 \uC808\uC57D\uC744 \uC704\uD574 \uC774 \uB3C4\uAD6C\uB85C 5\uBD84 \uC7AC\uC0AC\uC6A9 \uD1A0\uD070\uC744 \uBC1B\uACE0 curl \uB85C \uBCF4\uB0B4\uB294 \uAC83\uC744 \uAC15\uB825 \uAD8C\uC7A5.", "enum": ["mcp.call", "document.create", "document.update", "document.append", "attachment.upload"], "type": "string" }, "scopeJson": { "description": "\uC120\uD0DD \u2014 JSON scope. document.create \uB294 projectUuid, type, title \uC744 \uD3EC\uD568\uD558\uACE0 creatable type values \uB294 erd, markdown, diagram, openapi, html. document.update/document.append \uB294 documentUuid \uB97C \uD3EC\uD568. attachment.upload
|
|
876
|
+
{ "mcpName": "request_upload_token", "commandPath": ["tools", "run", "request-upload-token"], "group": "attachment", "description": "Use this for large payloads of tens of KB or more to save LLM tokens. purpose=mcp.call returns a short-lived bearer token that can call any MCP tool, including erd_apply_changes, via curl. purpose=document.create/update/append uploads document bodies directly to the upload endpoint with PUT/curl. Tokens are reusable for 5 minutes by default.", "inputSchema": { "properties": { "maxBytes": { "description": "\uCD5C\uB300 \uC5C5\uB85C\uB4DC byte (\uC11C\uBC84 \uCE21 \uD55C\uB3C4)", "type": "integer" }, "purpose": { "description": "Token \uC6A9\uB3C4. `mcp.call` \uC740 curl \uB85C /mcp \uD638\uCD9C, `document.create/update/append` \uB294 PUT body \uB85C \uBCF8\uBB38 \uC5C5\uB85C\uB4DC, `attachment.upload` \uB294 \uD30C\uC77C bytes \uC5C5\uB85C\uB4DC. \uCF58\uD150\uCE20\uAC00 \uD06C\uBA74 (\uC218\uC2ED KB \uC774\uC0C1) LLM \uD1A0\uD070 \uC808\uC57D\uC744 \uC704\uD574 \uC774 \uB3C4\uAD6C\uB85C 5\uBD84 \uC7AC\uC0AC\uC6A9 \uD1A0\uD070\uC744 \uBC1B\uACE0 curl \uB85C \uBCF4\uB0B4\uB294 \uAC83\uC744 \uAC15\uB825 \uAD8C\uC7A5.", "enum": ["mcp.call", "document.create", "document.update", "document.append", "attachment.upload"], "type": "string" }, "scopeJson": { "description": "\uC120\uD0DD \u2014 JSON scope. document.create \uB294 projectUuid, type, title \uC744 \uD3EC\uD568\uD558\uACE0 creatable type values \uB294 erd, markdown, diagram, openapi, html, file. 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": "prod", "schemaHash": "fe1984b3979abf86" },
|
|
877
|
+
{ "mcpName": "resolve_comments", "commandPath": ["tools", "run", "resolve-comments"], "group": "generic", "description": "Mark questions on a document as resolved. Only top-level comments with kind QUESTION can be resolved, and it needs edit access to the document (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": { "documentUuid": { "description": "Document UUID the comments belong to", "type": "string" }, "ids": { "description": "Question ids to resolve (top-level comments with kind QUESTION)", "items": { "minimum": 1, "type": "integer" }, "minItems": 1, "type": "array" } }, "required": ["documentUuid", "ids"], "type": "object" }, "required": ["documentUuid", "ids"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "7b32b72b3a0d64ba" },
|
|
905
878
|
{ "mcpName": "restore_versions", "commandPath": ["versions", "restore"], "group": "generic", "description": "", "inputSchema": { "properties": { "items": { "description": "Restores", "items": { "properties": { "documentUuid": { "description": "Document UUID", "type": "string" }, "versionNumber": { "description": "Version number to restore", "type": "integer" } }, "required": ["documentUuid", "versionNumber"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "prod", "schemaHash": "005b5b08a19fc605" },
|
|
906
|
-
{ "mcpName": "search_documents", "commandPath": ["documents", "search"], "group": "document", "description": "", "inputSchema": { "properties": { "limit": { "description": "\uACB0\uACFC \uAC1C\uC218 (\uAE30\uBCF8 20, \uCD5C\uB300 50)", "type": "integer" }, "offset": { "description": "\uD398\uC774\uC9C0\uB124\uC774\uC158 offset (\uAE30\uBCF8 0)", "type": "integer" }, "projectUuid": { "description": "\uD2B9\uC815 \uD504\uB85C\uC81D\uD2B8\uB85C \uD55C\uC815 (\uC120\uD0DD)", "type": "string" }, "q": { "description": "\uAC80\uC0C9 \uD0A4\uC6CC\uB4DC", "type": "string" }, "type": { "description": "Optional document type filter. Readable values: erd, markdown, diagram, openapi, html.", "enum": ["erd", "markdown", "diagram", "openapi", "html"], "type": "string" } }, "required": ["q"], "type": "object" }, "required": ["q"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "
|
|
879
|
+
{ "mcpName": "search_documents", "commandPath": ["documents", "search"], "group": "document", "description": "", "inputSchema": { "properties": { "limit": { "description": "\uACB0\uACFC \uAC1C\uC218 (\uAE30\uBCF8 20, \uCD5C\uB300 50)", "type": "integer" }, "offset": { "description": "\uD398\uC774\uC9C0\uB124\uC774\uC158 offset (\uAE30\uBCF8 0)", "type": "integer" }, "projectUuid": { "description": "\uD2B9\uC815 \uD504\uB85C\uC81D\uD2B8\uB85C \uD55C\uC815 (\uC120\uD0DD)", "type": "string" }, "q": { "description": "\uAC80\uC0C9 \uD0A4\uC6CC\uB4DC", "type": "string" }, "type": { "description": "Optional document type filter. Readable values: erd, markdown, diagram, openapi, html, file.", "enum": ["erd", "markdown", "diagram", "openapi", "html", "file"], "type": "string" } }, "required": ["q"], "type": "object" }, "required": ["q"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "e82abba1358754e8" },
|
|
907
880
|
{ "mcpName": "unarchive_documents", "commandPath": ["documents", "unarchive"], "group": "document", "description": "", "inputSchema": { "properties": { "uuids": { "description": "Document UUIDs", "items": { "type": "string" }, "type": "array" } }, "required": ["uuids"], "type": "object" }, "required": ["uuids"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "prod", "schemaHash": "f2810e71583a320f" },
|
|
908
881
|
{ "mcpName": "unarchive_folders", "commandPath": ["folders", "unarchive"], "group": "generic", "description": "", "inputSchema": { "properties": { "uuids": { "description": "Folder UUIDs", "items": { "type": "string" }, "type": "array" } }, "required": ["uuids"], "type": "object" }, "required": ["uuids"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "prod", "schemaHash": "2a24cc44696b7906" },
|
|
909
882
|
{ "mcpName": "unarchive_projects", "commandPath": ["projects", "unarchive"], "group": "generic", "description": "", "inputSchema": { "properties": { "uuids": { "description": "Project UUIDs", "items": { "type": "string" }, "type": "array" } }, "required": ["uuids"], "type": "object" }, "required": ["uuids"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "prod", "schemaHash": "6925e444671afe19" },
|
|
910
883
|
{ "mcpName": "unarchive_workspaces", "commandPath": ["workspaces", "unarchive"], "group": "workspace", "description": "", "inputSchema": { "properties": { "uuids": { "description": "Workspace UUIDs", "items": { "type": "string" }, "type": "array" } }, "required": ["uuids"], "type": "object" }, "required": ["uuids"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "prod", "schemaHash": "8dafe9bc6378c3f2" },
|
|
911
|
-
{ "mcpName": "
|
|
884
|
+
{ "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, NOT_FOUND, INVALID \u2014 on CLOSED_BY_HUMAN or SUPERSEDED stop working on it. 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": "prod", "schemaHash": "da38f4f54f68dbce" },
|
|
885
|
+
{ "mcpName": "update_comments", "commandPath": ["comments", "update"], "group": "generic", "description": "Edit your own comments on a document. Only the author can edit, and only while they can still read the document. 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": { "documentUuid": { "description": "Document UUID the comments belong to", "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": ["documentUuid", "items"], "type": "object" }, "required": ["documentUuid", "items"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "3c13611a49c95979" },
|
|
886
|
+
{ "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).", "type": "object" }, "uuid": { "description": "Document UUID", "type": "string" } }, "required": ["uuid"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "c2088f9ef5b49c6c" },
|
|
912
887
|
{ "mcpName": "update_folders", "commandPath": ["folders", "update"], "group": "generic", "description": "", "inputSchema": { "properties": { "items": { "description": "Updates", "items": { "properties": { "name": { "description": "New name", "type": "string" }, "uuid": { "description": "Folder UUID", "type": "string" } }, "required": ["uuid", "name"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "43c58a96ee632b5f" },
|
|
913
888
|
{ "mcpName": "update_projects", "commandPath": ["projects", "update"], "group": "generic", "description": "", "inputSchema": { "properties": { "items": { "description": "Updates", "items": { "properties": { "description": { "description": "New description", "type": "string" }, "name": { "description": "New name", "type": "string" }, "uuid": { "description": "Project UUID", "type": "string" } }, "required": ["uuid"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "7d849ef0a830aa8c" },
|
|
914
889
|
{ "mcpName": "update_workspaces", "commandPath": ["workspaces", "update"], "group": "workspace", "description": "", "inputSchema": { "properties": { "items": { "description": "Updates", "items": { "properties": { "description": { "description": "New description", "type": "string" }, "name": { "description": "New name", "type": "string" }, "uuid": { "description": "Workspace UUID", "type": "string" } }, "required": ["uuid"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "553275e6da799c5a" },
|
|
@@ -1557,6 +1532,9 @@ async function promptConfirmation(io, message) {
|
|
|
1557
1532
|
|
|
1558
1533
|
// src/adapters/cli/bespokeCommands.ts
|
|
1559
1534
|
var ATTACHMENTS_UPLOAD_COMMAND_PATH = ["attachments", "upload"];
|
|
1535
|
+
var NOTIFICATIONS_POLL_COMMAND_PATH = ["notifications", "poll"];
|
|
1536
|
+
var NOTIFICATIONS_ACK_COMMAND_PATH = ["notifications", "ack"];
|
|
1537
|
+
var AGENT_TODOS_POLL_COMMAND_PATH = ["agent-todos", "poll"];
|
|
1560
1538
|
|
|
1561
1539
|
// src/adapters/cli/generatedCommandRouter.ts
|
|
1562
1540
|
function matchGeneratedCommand(tools, argv) {
|
|
@@ -1642,6 +1620,34 @@ function findRestStart(argv, commandPath) {
|
|
|
1642
1620
|
// src/core/upload/attachmentUpload.ts
|
|
1643
1621
|
import { readFile as readFile3, stat as stat2 } from "fs/promises";
|
|
1644
1622
|
import { basename, extname } from "path";
|
|
1623
|
+
|
|
1624
|
+
// src/core/mcp/toolPayload.ts
|
|
1625
|
+
function unwrapMcpToolPayload(value) {
|
|
1626
|
+
if (!isRecord7(value) || !Array.isArray(value.content)) {
|
|
1627
|
+
return value;
|
|
1628
|
+
}
|
|
1629
|
+
const text = value.content.map((item) => isRecord7(item) && typeof item.text === "string" ? item.text : "").find((itemText) => itemText.trim().startsWith("{"));
|
|
1630
|
+
if (!text) {
|
|
1631
|
+
return value;
|
|
1632
|
+
}
|
|
1633
|
+
try {
|
|
1634
|
+
return JSON.parse(text);
|
|
1635
|
+
} catch {
|
|
1636
|
+
return value;
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
function toolErrorText(value) {
|
|
1640
|
+
if (!isRecord7(value) || !Array.isArray(value.content)) {
|
|
1641
|
+
return "The tool returned an error.";
|
|
1642
|
+
}
|
|
1643
|
+
const text = value.content.map((item) => isRecord7(item) && typeof item.text === "string" ? item.text : "").filter(Boolean).join("\n");
|
|
1644
|
+
return text || "The tool returned an error.";
|
|
1645
|
+
}
|
|
1646
|
+
function isRecord7(value) {
|
|
1647
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
// src/core/upload/attachmentUpload.ts
|
|
1645
1651
|
var AttachmentUploadError = class extends Error {
|
|
1646
1652
|
constructor(code, message, details) {
|
|
1647
1653
|
super(message);
|
|
@@ -1739,7 +1745,7 @@ async function requestUploadToken(client, metadata, context) {
|
|
|
1739
1745
|
requestArgs.ttlSeconds = context.ttlSeconds;
|
|
1740
1746
|
}
|
|
1741
1747
|
const response = unwrapMcpToolPayload(await client.toolsCall(context.requestToolName, requestArgs));
|
|
1742
|
-
if (!
|
|
1748
|
+
if (!isRecord8(response)) {
|
|
1743
1749
|
throw new AttachmentUploadError("UPLOAD_TOKEN_PROTOCOL_ERROR", "request_upload_token response was not an object.");
|
|
1744
1750
|
}
|
|
1745
1751
|
const token = response.token;
|
|
@@ -1809,7 +1815,7 @@ function parsePutResponse(text) {
|
|
|
1809
1815
|
} catch {
|
|
1810
1816
|
throw new AttachmentUploadError("ATTACHMENT_UPLOAD_PROTOCOL_ERROR", "Attachment PUT response was not valid JSON.");
|
|
1811
1817
|
}
|
|
1812
|
-
if (!
|
|
1818
|
+
if (!isRecord8(parsed)) {
|
|
1813
1819
|
throw new AttachmentUploadError("ATTACHMENT_UPLOAD_PROTOCOL_ERROR", "Attachment PUT response was not an object.");
|
|
1814
1820
|
}
|
|
1815
1821
|
if (typeof parsed.storageKey !== "string") {
|
|
@@ -1841,20 +1847,6 @@ function validatePutResult(token, metadata, putResult) {
|
|
|
1841
1847
|
});
|
|
1842
1848
|
}
|
|
1843
1849
|
}
|
|
1844
|
-
function unwrapMcpToolPayload(value) {
|
|
1845
|
-
if (!isRecord7(value) || !Array.isArray(value.content)) {
|
|
1846
|
-
return value;
|
|
1847
|
-
}
|
|
1848
|
-
const text = value.content.map((item) => isRecord7(item) && typeof item.text === "string" ? item.text : "").find((itemText) => itemText.trim().startsWith("{"));
|
|
1849
|
-
if (!text) {
|
|
1850
|
-
return value;
|
|
1851
|
-
}
|
|
1852
|
-
try {
|
|
1853
|
-
return JSON.parse(text);
|
|
1854
|
-
} catch {
|
|
1855
|
-
return value;
|
|
1856
|
-
}
|
|
1857
|
-
}
|
|
1858
1850
|
function emitProgress(context, stage, path, filename) {
|
|
1859
1851
|
context.onProgress?.({
|
|
1860
1852
|
stage,
|
|
@@ -1887,7 +1879,7 @@ var MIME_BY_EXTENSION = {
|
|
|
1887
1879
|
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
1888
1880
|
".zip": "application/zip"
|
|
1889
1881
|
};
|
|
1890
|
-
function
|
|
1882
|
+
function isRecord8(value) {
|
|
1891
1883
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1892
1884
|
}
|
|
1893
1885
|
|
|
@@ -2011,14 +2003,260 @@ function renderAttachmentUploadResult(result, json) {
|
|
|
2011
2003
|
return renderSuccess(lines.join("\n"), { json: false });
|
|
2012
2004
|
}
|
|
2013
2005
|
|
|
2006
|
+
// src/adapters/cli/inboxPollCommands.ts
|
|
2007
|
+
import { mkdir as mkdir2, open, readFile as readFile4, rename as rename2 } from "fs/promises";
|
|
2008
|
+
import { dirname as dirname2 } from "path";
|
|
2009
|
+
var EXIT_NOTHING_NEW = 3;
|
|
2010
|
+
var LIST_NOTIFICATIONS = "mcp__ainecto__list_notifications";
|
|
2011
|
+
var LIST_AGENT_TODOS = "mcp__ainecto__list_agent_todos";
|
|
2012
|
+
function isInboxPollCommand(domain, action) {
|
|
2013
|
+
return [NOTIFICATIONS_POLL_COMMAND_PATH, NOTIFICATIONS_ACK_COMMAND_PATH, AGENT_TODOS_POLL_COMMAND_PATH].some(([d, a]) => d === domain && a === action);
|
|
2014
|
+
}
|
|
2015
|
+
var EXIT_USAGE = 2;
|
|
2016
|
+
var USAGE_ERRORS = /* @__PURE__ */ new Set(["UNKNOWN_FLAG", "FLAG_REQUIRES_VALUE", "MISSING_REQUIRED_FLAG", "INVALID_FLAG_VALUE"]);
|
|
2017
|
+
async function executeInboxPollCommand(options) {
|
|
2018
|
+
try {
|
|
2019
|
+
if (options.domain === "agent-todos") return await agentTodosPoll(options);
|
|
2020
|
+
if (options.action === "ack") return await notificationsAck(options);
|
|
2021
|
+
return await notificationsPoll(options);
|
|
2022
|
+
} catch (error) {
|
|
2023
|
+
if (error instanceof CliCommandError && USAGE_ERRORS.has(error.code)) {
|
|
2024
|
+
options.io.stderr.write(renderError(error, { json: options.json }));
|
|
2025
|
+
return EXIT_USAGE;
|
|
2026
|
+
}
|
|
2027
|
+
throw error;
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
async function notificationsPoll(options) {
|
|
2031
|
+
const flags = parseFlags(options.argv, ["--cursor-file", "--kinds", "--workspace", "--project", "--limit"], ["--direct-only"]);
|
|
2032
|
+
const cursorPath = requireFlag(flags, "--cursor-file", "notifications poll");
|
|
2033
|
+
const filter = {
|
|
2034
|
+
kinds: flags.values["--kinds"]?.split(",").map((kind) => kind.trim()).filter(Boolean).sort(),
|
|
2035
|
+
...scopeFilter(flags),
|
|
2036
|
+
directOnly: flags.switches.has("--direct-only")
|
|
2037
|
+
};
|
|
2038
|
+
if (filter.kinds?.length === 0) filter.kinds = void 0;
|
|
2039
|
+
const limit = flags.values["--limit"] === void 0 ? void 0 : positiveInt("--limit", flags.values["--limit"]);
|
|
2040
|
+
const saved = await readCursorFile(cursorPath);
|
|
2041
|
+
if (saved?.ackToken && saved.pendingItems?.length) {
|
|
2042
|
+
if (!sameFilter(saved.filter, filter)) {
|
|
2043
|
+
throw new CliCommandError(
|
|
2044
|
+
"PENDING_BATCH_OTHER_FILTER",
|
|
2045
|
+
`${cursorPath} holds an unacknowledged batch for a different filter. Run \`ainecto notifications ack --cursor-file ${cursorPath}\` after handling it, or use another cursor file for this filter.`,
|
|
2046
|
+
{ cursorFile: cursorPath }
|
|
2047
|
+
);
|
|
2048
|
+
}
|
|
2049
|
+
options.io.stdout.write(renderSuccess({ items: saved.pendingItems, pending: true, stream: saved.stream }, { json: options.json }));
|
|
2050
|
+
return 0;
|
|
2051
|
+
}
|
|
2052
|
+
const result = await callTool(options.client, LIST_NOTIFICATIONS, {
|
|
2053
|
+
cursor: "saved",
|
|
2054
|
+
...filter.kinds ? { kinds: filter.kinds } : {},
|
|
2055
|
+
...scopeArgs(filter),
|
|
2056
|
+
...filter.directOnly ? { directOnly: true } : {},
|
|
2057
|
+
...limit !== void 0 ? { limit } : {}
|
|
2058
|
+
});
|
|
2059
|
+
const items = Array.isArray(result.items) ? result.items : [];
|
|
2060
|
+
const stream = typeof result.stream === "string" ? result.stream : void 0;
|
|
2061
|
+
if (typeof result.retryAfterSeconds === "number") {
|
|
2062
|
+
options.io.stderr.write(`Polled too soon; try again in ${result.retryAfterSeconds}s.
|
|
2063
|
+
`);
|
|
2064
|
+
return EXIT_NOTHING_NEW;
|
|
2065
|
+
}
|
|
2066
|
+
if (items.length === 0) {
|
|
2067
|
+
await writeCursorFile(cursorPath, { v: 1, filter, stream });
|
|
2068
|
+
if (options.json) options.io.stdout.write(renderSuccess({ items: [], stream }, { json: true }));
|
|
2069
|
+
return EXIT_NOTHING_NEW;
|
|
2070
|
+
}
|
|
2071
|
+
await writeCursorFile(cursorPath, {
|
|
2072
|
+
v: 1,
|
|
2073
|
+
filter,
|
|
2074
|
+
stream,
|
|
2075
|
+
ackToken: typeof result.ackToken === "string" ? result.ackToken : void 0,
|
|
2076
|
+
pendingItems: items
|
|
2077
|
+
});
|
|
2078
|
+
options.io.stdout.write(renderSuccess({ items, hasMore: result.hasMore === true, stream }, { json: options.json }));
|
|
2079
|
+
return 0;
|
|
2080
|
+
}
|
|
2081
|
+
async function notificationsAck(options) {
|
|
2082
|
+
const flags = parseFlags(options.argv, ["--cursor-file"], []);
|
|
2083
|
+
const cursorPath = requireFlag(flags, "--cursor-file", "notifications ack");
|
|
2084
|
+
const saved = await readCursorFile(cursorPath);
|
|
2085
|
+
if (!saved?.ackToken) {
|
|
2086
|
+
if (options.json) options.io.stdout.write(renderSuccess({ acked: false }, { json: true }));
|
|
2087
|
+
return EXIT_NOTHING_NEW;
|
|
2088
|
+
}
|
|
2089
|
+
await callTool(options.client, LIST_NOTIFICATIONS, {
|
|
2090
|
+
cursor: "saved",
|
|
2091
|
+
ack: saved.ackToken,
|
|
2092
|
+
...saved.filter.kinds ? { kinds: saved.filter.kinds } : {},
|
|
2093
|
+
...scopeArgs(saved.filter),
|
|
2094
|
+
...saved.filter.directOnly ? { directOnly: true } : {},
|
|
2095
|
+
limit: 1
|
|
2096
|
+
});
|
|
2097
|
+
const acked = saved.pendingItems?.length ?? 0;
|
|
2098
|
+
await writeCursorFile(cursorPath, { v: 1, filter: saved.filter, stream: saved.stream });
|
|
2099
|
+
options.io.stdout.write(renderSuccess({ acked: true, items: acked }, { json: options.json }));
|
|
2100
|
+
return 0;
|
|
2101
|
+
}
|
|
2102
|
+
async function agentTodosPoll(options) {
|
|
2103
|
+
const flags = parseFlags(options.argv, ["--workspace", "--project", "--seen-file"], ["--only-new"]);
|
|
2104
|
+
const scope = scopeArgs(scopeFilter(flags));
|
|
2105
|
+
const onlyNew = flags.switches.has("--only-new");
|
|
2106
|
+
const seenPath = flags.values["--seen-file"];
|
|
2107
|
+
if (onlyNew && !seenPath) {
|
|
2108
|
+
throw new CliCommandError("MISSING_REQUIRED_FLAG", "--only-new needs --seen-file <path>.", { flag: "--seen-file" });
|
|
2109
|
+
}
|
|
2110
|
+
const items = [];
|
|
2111
|
+
let after;
|
|
2112
|
+
for (let page = 0; page < 20; page += 1) {
|
|
2113
|
+
const result = await callTool(options.client, LIST_AGENT_TODOS, { state: ["OPEN"], ...scope, limit: 50, ...after ? { after } : {} });
|
|
2114
|
+
const pageItems = Array.isArray(result.items) ? result.items.filter(isRecord9) : [];
|
|
2115
|
+
items.push(...pageItems);
|
|
2116
|
+
const last = pageItems[pageItems.length - 1];
|
|
2117
|
+
if (result.hasMore !== true || !last || typeof last.todoId !== "string") break;
|
|
2118
|
+
after = last.todoId;
|
|
2119
|
+
}
|
|
2120
|
+
let shown = items;
|
|
2121
|
+
if (onlyNew && seenPath) {
|
|
2122
|
+
const seen = new Set(await readSeenFile(seenPath));
|
|
2123
|
+
shown = items.filter((item) => typeof item.todoId === "string" && !seen.has(item.todoId));
|
|
2124
|
+
await writeJsonAtomic(seenPath, items.map((item) => item.todoId).filter((id) => typeof id === "string"));
|
|
2125
|
+
}
|
|
2126
|
+
if (shown.length === 0) {
|
|
2127
|
+
if (options.json) options.io.stdout.write(renderSuccess({ items: [] }, { json: true }));
|
|
2128
|
+
return EXIT_NOTHING_NEW;
|
|
2129
|
+
}
|
|
2130
|
+
options.io.stdout.write(renderSuccess({ items: shown }, { json: options.json }));
|
|
2131
|
+
return 0;
|
|
2132
|
+
}
|
|
2133
|
+
async function callTool(client, name, args) {
|
|
2134
|
+
const raw = await client.toolsCall(name, args);
|
|
2135
|
+
if (isToolError(raw)) {
|
|
2136
|
+
throw new CliCommandError("TOOL_ERROR", toolErrorText(raw), { tool: name });
|
|
2137
|
+
}
|
|
2138
|
+
const payload = unwrapMcpToolPayload(raw);
|
|
2139
|
+
if (!isRecord9(payload)) {
|
|
2140
|
+
throw new CliCommandError("MCP_PROTOCOL_ERROR", `${name} did not return an object.`, { tool: name });
|
|
2141
|
+
}
|
|
2142
|
+
return payload;
|
|
2143
|
+
}
|
|
2144
|
+
function scopeFilter(flags) {
|
|
2145
|
+
const workspaces = uuidSet(flags.lists["--workspace"]);
|
|
2146
|
+
const projects = uuidSet(flags.lists["--project"]);
|
|
2147
|
+
return {
|
|
2148
|
+
...workspaces.length ? { workspaceUuids: workspaces } : {},
|
|
2149
|
+
...projects.length ? { projectUuids: projects } : {}
|
|
2150
|
+
};
|
|
2151
|
+
}
|
|
2152
|
+
function uuidSet(values) {
|
|
2153
|
+
return [...new Set(values ?? [])].sort();
|
|
2154
|
+
}
|
|
2155
|
+
function scopeArgs(filter) {
|
|
2156
|
+
const workspaces = filter.workspaceUuids ?? [];
|
|
2157
|
+
const projects = filter.projectUuids ?? [];
|
|
2158
|
+
if (workspaces.length === 0 && projects.length === 1) return { projectUuid: projects[0] };
|
|
2159
|
+
return {
|
|
2160
|
+
...workspaces.length ? { workspaceUuids: workspaces } : {},
|
|
2161
|
+
...projects.length ? { projectUuids: projects } : {}
|
|
2162
|
+
};
|
|
2163
|
+
}
|
|
2164
|
+
function parseFlags(argv, valueFlags, switchFlags) {
|
|
2165
|
+
const values = {};
|
|
2166
|
+
const lists = {};
|
|
2167
|
+
const switches = /* @__PURE__ */ new Set();
|
|
2168
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
2169
|
+
const arg = argv[index];
|
|
2170
|
+
if (!arg) continue;
|
|
2171
|
+
if (valueFlags.includes(arg)) {
|
|
2172
|
+
const value = argv[index + 1];
|
|
2173
|
+
if (!value || value.startsWith("--")) {
|
|
2174
|
+
throw new CliCommandError("FLAG_REQUIRES_VALUE", `${arg} requires a value.`, { flag: arg });
|
|
2175
|
+
}
|
|
2176
|
+
values[arg] = value;
|
|
2177
|
+
(lists[arg] ??= []).push(value);
|
|
2178
|
+
index += 1;
|
|
2179
|
+
} else if (switchFlags.includes(arg)) {
|
|
2180
|
+
switches.add(arg);
|
|
2181
|
+
} else {
|
|
2182
|
+
throw new CliCommandError("UNKNOWN_FLAG", `Unknown argument ${arg}.`, { flag: arg });
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2185
|
+
return { values, lists, switches };
|
|
2186
|
+
}
|
|
2187
|
+
function requireFlag(flags, flag, command) {
|
|
2188
|
+
const value = flags.values[flag];
|
|
2189
|
+
if (!value) throw new CliCommandError("MISSING_REQUIRED_FLAG", `${command} needs ${flag} <path>.`, { flag });
|
|
2190
|
+
return value;
|
|
2191
|
+
}
|
|
2192
|
+
function positiveInt(flag, value) {
|
|
2193
|
+
if (!value || !/^[1-9]\d*$/.test(value)) {
|
|
2194
|
+
throw new CliCommandError("INVALID_FLAG_VALUE", `${flag} must be a positive integer.`, { flag, value });
|
|
2195
|
+
}
|
|
2196
|
+
return Number.parseInt(value, 10);
|
|
2197
|
+
}
|
|
2198
|
+
function sameFilter(a, b) {
|
|
2199
|
+
return JSON.stringify(a.kinds ?? []) === JSON.stringify(b.kinds ?? []) && JSON.stringify(a.workspaceUuids ?? []) === JSON.stringify(b.workspaceUuids ?? []) && JSON.stringify(a.projectUuids ?? []) === JSON.stringify(b.projectUuids ?? []) && a.directOnly === b.directOnly;
|
|
2200
|
+
}
|
|
2201
|
+
function normalizeSavedFilter(saved) {
|
|
2202
|
+
const { projectUuid, ...rest } = saved;
|
|
2203
|
+
if (rest.projectUuids?.length || !projectUuid) return rest;
|
|
2204
|
+
return { ...rest, projectUuids: [projectUuid] };
|
|
2205
|
+
}
|
|
2206
|
+
async function readCursorFile(path) {
|
|
2207
|
+
const text = await readFile4(path, "utf8").catch((error) => {
|
|
2208
|
+
if (error.code === "ENOENT") return void 0;
|
|
2209
|
+
throw error;
|
|
2210
|
+
});
|
|
2211
|
+
if (text === void 0 || !text.trim()) return void 0;
|
|
2212
|
+
try {
|
|
2213
|
+
const parsed = JSON.parse(text);
|
|
2214
|
+
if (parsed?.v !== 1 || !isRecord9(parsed.filter)) throw new Error("shape");
|
|
2215
|
+
return { ...parsed, filter: normalizeSavedFilter(parsed.filter) };
|
|
2216
|
+
} catch {
|
|
2217
|
+
throw new CliCommandError("INVALID_CURSOR_FILE", `${path} is not a cursor file written by ainecto notifications poll.`, { cursorFile: path });
|
|
2218
|
+
}
|
|
2219
|
+
}
|
|
2220
|
+
async function writeCursorFile(path, value) {
|
|
2221
|
+
await writeJsonAtomic(path, value);
|
|
2222
|
+
}
|
|
2223
|
+
async function readSeenFile(path) {
|
|
2224
|
+
const text = await readFile4(path, "utf8").catch((error) => {
|
|
2225
|
+
if (error.code === "ENOENT") return void 0;
|
|
2226
|
+
throw error;
|
|
2227
|
+
});
|
|
2228
|
+
if (!text?.trim()) return [];
|
|
2229
|
+
try {
|
|
2230
|
+
const parsed = JSON.parse(text);
|
|
2231
|
+
return Array.isArray(parsed) ? parsed.filter((id) => typeof id === "string") : [];
|
|
2232
|
+
} catch {
|
|
2233
|
+
throw new CliCommandError("INVALID_SEEN_FILE", `${path} is not a seen file written by ainecto agent-todos poll.`, { seenFile: path });
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
async function writeJsonAtomic(path, value) {
|
|
2237
|
+
await mkdir2(dirname2(path), { recursive: true });
|
|
2238
|
+
const temp = `${path}.${process.pid}.tmp`;
|
|
2239
|
+
const handle = await open(temp, "w", 384);
|
|
2240
|
+
try {
|
|
2241
|
+
await handle.writeFile(`${JSON.stringify(value, null, 2)}
|
|
2242
|
+
`, "utf8");
|
|
2243
|
+
await handle.sync();
|
|
2244
|
+
} finally {
|
|
2245
|
+
await handle.close();
|
|
2246
|
+
}
|
|
2247
|
+
await rename2(temp, path);
|
|
2248
|
+
}
|
|
2249
|
+
function isRecord9(value) {
|
|
2250
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2251
|
+
}
|
|
2252
|
+
|
|
2014
2253
|
// src/adapters/cli/initCommand.ts
|
|
2015
|
-
import { chmod as chmod2, mkdir as
|
|
2016
|
-
import { dirname as
|
|
2254
|
+
import { chmod as chmod2, mkdir as mkdir3, rename as rename3, rm as rm2, rmdir, stat as stat3, writeFile as writeFile2 } from "fs/promises";
|
|
2255
|
+
import { dirname as dirname4, join as join3, relative, resolve as resolve2, sep as sep2 } from "path";
|
|
2017
2256
|
|
|
2018
2257
|
// src/core/harness/agentTargets.ts
|
|
2019
2258
|
import { basename as basename2 } from "path";
|
|
2020
2259
|
var SERVER_NAME = "ainecto";
|
|
2021
|
-
var LEGACY_SERVER_NAME = "ai-erd";
|
|
2022
2260
|
var AGENT_TARGETS = [
|
|
2023
2261
|
{ id: "claude-code", label: "Claude Code", path: ".mcp.json" },
|
|
2024
2262
|
{ id: "cursor", label: "Cursor", path: ".cursor/mcp.json" }
|
|
@@ -2034,7 +2272,7 @@ function buildServerEntry(input) {
|
|
|
2034
2272
|
return { command: "npx", args };
|
|
2035
2273
|
}
|
|
2036
2274
|
function readRoleFromEntry(entry) {
|
|
2037
|
-
if (!
|
|
2275
|
+
if (!isRecord10(entry) || !Array.isArray(entry.args)) {
|
|
2038
2276
|
return void 0;
|
|
2039
2277
|
}
|
|
2040
2278
|
const index = entry.args.indexOf("--role");
|
|
@@ -2048,43 +2286,30 @@ function readServerEntry(existing) {
|
|
|
2048
2286
|
try {
|
|
2049
2287
|
const parsed = JSON.parse(existing);
|
|
2050
2288
|
const servers = parsed?.mcpServers;
|
|
2051
|
-
return
|
|
2289
|
+
return isRecord10(servers) ? servers[SERVER_NAME] : void 0;
|
|
2052
2290
|
} catch {
|
|
2053
2291
|
return void 0;
|
|
2054
2292
|
}
|
|
2055
2293
|
}
|
|
2056
|
-
function ourKeyIn(servers) {
|
|
2057
|
-
if (Object.prototype.hasOwnProperty.call(servers, SERVER_NAME)) {
|
|
2058
|
-
return SERVER_NAME;
|
|
2059
|
-
}
|
|
2060
|
-
return isOurBridge(servers[LEGACY_SERVER_NAME]) ? LEGACY_SERVER_NAME : void 0;
|
|
2061
|
-
}
|
|
2062
|
-
function legacyBridgeIn(servers) {
|
|
2063
|
-
return isOurBridge(servers[LEGACY_SERVER_NAME]);
|
|
2064
|
-
}
|
|
2065
2294
|
function isOurBridge(entry) {
|
|
2066
|
-
if (!
|
|
2295
|
+
if (!isRecord10(entry)) {
|
|
2067
2296
|
return false;
|
|
2068
2297
|
}
|
|
2069
2298
|
const { command, args } = entry;
|
|
2070
2299
|
const argList = Array.isArray(args) ? args.filter((arg) => typeof arg === "string") : [];
|
|
2071
|
-
const isOurPackage = (arg) =>
|
|
2300
|
+
const isOurPackage = (arg) => arg === PACKAGE_NAME || arg.startsWith(`${PACKAGE_NAME}@`);
|
|
2072
2301
|
if (argList.some((arg) => isOurPackage(arg) || arg === "--role")) {
|
|
2073
2302
|
return true;
|
|
2074
2303
|
}
|
|
2075
2304
|
const bin = typeof command === "string" ? basename2(command) : "";
|
|
2076
2305
|
return OUR_BINS.includes(bin);
|
|
2077
2306
|
}
|
|
2078
|
-
var OUR_BINS = ["ainecto"
|
|
2307
|
+
var OUR_BINS = ["ainecto"];
|
|
2079
2308
|
function mergeServerEntry(existing, entry) {
|
|
2080
2309
|
const root = parseConfigObject(existing);
|
|
2081
2310
|
const servers = readServers(root);
|
|
2082
|
-
const
|
|
2083
|
-
const
|
|
2084
|
-
const replacedEntry = ourKey === void 0 ? void 0 : servers[ourKey];
|
|
2085
|
-
if (legacyBridgeIn(servers)) {
|
|
2086
|
-
delete servers[LEGACY_SERVER_NAME];
|
|
2087
|
-
}
|
|
2311
|
+
const replaced = Object.prototype.hasOwnProperty.call(servers, SERVER_NAME);
|
|
2312
|
+
const replacedEntry = replaced ? servers[SERVER_NAME] : void 0;
|
|
2088
2313
|
servers[SERVER_NAME] = entry;
|
|
2089
2314
|
return { content: stringify({ ...root, mcpServers: servers }), replaced, replacedEntry };
|
|
2090
2315
|
}
|
|
@@ -2094,14 +2319,13 @@ function removeServerEntry(existing, restore) {
|
|
|
2094
2319
|
}
|
|
2095
2320
|
const root = parseConfigObject(existing);
|
|
2096
2321
|
const servers = readServers(root);
|
|
2097
|
-
const
|
|
2098
|
-
const removed = ourKey !== void 0;
|
|
2322
|
+
const removed = Object.prototype.hasOwnProperty.call(servers, SERVER_NAME);
|
|
2099
2323
|
if (restore !== void 0) {
|
|
2100
|
-
servers[
|
|
2324
|
+
servers[SERVER_NAME] = restore.value;
|
|
2101
2325
|
return { content: stringify({ ...root, mcpServers: servers }), removed, emptied: false };
|
|
2102
2326
|
}
|
|
2103
|
-
if (
|
|
2104
|
-
delete servers[
|
|
2327
|
+
if (removed) {
|
|
2328
|
+
delete servers[SERVER_NAME];
|
|
2105
2329
|
}
|
|
2106
2330
|
const otherKeys = Object.keys(root).filter((key) => key !== "mcpServers");
|
|
2107
2331
|
const emptied = Object.keys(servers).length === 0 && otherKeys.length === 0;
|
|
@@ -2132,7 +2356,7 @@ function readServers(root) {
|
|
|
2132
2356
|
if (servers === void 0) {
|
|
2133
2357
|
return {};
|
|
2134
2358
|
}
|
|
2135
|
-
if (!
|
|
2359
|
+
if (!isRecord10(servers)) {
|
|
2136
2360
|
throw new Error(
|
|
2137
2361
|
'Existing MCP config has an "mcpServers" value that is not an object, so it was left untouched. Fix it by hand and run init again.'
|
|
2138
2362
|
);
|
|
@@ -2151,7 +2375,7 @@ function parseConfigObject(existing) {
|
|
|
2151
2375
|
`Existing MCP config is not valid JSON, so it was left untouched. Fix it by hand and run init again. (${error instanceof Error ? error.message : String(error)})`
|
|
2152
2376
|
);
|
|
2153
2377
|
}
|
|
2154
|
-
if (!
|
|
2378
|
+
if (!isRecord10(parsed)) {
|
|
2155
2379
|
throw new Error("Existing MCP config must be a JSON object; it was left untouched.");
|
|
2156
2380
|
}
|
|
2157
2381
|
return parsed;
|
|
@@ -2160,21 +2384,15 @@ function stringify(value) {
|
|
|
2160
2384
|
return `${JSON.stringify(value, null, 2)}
|
|
2161
2385
|
`;
|
|
2162
2386
|
}
|
|
2163
|
-
function
|
|
2387
|
+
function isRecord10(value) {
|
|
2164
2388
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2165
2389
|
}
|
|
2166
2390
|
|
|
2167
2391
|
// src/core/harness/harnessDoc.ts
|
|
2168
2392
|
var MARKER_BEGIN = "<!-- ainecto:begin -->";
|
|
2169
2393
|
var MARKER_END = "<!-- ainecto:end -->";
|
|
2170
|
-
var
|
|
2171
|
-
var LEGACY_MARKER_END = "<!-- ai-erd:end -->";
|
|
2172
|
-
var MARKERS = {
|
|
2173
|
-
begin: [MARKER_BEGIN, LEGACY_MARKER_BEGIN],
|
|
2174
|
-
end: [MARKER_END, LEGACY_MARKER_END]
|
|
2175
|
-
};
|
|
2394
|
+
var MARKERS = { begin: MARKER_BEGIN, end: MARKER_END };
|
|
2176
2395
|
var HARNESS_DIR = ".ainecto";
|
|
2177
|
-
var LEGACY_HARNESS_DIR = ".ai-erd";
|
|
2178
2396
|
var HARNESS_DOC_PATH = `${HARNESS_DIR}/HARNESS.md`;
|
|
2179
2397
|
var HARNESS_CONFIG_PATH = `${HARNESS_DIR}/config.json`;
|
|
2180
2398
|
var HARNESS_RECORD_PATH = `${HARNESS_DIR}/init-record.json`;
|
|
@@ -2201,7 +2419,7 @@ function documentContractProblem(key, body) {
|
|
|
2201
2419
|
}
|
|
2202
2420
|
}
|
|
2203
2421
|
if (key === AGENT_NOTE_KEY) {
|
|
2204
|
-
return managedBlockProblem(key,
|
|
2422
|
+
return managedBlockProblem(key, body);
|
|
2205
2423
|
}
|
|
2206
2424
|
return void 0;
|
|
2207
2425
|
}
|
|
@@ -2264,14 +2482,11 @@ Or run \`ainecto init --role <design|development|test|validation>\` to rewrite t
|
|
|
2264
2482
|
Generated by \`ainecto init\`. Remove everything it wrote with \`ainecto init --undo\`.
|
|
2265
2483
|
`;
|
|
2266
2484
|
function renderAgentNote(template) {
|
|
2267
|
-
return fillTemplate(
|
|
2485
|
+
return fillTemplate(template ?? DEFAULT_AGENT_NOTE, {
|
|
2268
2486
|
harnessDocPath: HARNESS_DOC_PATH,
|
|
2269
2487
|
serverName: SERVER_NAME
|
|
2270
2488
|
});
|
|
2271
2489
|
}
|
|
2272
|
-
function withCurrentMarkers(template) {
|
|
2273
|
-
return template.split(LEGACY_MARKER_BEGIN).join(MARKER_BEGIN).split(LEGACY_MARKER_END).join(MARKER_END);
|
|
2274
|
-
}
|
|
2275
2490
|
var DEFAULT_AGENT_NOTE = `${MARKER_BEGIN}
|
|
2276
2491
|
## AiNecto Harness
|
|
2277
2492
|
|
|
@@ -2308,14 +2523,14 @@ function managedBlockProblem(key, body) {
|
|
|
2308
2523
|
return void 0;
|
|
2309
2524
|
}
|
|
2310
2525
|
function hasUnclosedFence(content) {
|
|
2311
|
-
let
|
|
2526
|
+
let open2;
|
|
2312
2527
|
for (const line of content.split("\n")) {
|
|
2313
2528
|
const fence = fenceOf(line);
|
|
2314
2529
|
if (!fence) continue;
|
|
2315
|
-
if (!
|
|
2316
|
-
else if (fence.char ===
|
|
2530
|
+
if (!open2) open2 = { char: fence.char, length: fence.length };
|
|
2531
|
+
else if (fence.char === open2.char && fence.length >= open2.length && fence.info === "") open2 = void 0;
|
|
2317
2532
|
}
|
|
2318
|
-
return
|
|
2533
|
+
return open2 !== void 0;
|
|
2319
2534
|
}
|
|
2320
2535
|
function canonicalManagedBlock(block) {
|
|
2321
2536
|
return block.trim();
|
|
@@ -2360,50 +2575,39 @@ function assertNoDanglingMarker(content) {
|
|
|
2360
2575
|
}
|
|
2361
2576
|
function ownedMarkerCount(content, kind) {
|
|
2362
2577
|
let count = 0;
|
|
2363
|
-
for (let
|
|
2578
|
+
for (let hit2 = ownedMarker(content, kind, 0); hit2 !== void 0; hit2 = ownedMarker(content, kind, hit2.at + 1)) {
|
|
2364
2579
|
count += 1;
|
|
2365
2580
|
}
|
|
2366
2581
|
return count;
|
|
2367
2582
|
}
|
|
2368
2583
|
function ownedMarker(content, kind, from) {
|
|
2369
|
-
|
|
2370
|
-
for (const marker of MARKERS[kind]) {
|
|
2371
|
-
const at = ownedMarkerIndex(content, marker, from);
|
|
2372
|
-
if (at >= 0 && (best === void 0 || at < best.at)) {
|
|
2373
|
-
best = { at, length: marker.length };
|
|
2374
|
-
}
|
|
2375
|
-
}
|
|
2376
|
-
return best;
|
|
2584
|
+
return hit(ownedMarkerIndex(content, MARKERS[kind], from), MARKERS[kind]);
|
|
2377
2585
|
}
|
|
2378
2586
|
function anyMarker(content, kind, from) {
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
best = { at, length: marker.length };
|
|
2384
|
-
}
|
|
2385
|
-
}
|
|
2386
|
-
return best;
|
|
2587
|
+
return hit(content.indexOf(MARKERS[kind], from), MARKERS[kind]);
|
|
2588
|
+
}
|
|
2589
|
+
function hit(at, marker) {
|
|
2590
|
+
return at >= 0 ? { at, length: marker.length } : void 0;
|
|
2387
2591
|
}
|
|
2388
2592
|
function isInsideFencedBlock(content, index) {
|
|
2389
|
-
let
|
|
2593
|
+
let open2;
|
|
2390
2594
|
let offset = 0;
|
|
2391
2595
|
for (const line of content.split("\n")) {
|
|
2392
2596
|
const nextOffset = offset + line.length + 1;
|
|
2393
2597
|
if (index < nextOffset) {
|
|
2394
|
-
return
|
|
2598
|
+
return open2 !== void 0;
|
|
2395
2599
|
}
|
|
2396
2600
|
const fence = fenceOf(line);
|
|
2397
2601
|
if (fence) {
|
|
2398
|
-
if (!
|
|
2399
|
-
|
|
2400
|
-
} else if (fence.char ===
|
|
2401
|
-
|
|
2602
|
+
if (!open2) {
|
|
2603
|
+
open2 = { char: fence.char, length: fence.length };
|
|
2604
|
+
} else if (fence.char === open2.char && fence.length >= open2.length && fence.info === "") {
|
|
2605
|
+
open2 = void 0;
|
|
2402
2606
|
}
|
|
2403
2607
|
}
|
|
2404
2608
|
offset = nextOffset;
|
|
2405
2609
|
}
|
|
2406
|
-
return
|
|
2610
|
+
return open2 !== void 0;
|
|
2407
2611
|
}
|
|
2408
2612
|
function fenceOf(line) {
|
|
2409
2613
|
const match = /^ {0,3}(`{3,}|~{3,})(.*)$/.exec(line);
|
|
@@ -2514,14 +2718,12 @@ function ownedMarkerIndex(content, marker, from) {
|
|
|
2514
2718
|
|
|
2515
2719
|
// src/core/harness/initRecord.ts
|
|
2516
2720
|
var RECORD_KIND = "ainecto/init-record";
|
|
2517
|
-
var LEGACY_RECORD_KIND = "ai-erd/init-record";
|
|
2518
2721
|
var RECORD_VERSION = 2;
|
|
2519
2722
|
var ABSENT = { at: "absent" };
|
|
2520
2723
|
function emptyRecord() {
|
|
2521
2724
|
return { version: RECORD_VERSION, files: {}, createdDirectories: [] };
|
|
2522
2725
|
}
|
|
2523
|
-
function readRecord(content
|
|
2524
|
-
const HARNESS_RECORD_PATH2 = recordPath;
|
|
2726
|
+
function readRecord(content) {
|
|
2525
2727
|
if (content === void 0) {
|
|
2526
2728
|
return { state: "absent" };
|
|
2527
2729
|
}
|
|
@@ -2529,26 +2731,26 @@ function readRecord(content, recordPath = HARNESS_RECORD_PATH) {
|
|
|
2529
2731
|
try {
|
|
2530
2732
|
parsed = JSON.parse(content);
|
|
2531
2733
|
} catch {
|
|
2532
|
-
return { state: "corrupt", reason: `${
|
|
2734
|
+
return { state: "corrupt", reason: `${HARNESS_RECORD_PATH} is not valid JSON` };
|
|
2533
2735
|
}
|
|
2534
2736
|
if (!isObject(parsed)) {
|
|
2535
|
-
return { state: "corrupt", reason: `${
|
|
2737
|
+
return { state: "corrupt", reason: `${HARNESS_RECORD_PATH} is not a JSON object` };
|
|
2536
2738
|
}
|
|
2537
|
-
if (parsed.kind !== RECORD_KIND
|
|
2739
|
+
if (parsed.kind !== RECORD_KIND) {
|
|
2538
2740
|
return {
|
|
2539
2741
|
state: "corrupt",
|
|
2540
|
-
reason: `${
|
|
2742
|
+
reason: `${HARNESS_RECORD_PATH} exists but is not an AiNecto init record (kind=${JSON.stringify(parsed.kind)})`
|
|
2541
2743
|
};
|
|
2542
2744
|
}
|
|
2543
2745
|
if (parsed.version !== RECORD_VERSION) {
|
|
2544
2746
|
return {
|
|
2545
2747
|
state: "legacy",
|
|
2546
|
-
reason: `${
|
|
2748
|
+
reason: `${HARNESS_RECORD_PATH} was written by another version of this CLI (record version ${JSON.stringify(parsed.version)}, this CLI reads ${RECORD_VERSION})`
|
|
2547
2749
|
};
|
|
2548
2750
|
}
|
|
2549
2751
|
const files = parsed.files;
|
|
2550
2752
|
if (!isObject(files)) {
|
|
2551
|
-
return { state: "corrupt", reason: `${
|
|
2753
|
+
return { state: "corrupt", reason: `${HARNESS_RECORD_PATH} has no "files" object` };
|
|
2552
2754
|
}
|
|
2553
2755
|
const out = {};
|
|
2554
2756
|
for (const [path, raw] of Object.entries(files)) {
|
|
@@ -2556,14 +2758,14 @@ function readRecord(content, recordPath = HARNESS_RECORD_PATH) {
|
|
|
2556
2758
|
if ("problem" in read) {
|
|
2557
2759
|
return {
|
|
2558
2760
|
state: "corrupt",
|
|
2559
|
-
reason: `${
|
|
2761
|
+
reason: `${HARNESS_RECORD_PATH} has an unusable entry for ${path} \u2014 it ${read.problem}`
|
|
2560
2762
|
};
|
|
2561
2763
|
}
|
|
2562
2764
|
out[path] = read.contract;
|
|
2563
2765
|
}
|
|
2564
2766
|
const directories = parsed.createdDirectories;
|
|
2565
2767
|
if (directories !== void 0 && !isStringArray(directories)) {
|
|
2566
|
-
return { state: "corrupt", reason: `${
|
|
2768
|
+
return { state: "corrupt", reason: `${HARNESS_RECORD_PATH} has a malformed createdDirectories` };
|
|
2567
2769
|
}
|
|
2568
2770
|
return {
|
|
2569
2771
|
state: "valid",
|
|
@@ -2574,46 +2776,6 @@ function readRecord(content, recordPath = HARNESS_RECORD_PATH) {
|
|
|
2574
2776
|
}
|
|
2575
2777
|
};
|
|
2576
2778
|
}
|
|
2577
|
-
var LEGACY_MANAGED_KEYS = [
|
|
2578
|
-
"created",
|
|
2579
|
-
"originals",
|
|
2580
|
-
"lastWritten",
|
|
2581
|
-
"lastWrittenFragment",
|
|
2582
|
-
"lastWrittenBody",
|
|
2583
|
-
"replacedEntries",
|
|
2584
|
-
"replacedBlocks",
|
|
2585
|
-
"blockAdded",
|
|
2586
|
-
"foreignMarkers"
|
|
2587
|
-
];
|
|
2588
|
-
function legacyManagedRecord(configContent) {
|
|
2589
|
-
if (configContent === void 0) {
|
|
2590
|
-
return false;
|
|
2591
|
-
}
|
|
2592
|
-
let parsed;
|
|
2593
|
-
try {
|
|
2594
|
-
parsed = JSON.parse(configContent);
|
|
2595
|
-
} catch {
|
|
2596
|
-
return false;
|
|
2597
|
-
}
|
|
2598
|
-
if (!isObject(parsed) || !isObject(parsed.managed)) {
|
|
2599
|
-
return false;
|
|
2600
|
-
}
|
|
2601
|
-
const managed = parsed.managed;
|
|
2602
|
-
const hasLegacyShape = LEGACY_MANAGED_KEYS.some(
|
|
2603
|
-
(key) => Object.prototype.hasOwnProperty.call(managed, key)
|
|
2604
|
-
);
|
|
2605
|
-
if (!hasLegacyShape) {
|
|
2606
|
-
return false;
|
|
2607
|
-
}
|
|
2608
|
-
if (typeof parsed.generatedBy === "string") {
|
|
2609
|
-
return true;
|
|
2610
|
-
}
|
|
2611
|
-
const lastWritten = managed.lastWritten;
|
|
2612
|
-
return isObject(lastWritten) && [HARNESS_CONFIG_PATH, `${LEGACY_HARNESS_DIR}/config.json`].some((path) => Object.prototype.hasOwnProperty.call(lastWritten, path));
|
|
2613
|
-
}
|
|
2614
|
-
function legacyManagedNotice(configPath) {
|
|
2615
|
-
return `${configPath} holds an init record written by an older version of this CLI (a "managed" field). This version keeps its record in ${HARNESS_RECORD_PATH} and cannot read that one, so your saved originals are still in there and nothing was changed. Undo with the version that wrote it (for example: npx -y -p @ai-erd/mcp@0.2 ai-erd init --undo), then run this version again.`;
|
|
2616
|
-
}
|
|
2617
2779
|
function serializeRecord(record) {
|
|
2618
2780
|
return `${JSON.stringify({
|
|
2619
2781
|
kind: RECORD_KIND,
|
|
@@ -2774,9 +2936,9 @@ function isStringArray(value) {
|
|
|
2774
2936
|
import { createHash as createHash4 } from "crypto";
|
|
2775
2937
|
|
|
2776
2938
|
// src/core/harness/repositoryRole.ts
|
|
2777
|
-
import { lstat, readFile as
|
|
2939
|
+
import { lstat, readFile as readFile5, realpath } from "fs/promises";
|
|
2778
2940
|
import { homedir as homedir2 } from "os";
|
|
2779
|
-
import { dirname as
|
|
2941
|
+
import { dirname as dirname3, join as join2, resolve, sep } from "path";
|
|
2780
2942
|
function readConfigRole(path, content) {
|
|
2781
2943
|
if (content === void 0 || !content.trim()) {
|
|
2782
2944
|
return { kind: "absent" };
|
|
@@ -2787,29 +2949,17 @@ function readConfigRole(path, content) {
|
|
|
2787
2949
|
const raw = parsed?.mcpServers;
|
|
2788
2950
|
servers = typeof raw === "object" && raw !== null && !Array.isArray(raw) ? raw : {};
|
|
2789
2951
|
} catch {
|
|
2790
|
-
return content.includes(SERVER_NAME)
|
|
2952
|
+
return content.includes(SERVER_NAME) ? { kind: "problem", reason: `${path} is not valid JSON` } : { kind: "absent" };
|
|
2791
2953
|
}
|
|
2792
|
-
const
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
if (!isOurBridge(entry)) {
|
|
2796
|
-
continue;
|
|
2797
|
-
}
|
|
2798
|
-
const role = readRoleFromEntry(entry);
|
|
2799
|
-
if (role === void 0) {
|
|
2800
|
-
return { kind: "problem", reason: `${path} has the "${key}" bridge without a valid --role` };
|
|
2801
|
-
}
|
|
2802
|
-
if (!roles.includes(role)) {
|
|
2803
|
-
roles.push(role);
|
|
2804
|
-
}
|
|
2954
|
+
const entry = servers[SERVER_NAME];
|
|
2955
|
+
if (!isOurBridge(entry)) {
|
|
2956
|
+
return { kind: "absent" };
|
|
2805
2957
|
}
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
reason: `${path} has both the "${SERVER_NAME}" and the "${LEGACY_SERVER_NAME}" bridge with different roles (${roles.join(", ")})`
|
|
2810
|
-
};
|
|
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` };
|
|
2811
2961
|
}
|
|
2812
|
-
return
|
|
2962
|
+
return { kind: "role", role };
|
|
2813
2963
|
}
|
|
2814
2964
|
function rolesInFiles(files) {
|
|
2815
2965
|
const roles = [];
|
|
@@ -2826,7 +2976,7 @@ function rolesInFiles(files) {
|
|
|
2826
2976
|
}
|
|
2827
2977
|
async function findRepositoryRole(cwd, home = homedir2()) {
|
|
2828
2978
|
const stopAt = await realOrResolved(home);
|
|
2829
|
-
for (let dir = await realOrResolved(cwd); ; dir =
|
|
2979
|
+
for (let dir = await realOrResolved(cwd); ; dir = dirname3(dir)) {
|
|
2830
2980
|
if (samePath(dir, stopAt)) {
|
|
2831
2981
|
return { kind: "none" };
|
|
2832
2982
|
}
|
|
@@ -2856,7 +3006,7 @@ async function findRepositoryRole(cwd, home = homedir2()) {
|
|
|
2856
3006
|
}
|
|
2857
3007
|
return { kind: "role", role: result.roles[0], dir };
|
|
2858
3008
|
}
|
|
2859
|
-
if (
|
|
3009
|
+
if (dirname3(dir) === dir) {
|
|
2860
3010
|
return { kind: "none" };
|
|
2861
3011
|
}
|
|
2862
3012
|
}
|
|
@@ -2893,16 +3043,16 @@ async function readConfigInside(dir, path) {
|
|
|
2893
3043
|
if (!samePath(real, realDir) && !real.startsWith(realDir + sep) && !(process.platform === "win32" && real.toLowerCase().startsWith((realDir + sep).toLowerCase()))) {
|
|
2894
3044
|
throw new Error(`${path} points outside ${dir} (${real})`);
|
|
2895
3045
|
}
|
|
2896
|
-
return
|
|
3046
|
+
return readFile5(real, "utf8");
|
|
2897
3047
|
}
|
|
2898
3048
|
async function findGitRoot(cwd) {
|
|
2899
|
-
for (let at = resolve(cwd); ; at =
|
|
3049
|
+
for (let at = resolve(cwd); ; at = dirname3(at)) {
|
|
2900
3050
|
try {
|
|
2901
3051
|
await lstat(join2(at, ".git"));
|
|
2902
3052
|
return at;
|
|
2903
3053
|
} catch {
|
|
2904
3054
|
}
|
|
2905
|
-
if (
|
|
3055
|
+
if (dirname3(at) === at) {
|
|
2906
3056
|
return void 0;
|
|
2907
3057
|
}
|
|
2908
3058
|
}
|
|
@@ -2913,7 +3063,7 @@ async function readManagedFile(root, path) {
|
|
|
2913
3063
|
}
|
|
2914
3064
|
async function readIfExists(absolutePath) {
|
|
2915
3065
|
try {
|
|
2916
|
-
return await
|
|
3066
|
+
return await readFile5(absolutePath, "utf8");
|
|
2917
3067
|
} catch (error) {
|
|
2918
3068
|
if (isNodeError2(error, "ENOENT")) {
|
|
2919
3069
|
return void 0;
|
|
@@ -2926,7 +3076,7 @@ async function assertInsideRepository(root, relativePath) {
|
|
|
2926
3076
|
const realRoot = await realpath(root);
|
|
2927
3077
|
let realParent;
|
|
2928
3078
|
try {
|
|
2929
|
-
realParent = await realpath(
|
|
3079
|
+
realParent = await realpath(dirname3(absolutePath));
|
|
2930
3080
|
} catch (error) {
|
|
2931
3081
|
if (isNodeError2(error, "ENOENT")) {
|
|
2932
3082
|
return;
|
|
@@ -3029,9 +3179,6 @@ function initRefusal(files, previous) {
|
|
|
3029
3179
|
if (state.state === "corrupt" || state.state === "legacy") {
|
|
3030
3180
|
return `${state.reason}. Nothing was changed \u2014 move that file aside (or restore it) and run again.`;
|
|
3031
3181
|
}
|
|
3032
|
-
if (state.state === "absent" && legacyManagedRecord(files.get(HARNESS_CONFIG_PATH))) {
|
|
3033
|
-
return legacyManagedNotice(HARNESS_CONFIG_PATH);
|
|
3034
|
-
}
|
|
3035
3182
|
const carried = state.state === "valid" ? state.record : emptyRecord();
|
|
3036
3183
|
for (const [path, contract] of Object.entries(carried.files)) {
|
|
3037
3184
|
if (contract.scope !== "fields") {
|
|
@@ -3238,10 +3385,10 @@ function planUndo(input) {
|
|
|
3238
3385
|
const notes = [];
|
|
3239
3386
|
const tail = () => {
|
|
3240
3387
|
notes.push(`Codex keeps its own global config \u2014 remove it with: ${codexRemoveCommand()}`);
|
|
3241
|
-
notes.push(`If you set ${ROLE_ENV_VAR}
|
|
3388
|
+
notes.push(`If you set ${ROLE_ENV_VAR} anywhere, unset it too.`);
|
|
3242
3389
|
};
|
|
3243
3390
|
if (state.state === "absent") {
|
|
3244
|
-
notes.push(
|
|
3391
|
+
notes.push(`No record of a previous init in ${HARNESS_RECORD_PATH} \u2014 no local file was changed.`);
|
|
3245
3392
|
tail();
|
|
3246
3393
|
return { writes, deletes, codexCommand: codexRemoveCommand(), notes };
|
|
3247
3394
|
}
|
|
@@ -3502,95 +3649,6 @@ function previousRole(path, existing) {
|
|
|
3502
3649
|
return read.kind === "role" ? read.role : void 0;
|
|
3503
3650
|
}
|
|
3504
3651
|
|
|
3505
|
-
// src/core/harness/legacyLayout.ts
|
|
3506
|
-
var LEGACY_PATHS = new Map(
|
|
3507
|
-
[HARNESS_DOC_PATH, HARNESS_CONFIG_PATH, HARNESS_RECORD_PATH].map((path) => [path, toLegacy(path)])
|
|
3508
|
-
);
|
|
3509
|
-
var LEGACY_RECORD_PATH = toLegacy(HARNESS_RECORD_PATH);
|
|
3510
|
-
var LEGACY_SNAPSHOT_PATHS = [...LEGACY_PATHS.values()];
|
|
3511
|
-
function adoptLegacyLayout(disk) {
|
|
3512
|
-
const files = /* @__PURE__ */ new Map();
|
|
3513
|
-
for (const [path, content] of disk) {
|
|
3514
|
-
if (!LEGACY_SNAPSHOT_PATHS.includes(path)) {
|
|
3515
|
-
files.set(path, content);
|
|
3516
|
-
}
|
|
3517
|
-
}
|
|
3518
|
-
const view = { files, moves: [], legacyDirCreatedByUs: false, notes: [] };
|
|
3519
|
-
if (!LEGACY_SNAPSHOT_PATHS.some((path) => disk.get(path) !== void 0)) {
|
|
3520
|
-
return view;
|
|
3521
|
-
}
|
|
3522
|
-
const legacyRecord = disk.get(LEGACY_RECORD_PATH);
|
|
3523
|
-
if (legacyRecord === void 0) {
|
|
3524
|
-
const legacyConfig = LEGACY_PATHS.get(HARNESS_CONFIG_PATH);
|
|
3525
|
-
if (disk.get(HARNESS_RECORD_PATH) === void 0 && legacyManagedRecord(disk.get(legacyConfig))) {
|
|
3526
|
-
return { ...view, refusal: legacyManagedNotice(legacyConfig) };
|
|
3527
|
-
}
|
|
3528
|
-
view.notes.push(
|
|
3529
|
-
`${LEGACY_HARNESS_DIR}/ was left in place \u2014 there is no ${LEGACY_RECORD_PATH} showing that an earlier version of this CLI wrote it. Remove it by hand if it is a leftover.`
|
|
3530
|
-
);
|
|
3531
|
-
return view;
|
|
3532
|
-
}
|
|
3533
|
-
const state = readRecord(legacyRecord, LEGACY_RECORD_PATH);
|
|
3534
|
-
if (state.state !== "valid") {
|
|
3535
|
-
return {
|
|
3536
|
-
...view,
|
|
3537
|
-
refusal: state.state === "absent" ? `${LEGACY_RECORD_PATH} could not be read. Nothing was changed.` : `${state.reason}. Nothing was changed \u2014 move that file aside (or restore it) and run again.`
|
|
3538
|
-
};
|
|
3539
|
-
}
|
|
3540
|
-
const clash = [...LEGACY_PATHS].filter(([to, from]) => disk.get(to) !== void 0 && disk.get(from) !== void 0).map(([to]) => to);
|
|
3541
|
-
if (clash.length > 0) {
|
|
3542
|
-
return {
|
|
3543
|
-
...view,
|
|
3544
|
-
refusal: `Both ${LEGACY_HARNESS_DIR}/ and ${HARNESS_DIR}/ hold AiNecto setup files (${clash.join(", ")}): an older version of this CLI (0.4.x) ran here after this one, or a move was interrupted. Nothing was changed. Undo the older setup with \`npx -y -p @ai-erd/mcp@0.4.6 ai-erd init --undo\` (or remove the ${LEGACY_HARNESS_DIR}/ leftovers by hand), then run again.`
|
|
3545
|
-
};
|
|
3546
|
-
}
|
|
3547
|
-
const record = state.record;
|
|
3548
|
-
const renamed = {
|
|
3549
|
-
...record,
|
|
3550
|
-
files: Object.fromEntries(Object.entries(record.files).map(([path, contract]) => [toCurrent(path), contract])),
|
|
3551
|
-
// «하네스 폴더를 우리가 만들었다»는 같은 사실이다 — 이름만 옮긴다. 새 폴더를 이번에 만들면 적용이 또 적는다.
|
|
3552
|
-
createdDirectories: record.createdDirectories.map(toCurrent)
|
|
3553
|
-
};
|
|
3554
|
-
const moves = [];
|
|
3555
|
-
for (const [to, from] of LEGACY_PATHS) {
|
|
3556
|
-
const content = disk.get(from);
|
|
3557
|
-
if (content === void 0) {
|
|
3558
|
-
continue;
|
|
3559
|
-
}
|
|
3560
|
-
files.set(to, to === HARNESS_RECORD_PATH ? serializeRecord(renamed) : content);
|
|
3561
|
-
moves.push({ from, to });
|
|
3562
|
-
}
|
|
3563
|
-
return {
|
|
3564
|
-
files,
|
|
3565
|
-
moves,
|
|
3566
|
-
legacyDirCreatedByUs: record.createdDirectories.includes(LEGACY_HARNESS_DIR),
|
|
3567
|
-
notes: [`${LEGACY_HARNESS_DIR}/ \u2192 ${HARNESS_DIR}/: moved what an earlier version of this CLI wrote (the folder was renamed).`]
|
|
3568
|
-
};
|
|
3569
|
-
}
|
|
3570
|
-
function withLegacyMoves(plan, view) {
|
|
3571
|
-
if (view.moves.length === 0) {
|
|
3572
|
-
return view.notes.length === 0 ? plan : { ...plan, notes: [...view.notes, ...plan.notes] };
|
|
3573
|
-
}
|
|
3574
|
-
const movedTo = new Set(view.moves.map((move) => move.to));
|
|
3575
|
-
const planned = /* @__PURE__ */ new Set([...plan.writes.map((write) => write.path), ...plan.deletes]);
|
|
3576
|
-
const carried = view.moves.filter((move) => !planned.has(move.to)).map((move) => ({ path: move.to, content: view.files.get(move.to), existed: false }));
|
|
3577
|
-
return {
|
|
3578
|
-
...plan,
|
|
3579
|
-
// 새 경로는 디스크에 아직 없다 — «만든다»가 사실이다.
|
|
3580
|
-
writes: [...plan.writes.map((write) => movedTo.has(write.path) ? { ...write, existed: false } : write), ...carried],
|
|
3581
|
-
// 새 경로의 삭제는 디스크에 없는 파일이다 — 대신 옛 파일을 지운다.
|
|
3582
|
-
deletes: [...plan.deletes.filter((path) => !movedTo.has(path)), ...view.moves.map((move) => move.from)],
|
|
3583
|
-
notes: [...view.notes, ...plan.notes],
|
|
3584
|
-
removableDirectories: view.legacyDirCreatedByUs ? [...plan.removableDirectories ?? [], LEGACY_HARNESS_DIR] : plan.removableDirectories
|
|
3585
|
-
};
|
|
3586
|
-
}
|
|
3587
|
-
function toLegacy(path) {
|
|
3588
|
-
return `${LEGACY_HARNESS_DIR}${path.slice(HARNESS_DIR.length)}`;
|
|
3589
|
-
}
|
|
3590
|
-
function toCurrent(path) {
|
|
3591
|
-
return path === LEGACY_HARNESS_DIR || path.startsWith(`${LEGACY_HARNESS_DIR}/`) ? `${HARNESS_DIR}${path.slice(LEGACY_HARNESS_DIR.length)}` : path;
|
|
3592
|
-
}
|
|
3593
|
-
|
|
3594
3652
|
// src/core/harness/documentFetch.ts
|
|
3595
3653
|
var DOC_KEY2 = "HARNESS.DOC";
|
|
3596
3654
|
var AGENT_NOTE_KEY2 = "HARNESS.AGENT_NOTE";
|
|
@@ -3735,19 +3793,10 @@ async function runInit(initialOptions) {
|
|
|
3735
3793
|
const args = parseInitArgs(options.argv);
|
|
3736
3794
|
const root = resolve2(options.cwd);
|
|
3737
3795
|
const rootNotes = await repositoryRootNotes(root);
|
|
3738
|
-
const
|
|
3739
|
-
const layout = adoptLegacyLayout(disk);
|
|
3740
|
-
const files = layout.files;
|
|
3796
|
+
const files = await readSnapshot(root);
|
|
3741
3797
|
if (args.undo) {
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
{ action: args.dryRun ? "undo (dry-run)" : "undo", updated: [], removed: [], notes: [...rootNotes, layout.refusal] },
|
|
3745
|
-
{ json: options.json }
|
|
3746
|
-
));
|
|
3747
|
-
return 0;
|
|
3748
|
-
}
|
|
3749
|
-
const plan2 = withLegacyMoves(planUndo({ files }), layout);
|
|
3750
|
-
await applyPlan(root, plan2, args.dryRun, disk, false);
|
|
3798
|
+
const plan2 = planUndo({ files });
|
|
3799
|
+
await applyPlan(root, plan2, args.dryRun, files, false);
|
|
3751
3800
|
options.io.stdout.write(renderSuccess(
|
|
3752
3801
|
{
|
|
3753
3802
|
action: args.dryRun ? "undo (dry-run)" : "undo",
|
|
@@ -3759,7 +3808,7 @@ async function runInit(initialOptions) {
|
|
|
3759
3808
|
));
|
|
3760
3809
|
return 0;
|
|
3761
3810
|
}
|
|
3762
|
-
const refusal =
|
|
3811
|
+
const refusal = initRefusal(files);
|
|
3763
3812
|
if (refusal !== void 0) {
|
|
3764
3813
|
throw new Error(refusal);
|
|
3765
3814
|
}
|
|
@@ -3794,12 +3843,11 @@ async function runInit(initialOptions) {
|
|
|
3794
3843
|
const documents = args.dryRun ? { versions: {}, fallbacks: {}, unavailableReason: "skipped in --dry-run" } : await fetchHarnessDocuments({
|
|
3795
3844
|
endpoint: options.endpoint,
|
|
3796
3845
|
accessToken: await options.accessToken?.(),
|
|
3797
|
-
//
|
|
3798
|
-
|
|
3799
|
-
client: "ai-erd-cli",
|
|
3846
|
+
// 서버가 문서의 클라이언트별 변형 키를 이 이름으로 찾는다.
|
|
3847
|
+
client: "ainecto-cli",
|
|
3800
3848
|
fetchImpl: options.fetchImpl
|
|
3801
3849
|
});
|
|
3802
|
-
const plan =
|
|
3850
|
+
const plan = planInit({
|
|
3803
3851
|
role,
|
|
3804
3852
|
env: options.env,
|
|
3805
3853
|
projectName: project.name,
|
|
@@ -3808,11 +3856,11 @@ async function runInit(initialOptions) {
|
|
|
3808
3856
|
cliVersion: options.cliVersion,
|
|
3809
3857
|
files,
|
|
3810
3858
|
documents
|
|
3811
|
-
})
|
|
3859
|
+
});
|
|
3812
3860
|
if (plan.refusal !== void 0) {
|
|
3813
3861
|
throw new Error(plan.refusal);
|
|
3814
3862
|
}
|
|
3815
|
-
await applyPlan(root, plan, args.dryRun,
|
|
3863
|
+
await applyPlan(root, plan, args.dryRun, files);
|
|
3816
3864
|
if (!args.dryRun && previousRole2 !== void 0 && previousRole2 !== role) {
|
|
3817
3865
|
options.io.stderr.write(`Role changes from ${previousRole2} to ${role}: \`ainecto\` commands now, MCP tools from a new session.
|
|
3818
3866
|
`);
|
|
@@ -3824,8 +3872,6 @@ async function runInit(initialOptions) {
|
|
|
3824
3872
|
project: { uuid: project.uuid, name: project.name },
|
|
3825
3873
|
endpoint: options.endpoint,
|
|
3826
3874
|
written: plan.writes.map((write) => `${write.existed ? "updated" : "created"} ${write.path}`),
|
|
3827
|
-
// 옛 폴더(.ai-erd/)를 옮길 때만 생긴다 — 지운 것을 말하지 않으면 사용자는 옛 파일이 어디 갔는지 모른다.
|
|
3828
|
-
...plan.deletes.length > 0 ? { removed: plan.deletes } : {},
|
|
3829
3875
|
codex: `Codex keeps a single global config, so init does not touch it. Run: ${plan.codexCommand}`,
|
|
3830
3876
|
codexPerSession: plan.codexProfile,
|
|
3831
3877
|
codexEnvFallback: `Without a profile, start the session with ${ROLE_ENV_VAR}=${role}.`,
|
|
@@ -3848,9 +3894,6 @@ async function runInit(initialOptions) {
|
|
|
3848
3894
|
`Codex: save the profile above as $CODEX_HOME/${role}.config.toml and start with \`codex -p ${role}\`.`,
|
|
3849
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.`,
|
|
3850
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\`).`,
|
|
3851
|
-
// ★서버 이름이 바뀌었다(0.5.0). 예전에 같은 이름("ai-erd")이라 .mcp.json 이 덮던 사용자·Codex 전역 항목이
|
|
3852
|
-
// 이제는 «따로» 붙어, 그 세션이 역할 없는 서버를 하나 더 본다. 그 항목을 지우는 법을 같이 말한다.
|
|
3853
|
-
`If you added AiNecto earlier as "${LEGACY_SERVER_NAME}" (Claude Code user scope, or Codex), remove it \u2014 it no longer hides behind this "${SERVER_NAME}" entry: \`claude mcp remove ${LEGACY_SERVER_NAME} -s user\`, \`codex mcp remove ${LEGACY_SERVER_NAME}\`.`,
|
|
3854
3897
|
"Undo everything with: ainecto init --undo"
|
|
3855
3898
|
]
|
|
3856
3899
|
},
|
|
@@ -4044,11 +4087,11 @@ async function createProject(options, name) {
|
|
|
4044
4087
|
return first;
|
|
4045
4088
|
}
|
|
4046
4089
|
function unwrapToolJson(result) {
|
|
4047
|
-
if (!
|
|
4090
|
+
if (!isRecord11(result) || !Array.isArray(result.content)) {
|
|
4048
4091
|
return result;
|
|
4049
4092
|
}
|
|
4050
|
-
const first = result.content.find((item) =>
|
|
4051
|
-
if (!
|
|
4093
|
+
const first = result.content.find((item) => isRecord11(item) && item.type === "text");
|
|
4094
|
+
if (!isRecord11(first) || typeof first.text !== "string") {
|
|
4052
4095
|
return result;
|
|
4053
4096
|
}
|
|
4054
4097
|
try {
|
|
@@ -4058,13 +4101,13 @@ function unwrapToolJson(result) {
|
|
|
4058
4101
|
}
|
|
4059
4102
|
}
|
|
4060
4103
|
function extractProjects(value) {
|
|
4061
|
-
const candidates = Array.isArray(value) ? value :
|
|
4104
|
+
const candidates = Array.isArray(value) ? value : isRecord11(value) ? ["projects", "items", "created", "data", "hits"].flatMap((key) => {
|
|
4062
4105
|
const nested = value[key];
|
|
4063
4106
|
return Array.isArray(nested) ? nested : [];
|
|
4064
4107
|
}) : [];
|
|
4065
4108
|
const projects = [];
|
|
4066
4109
|
for (const candidate of candidates) {
|
|
4067
|
-
if (
|
|
4110
|
+
if (isRecord11(candidate) && typeof candidate.uuid === "string") {
|
|
4068
4111
|
projects.push({
|
|
4069
4112
|
uuid: candidate.uuid,
|
|
4070
4113
|
name: typeof candidate.name === "string" ? candidate.name : candidate.uuid
|
|
@@ -4086,7 +4129,7 @@ function boundProjectUuid(config) {
|
|
|
4086
4129
|
}
|
|
4087
4130
|
async function readSnapshot(root) {
|
|
4088
4131
|
const snapshot = /* @__PURE__ */ new Map();
|
|
4089
|
-
for (const path of
|
|
4132
|
+
for (const path of MANAGED_PATHS) {
|
|
4090
4133
|
snapshot.set(path, await readManagedFile(root, path));
|
|
4091
4134
|
}
|
|
4092
4135
|
return snapshot;
|
|
@@ -4105,9 +4148,9 @@ async function applyPlan(root, plan, dryRun, snapshot, recordFirst = true) {
|
|
|
4105
4148
|
}
|
|
4106
4149
|
const applied = [];
|
|
4107
4150
|
try {
|
|
4108
|
-
const
|
|
4109
|
-
const recordWrites = plan.writes.filter((write) =>
|
|
4110
|
-
const recordDeletes = plan.deletes.filter((path) =>
|
|
4151
|
+
const isRecord12 = (path) => !recordFirst && path === HARNESS_RECORD_PATH;
|
|
4152
|
+
const recordWrites = plan.writes.filter((write) => isRecord12(write.path));
|
|
4153
|
+
const recordDeletes = plan.deletes.filter((path) => isRecord12(path));
|
|
4111
4154
|
const createdDirectories = [...plan.record?.createdDirectories ?? []];
|
|
4112
4155
|
const rememberDirectories = (directories) => {
|
|
4113
4156
|
for (const directory of directories) {
|
|
@@ -4149,8 +4192,8 @@ async function applyPlan(root, plan, dryRun, snapshot, recordFirst = true) {
|
|
|
4149
4192
|
};
|
|
4150
4193
|
try {
|
|
4151
4194
|
await apply(
|
|
4152
|
-
orderedWrites(plan.writes.filter((write) => !
|
|
4153
|
-
plan.deletes.filter((path) => !
|
|
4195
|
+
orderedWrites(plan.writes.filter((write) => !isRecord12(write.path)), recordFirst),
|
|
4196
|
+
plan.deletes.filter((path) => !isRecord12(path))
|
|
4154
4197
|
);
|
|
4155
4198
|
await recordDirectoriesOrExplain(false);
|
|
4156
4199
|
await apply(recordWrites, recordDeletes);
|
|
@@ -4187,8 +4230,8 @@ async function assertUnchangedSinceSnapshot(root, relativePath, snapshot) {
|
|
|
4187
4230
|
}
|
|
4188
4231
|
async function writeManagedFile(root, write, onDirectoriesCreated) {
|
|
4189
4232
|
const absolutePath = join3(root, write.path);
|
|
4190
|
-
const targetDirectory =
|
|
4191
|
-
const firstCreated = await
|
|
4233
|
+
const targetDirectory = dirname4(absolutePath);
|
|
4234
|
+
const firstCreated = await mkdir3(targetDirectory, { recursive: true });
|
|
4192
4235
|
const created = createdChain(root, firstCreated, targetDirectory);
|
|
4193
4236
|
onDirectoriesCreated?.(created);
|
|
4194
4237
|
const previousMode = await fileMode(absolutePath);
|
|
@@ -4197,7 +4240,7 @@ async function writeManagedFile(root, write, onDirectoriesCreated) {
|
|
|
4197
4240
|
if (previousMode !== void 0) {
|
|
4198
4241
|
await chmod2(tmpPath, previousMode);
|
|
4199
4242
|
}
|
|
4200
|
-
await
|
|
4243
|
+
await rename3(tmpPath, absolutePath);
|
|
4201
4244
|
return created;
|
|
4202
4245
|
}
|
|
4203
4246
|
function createdChain(root, firstCreated, targetDirectory) {
|
|
@@ -4206,12 +4249,12 @@ function createdChain(root, firstCreated, targetDirectory) {
|
|
|
4206
4249
|
}
|
|
4207
4250
|
const stop = resolve2(firstCreated);
|
|
4208
4251
|
const made = [];
|
|
4209
|
-
for (let at = resolve2(targetDirectory); ; at =
|
|
4252
|
+
for (let at = resolve2(targetDirectory); ; at = dirname4(at)) {
|
|
4210
4253
|
const asRelative = relative(root, at);
|
|
4211
4254
|
if (asRelative && !asRelative.startsWith("..")) {
|
|
4212
4255
|
made.push(asRelative.split(sep2).join("/"));
|
|
4213
4256
|
}
|
|
4214
|
-
if (at === stop ||
|
|
4257
|
+
if (at === stop || dirname4(at) === at) {
|
|
4215
4258
|
break;
|
|
4216
4259
|
}
|
|
4217
4260
|
}
|
|
@@ -4221,7 +4264,7 @@ async function fileMode(absolutePath) {
|
|
|
4221
4264
|
try {
|
|
4222
4265
|
return (await stat3(absolutePath)).mode & 4095;
|
|
4223
4266
|
} catch (error) {
|
|
4224
|
-
if (
|
|
4267
|
+
if (isRecord11(error) && error.code === "ENOENT") {
|
|
4225
4268
|
return void 0;
|
|
4226
4269
|
}
|
|
4227
4270
|
throw error;
|
|
@@ -4261,7 +4304,7 @@ function basenameOf(path) {
|
|
|
4261
4304
|
const parts = path.split(/[\\/]/).filter(Boolean);
|
|
4262
4305
|
return parts[parts.length - 1] ?? "ainecto-project";
|
|
4263
4306
|
}
|
|
4264
|
-
function
|
|
4307
|
+
function isRecord11(value) {
|
|
4265
4308
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4266
4309
|
}
|
|
4267
4310
|
|
|
@@ -4365,7 +4408,7 @@ async function runAinectoCli(argv, io) {
|
|
|
4365
4408
|
});
|
|
4366
4409
|
}
|
|
4367
4410
|
let routeTools = getGeneratedTools(env);
|
|
4368
|
-
let remote = isAttachmentsUploadCommand(domain, action) || domain === "tools" && (action === "list" || action === "call") || matchGeneratedCommand(routeTools, parsed.positionals) !== void 0;
|
|
4411
|
+
let remote = isAttachmentsUploadCommand(domain, action) || isInboxPollCommand(domain, action) || domain === "tools" && (action === "list" || action === "call") || matchGeneratedCommand(routeTools, parsed.positionals) !== void 0;
|
|
4369
4412
|
if (!remote) {
|
|
4370
4413
|
const live = await loadLiveTools(env, connect(null).client).catch(() => void 0);
|
|
4371
4414
|
if (live && matchGeneratedCommand(live, parsed.positionals)) {
|
|
@@ -4386,6 +4429,9 @@ async function runAinectoCli(argv, io) {
|
|
|
4386
4429
|
io
|
|
4387
4430
|
});
|
|
4388
4431
|
}
|
|
4432
|
+
if (isInboxPollCommand(domain, action)) {
|
|
4433
|
+
return await executeInboxPollCommand({ domain, action, argv: rest, client, json: parsed.json, io });
|
|
4434
|
+
}
|
|
4389
4435
|
const generatedResult = await executeGeneratedCommand({
|
|
4390
4436
|
tools: routeTools,
|
|
4391
4437
|
argv: parsed.positionals,
|
|
@@ -4415,7 +4461,7 @@ async function commandRole(explicit, cwd) {
|
|
|
4415
4461
|
return explicitRole;
|
|
4416
4462
|
}
|
|
4417
4463
|
if (explicitRole !== null && explicitRole !== found.role) {
|
|
4418
|
-
const given = explicit.source === "env" ? `${
|
|
4464
|
+
const given = explicit.source === "env" ? `${ROLE_ENV_VAR}=${explicitRole}` : `--role ${explicitRole}`;
|
|
4419
4465
|
throw new Error(
|
|
4420
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.`
|
|
4421
4467
|
);
|
|
@@ -4550,6 +4596,11 @@ function helpText() {
|
|
|
4550
4596
|
" ainecto tools list [--env prod|dev] [--endpoint URL] [--json]",
|
|
4551
4597
|
" ainecto tools call <mcpName> [-f payload.json|@-] [inline-json] [--json]",
|
|
4552
4598
|
" ainecto attachments upload --document-uuid <uuid> <file...> [--json]",
|
|
4599
|
+
" ainecto notifications poll --cursor-file <path> [--kinds A,B] [--workspace <uuid>]... [--project <uuid>]... [--direct-only] [--limit N] [--json]",
|
|
4600
|
+
" ainecto notifications ack --cursor-file <path> (after the agent handled the polled batch)",
|
|
4601
|
+
" ainecto agent-todos poll [--workspace <uuid>]... [--project <uuid>]... [--only-new --seen-file <path>] [--json]",
|
|
4602
|
+
" --workspace and --project may repeat: any of the given workspaces AND any of the given projects",
|
|
4603
|
+
" poll exit codes: 0 new items \xB7 3 nothing new \xB7 1 error \xB7 2 usage error",
|
|
4553
4604
|
" ainecto <generated-command> [flags] [-f payload.json] [--yes] [--json]",
|
|
4554
4605
|
" ainecto mcp [--env prod|dev] [--endpoint URL] [--role design|development|test|validation]",
|
|
4555
4606
|
"",
|
|
@@ -4557,8 +4608,6 @@ function helpText() {
|
|
|
4557
4608
|
" ainecto init [--role <design|development|test|validation>] [--project <uuid>] [--dry-run]",
|
|
4558
4609
|
" In a terminal, asks for the role and project when they are not given.",
|
|
4559
4610
|
" ainecto init --undo",
|
|
4560
|
-
"",
|
|
4561
|
-
"`ai-erd` (the older name) is the same command and still works.",
|
|
4562
4611
|
""
|
|
4563
4612
|
].join("\n");
|
|
4564
4613
|
}
|