import{existsSync as R,statSync as A,readFileSync as L}from"fs";import{fileURLToPath as $}from"url";import{basename as U,dirname as T,resolve as v}from"path";import{resolveIntegrationToken as j,clearTokenCache as E}from"@zibby/core/backend-client.js";var y=Object.freeze({SENTRY:"sentry",JIRA:"jira",GITHUB:"github",GITLAB:"gitlab",SLACK:"slack",LARK:"lark",OPENAI_BILLING:"openai_billing",ANTHROPIC_BILLING:"anthropic_billing",CURSOR_ADMIN:"cursor_admin",NOTION:"notion",GOOGLE:"google",PLANE:"plane",LINEAR:"linear",FIGMA:"figma",HUBSPOT:"hubspot",OPEN_DESIGN:"open_design",LINKEDIN_PERSONAL:"linkedin_personal",LINKEDIN_BUSINESS:"linkedin_business",DISCORD:"discord"}),X=Object.freeze({sentry:{id:"sentry",name:"Sentry",connectPath:"/integrations?provider=sentry"},jira:{id:"jira",name:"Jira",connectPath:"/integrations?provider=jira"},github:{id:"github",name:"GitHub",connectPath:"/integrations?provider=github"},gitlab:{id:"gitlab",name:"GitLab",connectPath:"/integrations?provider=gitlab"},slack:{id:"slack",name:"Slack",connectPath:"/integrations?provider=slack"},lark:{id:"lark",name:"Lark",connectPath:"/integrations?provider=lark"},openai_billing:{id:"openai_billing",name:"OpenAI Admin",connectPath:"/integrations?provider=openai_billing"},anthropic_billing:{id:"anthropic_billing",name:"Anthropic Admin",connectPath:"/integrations?provider=anthropic_billing"},cursor_admin:{id:"cursor_admin",name:"Cursor Admin",connectPath:"/integrations?provider=cursor_admin"},notion:{id:"notion",name:"Notion",connectPath:"/integrations?provider=notion"},google:{id:"google",name:"Google Docs",connectPath:"/integrations?provider=google"},plane:{id:"plane",name:"Plane",connectPath:"/integrations?provider=plane"},linear:{id:"linear",name:"Linear",connectPath:"/integrations?provider=linear"},figma:{id:"figma",name:"Figma",connectPath:"/integrations?provider=figma"},hubspot:{id:"hubspot",name:"HubSpot",connectPath:"/integrations?provider=hubspot"},open_design:{id:"open_design",name:"OpenDesign",connectPath:"/integrations?provider=open_design"},linkedin_personal:{id:"linkedin_personal",name:"LinkedIn (Personal)",connectPath:"/integrations?provider=linkedin_personal"},linkedin_business:{id:"linkedin_business",name:"LinkedIn (Business)",connectPath:"/integrations?provider=linkedin_business"},discord:{id:"discord",name:"Discord",connectPath:"/integrations?provider=discord"}});function q(){if(process.env.MCP_SKILL_PATH)return process.env.MCP_SKILL_PATH;let i=T($(import.meta.url)),e=v(i,"..","bin","mcp-skill.mjs");return R(e)?e:null}var B="2022-06-28",C="https://api.notion.com/v1",h=2e4,I=25,D=25,b=100,N=500,J=20*1024*1024;function u(i){if(!i||typeof i!="string")return null;let t=i.trim().split(/[?#]/)[0],n=t.match(/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/);if(n)return n[0].toLowerCase();let o=t.match(/[0-9a-fA-F]{32}/g);if(o&&o.length){let r=o[o.length-1].toLowerCase();return`${r.slice(0,8)}-${r.slice(8,12)}-${r.slice(12,16)}-${r.slice(16,20)}-${r.slice(20)}`}return null}async function p(i,e={}){let t=async()=>{let{token:n}=await j("notion");if(typeof n!="string"||!n)throw new Error(`Invalid notion token type: ${typeof n}`);let o=await fetch(`${C}${i}`,{method:e.method||"GET",headers:{Authorization:`Bearer ${n}`,"Notion-Version":B,Accept:"application/json",...e.body&&!e.formData?{"Content-Type":"application/json"}:{},...e.headers},body:e.formData?e.formData:e.body?JSON.stringify(e.body):void 0});if(!o.ok){let s=await o.text().catch(()=>"");throw new Error(`Notion API ${o.status}: ${s.slice(0,300)}`)}let r=await o.text().catch(()=>"");if(!r||!r.trim())return{};try{return JSON.parse(r)}catch{return{raw:r}}};try{return await t()}catch(n){let o=String(n?.message||n||"").toLowerCase();if(!(o.includes("token")||o.includes("401")||o.includes("unauthorized")))throw n;return E("notion"),t()}}function g(i){if(!Array.isArray(i))return"";let e="";for(let t of i){let n=t?.plain_text??t?.text?.content??"";if(!n)continue;let o=t.annotations||{};o.code&&(n=`\`${n}\``),o.bold&&(n=`**${n}**`),o.italic&&(n=`_${n}_`),o.strikethrough&&(n=`~~${n}~~`);let r=t?.href||t?.text?.link?.url;r&&(n=`[${n}](${r})`),e+=n}return e}function M(i,e,t){let n=i?.type,o=i?.[n]||{},r="  ".repeat(Math.max(0,e)),s=(c="rich_text")=>g(o[c]),a;switch(n){case"paragraph":a=s();break;case"heading_1":a=`# ${s()}`;break;case"heading_2":a=`## ${s()}`;break;case"heading_3":a=`### ${s()}`;break;case"bulleted_list_item":a=`${r}- ${s()}`;break;case"numbered_list_item":a=`${r}1. ${s()}`;break;case"to_do":a=`${r}- [${o.checked?"x":" "}] ${s()}`;break;case"toggle":a=`${r}- ${s()}`;break;case"quote":a=`> ${s()}`;break;case"callout":{a=`> ${o.icon?.emoji?`${o.icon.emoji} `:""}${s()}`;break}case"code":{a=`\`\`\`${o.language||""}
${s()}
\`\`\``;break}case"divider":a="---";break;case"child_page":a=`[child page: ${o.title||""}]`;break;case"child_database":a=`[child database: ${o.title||""}]`;break;case"bookmark":case"embed":case"link_preview":a=o.url?`<${o.url}>`:"";break;case"equation":a=o.expression?`$${o.expression}$`:"";break;case"table":case"column_list":case"column":a="";break;case"table_row":{let c=(o.cells||[]).map(l=>g(l).trim());a=`${r}| ${c.join(" | ")} |`;break}default:a=s();break}let d=[];return a&&a.trim()&&d.push(a),t&&t.trim()&&d.push(t),d.join(`
`)}async function S(i,e,t){let n=[],o,r=0;do{if(t.used>=h)break;let s=new URLSearchParams({page_size:"100"});o&&s.set("start_cursor",o);let a=await p(`/blocks/${i}/children?${s.toString()}`),d=Array.isArray(a.results)?a.results:[];for(let c of d){let l="";c.has_children&&(l=await S(c.id,e+1,t));let m=M(c,e,l);if(m&&(n.push(m),t.used+=m.length+1),t.used>=h)break}o=a.has_more?a.next_cursor:void 0,r+=1}while(o&&r<D);return n.join(`
`)}function w(i){let e=i?.properties||{};for(let t of Object.values(e))if(t?.type==="title"){let n=g(t.title).trim();if(n)return n}return""}function f(i){let e=String(i??"");if(!e)return[{type:"text",text:{content:""}}];let t=[];for(let n=0;n<e.length;n+=2e3)t.push({type:"text",text:{content:e.slice(n,n+2e3)}});return t}function G(i){return{id:i?.id||"",discussionId:i?.discussion_id||"",text:g(i?.rich_text).trim(),author:i?.created_by?.id||"",createdTime:i?.created_time||""}}function F(i){if(!i||!i.type)return"";switch(i.type){case"title":return g(i.title).trim();case"rich_text":return g(i.rich_text).trim();case"number":return i.number==null?"":String(i.number);case"select":return i.select?.name||"";case"status":return i.status?.name||"";case"multi_select":return(i.multi_select||[]).map(t=>t.name).join(", ");case"checkbox":return i.checkbox?"true":"false";case"url":return i.url||"";case"email":return i.email||"";case"phone_number":return i.phone_number||"";case"date":return i.date?.start||"";case"people":return(i.people||[]).map(t=>t.name||t.id).join(", ");default:return""}}function k(i){return`https://www.notion.so/${String(i||"").replace(/-/g,"")}`}function H(i){let e=String(i??"").replace(/\r\n/g,`
`),t=[];for(let n of e.split(`
`)){let o=n.replace(/\s+$/,"");if(!o.trim())continue;let r=/^(#{1,3})\s+(.*)$/.exec(o),s=/^\s*[-*]\s+(.*)$/.exec(o),a=/^\s*\d+[.)]\s+(.*)$/.exec(o),d,c;r?(d=`heading_${r[1].length}`,c=r[2]):s?(d="bulleted_list_item",c=s[1]):a?(d="numbered_list_item",c=a[1]):(d="paragraph",c=o),t.push({object:"block",type:d,[d]:{rich_text:f(c)}})}return t}function x(i){return Array.isArray(i?.blocks)&&i.blocks.length?i.blocks.slice(0,N):typeof i?.markdown=="string"&&i.markdown.trim()?H(i.markdown).slice(0,N):null}async function P(i,e){for(let t=0;t<e.length;t+=b)await p(`/blocks/${i}/children`,{method:"PATCH",body:{children:e.slice(t,t+b)}})}function z(i){let e=typeof i=="string"?i.trim():"";if(!e)throw new Error("imagePath is required");if(!R(e)||!A(e).isFile())throw new Error(`imagePath not found (or not a file): ${e}`);if(!/\.(png|jpe?g)$/i.test(e))throw new Error("imagePath must be a .png or .jpg/.jpeg file");let t=L(e);if(t.length>J)throw new Error(`image is ${(t.length/(1024*1024)).toFixed(1)}MB \u2014 max 20MB (Notion single_part upload cap)`);return t}async function K(i){let e=z(i),t=U(i.trim()),n=/\.png$/i.test(t)?"image/png":"image/jpeg",r=(await p("/file_uploads",{method:"POST",body:{mode:"single_part",filename:t}}))?.id;if(!r)throw new Error("Notion file upload create returned no id");let s=new FormData;return s.set("file",new Blob([e],{type:n}),t),await p(`/file_uploads/${r}/send`,{method:"POST",formData:s}),r}var te={id:"notion",serverName:"notion",allowedTools:["mcp__notion__*"],requiresIntegration:y.NOTION,description:"Notion \u2014 read pages/databases as context + create pages, append blocks, and insert images",promptFragment:`## Notion (connected)
You can read Notion content as context AND write to Notion (create pages, append blocks, insert images).
- notion_get_page: pass a Notion page id OR a full Notion URL; returns { id, title, url, text } where text is the page flattened to markdown (truncated to ~20k chars). Use the text as reference context.
- notion_query_database: pass a database id/URL; returns a small list of rows ({ id, title, url, props }). Use to find a specific page, then notion_get_page it.
- notion_create_page: create a page under a parent page ({ parentPageId, title, markdown }) or in a database ({ databaseId, title, markdown }) \u2014 one parent is required. Body from markdown (#/##/### headings, - bullets, 1. ordered) or raw Notion blocks. Returns { ok, pageId, url } \u2014 share the url.
- notion_append_blocks: append markdown (or raw blocks) to the end of an existing page \u2014 pass { pageId, markdown }. Returns { ok, pageId, url }.
- notion_insert_image: append an image block to a page. Pass { pageId, imagePath } for a LOCAL png/jpg file (\u226420MB, uploaded via Notion's File Upload API) or { pageId, imageUrl } for an external, publicly reachable image URL. Optional caption. Returns { ok, pageId, url }.
- notion_list_comments: pass a page/block id OR URL; returns the open comment discussions on it ({ id, discussionId, text, author }). Use to read the comment thread you are replying to.
- notion_add_comment: post a comment. To REPLY in an existing discussion pass { discussionId, text }; to start a NEW top-level comment on a page pass { pageId, text }. Returns { ok, id, discussionId }. Use this to answer a user who @mentioned Zibby in a Notion comment \u2014 reply in the SAME discussionId.
Do not block the task if Notion is unavailable \u2014 these tools return { ok:false, error } on failure; treat a missing page as "no extra context" / "cannot deliver to Notion" and continue.`,resolve(){let i=q();return i?{type:"stdio",command:"node",args:[i,"../dist/notion.js","notionSkill"],env:{},description:this.description,alwaysLoad:!0}:null},async handleToolCall(i,e){try{switch(i){case"notion_get_page":{let t=e?.pageId||e?.page||e?.url||e?.id,n=u(t);if(!n)return JSON.stringify({ok:!1,error:"A valid Notion page id or URL is required"});let o=await p(`/pages/${n}`),r=w(o),s=o?.url||`https://www.notion.so/${n.replace(/-/g,"")}`,d=await S(n,0,{used:0}),c=!1;return d.length>h&&(d=d.slice(0,h),c=!0),JSON.stringify({ok:!0,id:n,title:r,url:s,text:d,...c?{truncated:!0}:{}})}case"notion_query_database":{let t=e?.databaseId||e?.database||e?.url||e?.id,n=u(t);if(!n)return JSON.stringify({ok:!1,error:"A valid Notion database id or URL is required"});let r={page_size:Math.max(1,Math.min(Number(e?.maxResults)||I,I))};e?.filter&&typeof e.filter=="object"&&(r.filter=e.filter);let s=await p(`/databases/${n}/query`,{method:"POST",body:r}),d=(Array.isArray(s.results)?s.results:[]).map(c=>{let l={};for(let[m,O]of Object.entries(c.properties||{})){let _=F(O);_&&(l[m]=_)}return{id:c.id,title:w(c),url:c.url||`https://www.notion.so/${String(c.id||"").replace(/-/g,"")}`,props:l}});return JSON.stringify({ok:!0,id:n,count:d.length,hasMore:!!s.has_more,rows:d})}case"notion_create_page":{let t=typeof e?.title=="string"&&e.title.trim()?e.title.trim():null;if(!t)return JSON.stringify({ok:!1,error:"title is required"});let n=e?.databaseId?u(e.databaseId):null,o=e?.parentPageId?u(e.parentPageId):u(e?.parent||e?.pageId);if(!n&&!o)return JSON.stringify({ok:!1,error:"A valid parentPageId or databaseId is required"});let r=n?{database_id:n}:{page_id:o},s={title:{title:f(t)}},a=x(e)||[],d=a.slice(0,b),c=await p("/pages",{method:"POST",body:{parent:r,properties:s,...d.length?{children:d}:{}}}),l=c?.id;if(!l)return JSON.stringify({ok:!1,error:"Notion page create returned no id"});let m=a.slice(b);return m.length&&await P(l,m),JSON.stringify({ok:!0,pageId:l,url:c?.url||k(l)})}case"notion_append_blocks":{let t=u(e?.pageId||e?.blockId||e?.url||e?.id);if(!t)return JSON.stringify({ok:!1,error:"A valid Notion page id or URL is required"});let n=x(e);return!n||!n.length?JSON.stringify({ok:!1,error:"markdown or blocks content is required"}):(await P(t,n),JSON.stringify({ok:!0,pageId:t,url:k(t)}))}case"notion_insert_image":{let t=u(e?.pageId||e?.url||e?.id);if(!t)return JSON.stringify({ok:!1,error:"A valid Notion page id or URL is required"});let n=typeof e?.caption=="string"&&e.caption.trim()?f(e.caption.trim()):null,o,r;if(typeof e?.imagePath=="string"&&e.imagePath.trim())r=await K(e.imagePath),o={type:"file_upload",file_upload:{id:r}};else if(typeof e?.imageUrl=="string"&&e.imageUrl.trim())o={type:"external",external:{url:e.imageUrl.trim()}};else return JSON.stringify({ok:!1,error:"imagePath (local png/jpg) or imageUrl is required"});return n&&(o.caption=n),await p(`/blocks/${t}/children`,{method:"PATCH",body:{children:[{object:"block",type:"image",image:o}]}}),JSON.stringify({ok:!0,pageId:t,url:k(t),...r?{fileUploadId:r}:{}})}case"notion_list_comments":{let t=e?.blockId||e?.pageId||e?.block||e?.page||e?.url||e?.id,n=u(t);if(!n)return JSON.stringify({ok:!1,error:"A valid Notion page/block id or URL is required"});let o=new URLSearchParams({block_id:n,page_size:"100"}),r=await p(`/comments?${o.toString()}`),a=(Array.isArray(r.results)?r.results:[]).map(G);return JSON.stringify({ok:!0,id:n,count:a.length,comments:a})}case"notion_add_comment":{let t=typeof e?.text=="string"?e.text:typeof e?.body=="string"?e.body:"";if(!t||!t.trim())return JSON.stringify({ok:!1,error:"text is required"});let n=e?.discussionId||e?.discussion_id||null,o;if(n)o={discussion_id:String(n),rich_text:f(t)};else{let s=e?.pageId||e?.page||e?.url||e?.id,a=u(s);if(!a)return JSON.stringify({ok:!1,error:"Either discussionId (to reply) or a valid pageId (to start a comment) is required"});o={parent:{page_id:a},rich_text:f(t)}}let r=await p("/comments",{method:"POST",body:o});return JSON.stringify({ok:!0,id:r?.id||"",discussionId:r?.discussion_id||n||""})}default:return JSON.stringify({ok:!1,error:`Unknown tool: ${i}`})}}catch(t){return JSON.stringify({ok:!1,error:t.message})}},tools:[{name:"notion_get_page",description:"Fetch a Notion page and its content flattened to markdown, for use as read-only context. Accepts a raw page id OR a full Notion URL. Returns { ok, id, title, url, text }. Text is truncated to ~20k chars.",input_schema:{type:"object",properties:{pageId:{type:"string",description:"Notion page id (dashed UUID or 32-char) OR a full Notion page URL."}},required:["pageId"]}},{name:"notion_query_database",description:"Query a Notion database and return a bounded list of rows (id, title, url, key props). Accepts a database id OR full Notion URL. Optional Notion filter object. Returns at most 25 rows.",input_schema:{type:"object",properties:{databaseId:{type:"string",description:"Notion database id (dashed UUID or 32-char) OR a full Notion database URL."},filter:{type:"object",description:"Optional Notion filter object (Notion query filter syntax).",additionalProperties:!0},maxResults:{type:"number",description:"Max rows to return (default 25, max 25)."}},required:["databaseId"]}},{name:"notion_create_page",description:"Create a new Notion page under a parent page (parentPageId) OR in a database (databaseId) \u2014 exactly one parent is required. Body from markdown (#/##/### headings, - bullets, 1. ordered lists; inline marks kept as literal text) or raw Notion block objects. Long bodies are chunked automatically (Notion caps 100 blocks/request). Returns { ok, pageId, url } \u2014 share the url with the user.",input_schema:{type:"object",properties:{parentPageId:{type:"string",description:"Parent PAGE id/URL to create the page under (used when databaseId is absent)."},databaseId:{type:"string",description:"Parent DATABASE id/URL to create the page in (title goes into the title property)."},title:{type:"string",description:"Page title."},markdown:{type:"string",description:"Page body as markdown (preferred)."},blocks:{type:"array",description:"Raw Notion block objects (advanced; used instead of markdown, max 500).",items:{type:"object",additionalProperties:!0}}},required:["title"]}},{name:"notion_append_blocks",description:"Append markdown (or raw Notion blocks) to the END of an existing Notion page. Accepts a page id or full Notion URL. Chunks automatically at Notion's 100-blocks-per-request cap. Returns { ok, pageId, url }.",input_schema:{type:"object",properties:{pageId:{type:"string",description:"Notion page id (dashed UUID or 32-char) OR a full Notion page URL."},markdown:{type:"string",description:"Content to append, as markdown (preferred)."},blocks:{type:"array",description:"Raw Notion block objects (advanced; used instead of markdown, max 500).",items:{type:"object",additionalProperties:!0}}},required:["pageId"]}},{name:"notion_insert_image",description:"Append an image block to a Notion page. Pass imagePath for a LOCAL .png/.jpg file (max 20MB \u2014 uploaded via Notion's File Upload API, stored by Notion) or imageUrl for an external publicly-reachable image URL. Optional caption. Returns { ok, pageId, url }.",input_schema:{type:"object",properties:{pageId:{type:"string",description:"Notion page id (dashed UUID or 32-char) OR a full Notion page URL."},imagePath:{type:"string",description:"Local filesystem path to a .png or .jpg/.jpeg image (max 20MB). Preferred for locally rendered charts."},imageUrl:{type:"string",description:"External image URL (must be publicly reachable; used when imagePath is absent)."},caption:{type:"string",description:"Optional image caption."}},required:["pageId"]}},{name:"notion_list_comments",description:"List the open comment discussions on a Notion page/block. Accepts a page/block id OR a full Notion URL. Returns { ok, comments:[{ id, discussionId, text, author }] }. Use to read the comment thread you are replying to.",input_schema:{type:"object",properties:{blockId:{type:"string",description:"Notion page or block id (dashed UUID or 32-char) OR a full Notion URL."}},required:["blockId"]}},{name:"notion_add_comment",description:"Post a comment on Notion. To REPLY within an existing discussion, pass { discussionId, text }. To start a NEW top-level comment on a page, pass { pageId, text }. Returns { ok, id, discussionId }. Use this to answer a user who @mentioned Zibby in a Notion comment (reply in the same discussionId).",input_schema:{type:"object",properties:{discussionId:{type:"string",description:"The discussion_id to reply into (from notion_list_comments). Preferred for replies."},pageId:{type:"string",description:"Page id/URL to start a NEW top-level comment on (used when discussionId is absent)."},text:{type:"string",description:"The comment body (plain text)."}},required:["text"]}}]};export{H as markdownToNotionBlocks,p as notionApi,te as notionSkill,u as parseNotionId};
