import{existsSync as L,readFileSync as w}from"fs";import{fileURLToPath as A}from"url";import{dirname as T,resolve as P}from"path";import{resolveIntegrationToken as y,clearTokenCache as N}from"@zibby/core/backend-client.js";function S(){if(process.env.MCP_SKILL_PATH)return process.env.MCP_SKILL_PATH;let t=T(A(import.meta.url)),e=P(t,"..","bin","mcp-skill.mjs");return L(e)?e:null}function E(){let t=String(process.env.ZIBBY_INJECTED_LINKEDIN_TOKEN||"").trim();if(!t)return null;let e=String(process.env.ZIBBY_INJECTED_LINKEDIN_MEMBER_ID||"").trim();return{token:t,memberId:e}}function v(){return String(process.env.ZIBBY_CHAT_STRICT_PERSONAL||"").trim()==="1"}function O(){return String(process.env.ZIBBY_SENDER_IS_NON_OWNER||"").trim()==="1"}var R="You haven't connected your own LinkedIn account \u2014 connect it at https://studio.zibby.dev/integrations (LinkedIn). For privacy, I can't post or act as anyone else's LinkedIn (including the project owner's) on your behalf.";function f(){let t=E();if(t)return t;if(v()||O())throw new Error(R);return null}var x="202506",D="https://api.linkedin.com";function b(t){if(t==null)return null;let e=String(t).trim();if(!e)return null;let n=e.match(/urn:li:organization:(\d+)/i);return n?n[1]:/^\d+$/.test(e)?e:null}function _(t){if(!t)return"";if(t.localizedName)return String(t.localizedName);let e=t.name&&t.name.localized;if(e&&typeof e=="object"){let n=Object.values(e)[0];if(n)return String(n)}return""}function h(t,e){return t?typeof t.get=="function"?t.get(e):t[e]||t[e.toLowerCase()]||null:null}async function p(t,e={},n="linkedin_business"){let l=async()=>{let i;if(n==="linkedin_personal"){let s=f();s&&(i=s.token)}if(i||({token:i}=await y(n)),typeof i!="string"||!i)throw new Error("LinkedIn is not connected: no access token available. Connect LinkedIn in Integrations.");let a=t.startsWith("https://")?t:`${D}${t}`,o=await fetch(a,{method:e.method||"GET",headers:{Authorization:`Bearer ${i}`,"LinkedIn-Version":x,"X-Restli-Protocol-Version":"2.0.0",Accept:"application/json",...e.body?{"Content-Type":"application/json"}:{},...e.headers},body:e.body?JSON.stringify(e.body):void 0});if(!o.ok){let s=await o.text().catch(()=>"");throw new Error(`LinkedIn API ${o.status}: ${s.slice(0,300)}`)}let r=await o.text().catch(()=>""),u={};if(r&&r.trim())try{u=JSON.parse(r)}catch{u={raw:r}}return{status:o.status,headers:o.headers,body:u}};try{return await l()}catch(i){let a=String(i?.message||i||"").toLowerCase();if(!(a.includes("token")||a.includes("401")||a.includes("unauthorized")))throw i;return N(n),l()}}async function U(t){let e;if(t==="linkedin_personal"){let n=f();n&&(e=n.token)}if(e||({token:e}=await y(t)),typeof e!="string"||!e)throw new Error("LinkedIn is not connected: no access token available. Connect LinkedIn in Integrations.");return e}function B(t){switch(String(t||"").toLowerCase().match(/\.([a-z0-9]+)$/)?.[1]){case"jpg":case"jpeg":return"image/jpeg";case"gif":return"image/gif";case"webp":return"image/webp";default:return"image/png"}}async function k(t,e,n,l){if(typeof n!="string"||!n.trim())throw new Error("imagePath (a local image file path) is required to upload an image");let{body:i}=await p("/rest/images?action=initializeUpload",{method:"POST",body:{initializeUploadRequest:{owner:e}}},t),a=i?.value?.uploadUrl,o=i?.value?.image;if(!a||!o)throw new Error(`LinkedIn image initializeUpload returned no uploadUrl/image (got: ${JSON.stringify(i).slice(0,200)})`);let r=w(n),u=await U(t),s=await fetch(a,{method:"PUT",headers:{Authorization:`Bearer ${u}`,"Content-Type":l||B(n)},body:r});if(!s.ok){let c=await s.text().catch(()=>"");throw new Error(`LinkedIn image upload ${s.status}: ${c.slice(0,300)}`)}return o}var j={id:"linkedin",serverName:"linkedin",allowedTools:["mcp__linkedin__*"],envKeys:[],description:"LinkedIn \u2014 (business) list admin Organizations + create DRAFT posts on a company Page, and (personal) PUBLISH a post to your own member profile feed",promptFragment:`## LinkedIn (connected)
You can post to LinkedIn two ways \u2014 an ORG company Page (business) and your own member PROFILE (personal). Each path needs its own LinkedIn integration connected.

Business (company Page) \u2014 DRAFT only:
- linkedin_list_organizations: List the Organizations (company Pages) the member ADMINISTERS. Returns [{ id, urn, name, vanityName }]. Call this first to choose the author org. (needs LinkedIn Business connected)
- linkedin_create_draft_post: Create a DRAFT post on a company Page. Pass organizationId (or organizationUrn) + text (the post commentary). The post is created in DRAFT state (never published automatically) so a human reviews and publishes it in LinkedIn. Returns { postUrn }. Set dry_run:true to VALIDATE which LinkedIn account/profile the post would go to (and preview the text) WITHOUT posting \u2014 nothing is published; returns { dryRun, target, wouldPostAs, textPreview }. (needs LinkedIn Business connected)

Personal (your own profile) \u2014 PUBLISHES IMMEDIATELY:
- linkedin_publish_post: PUBLISH a post to your OWN member profile feed. Pass text (the post body) + optional visibility ('PUBLIC' default, or 'CONNECTIONS'). UNLIKE the org draft tool, this PUBLISHES the post immediately \u2014 LinkedIn has no DRAFT state for member profiles, so there is no human review step. Returns { postUrn }. Set dry_run:true to VALIDATE which member profile the post would publish to (and preview the text) WITHOUT publishing \u2014 nothing is posted; returns { dryRun, target, wouldPostAs, textPreview }. Use this to confirm the identity before an approved publish. (needs LinkedIn Personal connected)

Attaching an image (optional, both tools):
- Both linkedin_create_draft_post and linkedin_publish_post accept imagePath (a LOCAL image file path, e.g. a PNG returned by social_card_render) + optional imageAltText. When imagePath is set, the image is uploaded to LinkedIn's Images API (same connected account/token as the post) and attached to the post as its media. On a dry_run nothing is uploaded \u2014 the result just reports imageWouldAttach:true.

Notes:
- Org-page posts are ALWAYS created as a DRAFT; personal-profile posts are ALWAYS published live \u2014 choose the tool accordingly.
- PUBLISHING IS OUTWARD-FACING AND IRREVERSIBLE. linkedin_publish_post posts live to a real audience the instant you call it \u2014 there is no undo and no draft state for member profiles. Only call it after the human has EXPLICITLY approved publishing THIS specific text in THIS conversation. If you wrote or edited the text, show it and get an explicit "yes, publish" first; if you are unsure, HOLD/draft rather than publish. The org draft tool (linkedin_create_draft_post) is the safe default when a human still needs to review.
- If the relevant LinkedIn integration is not connected these tools return { ok:false, error }; treat that as "LinkedIn unavailable" and continue.`,resolve(){let t=S();if(!t)return{command:null,args:[],env:{},description:this.description};let e={};for(let n of["ZIBBY_INJECTED_LINKEDIN_TOKEN","ZIBBY_INJECTED_LINKEDIN_MEMBER_ID","ZIBBY_SENDER_IS_NON_OWNER","ZIBBY_CHAT_STRICT_PERSONAL"])process.env[n]&&(e[n]=process.env[n]);return{type:"stdio",command:"node",args:[t,"../dist/linkedin.js","linkedinSkill"],env:e,description:this.description,alwaysLoad:!0}},async handleToolCall(t,e){try{switch(t){case"linkedin_list_organizations":{let{body:n}=await p("/rest/organizationAcls?q=roleAssignee&role=ADMINISTRATOR&state=APPROVED",{},"linkedin_business"),l=Array.isArray(n.elements)?n.elements:[],i=[];for(let a of l){let o=a.organizationalTarget||a["organizationalTarget~"]||a.organization,r=b(o);if(!r)continue;let u="",s="";try{let c=(await p(`/rest/organizations/${r}`,{},"linkedin_business")).body;u=_(c),s=c.vanityName||""}catch{}i.push({id:r,urn:`urn:li:organization:${r}`,name:u,vanityName:s})}return JSON.stringify({ok:!0,count:i.length,organizations:i})}case"linkedin_create_draft_post":{let n=e?.organizationUrn||e?.organizationId||e?.organization||e?.orgId,l=b(n);if(!l)return JSON.stringify({ok:!1,error:"A valid organizationId or organizationUrn (urn:li:organization:{id}) is required"});let i=e?.text;if(typeof i!="string"||!i.trim())return JSON.stringify({ok:!1,error:"text (the post commentary) is required"});let a=e?.visibility?String(e.visibility).toUpperCase():"PUBLIC",o=`urn:li:organization:${l}`,r=typeof e?.imagePath=="string"?e.imagePath.trim():"",u=typeof e?.imageAltText=="string"?e.imageAltText:"";if(e?.dry_run===!0)try{let d="";try{let g=(await p(`/rest/organizations/${l}`,{},"linkedin_business")).body;d=_(g)}catch{}return JSON.stringify({dryRun:!0,target:"organization",wouldPostAs:{name:d,id:l,urn:o},visibility:a,textPreview:i,...r?{imageWouldAttach:!0,imagePath:r}:{},note:"DRY RUN \u2014 nothing was posted"})}catch(d){return JSON.stringify({dryRun:!0,ok:!1,error:d.message})}let s={author:o,commentary:i,visibility:a,distribution:{feedDistribution:"MAIN_FEED",targetEntities:[],thirdPartyDistributionChannels:[]},lifecycleState:"DRAFT",isReshareDisabledByAuthor:!1};if(r){let d=await k("linkedin_business",o,r);s.content={media:{id:d,altText:u||""}}}let c=await p("/rest/posts",{method:"POST",body:s},"linkedin_business"),m=h(c.headers,"x-restli-id")||h(c.headers,"x-linkedin-id")||c.body?.id||null;return JSON.stringify({ok:!0,postUrn:m,author:o,lifecycleState:"DRAFT",visibility:a,status:c.status})}case"linkedin_publish_post":{let n=f(),l=n?n.memberId:(await y("linkedin_personal")).memberId;if(!l)return JSON.stringify({ok:!1,error:"LinkedIn (personal) not connected or member id unavailable \u2014 reconnect LinkedIn Personal"});let i=e?.text;if(typeof i!="string"||!i.trim())return JSON.stringify({ok:!1,error:"text (the post body) is required"});let a=e?.visibility?String(e.visibility).toUpperCase():"PUBLIC",o=`urn:li:person:${l}`,r=typeof e?.imagePath=="string"?e.imagePath.trim():"",u=typeof e?.imageAltText=="string"?e.imageAltText:"";if(e?.dry_run===!0)try{let d=(await p("/v2/userinfo",{},"linkedin_personal")).body,g=d?.name||[d?.given_name,d?.family_name].filter(Boolean).join(" ")||"",I=d?.sub?String(d.sub):l;return JSON.stringify({dryRun:!0,target:"member",wouldPostAs:{name:g,id:I,urn:`urn:li:person:${I}`},visibility:a,textPreview:i,...r?{imageWouldAttach:!0,imagePath:r}:{},note:"DRY RUN \u2014 nothing was posted"})}catch(d){return JSON.stringify({dryRun:!0,ok:!1,error:d.message})}let s={author:o,commentary:i,visibility:a,distribution:{feedDistribution:"MAIN_FEED",targetEntities:[],thirdPartyDistributionChannels:[]},lifecycleState:"PUBLISHED",isReshareDisabledByAuthor:!1};if(r){let d=await k("linkedin_personal",o,r);s.content={media:{id:d,altText:u||""}}}let c=await p("/rest/posts",{method:"POST",body:s},"linkedin_personal"),m=h(c.headers,"x-restli-id")||h(c.headers,"x-linkedin-id")||c.body?.id||null;return JSON.stringify({ok:!0,postUrn:m,author:o,lifecycleState:"PUBLISHED",visibility:a,status:c.status})}default:return JSON.stringify({ok:!1,error:`Unknown tool: ${t}`})}}catch(n){return JSON.stringify({ok:!1,error:n.message})}},tools:[{name:"linkedin_list_organizations",description:"List the LinkedIn Organizations (company Pages) the authenticated member ADMINISTERS. Returns [{ id, urn, name, vanityName }]. Call this first to choose the author org for a draft post.",input_schema:{type:"object",properties:{}}},{name:"linkedin_create_draft_post",description:"Create a DRAFT post on a LinkedIn Organization (company Page). The post is created in DRAFT state (never published automatically) so a human can review and publish it in LinkedIn. Returns the created post URN. Optionally ATTACH an image: pass imagePath (a local PNG file path, e.g. one returned by social_card_render) and it is uploaded to LinkedIn and attached to the post. Set dry_run:true to VALIDATE which LinkedIn account/profile the post would go to (and preview the text) WITHOUT posting \u2014 nothing is published or uploaded (a set imagePath is reported as imageWouldAttach:true).",input_schema:{type:"object",properties:{organizationId:{type:"string",description:'The numeric organization id (e.g. "12345"). Alternative to organizationUrn.'},organizationUrn:{type:"string",description:'The organization URN, e.g. "urn:li:organization:12345". Alternative to organizationId.'},text:{type:"string",description:"The post commentary (the body text of the post)."},visibility:{type:"string",enum:["PUBLIC"],description:"Post visibility. Defaults to PUBLIC."},imagePath:{type:"string",description:"Optional. A LOCAL image file path (e.g. a PNG returned by social_card_render). When set, the image is uploaded to LinkedIn and attached to the post as its media. Not uploaded on a dry_run."},imageAltText:{type:"string",description:"Optional alt text for the attached image (accessibility). Only used when imagePath is set."},dry_run:{type:"boolean",description:"Set dry_run:true to VALIDATE which LinkedIn account/profile the post would go to (and preview the text) WITHOUT posting \u2014 nothing is published or uploaded. Returns { dryRun, target, wouldPostAs, visibility, textPreview, imageWouldAttach? }. Defaults to false."}},required:["text"]}},{name:"linkedin_publish_post",description:"PUBLISH a post to the authenticated member's OWN LinkedIn profile feed (personal). UNLIKE linkedin_create_draft_post (which only drafts on a company Page), this PUBLISHES the post IMMEDIATELY \u2014 LinkedIn has no DRAFT state for member profiles, so there is no human review step. Returns the created post URN. Requires the LinkedIn Personal integration connected. Optionally ATTACH an image: pass imagePath (a local PNG file path, e.g. one returned by social_card_render) and it is uploaded to LinkedIn and attached to the post. Set dry_run:true to VALIDATE which LinkedIn account/profile the post would go to (and preview the text) WITHOUT posting \u2014 nothing is published or uploaded (a set imagePath is reported as imageWouldAttach:true).",input_schema:{type:"object",properties:{text:{type:"string",description:"The post body (the commentary text of the post)."},visibility:{type:"string",enum:["PUBLIC","CONNECTIONS"],description:"Post visibility: PUBLIC (anyone) or CONNECTIONS (your connections only). Defaults to PUBLIC."},imagePath:{type:"string",description:"Optional. A LOCAL image file path (e.g. a PNG returned by social_card_render). When set, the image is uploaded to LinkedIn and attached to the post as its media. Not uploaded on a dry_run."},imageAltText:{type:"string",description:"Optional alt text for the attached image (accessibility). Only used when imagePath is set."},dry_run:{type:"boolean",description:"Set dry_run:true to VALIDATE which LinkedIn account/profile the post would go to (and preview the text) WITHOUT posting \u2014 nothing is published or uploaded. Returns { dryRun, target, wouldPostAs, visibility, textPreview, imageWouldAttach? }. Defaults to false."}},required:["text"]}}]};export{R as PERSONAL_REFUSAL,v as chatStrictPersonal,E as injectedPersonalToken,p as linkedinApi,j as linkedinSkill,b as parseOrgId,f as personalTierCredentialGate,O as senderIsNonOwner,k as uploadImage};
