@ainecto/cli 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.
@@ -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 = "ai-erd:role:";
12
- var ROLE_HEADER = "X-AI-ERD-Role";
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
- for (const envVar of [ROLE_ENV_VAR, LEGACY_ROLE_ENV_VAR]) {
21
- const fromEnv = envVars[envVar];
22
- if (fromEnv && fromEnv.trim()) {
23
- return { role: assertRole(fromEnv), source: "env", envVar };
24
- }
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.ai-erd.com/mcp"
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) || LEGACY_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.loadStored();
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.loadStored();
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.5.0" : "dev";
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": "create_documents", "commandPath": ["documents", "create"], "group": "document", "description": "Create documents. If a document body is tens of KB or more, prefer request_upload_token (purpose=document.create) and upload the body with curl.", "inputSchema": { "properties": { "items": { "description": "Documents", "items": { "properties": { "body": { "description": "Deprecated compatibility alias for typePayload.body when document type is markdown. \uBCF8\uBB38\uC774 \uC218\uC2ED KB \uC774\uC0C1\uC73C\uB85C \uD06C\uBA74 LLM \uD1A0\uD070 \uC808\uC57D\uC744 \uC704\uD574 `request_upload_token (purpose=document.create)` \uB97C \uD638\uCD9C\uD574 uploadUrl \uC744 \uBC1B\uACE0 curl \uB85C \uC9C1\uC811 PUT \uC73C\uB85C \uBCF4\uB0BC \uAC83.", "type": "string" }, "folderUuid": { "description": "Optional folder UUID", "type": "string" }, "projectUuid": { "description": "Project UUID", "type": "string" }, "title": { "description": "Document title", "type": "string" }, "type": { "description": "Document type. Creatable values: erd, markdown, diagram, openapi, html.", "enum": ["erd", "markdown", "diagram", "openapi", "html"], "type": "string" }, "typePayload": { "description": "Type-specific payload for enabled document types. markdown: {body}; diagram: {diagramSource,sourceLanguage,edgeStyle,isModule,metadata}; openapi: {content}; html: {content} (a full HTML page).", "type": "object" } }, "required": ["projectUuid", "type", "title"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "105d051791cf595b" },
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": "Bridge type", "type": "string" }, "fromUuid": { "description": "Source", "type": "string" }, "label": { "description": "Edge label", "type": "string" }, "lineStyle": { "description": "Line style", "type": "string" }, "strokeColor": { "description": "Stroke color", "type": "string" }, "strokeWidth": { "description": "Stroke width", "type": "integer" }, "toUuid": { "description": "Target", "type": "string" }, "uuid": { "description": "Required for update/delete", "type": "string" } }, "required": ["action"], "type": "object" }, "type": "array" }, "groups": { "description": "Group operations", "items": { "properties": { "action": { "description": "Operation kind", "enum": ["create", "update", "delete"], "type": "string" }, "collapsed": { "description": "Collapsed state", "type": "boolean" }, "color": { "description": "Group color", "type": "string" }, "height": { "description": "Explicit height; omit to keep it derived from member nodes", "type": "number" }, "linkMode": { "description": "Link mode", "type": "string" }, "linkedDiagramUuid": { "description": "Linked diagram UUID", "type": "string" }, "name": { "description": "Group name", "type": "string" }, "parentGroupUuid": { "description": "Parent group UUID", "type": "string" }, "sortOrder": { "description": "Sort order", "type": "integer" }, "uuid": { "description": "Required for update/delete", "type": "string" }, "width": { "description": "Explicit width; omit to keep it derived from member nodes", "type": "number" } }, "required": ["action"], "type": "object" }, "type": "array" }, "nodes": { "description": "Node operations", "items": { "properties": { "action": { "description": "Operation kind", "enum": ["create", "update", "delete"], "type": "string" }, "fillColor": { "description": "Fill color", "type": "string" }, "groupUuid": { "description": "Group UUID; empty string clears membership", "type": "string" }, "height": { "description": "Height", "type": "number" }, "shape": { "description": "Node shape (start/end/diamond/cylinder/parallelogram/stadium/hexagon/rect/roundRect/...)", "enum": ["rect", "roundRect", "stadium", "diamond", "decision", "circle", "start", "doubleCircle", "end", "hexagon", "cylinder", "parallelogram"], "type": "string" }, "strokeColor": { "description": "Stroke color", "type": "string" }, "text": { "description": "Node text/label. Supports newlines (\\n) for multi-line display.", "maxLength": 4e3, "type": "string" }, "textColor": { "description": "Text color", "type": "string" }, "type": { "description": "Node type", "type": "string" }, "uuid": { "description": "Required for update/delete; optional for create", "type": "string" }, "width": { "description": "Width", "type": "number" }, "x": { "description": "X", "type": "number" }, "y": { "description": "Y", "type": "number" } }, "required": ["action"], "type": "object" }, "type": "array" } }, "required": ["diagramUuid", "description"], "type": "object" }, "required": ["description", "diagramUuid"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "add78d8edce88c86" },
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": "5de278aad6b11bc5" },
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": "list_documents", "commandPath": ["documents", "list"], "group": "document", "description": "", "inputSchema": { "properties": { "folderUuid": { "description": "Optional folder UUID", "type": "string" }, "limit": { "description": "Result cap when q is set (default 50)", "type": "integer" }, "projectUuid": { "description": "Project UUID", "type": "string" }, "q": { "description": "Optional full-text query. When provided, results come from the search index instead of folder listing.", "type": "string" }, "type": { "description": "Optional document type filter. Readable values: erd, markdown, diagram, openapi, html.", "enum": ["erd", "markdown", "diagram", "openapi", "html"], "type": "string" } }, "type": "object" }, "required": [], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "4086c52be4dd0c15" },
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 typed: {parentType:TEST_CASE|TEST_EXECUTION,parentUuid,filename,contentType,sizeBytes}.", "type": "string" }, "ttlSeconds": { "description": "TTL (\uCD08). \uAE30\uBCF8 300 (5\uBD84)", "type": "integer" } }, "required": ["purpose"], "type": "object" }, "required": ["purpose"], "payloadMode": "binary-upload", "destructive": false, "sourceCatalog": "prod", "schemaHash": "85867a646f6c652f" },
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": "d24e109383700433" },
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": "update_documents", "commandPath": ["documents", "update"], "group": "document", "description": "Update documents. If a replacement body is tens of KB or more, prefer request_upload_token (purpose=document.update) and upload the body with curl.", "inputSchema": { "properties": { "items": { "description": "Updates", "items": { "properties": { "body": { "description": "Deprecated compatibility alias for typePayload.body when document type is markdown. \uBCF8\uBB38\uC774 \uC218\uC2ED KB \uC774\uC0C1\uC73C\uB85C \uD06C\uBA74 LLM \uD1A0\uD070 \uC808\uC57D\uC744 \uC704\uD574 `request_upload_token (purpose=document.update)` \uB97C \uD638\uCD9C\uD574 uploadUrl \uC744 \uBC1B\uACE0 curl \uB85C \uC9C1\uC811 PUT \uC73C\uB85C \uBCF4\uB0BC \uAC83.", "type": "string" }, "title": { "description": "New title", "type": "string" }, "typePayload": { "description": "Type-specific payload for enabled document types. markdown: {body}; diagram: {diagramSource,sourceLanguage,edgeStyle,isModule,metadata}; openapi: {content}; html: {content} (a full HTML page).", "type": "object" }, "uuid": { "description": "Document UUID", "type": "string" } }, "required": ["uuid"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "4c3cfe4d25def672" },
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 (!isRecord7(response)) {
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 (!isRecord7(parsed)) {
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 isRecord7(value) {
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 mkdir2, rename as rename2, rm as rm2, rmdir, stat as stat3, writeFile as writeFile2 } from "fs/promises";
2016
- import { dirname as dirname3, join as join3, relative, resolve as resolve2, sep as sep2 } from "path";
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 (!isRecord8(entry) || !Array.isArray(entry.args)) {
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 isRecord8(servers) ? servers[ourKeyIn(servers) ?? SERVER_NAME] : void 0;
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 (!isRecord8(entry)) {
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) => [PACKAGE_NAME, LEGACY_PACKAGE_NAME].some((name) => arg === name || arg.startsWith(`${name}@`));
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", "ai-erd", "ai-erd-mcp"];
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 ourKey = ourKeyIn(servers);
2083
- const replaced = ourKey !== void 0;
2084
- const replacedEntry = ourKey === void 0 ? void 0 : servers[ourKey];
2085
- if (legacyBridgeIn(servers)) {
2086
- delete servers[LEGACY_SERVER_NAME];
2087
- }
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 ourKey = ourKeyIn(servers);
2098
- const removed = ourKey !== void 0;
2322
+ const removed = Object.prototype.hasOwnProperty.call(servers, SERVER_NAME);
2099
2323
  if (restore !== void 0) {
2100
- servers[ourKey ?? SERVER_NAME] = restore.value;
2324
+ servers[SERVER_NAME] = restore.value;
2101
2325
  return { content: stringify({ ...root, mcpServers: servers }), removed, emptied: false };
2102
2326
  }
2103
- if (ourKey !== void 0) {
2104
- delete servers[ourKey];
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 (!isRecord8(servers)) {
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 (!isRecord8(parsed)) {
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 isRecord8(value) {
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 LEGACY_MARKER_BEGIN = "<!-- ai-erd:begin -->";
2171
- var LEGACY_MARKER_END = "<!-- ai-erd:end -->";
2172
- var MARKERS = {
2173
- begin: [MARKER_BEGIN, LEGACY_MARKER_BEGIN],
2174
- end: [MARKER_END, LEGACY_MARKER_END]
2175
- };
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, withCurrentMarkers(body));
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(withCurrentMarkers(template ?? DEFAULT_AGENT_NOTE), {
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 open;
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 (!open) open = { char: fence.char, length: fence.length };
2316
- else if (fence.char === open.char && fence.length >= open.length && fence.info === "") open = void 0;
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 open !== void 0;
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 hit = ownedMarker(content, kind, 0); hit !== void 0; hit = ownedMarker(content, kind, hit.at + 1)) {
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
- let best;
2370
- for (const marker of MARKERS[kind]) {
2371
- const at = ownedMarkerIndex(content, marker, from);
2372
- if (at >= 0 && (best === void 0 || at < best.at)) {
2373
- best = { at, length: marker.length };
2374
- }
2375
- }
2376
- return best;
2584
+ return hit(ownedMarkerIndex(content, MARKERS[kind], from), MARKERS[kind]);
2377
2585
  }
2378
2586
  function anyMarker(content, kind, from) {
2379
- let best;
2380
- for (const marker of MARKERS[kind]) {
2381
- const at = content.indexOf(marker, from);
2382
- if (at >= 0 && (best === void 0 || at < best.at)) {
2383
- best = { at, length: marker.length };
2384
- }
2385
- }
2386
- return best;
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 open;
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 open !== void 0;
2598
+ return open2 !== void 0;
2395
2599
  }
2396
2600
  const fence = fenceOf(line);
2397
2601
  if (fence) {
2398
- if (!open) {
2399
- open = { char: fence.char, length: fence.length };
2400
- } else if (fence.char === open.char && fence.length >= open.length && fence.info === "") {
2401
- open = void 0;
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 open !== void 0;
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, recordPath = HARNESS_RECORD_PATH) {
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: `${HARNESS_RECORD_PATH2} is not valid JSON` };
2734
+ return { state: "corrupt", reason: `${HARNESS_RECORD_PATH} is not valid JSON` };
2533
2735
  }
2534
2736
  if (!isObject(parsed)) {
2535
- return { state: "corrupt", reason: `${HARNESS_RECORD_PATH2} is not a JSON object` };
2737
+ return { state: "corrupt", reason: `${HARNESS_RECORD_PATH} is not a JSON object` };
2536
2738
  }
2537
- if (parsed.kind !== RECORD_KIND && parsed.kind !== LEGACY_RECORD_KIND) {
2739
+ if (parsed.kind !== RECORD_KIND) {
2538
2740
  return {
2539
2741
  state: "corrupt",
2540
- reason: `${HARNESS_RECORD_PATH2} exists but is not an AiNecto init record (kind=${JSON.stringify(parsed.kind)})`
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: `${HARNESS_RECORD_PATH2} was written by another version of this CLI (record version ${JSON.stringify(parsed.version)}, this CLI reads ${RECORD_VERSION})`
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: `${HARNESS_RECORD_PATH2} has no "files" object` };
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: `${HARNESS_RECORD_PATH2} has an unusable entry for ${path} \u2014 it ${read.problem}`
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: `${HARNESS_RECORD_PATH2} has a malformed createdDirectories` };
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 readFile4, realpath } from "fs/promises";
2939
+ import { lstat, readFile as readFile5, realpath } from "fs/promises";
2778
2940
  import { homedir as homedir2 } from "os";
2779
- import { dirname as dirname2, join as join2, resolve, sep } from "path";
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) || content.includes(LEGACY_SERVER_NAME) ? { kind: "problem", reason: `${path} is not valid JSON` } : { kind: "absent" };
2952
+ return content.includes(SERVER_NAME) ? { kind: "problem", reason: `${path} is not valid JSON` } : { kind: "absent" };
2791
2953
  }
2792
- const roles = [];
2793
- for (const key of [SERVER_NAME, LEGACY_SERVER_NAME]) {
2794
- const entry = servers[key];
2795
- if (!isOurBridge(entry)) {
2796
- continue;
2797
- }
2798
- const role = readRoleFromEntry(entry);
2799
- if (role === void 0) {
2800
- return { kind: "problem", reason: `${path} has the "${key}" bridge without a valid --role` };
2801
- }
2802
- if (!roles.includes(role)) {
2803
- roles.push(role);
2804
- }
2954
+ const entry = servers[SERVER_NAME];
2955
+ if (!isOurBridge(entry)) {
2956
+ return { kind: "absent" };
2805
2957
  }
2806
- if (roles.length > 1) {
2807
- return {
2808
- kind: "problem",
2809
- reason: `${path} has both the "${SERVER_NAME}" and the "${LEGACY_SERVER_NAME}" bridge with different roles (${roles.join(", ")})`
2810
- };
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 roles.length === 0 ? { kind: "absent" } : { kind: "role", role: roles[0] };
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 = dirname2(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 (dirname2(dir) === dir) {
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 readFile4(real, "utf8");
3046
+ return readFile5(real, "utf8");
2897
3047
  }
2898
3048
  async function findGitRoot(cwd) {
2899
- for (let at = resolve(cwd); ; at = dirname2(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 (dirname2(at) === at) {
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 readFile4(absolutePath, "utf8");
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(dirname2(absolutePath));
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} (or the older ${LEGACY_ROLE_ENV_VAR}) anywhere, unset it too.`);
3388
+ notes.push(`If you set ${ROLE_ENV_VAR} anywhere, unset it too.`);
3242
3389
  };
3243
3390
  if (state.state === "absent") {
3244
- notes.push(legacyManagedRecord(input.files.get(HARNESS_CONFIG_PATH)) ? legacyManagedNotice(HARNESS_CONFIG_PATH) : `No record of a previous init in ${HARNESS_RECORD_PATH} \u2014 no local file was changed.`);
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 disk = await readSnapshot(root);
3739
- const layout = adoptLegacyLayout(disk);
3740
- const files = layout.files;
3796
+ const files = await readSnapshot(root);
3741
3797
  if (args.undo) {
3742
- if (layout.refusal !== void 0) {
3743
- options.io.stdout.write(renderSuccess(
3744
- { action: args.dryRun ? "undo (dry-run)" : "undo", updated: [], removed: [], notes: [...rootNotes, layout.refusal] },
3745
- { json: options.json }
3746
- ));
3747
- return 0;
3748
- }
3749
- const plan2 = withLegacyMoves(planUndo({ files }), layout);
3750
- await applyPlan(root, plan2, args.dryRun, disk, false);
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 = layout.refusal ?? initRefusal(files);
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
- // ⛔이 이름은 바꾸지 않는다 — 서버가 문서의 클라이언트별 변형 키(HARNESS.DOC.AI_ERD_CLI 등)를 이것으로
3798
- // 찾는다. 사람에게 안 보이는 서버 쪽 식별자다(역할 헤더·scope 와 같은 이유, 2026-10-11).
3799
- client: "ai-erd-cli",
3846
+ // 서버가 문서의 클라이언트별 변형 키를 이 이름으로 찾는다.
3847
+ client: "ainecto-cli",
3800
3848
  fetchImpl: options.fetchImpl
3801
3849
  });
3802
- const plan = withLegacyMoves(planInit({
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
- }), layout);
3859
+ });
3812
3860
  if (plan.refusal !== void 0) {
3813
3861
  throw new Error(plan.refusal);
3814
3862
  }
3815
- await applyPlan(root, plan, args.dryRun, disk);
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 (!isRecord9(result) || !Array.isArray(result.content)) {
4090
+ if (!isRecord11(result) || !Array.isArray(result.content)) {
4048
4091
  return result;
4049
4092
  }
4050
- const first = result.content.find((item) => isRecord9(item) && item.type === "text");
4051
- if (!isRecord9(first) || typeof first.text !== "string") {
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 : isRecord9(value) ? ["projects", "items", "created", "data", "hits"].flatMap((key) => {
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 (isRecord9(candidate) && typeof candidate.uuid === "string") {
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 [...MANAGED_PATHS, ...LEGACY_SNAPSHOT_PATHS]) {
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 isRecord10 = (path) => !recordFirst && (path === HARNESS_RECORD_PATH || path === LEGACY_RECORD_PATH);
4109
- const recordWrites = plan.writes.filter((write) => isRecord10(write.path));
4110
- const recordDeletes = plan.deletes.filter((path) => isRecord10(path));
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) => !isRecord10(write.path)), recordFirst),
4153
- plan.deletes.filter((path) => !isRecord10(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 = dirname3(absolutePath);
4191
- const firstCreated = await mkdir2(targetDirectory, { recursive: true });
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 rename2(tmpPath, absolutePath);
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 = dirname3(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 || dirname3(at) === at) {
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 (isRecord9(error) && error.code === "ENOENT") {
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 isRecord9(value) {
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" ? `${explicit.envVar}=${explicitRole}` : `--role ${explicitRole}`;
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
  }