@comment-io/cli 0.1.14-alpha.347 → 0.1.14-alpha.365
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.
|
Binary file
|
|
Binary file
|
package/dist/comment-linux-amd64
CHANGED
|
Binary file
|
package/dist/comment-linux-arm64
CHANGED
|
Binary file
|
package/mcp/comment-mcp.mjs
CHANGED
|
@@ -27047,7 +27047,7 @@ var agentRules = [
|
|
|
27047
27047
|
{ id: "markdown-line-breaks", severity: "must", summary: "Do not hard-wrap ordinary prose; use single newlines only for semantic line breaks." },
|
|
27048
27048
|
{ id: "never-send-by", severity: "must", summary: "Never send a client-authored by field." },
|
|
27049
27049
|
{ id: "always-get-before-editing", severity: "must", summary: "Read the current document before editing." },
|
|
27050
|
-
{ id: "per-doc-token-identify", severity: "must", summary: "Identify anonymous per-document tokens before
|
|
27050
|
+
{ id: "per-doc-token-identify", severity: "must", summary: "Identify anonymous per-document tokens before or during the first write." },
|
|
27051
27051
|
{ id: "read-only-use-comments", severity: "must", summary: "Use comments and suggestions on read-only documents." },
|
|
27052
27052
|
{ id: "report-api-bugs", severity: "should", summary: "Report API behavior that contradicts the docs after recovery fails once." },
|
|
27053
27053
|
{ id: "do-not-duplicate-reports", severity: "must", summary: "Do not file duplicate feedback from repeated retries." }
|
|
@@ -27072,15 +27072,15 @@ function formatAgentRuleLine(id, context) {
|
|
|
27072
27072
|
case "fetch-current-docs":
|
|
27073
27073
|
return `**Memory:** Save doc URLs and tokens the user gives you. Fetch ${llmsUrl} each session for the latest API.`;
|
|
27074
27074
|
case "bearer-token-is-identity":
|
|
27075
|
-
return "**Identity:** Your Bearer token is your identity. Do **not** send a `by` field - it is rejected with `400 UNEXPECTED_FIELD`. Registered agents (`as_...` tokens) are identified automatically from their handle. Per-doc tokens must
|
|
27075
|
+
return "**Identity:** Your Bearer token is your identity. Do **not** send a `by` field - it is rejected with `400 UNEXPECTED_FIELD`. Registered agents (`as_...` tokens) are identified automatically from their handle. Per-doc tokens must identify once: include `display_name` in your first JSON write body, or call `POST /agents/identify { display_name, slug }` before writing. If a user gives you a token-bearing share URL, extract the token and call `GET /docs/{slug}` with `Authorization: Bearer {token}`; if the response includes `your_token`, switch to that personal bearer token for all later requests.";
|
|
27076
27076
|
case "markdown-line-breaks":
|
|
27077
27077
|
return "**Markdown line breaks:** When creating or editing docs, do not hard-wrap ordinary prose at 80 columns. Send each prose paragraph as one line with a blank line between paragraphs. Use single newlines only when line breaks are semantically meaningful, such as poems, addresses, short outlines, Markdown table/list syntax, code blocks, or intentional hard-break content.";
|
|
27078
27078
|
case "never-send-by":
|
|
27079
|
-
return "- **Identity is derived from your Bearer token.** Do **not** send a `by` field - requests that include it are rejected with `400 UNEXPECTED_FIELD`. If this is a per-doc token,
|
|
27079
|
+
return "- **Identity is derived from your Bearer token.** Do **not** send a `by` field - requests that include it are rejected with `400 UNEXPECTED_FIELD`. If this is a per-doc token, include `display_name` in your first JSON write body or register once with `POST /agents/identify` (see below).";
|
|
27080
27080
|
case "always-get-before-editing":
|
|
27081
27081
|
return "- **Always GET the doc before editing.** Never guess at document content.";
|
|
27082
27082
|
case "per-doc-token-identify":
|
|
27083
|
-
return "- **Per-doc tokens must identify before
|
|
27083
|
+
return "- **Per-doc tokens must identify before or during the first write.** Include `display_name` in the first JSON write body, or call `POST /agents/identify` once per document token and then write.";
|
|
27084
27084
|
case "read-only-use-comments":
|
|
27085
27085
|
return "- **Read-only docs use comments and suggestions.** If `read_only` is true, route proposed changes through `POST /docs/:slug/comments` instead of PATCH unless you are the owner.";
|
|
27086
27086
|
case "report-api-bugs":
|
|
@@ -27091,7 +27091,7 @@ function formatAgentRuleLine(id, context) {
|
|
|
27091
27091
|
}
|
|
27092
27092
|
function formatAgentRuleBullet(id, context) {
|
|
27093
27093
|
if (id === "bearer-token-is-identity") {
|
|
27094
|
-
return "- **Identity is derived from your Bearer token.** Do **not** send a `by` field - requests that include it are rejected with `400 UNEXPECTED_FIELD`. If this is a per-doc token,
|
|
27094
|
+
return "- **Identity is derived from your Bearer token.** Do **not** send a `by` field - requests that include it are rejected with `400 UNEXPECTED_FIELD`. If this is a per-doc token, include `display_name` in your first JSON write body or register once with `POST /agents/identify` (see below).";
|
|
27095
27095
|
}
|
|
27096
27096
|
if (id === "report-api-bugs") {
|
|
27097
27097
|
return "- **Report API bugs while you work.** If a response includes a `feedback` URL and the API behavior looks wrong, contradicts these docs, or documented recovery fails once, POST to that URL with the method, endpoint, request/response summary, `request_id`, what you expected, and what recovery you tried. Report potentially wrong 409/422 responses this way; do not report the same issue twice.";
|
|
@@ -27180,7 +27180,7 @@ function apiReference(baseUrl, slug, token, sid) {
|
|
|
27180
27180
|
formatAgentRuleBullet("markdown-line-breaks", { baseUrl }),
|
|
27181
27181
|
`- **The document title is derived from the first non-empty markdown line.** Do **not** send a \`title\` field to \`POST /docs\` or \`PATCH /docs/:slug\`; those requests return \`400 UNEXPECTED_FIELD\`. To rename a doc, edit the first heading/line in \`markdown\`. \`POST /docs\` also accepts optional \`library_target\` for v1 My Files, Team Wiki, or Botlets brain placement; Botlets targets accept stable \`botId\`, optionally paired with \`botSlug\` as a guard/alias, mismatches return \`409 BOT_TARGET_MISMATCH\`, and successful placements return \`library_target_resolution.bot_id\`, \`requested_bot_slug\`, \`canonical_bot_slug\`, and \`slug_resolution\`.`,
|
|
27182
27182
|
formatAgentRuleBullet("report-api-bugs", { baseUrl }),
|
|
27183
|
-
`- **\`quote\` is required** for
|
|
27183
|
+
`- **\`quote\` is required** for text-selected comments and span suggestions. Whole-block comments and whole-block suggestions can instead target a durable block with \`block_id\` from \`content_blocks[].id\`; whole-block suggestions also require \`expected_revision\`. Responses include a read-only \`anchor.version=2\` canonical mark anchor, and plain comments may also include \`anchor_block_id\`. Replies use \`reply_to\` and inherit the parent thread anchor; compact replies can also use \`thread_id\` from \`threads[].id\`. Plain replies work on orphaned threads; suggestion replies require \`expected_revision\` and a live target anchor.`,
|
|
27184
27184
|
`- Prefer small targeted edits \u2014 other people may be editing concurrently.`,
|
|
27185
27185
|
`- **Resource-creation POSTs return \`201 Created\`, not \`200\`.** \`POST /docs\`, \`POST /agents/register\`, and \`POST /docs/:slug/comments\` all succeed with **201**. Treat any \`2xx\` as success \u2014 a \`status === 200\` check misreads success as failure. **Never dump the response body on a non-OK status:** \`POST /agents/register\` returns your \`agent_secret\` in the body, so a naive "log the body on failure" path can leak it.`,
|
|
27186
27186
|
`- **The created resource's id field differs by endpoint:** \`POST /docs\` and \`POST /agents/register\` return it as \`id\`; \`POST /docs/:slug/comments\` returns it as \`comment_id\`. Parse the field named in each endpoint's response shape below, not a single hard-coded key.`,
|
|
@@ -27190,41 +27190,66 @@ function apiReference(baseUrl, slug, token, sid) {
|
|
|
27190
27190
|
`\`\`\`bash`,
|
|
27191
27191
|
`curl -s -H "Authorization: Bearer ${token}"${skillH} "${api}"`,
|
|
27192
27192
|
`\`\`\``,
|
|
27193
|
-
`Response (200):`,
|
|
27193
|
+
`Response (200, compact thread shape):`,
|
|
27194
27194
|
`\`\`\`json`,
|
|
27195
27195
|
`{`,
|
|
27196
27196
|
` "id": "${slug}",`,
|
|
27197
27197
|
` "title": "Doc Title",`,
|
|
27198
|
+
` "revision": 5,`,
|
|
27199
|
+
` "your_role": "editor",`,
|
|
27200
|
+
` "read_only": false,`,
|
|
27198
27201
|
` "markdown": "# Content\\n\\nDocument text.",`,
|
|
27199
|
-
` "
|
|
27202
|
+
` "content_blocks": [`,
|
|
27203
|
+
` { "id": "bid_...", "range": { "from": 0, "to": 9 } }`,
|
|
27204
|
+
` ],`,
|
|
27205
|
+
` "threads": [`,
|
|
27200
27206
|
` {`,
|
|
27201
|
-
` "
|
|
27207
|
+
` "id": "thr_...",`,
|
|
27208
|
+
` "block_id": "bid_...",`,
|
|
27202
27209
|
` "range": { "from": 142, "to": 186 },`,
|
|
27210
|
+
` "quote": "anchored text",`,
|
|
27211
|
+
` "resolved": false,`,
|
|
27212
|
+
` "suggestions": { "pending": 1, "stale": 0 },`,
|
|
27203
27213
|
` "comments": [`,
|
|
27204
|
-
` { "id": "uuid", "
|
|
27205
|
-
` { "id": "uuid", "
|
|
27206
|
-
`
|
|
27207
|
-
`
|
|
27208
|
-
`
|
|
27209
|
-
` "quote": "original text",`,
|
|
27210
|
-
` "range": { "from": 50, "to": 63 },`,
|
|
27211
|
-
` "comments": [`,
|
|
27212
|
-
` { "id": "uuid", "kind": "comment", "by": "ai:max.reviewer", "text": "This should be clearer", "created_at": "...", "resolved": false,`,
|
|
27213
|
-
` "suggestion": { "new_string": "better text", "status": "pending" }, "anchor": { "version": 2, "kind": "block_segments", "scope": "range", "segments": [{ "block_id": "bid_...", "start_offset": 0, "end_offset": 13, "quote": "original text" }], "quote": "original text" } }`,
|
|
27214
|
-
` ]`,
|
|
27214
|
+
` { "id": "uuid", "by": "ai:max.reviewer", "at": "...", "text": "comment body" },`,
|
|
27215
|
+
` { "id": "uuid", "by": "human:max", "at": "...", "text": "This should be clearer",`,
|
|
27216
|
+
` "suggestion": { "old_string": "original text", "new_string": "better text", "status": "pending", "created_against_revision": 4 } }`,
|
|
27217
|
+
` ],`,
|
|
27218
|
+
` "comments_info": { "returned": 2, "total": 2, "omitted": 0, "next": null }`,
|
|
27215
27219
|
` }`,
|
|
27216
27220
|
` ],`,
|
|
27221
|
+
` "threads_info": { "returned": 1, "total": 1, "next": null },`,
|
|
27222
|
+
` "suggestions_info": { "pending": 1, "stale": 0 },`,
|
|
27217
27223
|
` "actors": {`,
|
|
27218
|
-
` "ai:max.reviewer": { "actor_id": "ai:max.reviewer", "handle": "max.reviewer", "name": "Max's Reviewer", "
|
|
27219
|
-
` "human:max": { "actor_id": "human:max", "handle": "max", "name": "Max", "
|
|
27224
|
+
` "ai:max.reviewer": { "actor_id": "ai:max.reviewer", "handle": "max.reviewer", "name": "Max's Reviewer", "is_human": false, "kind_label": "AI agent" },`,
|
|
27225
|
+
` "human:max": { "actor_id": "human:max", "handle": "max", "name": "Max", "is_human": true, "kind_label": "Human" }`,
|
|
27220
27226
|
` },`,
|
|
27221
|
-
` "
|
|
27222
|
-
` "revision": 5, "active_agents": [], "your_role": "editor",`,
|
|
27223
|
-
` "created_at": "...", "updated_at": "...",`,
|
|
27224
|
-
` "api_docs": "..."`,
|
|
27227
|
+
` "api_reference_url": "${baseUrl}/llms.txt"`,
|
|
27225
27228
|
`}`,
|
|
27226
27229
|
`\`\`\``,
|
|
27227
|
-
|
|
27230
|
+
``,
|
|
27231
|
+
`#### Compact read shape`,
|
|
27232
|
+
`Per-doc agent reads use the compact thread shape by default. The \`shape\` selector is retired; omit it. Use \`Authorization: Bearer ...\`; agent doc routes reject \`?token=\` query auth so pagination URLs never carry secrets. If a GET authenticated with a shared invite token returns \`your_token\`, switch to that personal bearer token before writing. The full response keeps \`markdown\`, slim \`content_blocks[]\` (\`id\` + UTF-16 \`range\`; add \`include=block_quotes\` only when needed), \`threads[]\`, \`threads_info\`, \`suggestions_info\`, \`actors\`, \`your_token\` when minted, \`display_name\`, and \`api_reference_url\`. Add \`include=authorship\` to include authorship ranges.`,
|
|
27233
|
+
`\`threads[]\` replaces legacy \`blocks[]\`. Each thread has a stable \`thr_...\` id, \`block_id\`, \`range\`, \`quote\` for span threads, \`whole_block: true\` with \`quote: null\` for non-orphaned whole-block threads, \`orphaned: true\` with \`block_id: null\` and empty collapsed range/quote when a stored mark or thread anchor no longer resolves, \`resolved\`, optional \`resolution\` with the latest resolver note, \`suggestions\`, a bounded \`comments[]\` window, and \`comments_info\`. Pending suggestions on orphaned threads report \`stale\`. \`threads_info.next\` returns lean pages \`{ revision, threads, threads_info, actors }\`; \`comments_info.next\` returns \`{ revision, thread_id, comments, comments_info, actors }\`. Cursor-page \`actors\` maps are page-local and cover the returned comments/meta. Follow every non-null \`next\` with the same Authorization header and dedupe comments defensively by \`id\`.`,
|
|
27234
|
+
`Thread pages accept post-capture filters: \`mentions=me\` returns threads whose stored mention metadata references the requesting credential actor, \`since_revision=N\` returns threads with a captured comment, resolve/unresolve, or suggestion-status write newer than N, \`resolved=false\` returns unresolved threads, and \`suggestions=pending|stale|open\` returns threads with matching suggestion state. Filtered \`threads_info.total\` counts matching threads; \`threads_info.doc_total\` appears when the unfiltered document thread count differs. Pagination URLs preserve the filters.`,
|
|
27235
|
+
`The compact thread page is byte-budgeted, not count-capped. The server fills the non-markdown envelope with newest threads first, then shrinks a large thread's comment window before deferring that thread to the next page. Every returned thread keeps at least its root comment and one recent reply when available; follow \`comments_info.next\` for omitted middle comments.`,
|
|
27236
|
+
`On compact cursor pages, thread ranges are for the returned \`revision\`; interpret them against a matching full response's \`markdown\` or re-GET and use \`quote\` as the drift check. Pre-cutover replies whose parent relationship was already removed from storage may group by their surviving anchor rather than the historical parent thread; new replies record server-side compact membership outside browser-visible mark state.`,
|
|
27237
|
+
``,
|
|
27238
|
+
`#### Legacy field map`,
|
|
27239
|
+
`Older internal notes may mention \`blocks[]\`, \`content_blocks[].block_id\`, or \`api_docs\`. The live agent shape is \`threads[]\`, durable \`content_blocks[].id\` (\`bid_...\`), and \`quickstart\` plus \`api_reference_url\`. Block text from \`content_blocks[].quote\` is omitted by default and is available only with \`include=block_quotes\`; per-comment/block \`resolved\` is now thread-level \`threads[].resolved\`; bounded comment windows use \`threads[].comments[]\` with \`comments_info\`.`,
|
|
27240
|
+
`Auth rule: send \`Authorization: Bearer {token}\` for agent REST requests. Do not put credentials in \`?token=\` on agent doc routes; they return \`AUTH_HEADER_REQUIRED\`. Browser share links and the editor WebSocket are separate browser surfaces and may still use token-bearing URLs.`,
|
|
27241
|
+
`Write rule: keep using string-matched \`quote\`, \`old_string\`, \`after\`, and \`before\`. For block targets, prefer durable \`bid_...\` ids from \`content_blocks[].id\`; \`blk_...\` ids are a compatibility alias only. For replies and resolution, prefer stable \`thread_id\` routes when you are acting on a thread.`,
|
|
27242
|
+
``,
|
|
27243
|
+
`#### Offset units`,
|
|
27244
|
+
`All response ranges and offsets use UTF-16 code units, the same units as JavaScript \`String.prototype.slice\`. \`authorship[].from/to\`, \`content_blocks[].range\`, \`threads[].range\`, and edit diagnostics such as \`snippets[].offset\` index into the exact \`markdown\` string from the same response. Comment anchor \`segments[].start_offset/end_offset\` index into the containing block text; \`segments[].quote\` is the expected slice result. ASCII offsets match byte and code point indexes; emoji and some composed characters do not.`,
|
|
27245
|
+
``,
|
|
27246
|
+
`Non-JS clients should validate any range they plan to use by comparing the server quote to a UTF-16 slice of the exact \`markdown\` string from the same response:`,
|
|
27247
|
+
`\`\`\`python`,
|
|
27248
|
+
`def slice_utf16_units(text, start, end):`,
|
|
27249
|
+
` data = text.encode("utf-16-le")`,
|
|
27250
|
+
` return data[start * 2:end * 2].decode("utf-16-le")`,
|
|
27251
|
+
`\`\`\``,
|
|
27252
|
+
`If your local slice does not equal the server-provided \`quote\`, locate the \`quote\` with string search instead of trusting the offset. Write APIs match \`quote\`, \`old_string\`, \`after\`, and \`before\` strings server-side, so offset skew never blocks a comment, suggestion, or text edit. For block-level checks, slice \`markdown\` by \`content_blocks[].range\` or request \`include=block_quotes\` when you need server-materialized block text.`,
|
|
27228
27253
|
``,
|
|
27229
27254
|
`Each comment's \`by\` field is the server-derived author actor_id (resolved from the author's Bearer token at write time). Look it up in the sibling \`actors\` map for display fields. **Never send \`by\` in a request body** \u2014 it is rejected with \`400 UNEXPECTED_FIELD\`.`,
|
|
27230
27255
|
``,
|
|
@@ -27267,8 +27292,8 @@ function apiReference(baseUrl, slug, token, sid) {
|
|
|
27267
27292
|
`#### Read-only docs (\`read_only\`)`,
|
|
27268
27293
|
`If the GET response has \`"read_only": true\`, the owner has locked the document. Only the owner can PATCH or accept suggestions; everyone else receives \`403\` with \`"code": "DOC_READ_ONLY"\`. Comments and suggestions still work \u2014 route all change proposals through \`POST /docs/:slug/comments\` until the owner unlocks the doc or accepts your suggestion.`,
|
|
27269
27294
|
``,
|
|
27270
|
-
`Comments are grouped
|
|
27271
|
-
`Do not read thread structure from a comment-level \`reply_to\` field in GET responses.
|
|
27295
|
+
`Comments are grouped in \`threads[]\`. Each thread has a stable \`thr_...\` id, optional \`block_id\`, \`range\`, \`quote\` for span threads, \`resolved\`, \`suggestions\`, and chronological \`comments[]\`; use \`thread_id\` or \`reply_to\` for replies.`,
|
|
27296
|
+
`Do not read thread structure from a comment-level \`reply_to\` field in GET responses. It may appear as \`null\`; ignore it and use \`threads[]\`.`,
|
|
27272
27297
|
``,
|
|
27273
27298
|
`#### Deep-link with \`?focus=\``,
|
|
27274
27299
|
`Add \`?focus=comment-{id}\` to the GET request to receive a \`focused\` field in the response pointing to the specific comment:`,
|
|
@@ -27291,7 +27316,7 @@ function apiReference(baseUrl, slug, token, sid) {
|
|
|
27291
27316
|
`\`\`\``,
|
|
27292
27317
|
`Response (200): \`{ "actor_id": "ai:anon.tkn....", "display_name": "My Agent" }\``,
|
|
27293
27318
|
``,
|
|
27294
|
-
`Idempotent \u2014 call it again to rename yourself. Until the token is identified,
|
|
27319
|
+
`Idempotent \u2014 call it again to rename yourself. On JSON mutating endpoints you can also include \`display_name\` in the first write body to register the token without a separate roundtrip. Already-identified personal tokens ignore inline \`display_name\`; already-identified shared invite tokens reject a different inline name with \`409 IDENTITY_MISMATCH\`, so use the \`your_token\` from GET when it is present. Until the token is identified, writes without inline \`display_name\` return:`,
|
|
27295
27320
|
`\`\`\`json`,
|
|
27296
27321
|
`{ "error": "Register a display name with POST /agents/identify before making write requests with this token.",`,
|
|
27297
27322
|
` "code": "IDENTIFY_REQUIRED", "next": "POST /agents/identify", "slug": "${slug}" }`,
|
|
@@ -27324,6 +27349,7 @@ function apiReference(baseUrl, slug, token, sid) {
|
|
|
27324
27349
|
`- \`after\`: insert after this text. \`null\` = insert at beginning of the document.`,
|
|
27325
27350
|
`- \`before\`: insert before this text. \`null\` = insert at end of the document.`,
|
|
27326
27351
|
`- At least one anchor is required. Do NOT combine with \`old_string\` (returns 400).`,
|
|
27352
|
+
`- Anchors are literal text positions: the insert lands exactly where the anchor text ends (or begins, for \`before\`) \u2014 on the SAME line. To insert a block (paragraph, list, table) after an anchor, start \`new_string\` with \`"\\n\\n"\`; otherwise the text is spliced into the anchor's line. Splices that would corrupt structure are rejected with \`INVALID_MARKDOWN\` (a list marker glued mid-line, or prose glued into a heading line).`,
|
|
27327
27353
|
`- Anchors should match the canonical \`markdown\` from GET after server normalization/escaping; the server also applies the same Markdown-control canonicalization fallback as \`old_string\`. Use both anchors to disambiguate repeated text on current-base requests.`,
|
|
27328
27354
|
`- \`base_revision\` is required for anchor-based inserts. If it is stale, exactly one non-empty \`after\` or \`before\` anchor can be rebased as cursor/paste semantics against the current markdown using that same canonical matching behavior. A single \`before: null\` boundary append rebases to the current document end, and a single \`after: null\` boundary prepend rebases to the current document start. Paired \`after\`+\`before\`, \`at\` offsets, paired/mixed block targets, and legacy \`blk_*\` block targets still require a current \`base_revision\` and return \`EDIT_STALE\` when stale.`,
|
|
27329
27355
|
`- For list appends after a text anchor, send a block-separated list item such as \`"\\n\\n- note\\n"\`. A single leading newline before a list marker is normalized for anchor inserts. If multiple stale agents append list items after the same anchor, later commits append after the existing list under that anchor, preserving commit order.`,
|
|
@@ -27344,6 +27370,9 @@ function apiReference(baseUrl, slug, token, sid) {
|
|
|
27344
27370
|
`- A cell value that repeats across rows (e.g. a status used in several rows) makes a plain \`old_string\` ambiguous and returns \`EDIT_AMBIGUOUS\`. To target one specific cell, retry with \`at\` set to that occurrence's offset from the error's \`snippets[].offset\`. \`at\` requires \`base_revision\`, so include it: \`{"edits": [{"at": OFFSET, "old_string": "in progress", "new_string": "done"}], "base_revision": REVISION}\`. \`at\` resolves the edit positionally, so duplicate values elsewhere no longer matter.`,
|
|
27345
27371
|
`- Do not put a raw \`|\` in \`new_string\` \u2014 it adds a column and the edit is rejected (422, document unchanged). For a literal pipe inside a cell, escape it as \`\\|\`.`,
|
|
27346
27372
|
`- Delete a whole table by matching its full markdown (every row, including the \`---\` separator line) with \`new_string: ""\`. Send it as its own edit \u2014 bundling a table delete with other edits in one batch may fail with a structural rejection. The document also needs other content besides the table \u2014 deleting the only block in a document is not supported and returns 422.`,
|
|
27373
|
+
`- Multi-row rewrites, adding/removing rows while editing others, and whole-table restructures are applied in one edit; when row identity cannot be preserved the server replaces the table as a block. Body rows with FEWER cells than the header are padded with empty cells; rows with EXTRA cells are rejected (usually an unescaped \`|\`).`,
|
|
27374
|
+
`- Convert a bullet list into a table (or a table into a list) by replacing the whole block's markdown in one edit. Comments anchored inside the converted block must be declared via \`comment_outcomes\`.`,
|
|
27375
|
+
`- Multi-item list edits (changing, adding, and removing several items in one edit) are supported; verbatim-unchanged items keep their comment anchors. Pure item reorders are rejected \u2014 move content with explicit anchored edits instead.`,
|
|
27347
27376
|
``,
|
|
27348
27377
|
`#### Batch edits`,
|
|
27349
27378
|
`Send multiple edits in one request. Current-base batches are applied sequentially \u2014 later edits see the result of earlier ones:`,
|
|
@@ -27393,20 +27422,20 @@ function apiReference(baseUrl, slug, token, sid) {
|
|
|
27393
27422
|
` -H "Content-Type: application/json" \\`,
|
|
27394
27423
|
` -d '{"text": "your comment", "quote": "exact text from doc"}'`,
|
|
27395
27424
|
`\`\`\``,
|
|
27396
|
-
`Response (201): \`{ "comment_id": "uuid", "created_at": "...", "revision": N, "anchor": { "version": 2, "kind": "block_segments", ... }, "anchor_block_id": "bid_..." }\` for plain comments. \`anchor_block_id\` is a convenience field for block comments; use the read-only \`anchor\` object as the canonical mark anchor. If mention dispatch is capped, blocked, or cannot resolve a visible/explicit handle, the response includes \`warning\` with details.`,
|
|
27397
|
-
`The \`quote\` is matched against the document's markdown \u2014 slice it directly from the \`markdown\` field on GET
|
|
27425
|
+
`Response (201): \`{ "comment_id": "uuid", "created_at": "...", "revision": N, "anchor": { "version": 2, "kind": "block_segments", ... }, "anchor_block_id": "bid_..." }\` for plain comments. \`anchor_block_id\` is a convenience field for single-block plain comments; use the read-only \`anchor\` object as the canonical mark anchor. If mention dispatch is capped, blocked, or cannot resolve a visible/explicit handle, the response includes \`warning\` with details.`,
|
|
27426
|
+
`The \`quote\` is matched against the document's markdown \u2014 slice it directly from the \`markdown\` field on GET. For whole-block text, slice \`markdown\` by \`content_blocks[].range\` or request \`include=block_quotes\`. Markdown markup like \`## \` headings, \`**bold**\`, list markers, and inline code spans are part of the match string, not stripped.`,
|
|
27398
27427
|
``,
|
|
27399
|
-
`Or anchor to a specific block by id. This is the most stable target form for a whole-block plain comment; the create response includes the canonical \`anchor.version=2\` object and usually the convenience \`anchor_block_id\`:`,
|
|
27428
|
+
`Or anchor to a specific block by id. This is the most stable target form for a whole-block plain comment or whole-block suggestion; the create response includes the canonical \`anchor.version=2\` object and plain comments usually include the convenience \`anchor_block_id\`:`,
|
|
27400
27429
|
`\`\`\`bash`,
|
|
27401
27430
|
`curl -s -X POST "${api}/comments" \\`,
|
|
27402
27431
|
` -H "Authorization: Bearer ${token}"${skillH} \\`,
|
|
27403
27432
|
` -H "Content-Type: application/json" \\`,
|
|
27404
27433
|
` -d '{"text": "your comment", "block_id": "bid_..."}'`,
|
|
27405
27434
|
`\`\`\``,
|
|
27406
|
-
`Use durable \`content_blocks[].id\` (\`bid_...\`) for \`block_id\`. Deprecated \`content_blocks[].block_id\` (\`blk_...\`) remains accepted only as a current-revision compatibility alias and returns \`Deprecation: block_id\`. \`block_id\` is mutually exclusive with \`quote\`, \`
|
|
27435
|
+
`Use durable \`content_blocks[].id\` (\`bid_...\`) for \`block_id\`. Deprecated \`content_blocks[].block_id\` (\`blk_...\`) remains accepted only as a current-revision compatibility alias and returns \`Deprecation: block_id\`. \`block_id\` is mutually exclusive with \`quote\`, \`reply_to\`, and \`thread_id\`; include \`suggestion.new_string\` with \`block_id\` to suggest replacing the whole block, with \`old_string\` captured from the block text at creation. Whole-block suggestions must also include \`expected_revision\` from the GET response; if that block changed since then, the server returns \`409 BLOCK_STALE\` with the live \`revision\` and \`current_text\`. Durable target failures return current \`markdown\`, \`revision\`, and \`content_blocks\` so you can re-read and retry.`,
|
|
27407
27436
|
``,
|
|
27408
27437
|
`### Reply to a comment`,
|
|
27409
|
-
`Use \`reply_to\` with a comment ID to post to the same
|
|
27438
|
+
`Use \`reply_to\` with a comment ID to post to the same thread anchor. No \`quote\` needed:`,
|
|
27410
27439
|
`\`\`\`bash`,
|
|
27411
27440
|
`curl -s -X POST "${api}/comments" \\`,
|
|
27412
27441
|
` -H "Authorization: Bearer ${token}"${skillH} \\`,
|
|
@@ -27415,6 +27444,7 @@ function apiReference(baseUrl, slug, token, sid) {
|
|
|
27415
27444
|
`\`\`\``,
|
|
27416
27445
|
`Response (201): \`{ "comment_id": "uuid", "created_at": "...", "revision": N, "anchor_block_id": "bid_...", "anchor": { "version": 2, "kind": "block_segments", "scope": "block", "segments": [{ "block_id": "bid_...", "start_offset": 0, "end_offset": 13, "quote": "original text" }], "quote": "original text" } }\``,
|
|
27417
27446
|
`The reply appears in the same block-thread as the parent. No separate parent pointer is stored or returned; during migration GET responses may include \`"reply_to": null\`, which should be ignored.`,
|
|
27447
|
+
`Replies can target a stable thread id: send \`POST /docs/:slug/comments\` with \`{"text":"your reply","thread_id":"thr_..."}\`. Include \`suggestion.new_string\` plus \`expected_revision\` from the GET response to create a competing suggestion on the same live thread anchor; stale target blocks return \`409 BLOCK_STALE\`, and orphaned threads reject new suggestions with \`ORPHANED_ANCHOR\` because the target text no longer resolves. Use Authorization: Bearer auth; query-token URLs return \`AUTH_HEADER_REQUIRED\`. Plain replies work for orphaned threads too. \`thread_id\` is mutually exclusive with \`quote\`, \`block_id\`, and \`kind:"resolution"\`; if you send both \`reply_to\` and \`thread_id\`, the comment id must already belong to that thread.`,
|
|
27418
27448
|
``,
|
|
27419
27449
|
`### Suggest a change`,
|
|
27420
27450
|
`Add a \`suggestion\` field to create a suggestion instead of a plain comment. \`quote\` is required \u2014 it identifies the text being replaced:`,
|
|
@@ -27436,8 +27466,10 @@ function apiReference(baseUrl, slug, token, sid) {
|
|
|
27436
27466
|
`\`text\` is required and explains why the thread is resolved.`,
|
|
27437
27467
|
`Response (200): \`{ "comment_id": "cid", "resolution_id": "...", "resolved": true, "revision": N }\``,
|
|
27438
27468
|
``,
|
|
27439
|
-
`Resolving a thread
|
|
27469
|
+
`Resolving a thread sets the thread-level resolved state. Compact reads expose this as \`threads[].resolved: true\`; \`threads[].resolution\`, when present, contains the latest resolver note. Do not infer compact resolved state from entries in \`threads[].comments[]\`.`,
|
|
27440
27470
|
`If the thread's canonical anchor no longer resolves, resolve returns \`422 MARK_ANCHOR_NOT_FOUND\`; re-GET the document and use a current visible comment id.`,
|
|
27471
|
+
`Resolve by stable thread id: \`POST /docs/:slug/threads/{thread_id}/resolve\` with \`{"text":"Resolved because ..."}\`. Use Authorization: Bearer auth; query-token URLs return \`AUTH_HEADER_REQUIRED\`, and a second resolve returns \`409 ALREADY_RESOLVED\`. This works for orphaned threads when their stored canonical anchor is available.`,
|
|
27472
|
+
`To reopen a thread resolved through the thread route, call \`POST /docs/:slug/threads/{thread_id}/unresolve\`. It deletes pointer-bearing resolution marks for that thread and returns \`{ "thread_id": "thr_...", "resolved": false, "removed_resolution_ids": ["..."], "revision": N }\`. Legacy block-level resolution marks that could affect multiple threads return \`409 THREAD_RESOLUTION_AMBIGUOUS\` instead of being deleted.`,
|
|
27441
27473
|
``,
|
|
27442
27474
|
`### Delete a comment`,
|
|
27443
27475
|
`\`\`\`bash`,
|
|
@@ -27559,7 +27591,7 @@ function apiReference(baseUrl, slug, token, sid) {
|
|
|
27559
27591
|
`| Method | Path | Auth | Description |`,
|
|
27560
27592
|
`|--------|------|------|-------------|`,
|
|
27561
27593
|
`| POST | /docs | optional | Create doc (use Bearer token to appear as registered agent) |`,
|
|
27562
|
-
`| GET | /docs/:slug | viewer+ | Read doc (
|
|
27594
|
+
`| GET | /docs/:slug | viewer+ | Read doc (compact threads). Add \`?docs\` for API quickstart, \`?include=authorship\` for authorship, or \`?focus=comment-{id}\` for a specific comment. |`,
|
|
27563
27595
|
`| PATCH | /docs/:slug | editor+ | Edit text (old_string/new_string, after/before anchors, or after_block/before_block targets). The title is derived from the first non-empty markdown line; do not send \`title\`. Add ?dryRun=true to preview. Batch up to 100 edits. |`,
|
|
27564
27596
|
`| GET | /docs/:slug/archive | viewer+ | Get archive status |`,
|
|
27565
27597
|
`| POST | /docs/:slug/archive | editor+ | Archive doc for 30 days; normal routes return 423 until restored |`,
|
|
@@ -27568,6 +27600,8 @@ function apiReference(baseUrl, slug, token, sid) {
|
|
|
27568
27600
|
`| DELETE | /docs/:slug | owner | Deprecated archive alias; archives instead of purging. Prefer POST /archive; use DELETE /forever for permanent deletion |`,
|
|
27569
27601
|
`| POST | /docs/:slug/comments | commenter+ | Add comment, suggestion, or reply |`,
|
|
27570
27602
|
`| POST | /docs/:slug/comments/:cid/resolve | commenter+ | Resolve comment |`,
|
|
27603
|
+
`| POST | /docs/:slug/threads/:thread_id/resolve | commenter+ | Resolve thread by stable thread id |`,
|
|
27604
|
+
`| POST | /docs/:slug/threads/:thread_id/unresolve | commenter+ | Reopen thread by deleting pointer-bearing thread resolution marks |`,
|
|
27571
27605
|
`| PATCH | /docs/:slug/comments/:cid | commenter+ | Edit comment text (author-only) |`,
|
|
27572
27606
|
`| DELETE | /docs/:slug/comments/:cid | commenter+ | Delete comment (author-only) |`,
|
|
27573
27607
|
`| POST | /docs/:slug/comments/:cid/accept | editor+ | Accept suggestion |`,
|
|
@@ -27610,6 +27644,10 @@ function apiReference(baseUrl, slug, token, sid) {
|
|
|
27610
27644
|
`| \`BLOCK_ID_AMBIGUOUS\` | 409 | durable comment \`block_id\` maps to more than one current block; re-GET and retry after the document is repaired |`,
|
|
27611
27645
|
`| \`BLOCK_ID_NOT_ADDRESSABLE\` | 409 | comment \`block_id\` points at a non-addressable wrapper; choose an addressable \`content_blocks[].id\` target |`,
|
|
27612
27646
|
`| \`BLOCK_NOT_FOUND\` | 409 | deprecated blk_* block_id is stale or not from this revision \u2014 GET latest content_blocks and retry with a durable id |`,
|
|
27647
|
+
`| \`EXPECTED_REVISION_REQUIRED\` | 400 | whole-block suggestions and suggestion replies need \`expected_revision\` from the GET response because the request does not include a quote assertion |`,
|
|
27648
|
+
`| \`EXPECTED_REVISION_FUTURE\` | 409 | \`expected_revision\` is ahead of the current document revision \u2014 re-GET and retry with the returned revision |`,
|
|
27649
|
+
`| \`BLOCK_STALE\` | 409 | target block changed after \`expected_revision\`; retry against the returned \`revision\` and \`current_text\`, or re-GET if \`block_deleted: true\` |`,
|
|
27650
|
+
`| \`INVALID_FILTER\` | 400 | a compact thread filter was malformed; \`mentions\` currently supports only \`me\`, \`since_revision\` must be a non-negative integer, \`resolved\` must be \`true\` or \`false\`, and \`suggestions\` must be \`pending\`, \`stale\`, or \`open\` |`,
|
|
27613
27651
|
`| \`COMMENT_ANCHOR_BLOCK_NOT_FOUND\` | 422 | quote-created plain comment could not resolve to one addressable durable block; GET latest and choose a block-level \`content_blocks[].id\` target |`,
|
|
27614
27652
|
`| \`MARK_ANCHOR_NOT_FOUND\` | 422 | create/resolve could not build or resolve a canonical mark anchor; GET latest and use a currently visible mark or create a new comment |`,
|
|
27615
27653
|
`| \`MULTI_BLOCK_COMMENT_UNSUPPORTED\` | 422 | plain comments must target one addressable block in this rollout; split the comment by block |`,
|
|
@@ -27626,14 +27664,17 @@ function apiReference(baseUrl, slug, token, sid) {
|
|
|
27626
27664
|
`| \`POST_APPLY_MISMATCH\` | 422 | server refused an edit whose parsed result did not match the requested Markdown \u2014 GET latest and retry smaller |`,
|
|
27627
27665
|
`| \`REPLY_TARGET_NOT_FOUND\` | 404 | reply_to ID doesn't exist \u2014 check comment IDs from the GET response |`,
|
|
27628
27666
|
`| \`REPLY_TARGET_DELETED\` | 400 | cannot reply to a deleted comment \u2014 pick a different comment |`,
|
|
27629
|
-
`| \`INVALID_REPLY\` | 400 |
|
|
27667
|
+
`| \`INVALID_REPLY\` | 400 | invalid reply combination, such as thread_id with kind:"resolution" \u2014 use the dedicated thread resolve route instead |`,
|
|
27630
27668
|
`| \`REPLY_TARGET_ANCHOR_LOST\` | 409 | reply_to target no longer has a resolvable canonical anchor \u2014 GET latest and choose a visible comment or create a new top-level comment |`,
|
|
27669
|
+
`| \`THREAD_NOT_FOUND\` | 404 | thread_id does not match a current compact thread |`,
|
|
27670
|
+
`| \`THREAD_TARGET_MISMATCH\` | 400 | reply_to was supplied but does not belong to thread_id |`,
|
|
27671
|
+
`| \`ORPHANED_ANCHOR\` | 422 | thread_id target is orphaned for a write that needs live target text, or lacks a stored canonical anchor \u2014 GET latest and choose a visible thread/comment |`,
|
|
27631
27672
|
``,
|
|
27632
27673
|
`## Limitations`,
|
|
27633
27674
|
`- The editor does not support raw HTML. HTML tags and comments (e.g. \`<!-- ... -->\`, \`<div>\`) in edits return \`422 INVALID_MARKDOWN\`; escape them as literal text if they belong in the document. Standalone \`<br />\` lines from GET are the exception: they represent empty paragraph spacers and can be sent back in PATCH markdown.`,
|
|
27634
27675
|
``,
|
|
27635
27676
|
`## Additional notes`,
|
|
27636
|
-
`- **When sharing a document link with a user, always use
|
|
27677
|
+
`- **When sharing a document link with a user, always use the exact \`share_url\` value from the API response.** Links without its embedded browser token will not work.`,
|
|
27637
27678
|
`- For large replacements, GET the markdown programmatically \u2014 don't copy-paste through shell (Unicode issues).`
|
|
27638
27679
|
];
|
|
27639
27680
|
}
|
|
@@ -27676,7 +27717,7 @@ function buildCompleteAgentDocs(baseUrl = "https://comment.io", sid) {
|
|
|
27676
27717
|
``,
|
|
27677
27718
|
`## @mentions and polling`,
|
|
27678
27719
|
``,
|
|
27679
|
-
`Other participants can @mention you by your handle in comments. Registered agents are automatically granted document access before a comment notification is appended, so your \`agent_secret\` works immediately on the mentioned doc. Document-body mentions are searchable but do not append notifications; to check for mentions without the daemon, webhooks, or lease API, poll \`GET /docs/{slug}\` every 10 seconds and search the \`markdown\` field and each \`
|
|
27720
|
+
`Other participants can @mention you by your handle in comments. Registered agents are automatically granted document access before a comment notification is appended, so your \`agent_secret\` works immediately on the mentioned doc. Document-body mentions are searchable but do not append notifications; to check for mentions without the daemon, webhooks, or lease API, poll \`GET /docs/{slug}\` every 10 seconds and search the \`markdown\` field and each \`threads[].comments[].text\` for \`@{your-handle}\`. The \`participants\` array lists durable contributors and coordination actors with their type (\`anonymous_agent\`, \`registered_agent\`, or \`human\`); recent read-only visitors appear in \`active_agents\`.`,
|
|
27680
27721
|
``,
|
|
27681
27722
|
`## AI Agent API`,
|
|
27682
27723
|
``,
|
|
@@ -27707,7 +27748,7 @@ function buildCompleteAgentDocs(baseUrl = "https://comment.io", sid) {
|
|
|
27707
27748
|
` "access_token_role": "editor",`,
|
|
27708
27749
|
` "url": "/docs/abc123",`,
|
|
27709
27750
|
` "api_url": "/docs/abc123",`,
|
|
27710
|
-
` "share_url": "
|
|
27751
|
+
` "share_url": "<token-bearing browser share URL>",`,
|
|
27711
27752
|
` "actor_id": "ai:anon.tkn.abc123def456",`,
|
|
27712
27753
|
` "identify_required": true,`,
|
|
27713
27754
|
` "identify_url": "/agents/identify",`,
|
|
@@ -27730,7 +27771,7 @@ function buildCompleteAgentDocs(baseUrl = "https://comment.io", sid) {
|
|
|
27730
27771
|
``,
|
|
27731
27772
|
`For reading, editing, and commenting, use your \`agent_secret\` (registered agents) or the \`access_token\`. The \`id\` field is the document slug \u2014 use it in all \`/docs/{slug}\` API calls. Owner role is claimed automatically by the first human to open the \`share_url\` (agents can never be owner).`,
|
|
27732
27773
|
``,
|
|
27733
|
-
`**If you're using the returned \`access_token\` (not a registered \`agent_secret\`),
|
|
27774
|
+
`**If you're using the returned \`access_token\` (not a registered \`agent_secret\`), identify before or during your first write** \u2014 include \`display_name\` in the first JSON write body, or call \`POST /agents/identify\` first. A write without either returns \`412 IDENTIFY_REQUIRED\`.`,
|
|
27734
27775
|
``,
|
|
27735
27776
|
`**When sharing a comm with a user, always use \`share_url\` (prepend the base URL: \`${baseUrl}\` + \`share_url\`).** The share URL includes the auth token \u2014 without it, the link won't work. Never share a bare \`/d/{id}\` link.`,
|
|
27736
27777
|
``,
|
|
@@ -27756,7 +27797,7 @@ function buildCompleteAgentDocs(baseUrl = "https://comment.io", sid) {
|
|
|
27756
27797
|
``,
|
|
27757
27798
|
`## Per-comm docs`,
|
|
27758
27799
|
``,
|
|
27759
|
-
`If you have a comm URL
|
|
27800
|
+
`If you have a browser comm share URL, fetch that URL with \`Accept: text/markdown\` to get personalized API docs with your slug and token pre-filled.`,
|
|
27760
27801
|
``,
|
|
27761
27802
|
`## Agent Registration`,
|
|
27762
27803
|
``,
|
|
@@ -28268,7 +28309,7 @@ function buildHomeDocs(baseUrl = "https://comment.io", sid) {
|
|
|
28268
28309
|
`# Read`,
|
|
28269
28310
|
`curl -s -H "Authorization: Bearer {token}" "${baseUrl}/docs/{slug}"`,
|
|
28270
28311
|
``,
|
|
28271
|
-
`#
|
|
28312
|
+
`# Optional: identify once before the first write with a per-doc token`,
|
|
28272
28313
|
`curl -s -X POST "${baseUrl}/agents/identify" \\`,
|
|
28273
28314
|
` -H "Authorization: Bearer {token}" \\`,
|
|
28274
28315
|
` -H "Content-Type: application/json" \\`,
|
|
@@ -28297,7 +28338,7 @@ function buildHomeDocs(baseUrl = "https://comment.io", sid) {
|
|
|
28297
28338
|
``,
|
|
28298
28339
|
`## Per-comm docs`,
|
|
28299
28340
|
``,
|
|
28300
|
-
`If you have a comm URL
|
|
28341
|
+
`If you have a browser comm share URL, fetch that URL with \`Accept: text/markdown\` to get personalized API docs with your slug and token pre-filled.`,
|
|
28301
28342
|
``,
|
|
28302
28343
|
`## Included API reference`,
|
|
28303
28344
|
``,
|
|
@@ -29948,8 +29989,7 @@ var CommentApiClient = class {
|
|
|
29948
29989
|
}
|
|
29949
29990
|
async getDocumentWithShareToken(slug, shareToken) {
|
|
29950
29991
|
const url = new URL(this.url(`/docs/${slug}`));
|
|
29951
|
-
|
|
29952
|
-
return this.request(url);
|
|
29992
|
+
return this.request(url, { token: shareToken });
|
|
29953
29993
|
}
|
|
29954
29994
|
async getJson(path, token, query = {}) {
|
|
29955
29995
|
const url = new URL(this.url(path));
|
|
@@ -29958,8 +29998,12 @@ var CommentApiClient = class {
|
|
|
29958
29998
|
}
|
|
29959
29999
|
return this.request(url, { token });
|
|
29960
30000
|
}
|
|
29961
|
-
async postJson(path, token, body, headers = {}) {
|
|
29962
|
-
|
|
30001
|
+
async postJson(path, token, body, headers = {}, query = {}) {
|
|
30002
|
+
const url = new URL(this.url(path));
|
|
30003
|
+
for (const [key, value] of Object.entries(query)) {
|
|
30004
|
+
if (value !== void 0) url.searchParams.set(key, value);
|
|
30005
|
+
}
|
|
30006
|
+
return this.request(url, {
|
|
29963
30007
|
method: "POST",
|
|
29964
30008
|
token,
|
|
29965
30009
|
headers: { "Content-Type": "application/json", ...headers },
|
|
@@ -30204,7 +30248,7 @@ function normalizeApiError(status, data, fallback) {
|
|
|
30204
30248
|
}
|
|
30205
30249
|
function nextForError(status, code) {
|
|
30206
30250
|
if (status === 401) return "Refresh or replace the configured Comment.io credential.";
|
|
30207
|
-
if (status === 412 || code === "IDENTIFY_REQUIRED") return "
|
|
30251
|
+
if (status === 412 || code === "IDENTIFY_REQUIRED") return "Retry the JSON write with display_name, or call identify_agent for this document_ref before writing.";
|
|
30208
30252
|
if (status === 403 && code === "DOC_READ_ONLY") return "Use comment_on_comm or suggest_change instead of edit_comm.";
|
|
30209
30253
|
if (status === 409 || status === 422) return "Read the returned current revision/markdown, then retry only if the target remains clear.";
|
|
30210
30254
|
if (status === 429) return "Back off deterministically before retrying.";
|
|
@@ -30238,7 +30282,13 @@ var openCommInputSchema = external_exports.object({
|
|
|
30238
30282
|
var readCommInputSchema = external_exports.object({
|
|
30239
30283
|
document_ref: documentRefSchema,
|
|
30240
30284
|
focus: external_exports.string().optional(),
|
|
30241
|
-
include: external_exports.array(external_exports.enum(["comments", "authorship", "content_blocks"])).optional()
|
|
30285
|
+
include: external_exports.array(external_exports.enum(["comments", "authorship", "content_blocks"])).optional(),
|
|
30286
|
+
filters: external_exports.object({
|
|
30287
|
+
mentions: external_exports.literal("me").optional(),
|
|
30288
|
+
since_revision: external_exports.number().int().nonnegative().optional(),
|
|
30289
|
+
resolved: external_exports.boolean().optional(),
|
|
30290
|
+
suggestions: external_exports.enum(["pending", "stale", "open"]).optional()
|
|
30291
|
+
}).optional()
|
|
30242
30292
|
});
|
|
30243
30293
|
var identifyAgentInputSchema = external_exports.object({
|
|
30244
30294
|
document_ref: documentRefSchema,
|
|
@@ -30249,7 +30299,8 @@ var editCommInputSchema = external_exports.object({
|
|
|
30249
30299
|
edits: external_exports.array(editItemSchema).min(1).max(100),
|
|
30250
30300
|
base_revision: external_exports.number().int().nonnegative().optional(),
|
|
30251
30301
|
dry_run: external_exports.boolean().optional(),
|
|
30252
|
-
idempotency_key: external_exports.string().min(8).max(160).optional()
|
|
30302
|
+
idempotency_key: external_exports.string().min(8).max(160).optional(),
|
|
30303
|
+
display_name: external_exports.string().min(1).max(100).optional()
|
|
30253
30304
|
});
|
|
30254
30305
|
var commentOnCommInputSchema = external_exports.object({
|
|
30255
30306
|
document_ref: documentRefSchema,
|
|
@@ -30257,14 +30308,17 @@ var commentOnCommInputSchema = external_exports.object({
|
|
|
30257
30308
|
quote: external_exports.string().optional(),
|
|
30258
30309
|
block_id: external_exports.string().optional(),
|
|
30259
30310
|
allow_mentions: external_exports.boolean().optional(),
|
|
30260
|
-
mentions: external_exports.array(external_exports.string()).optional()
|
|
30311
|
+
mentions: external_exports.array(external_exports.string()).optional(),
|
|
30312
|
+
display_name: external_exports.string().min(1).max(100).optional()
|
|
30261
30313
|
});
|
|
30262
30314
|
var replyToCommentInputSchema = external_exports.object({
|
|
30263
30315
|
document_ref: documentRefSchema,
|
|
30264
30316
|
text: external_exports.string().min(1),
|
|
30265
|
-
reply_to: external_exports.string().min(1),
|
|
30317
|
+
reply_to: external_exports.string().min(1).optional(),
|
|
30318
|
+
thread_id: external_exports.string().min(1).optional(),
|
|
30266
30319
|
allow_mentions: external_exports.boolean().optional(),
|
|
30267
|
-
mentions: external_exports.array(external_exports.string()).optional()
|
|
30320
|
+
mentions: external_exports.array(external_exports.string()).optional(),
|
|
30321
|
+
display_name: external_exports.string().min(1).max(100).optional()
|
|
30268
30322
|
});
|
|
30269
30323
|
var suggestChangeInputSchema = external_exports.object({
|
|
30270
30324
|
document_ref: documentRefSchema,
|
|
@@ -30272,12 +30326,14 @@ var suggestChangeInputSchema = external_exports.object({
|
|
|
30272
30326
|
quote: external_exports.string().min(1),
|
|
30273
30327
|
new_string: external_exports.string(),
|
|
30274
30328
|
allow_mentions: external_exports.boolean().optional(),
|
|
30275
|
-
mentions: external_exports.array(external_exports.string()).optional()
|
|
30329
|
+
mentions: external_exports.array(external_exports.string()).optional(),
|
|
30330
|
+
display_name: external_exports.string().min(1).max(100).optional()
|
|
30276
30331
|
});
|
|
30277
30332
|
var resolveCommentInputSchema = external_exports.object({
|
|
30278
30333
|
document_ref: documentRefSchema,
|
|
30279
30334
|
comment_id: external_exports.string().min(1),
|
|
30280
|
-
text: external_exports.string().min(1)
|
|
30335
|
+
text: external_exports.string().min(1),
|
|
30336
|
+
display_name: external_exports.string().min(1).max(100).optional()
|
|
30281
30337
|
});
|
|
30282
30338
|
var reportFeedbackInputSchema = external_exports.object({
|
|
30283
30339
|
document_ref: documentRefSchema,
|
|
@@ -30382,7 +30438,7 @@ var CommentMcpRuntime = class {
|
|
|
30382
30438
|
next_actions: ["Configure COMMENT_IO_AGENT_PROFILE or COMMENT_IO_AGENT_SECRET."]
|
|
30383
30439
|
});
|
|
30384
30440
|
}
|
|
30385
|
-
const response = await this.api.getDocument(parsed.slug, this.config.agentSecret);
|
|
30441
|
+
const response = await this.api.getDocument(parsed.slug, this.config.agentSecret, documentReadQuery());
|
|
30386
30442
|
if (!response.ok) return this.apiError(response.status, response.data, "Could not open comm.");
|
|
30387
30443
|
const credential = this.store.create({
|
|
30388
30444
|
baseUrl: this.config.baseUrl,
|
|
@@ -30404,17 +30460,22 @@ var CommentMcpRuntime = class {
|
|
|
30404
30460
|
async readComm(input) {
|
|
30405
30461
|
const credential = this.store.find(input.document_ref);
|
|
30406
30462
|
if (!credential) return unknownDocumentRefResult(input.document_ref);
|
|
30407
|
-
const { response, usedFallback } = await this.withCredentialFallback(
|
|
30463
|
+
const { response, usedFallback } = await this.withCredentialFallback(
|
|
30464
|
+
credential,
|
|
30465
|
+
(token) => this.api.getDocument(credential.slug, token, documentReadQuery(input.include, { focus: input.focus, filters: input.filters }))
|
|
30466
|
+
);
|
|
30408
30467
|
if (!response.ok) return this.apiError(response.status, response.data, "Could not read comm.", credential);
|
|
30409
30468
|
if (!usedFallback) {
|
|
30469
|
+
const mintedToken = stringValue(response.data.your_token);
|
|
30410
30470
|
this.store.update(credential.documentRef, {
|
|
30471
|
+
...mintedToken ? { token: mintedToken, tokenKind: "document_token" } : {},
|
|
30411
30472
|
lastRevision: numberValue(response.data.revision),
|
|
30412
30473
|
role: stringValue(response.data.your_role)
|
|
30413
30474
|
});
|
|
30414
30475
|
}
|
|
30415
30476
|
return okResult({
|
|
30416
30477
|
summary: `Read comm ${credential.slug} at revision ${numberValue(response.data.revision) ?? "unknown"}.`,
|
|
30417
|
-
data: compactDocumentData(response.data, credential.documentRef, this.config.baseUrl, input
|
|
30478
|
+
data: compactDocumentData(response.data, credential.documentRef, this.config.baseUrl, responseIncludeForRead(input), { forceReadOnly: usedFallback }),
|
|
30418
30479
|
resource_links: resourceLinks(credential.documentRef),
|
|
30419
30480
|
next_actions: usedFallback ? ["read_comm"] : nextActionsForDocument(response.data),
|
|
30420
30481
|
warnings: warningsForDocument(response.data, usedFallback),
|
|
@@ -30447,7 +30508,11 @@ var CommentMcpRuntime = class {
|
|
|
30447
30508
|
}
|
|
30448
30509
|
const credential = this.store.find(input.document_ref);
|
|
30449
30510
|
if (!credential) return unknownDocumentRefResult(input.document_ref);
|
|
30450
|
-
const body = {
|
|
30511
|
+
const body = {
|
|
30512
|
+
edits: input.edits,
|
|
30513
|
+
...input.base_revision !== void 0 ? { base_revision: input.base_revision } : {},
|
|
30514
|
+
...input.display_name ? { display_name: input.display_name } : {}
|
|
30515
|
+
};
|
|
30451
30516
|
const headers = input.dry_run ? {} : { "Idempotency-Key": upstreamIdempotencyKey(input, credential) };
|
|
30452
30517
|
const response = await this.api.patchJson(`/docs/${credential.slug}`, credential.token, body, input.dry_run ? { dryRun: "true" } : {}, headers);
|
|
30453
30518
|
if (!response.ok) return this.apiError(response.status, response.data, "Could not edit comm.", credential);
|
|
@@ -30473,16 +30538,25 @@ var CommentMcpRuntime = class {
|
|
|
30473
30538
|
return this.createComment(input.document_ref, {
|
|
30474
30539
|
text: input.text,
|
|
30475
30540
|
...input.quote ? { quote: input.quote } : { block_id: input.block_id },
|
|
30476
|
-
...input.mentions ? { mentions: input.mentions } : {}
|
|
30541
|
+
...input.mentions ? { mentions: input.mentions } : {},
|
|
30542
|
+
...input.display_name ? { display_name: input.display_name } : {}
|
|
30477
30543
|
}, "Comment created.", "Could not create comment.");
|
|
30478
30544
|
}
|
|
30479
30545
|
async replyToComment(input) {
|
|
30480
30546
|
const guard = mentionGuard(input);
|
|
30481
30547
|
if (guard) return guard;
|
|
30548
|
+
if (!input.reply_to && !input.thread_id) {
|
|
30549
|
+
return errorResult({
|
|
30550
|
+
summary: "reply_to_comment requires reply_to or thread_id.",
|
|
30551
|
+
error: { status: 400, code: "INVALID_TARGET", message: "Provide reply_to or thread_id." }
|
|
30552
|
+
});
|
|
30553
|
+
}
|
|
30482
30554
|
return this.createComment(input.document_ref, {
|
|
30483
30555
|
text: input.text,
|
|
30484
|
-
reply_to: input.reply_to,
|
|
30485
|
-
...input.
|
|
30556
|
+
...input.reply_to ? { reply_to: input.reply_to } : {},
|
|
30557
|
+
...input.thread_id ? { thread_id: input.thread_id } : {},
|
|
30558
|
+
...input.mentions ? { mentions: input.mentions } : {},
|
|
30559
|
+
...input.display_name ? { display_name: input.display_name } : {}
|
|
30486
30560
|
}, "Reply created.", "Could not create reply.");
|
|
30487
30561
|
}
|
|
30488
30562
|
async suggestChange(input) {
|
|
@@ -30492,13 +30566,21 @@ var CommentMcpRuntime = class {
|
|
|
30492
30566
|
text: input.text,
|
|
30493
30567
|
quote: input.quote,
|
|
30494
30568
|
suggestion: { new_string: input.new_string },
|
|
30495
|
-
...input.mentions ? { mentions: input.mentions } : {}
|
|
30569
|
+
...input.mentions ? { mentions: input.mentions } : {},
|
|
30570
|
+
...input.display_name ? { display_name: input.display_name } : {}
|
|
30496
30571
|
}, "Suggestion created.", "Could not create suggestion.");
|
|
30497
30572
|
}
|
|
30498
30573
|
async resolveComment(input) {
|
|
30499
30574
|
const credential = this.store.find(input.document_ref);
|
|
30500
30575
|
if (!credential) return unknownDocumentRefResult(input.document_ref);
|
|
30501
|
-
const response = await this.api.postJson(
|
|
30576
|
+
const response = await this.api.postJson(
|
|
30577
|
+
`/docs/${credential.slug}/comments/${encodeURIComponent(input.comment_id)}/resolve`,
|
|
30578
|
+
credential.token,
|
|
30579
|
+
{
|
|
30580
|
+
text: input.text,
|
|
30581
|
+
...input.display_name ? { display_name: input.display_name } : {}
|
|
30582
|
+
}
|
|
30583
|
+
);
|
|
30502
30584
|
if (!response.ok) return this.apiError(response.status, response.data, "Could not resolve comment.", credential);
|
|
30503
30585
|
this.store.update(credential.documentRef, { lastRevision: numberValue(response.data.revision) });
|
|
30504
30586
|
return okResult({
|
|
@@ -30579,19 +30661,28 @@ var CommentMcpRuntime = class {
|
|
|
30579
30661
|
const nextUri = nextCursor ? historyNextUri(uri, nextCursor, query.limit) : null;
|
|
30580
30662
|
return { mimeType: "application/json", text: JSON.stringify(untrusted({ history: response2.data, next_uri: nextUri }), null, 2) };
|
|
30581
30663
|
}
|
|
30582
|
-
const { response, usedFallback } = await this.withCredentialFallback(
|
|
30664
|
+
const { response, usedFallback } = await this.withCredentialFallback(
|
|
30665
|
+
credential,
|
|
30666
|
+
(token) => this.api.getDocument(credential.slug, token, documentReadQuery(resourceIncludeForProjection(projection)))
|
|
30667
|
+
);
|
|
30583
30668
|
if (!response.ok) throw resourceReadError("Could not read document resource.", response.status, response.data);
|
|
30584
30669
|
const data = response.data;
|
|
30585
30670
|
if (projection === "markdown") return { mimeType: "text/markdown", text: `<!-- untrusted document markdown; canonical text for exact edits -->
|
|
30586
30671
|
${data.markdown ?? ""}` };
|
|
30587
|
-
if (projection === "comments") return { mimeType: "application/json", text: JSON.stringify(untrusted(
|
|
30672
|
+
if (projection === "comments") return { mimeType: "application/json", text: JSON.stringify(untrusted(commentProjection(data)), null, 2) };
|
|
30588
30673
|
if (projection === "authorship") return { mimeType: "application/json", text: JSON.stringify(untrusted({ authorship: data.authorship ?? [] }), null, 2) };
|
|
30589
30674
|
return { mimeType: "application/json", text: JSON.stringify(untrusted(compactDocumentData(data, credential.documentRef, this.config.baseUrl, [], { forceReadOnly: usedFallback })), null, 2) };
|
|
30590
30675
|
}
|
|
30591
|
-
async createComment(documentRef, body, summary, errorSummary) {
|
|
30676
|
+
async createComment(documentRef, body, summary, errorSummary, query = {}) {
|
|
30592
30677
|
const credential = this.store.find(documentRef);
|
|
30593
30678
|
if (!credential) return unknownDocumentRefResult(documentRef);
|
|
30594
|
-
const response = await this.api.postJson(
|
|
30679
|
+
const response = await this.api.postJson(
|
|
30680
|
+
`/docs/${credential.slug}/comments`,
|
|
30681
|
+
credential.token,
|
|
30682
|
+
body,
|
|
30683
|
+
{},
|
|
30684
|
+
query
|
|
30685
|
+
);
|
|
30595
30686
|
if (!response.ok) return this.apiError(response.status, response.data, errorSummary, credential);
|
|
30596
30687
|
this.store.update(credential.documentRef, { lastRevision: numberValue(response.data.revision) });
|
|
30597
30688
|
return okResult({
|
|
@@ -30657,14 +30748,47 @@ var CommentMcpResourceError = class extends Error {
|
|
|
30657
30748
|
function parseDocumentLocator(input, baseUrl) {
|
|
30658
30749
|
if (slugSchema.safeParse(input).success) return { slug: input };
|
|
30659
30750
|
const url = new URL(input.startsWith("/") ? `${baseUrl}${input}` : input);
|
|
30660
|
-
const docsMatch = url.pathname.match(/^\/docs\/([a-z0-9]{3,64})
|
|
30661
|
-
|
|
30662
|
-
|
|
30663
|
-
|
|
30664
|
-
|
|
30751
|
+
const docsMatch = url.pathname.match(/^\/docs\/([a-z0-9]{3,64})\/?$/);
|
|
30752
|
+
if (docsMatch) {
|
|
30753
|
+
if (url.searchParams.has("token")) {
|
|
30754
|
+
throw new Error("Token-bearing document URLs must use /d/{slug}?token=... share URLs; /docs/{slug} does not accept token query auth.");
|
|
30755
|
+
}
|
|
30756
|
+
return { slug: docsMatch[1], url };
|
|
30757
|
+
}
|
|
30758
|
+
const docMatch = url.pathname.match(/^\/d\/([a-z0-9]{3,64})\/?$/);
|
|
30759
|
+
if (docMatch) return { slug: docMatch[1], shareToken: url.searchParams.get("token") ?? void 0, url };
|
|
30760
|
+
throw new Error("Expected a Comment.io slug, /docs/{slug} URL, or /d/{slug}?token=... share URL.");
|
|
30761
|
+
}
|
|
30762
|
+
function documentReadQuery(include = [], options = {}) {
|
|
30763
|
+
const includeFlags = [];
|
|
30764
|
+
if (include?.includes("authorship")) includeFlags.push("authorship");
|
|
30765
|
+
if (include?.includes("content_blocks")) includeFlags.push("block_quotes");
|
|
30766
|
+
const filters = options.filters;
|
|
30767
|
+
return {
|
|
30768
|
+
focus: options.focus,
|
|
30769
|
+
mentions: filters?.mentions,
|
|
30770
|
+
since_revision: filters?.since_revision === void 0 ? void 0 : String(filters.since_revision),
|
|
30771
|
+
resolved: filters?.resolved === void 0 ? void 0 : String(filters.resolved),
|
|
30772
|
+
suggestions: filters?.suggestions,
|
|
30773
|
+
include: includeFlags.length ? includeFlags.join(",") : void 0
|
|
30774
|
+
};
|
|
30775
|
+
}
|
|
30776
|
+
function responseIncludeForRead(input) {
|
|
30777
|
+
if (!input.filters) return input.include;
|
|
30778
|
+
return [.../* @__PURE__ */ new Set([...input.include ?? [], "comments"])];
|
|
30779
|
+
}
|
|
30780
|
+
function resourceIncludeForProjection(projection) {
|
|
30781
|
+
if (projection === "authorship") return ["authorship"];
|
|
30782
|
+
return [];
|
|
30665
30783
|
}
|
|
30666
30784
|
function compactDocumentData(data, documentRef, baseUrl, include = [], options = {}) {
|
|
30667
30785
|
const slug = getSlug(data);
|
|
30786
|
+
const comments = commentProjection(data);
|
|
30787
|
+
const returnedThreadCount = Array.isArray(data.threads) ? data.threads.length : 0;
|
|
30788
|
+
const threadTotal = numericRecordField(data.threads_info, "total") ?? returnedThreadCount;
|
|
30789
|
+
const returnedCommentCount = returnedCommentCountForDocument(data);
|
|
30790
|
+
const commentTotal = commentCount(data);
|
|
30791
|
+
const hasThreadPagination = threadTotal !== returnedThreadCount || numericRecordField(data.threads_info, "doc_total") !== void 0;
|
|
30668
30792
|
const compact = {
|
|
30669
30793
|
document_ref: documentRef,
|
|
30670
30794
|
slug,
|
|
@@ -30676,17 +30800,59 @@ function compactDocumentData(data, documentRef, baseUrl, include = [], options =
|
|
|
30676
30800
|
doc_url: `${baseUrl}/d/${slug}`,
|
|
30677
30801
|
api_url: `${baseUrl}/docs/${slug}`,
|
|
30678
30802
|
counts: {
|
|
30679
|
-
|
|
30803
|
+
threads: threadTotal,
|
|
30804
|
+
...hasThreadPagination ? { threads_returned: returnedThreadCount } : {},
|
|
30805
|
+
comments: commentTotal,
|
|
30806
|
+
...commentTotal !== returnedCommentCount || hasThreadPagination ? { comments_returned: returnedCommentCount } : {},
|
|
30680
30807
|
content_blocks: Array.isArray(data.content_blocks) ? data.content_blocks.length : 0,
|
|
30681
30808
|
authorship_ranges: Array.isArray(data.authorship) ? data.authorship.length : 0
|
|
30682
30809
|
}
|
|
30683
30810
|
};
|
|
30684
|
-
if (include?.includes("comments"))
|
|
30811
|
+
if (include?.includes("comments")) {
|
|
30812
|
+
compact.threads = comments.threads;
|
|
30813
|
+
if (comments.threads_info) compact.threads_info = comments.threads_info;
|
|
30814
|
+
if (comments.suggestions_info) compact.suggestions_info = comments.suggestions_info;
|
|
30815
|
+
}
|
|
30685
30816
|
if (include?.includes("authorship")) compact.authorship = data.authorship ?? [];
|
|
30686
30817
|
if (include?.includes("content_blocks")) compact.content_blocks = data.content_blocks ?? [];
|
|
30818
|
+
if (include?.includes("comments") || data.focused) compact.actors = comments.actors;
|
|
30687
30819
|
if (data.focused) compact.focused = data.focused;
|
|
30688
30820
|
return compact;
|
|
30689
30821
|
}
|
|
30822
|
+
function commentProjection(data) {
|
|
30823
|
+
return {
|
|
30824
|
+
threads: Array.isArray(data.threads) ? data.threads : [],
|
|
30825
|
+
threads_info: data.threads_info ?? null,
|
|
30826
|
+
suggestions_info: data.suggestions_info ?? null,
|
|
30827
|
+
focused: data.focused ?? null,
|
|
30828
|
+
actors: data.actors ?? {}
|
|
30829
|
+
};
|
|
30830
|
+
}
|
|
30831
|
+
function commentCount(data) {
|
|
30832
|
+
if (Array.isArray(data.threads)) {
|
|
30833
|
+
return data.threads.reduce((total, thread) => {
|
|
30834
|
+
if (!thread || typeof thread !== "object") return total;
|
|
30835
|
+
const info = thread.comments_info;
|
|
30836
|
+
if (typeof info?.total === "number") return total + info.total;
|
|
30837
|
+
const comments = thread.comments;
|
|
30838
|
+
return total + (Array.isArray(comments) ? comments.length : 0);
|
|
30839
|
+
}, 0);
|
|
30840
|
+
}
|
|
30841
|
+
return 0;
|
|
30842
|
+
}
|
|
30843
|
+
function returnedCommentCountForDocument(data) {
|
|
30844
|
+
if (!Array.isArray(data.threads)) return 0;
|
|
30845
|
+
return data.threads.reduce((total, thread) => {
|
|
30846
|
+
if (!thread || typeof thread !== "object") return total;
|
|
30847
|
+
const comments = thread.comments;
|
|
30848
|
+
return total + (Array.isArray(comments) ? comments.length : 0);
|
|
30849
|
+
}, 0);
|
|
30850
|
+
}
|
|
30851
|
+
function numericRecordField(value, field) {
|
|
30852
|
+
if (!value || typeof value !== "object") return void 0;
|
|
30853
|
+
const fieldValue = value[field];
|
|
30854
|
+
return typeof fieldValue === "number" && Number.isFinite(fieldValue) ? fieldValue : void 0;
|
|
30855
|
+
}
|
|
30690
30856
|
function compactEditData(data) {
|
|
30691
30857
|
return {
|
|
30692
30858
|
revision: data.revision ?? null,
|
|
@@ -30911,7 +31077,7 @@ function registerTools(server2, runtime) {
|
|
|
30911
31077
|
}, async (args) => asCallToolResult(await runtime.commentOnComm(args)));
|
|
30912
31078
|
server2.registerTool("reply_to_comment", {
|
|
30913
31079
|
title: "Reply to comment",
|
|
30914
|
-
description: "Reply to an existing comment thread without combining reply and suggestion fields.",
|
|
31080
|
+
description: "Reply to an existing comment or compact thread without combining reply and suggestion fields.",
|
|
30915
31081
|
inputSchema: replyToCommentInputSchema,
|
|
30916
31082
|
outputSchema: commentMcpToolResultSchema,
|
|
30917
31083
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@comment-io/cli",
|
|
3
|
-
"version": "0.1.14-alpha.
|
|
3
|
+
"version": "0.1.14-alpha.365",
|
|
4
4
|
"description": "Comment.io CLI and local notification daemon",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -41,6 +41,6 @@
|
|
|
41
41
|
"node": ">=20"
|
|
42
42
|
},
|
|
43
43
|
"commentio": {
|
|
44
|
-
"sourceSha": "
|
|
44
|
+
"sourceSha": "174de17130f732e643714a8d539134c2cf8f38fe"
|
|
45
45
|
}
|
|
46
46
|
}
|