@seoagent-official/seoagent 1.95.2 → 1.95.3

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.
Files changed (2) hide show
  1. package/index.js +7 -7
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import{Command as fk}from"commander";import{dirname as jp,relative as St}from"path";var xu={nl:"nl",be:"nl",de:"de",at:"de",ch:"de",fr:"fr",es:"es",it:"it",pt:"pt",br:"pt",dk:"da",se:"sv",no:"nb",fi:"fi",is:"is",gr:"el",pl:"pl",cz:"cs",sk:"sk",hu:"hu",ro:"ro",bg:"bg",hr:"hr",si:"sl",rs:"sr",lt:"lt",lv:"lv",ee:"et",ua:"uk",ru:"ru",tr:"tr",il:"he",sa:"ar",eg:"ar",mx:"es",ar:"es",cl:"es",co:"es",pe:"es",jp:"ja",kr:"ko",tw:"zh-TW",id:"id",vn:"vi",th:"th"},Ii={"com.br":"pt","com.mx":"es","com.ar":"es","com.co":"es","co.jp":"ja","co.kr":"ko","com.tw":"zh-TW","co.id":"id","com.vn":"vi","co.th":"th","co.il":"he","com.tr":"tr"},Fr="en";function Su(e){return e.trim().toLowerCase().replace(/^[a-z][a-z0-9+.-]*:\/\//,"").split(/[/?#]/)[0].split(":")[0].replace(/^www\./,"").replace(/\.$/,"")}function Ti(e){if(!e)return Fr;let t=Su(e).split(".").filter(Boolean);if(t.length<2)return Fr;if(t.length>=3){let n=`${t[t.length-2]}.${t[t.length-1]}`;if(Ii[n])return Ii[n]}return xu[t[t.length-1]]??Fr}import{intro as Xs,outro as Dp,text as Qs,select as mo,spinner as Fp,isCancel as ot,cancel as it,confirm as Zs}from"@clack/prompts";var pt=[".env.local",".env.production",".env"],Oi=["NEXT_PUBLIC_SITE_URL","SITE_URL","NEXT_PUBLIC_URL","NEXTAUTH_URL","VITE_SITE_URL"];var Re="@seoagent-official/seoagent",On=`${Re}@latest`;import{homedir as _u}from"os";import{join as ft}from"path";var b=".seoagent",Li=["audit","strategy/clusters","briefs","content","content/images","performance"],Ee={AGENTS:".agents/skills/seoagent",CLAUDE:".claude/skills/seoagent"},Ft=".claude/settings.json",Ln=".pull-receipt.json",Ni=".ack-queue.json",ji=".sync.lock",Di=`# SEOAgent local files
2
+ import{Command as mk}from"commander";import{dirname as jp,relative as St}from"path";var xu={nl:"nl",be:"nl",de:"de",at:"de",ch:"de",fr:"fr",es:"es",it:"it",pt:"pt",br:"pt",dk:"da",se:"sv",no:"nb",fi:"fi",is:"is",gr:"el",pl:"pl",cz:"cs",sk:"sk",hu:"hu",ro:"ro",bg:"bg",hr:"hr",si:"sl",rs:"sr",lt:"lt",lv:"lv",ee:"et",ua:"uk",ru:"ru",tr:"tr",il:"he",sa:"ar",eg:"ar",mx:"es",ar:"es",cl:"es",co:"es",pe:"es",jp:"ja",kr:"ko",tw:"zh-TW",id:"id",vn:"vi",th:"th"},Ii={"com.br":"pt","com.mx":"es","com.ar":"es","com.co":"es","co.jp":"ja","co.kr":"ko","com.tw":"zh-TW","co.id":"id","com.vn":"vi","co.th":"th","co.il":"he","com.tr":"tr"},Fr="en";function Su(e){return e.trim().toLowerCase().replace(/^[a-z][a-z0-9+.-]*:\/\//,"").split(/[/?#]/)[0].split(":")[0].replace(/^www\./,"").replace(/\.$/,"")}function Ti(e){if(!e)return Fr;let t=Su(e).split(".").filter(Boolean);if(t.length<2)return Fr;if(t.length>=3){let n=`${t[t.length-2]}.${t[t.length-1]}`;if(Ii[n])return Ii[n]}return xu[t[t.length-1]]??Fr}import{intro as Xs,outro as Dp,text as Qs,select as mo,spinner as Fp,isCancel as ot,cancel as it,confirm as Zs}from"@clack/prompts";var pt=[".env.local",".env.production",".env"],Oi=["NEXT_PUBLIC_SITE_URL","SITE_URL","NEXT_PUBLIC_URL","NEXTAUTH_URL","VITE_SITE_URL"];var Re="@seoagent-official/seoagent",On=`${Re}@latest`;import{homedir as _u}from"os";import{join as ft}from"path";var b=".seoagent",Li=["audit","strategy/clusters","briefs","content","content/images","performance"],Ee={AGENTS:".agents/skills/seoagent",CLAUDE:".claude/skills/seoagent"},Ft=".claude/settings.json",Ln=".pull-receipt.json",Ni=".ack-queue.json",ji=".sync.lock",Di=`# SEOAgent local files
3
3
  .legacy/
4
4
  content/images/*
5
5
  !content/images/.gitkeep
@@ -47,7 +47,7 @@ Then show the owner the draft for review, and delete this comment.
47
47
  `;function wt(e){let t=ro(e,".seoagent",oo);if(!no(t))return null;let n=xs(t,"utf-8");return to(n)}function Ss(e,t){let n=ro(e,".seoagent",oo);return no(n)?!1:(Id(n,Td+$s({business:{type:t||""},writingInstructions:[],referenceUrls:[],topicsToAvoid:[],contentTone:null,additionalNotes:null}),"utf-8"),!0)}function Yn(e){let t=ro(e,".seoagent",oo);if(!no(t))return!0;try{return ye(xs(t,"utf-8"))}catch{return!1}}import{existsSync as Wt,readFileSync as _s,readdirSync as Od,statSync as Cs}from"fs";import{join as tt}from"path";var Ld=[{cms:"strapi",deps:["strapi","@strapi/strapi","@strapi/"],envKeys:["STRAPI_URL","STRAPI_API_URL","NEXT_PUBLIC_STRAPI_URL","STRAPI_API_TOKEN"],fsMarkers:["strapi/","apps/strapi/","packages/strapi/","cms/"]},{cms:"wordpress",deps:["wpapi","wp-graphql","@wordpress/api-fetch","@wordpress/"],envKeys:["WORDPRESS_API_URL","WP_API_URL","NEXT_PUBLIC_WORDPRESS_URL"],fsMarkers:[]},{cms:"sanity",deps:["@sanity/client","next-sanity","sanity"],envKeys:["SANITY_PROJECT_ID","NEXT_PUBLIC_SANITY_PROJECT_ID","SANITY_API_TOKEN"],fsMarkers:["sanity/","studio/","sanity.config.ts","sanity.config.js"]},{cms:"contentful",deps:["contentful","@contentful/rich-text-react-renderer","@contentful/"],envKeys:["CONTENTFUL_SPACE_ID","CONTENTFUL_ACCESS_TOKEN","NEXT_PUBLIC_CONTENTFUL_SPACE_ID"],fsMarkers:[]},{cms:"ghost",deps:["@tryghost/content-api","@tryghost/"],envKeys:["GHOST_URL","GHOST_API_KEY","NEXT_PUBLIC_GHOST_URL"],fsMarkers:[]},{cms:"webflow",deps:["webflow-api"],envKeys:["WEBFLOW_API_TOKEN","WEBFLOW_SITE_ID"],fsMarkers:[]},{cms:"shopify",deps:["@shopify/hydrogen","@shopify/storefront-api-client","@shopify/shopify-api","@shopify/"],envKeys:["SHOPIFY_STOREFRONT_TOKEN","SHOPIFY_STORE_DOMAIN","SHOPIFY_ADMIN_API_TOKEN"],fsMarkers:["shopify.config.ts","shopify.config.js"]},{cms:"payload",deps:["payload","@payloadcms/next","@payloadcms/"],envKeys:["PAYLOAD_SECRET","PAYLOAD_PUBLIC_SERVER_URL"],fsMarkers:["payload.config.ts","payload.config.js"]},{cms:"directus",deps:["@directus/sdk","directus"],envKeys:["DIRECTUS_URL","DIRECTUS_TOKEN"],fsMarkers:[]}];function Nd(e){let t=tt(e,"package.json");if(!Wt(t))return{deps:{},source:"package.json"};try{let n=JSON.parse(_s(t,"utf-8"));return{deps:{...n.dependencies,...n.devDependencies,...n.peerDependencies},source:"package.json"}}catch{return{deps:{},source:"package.json"}}}function jd(e,t){for(let n of t)if(n.endsWith("/")){if(e.startsWith(n))return!0}else if(e===n)return!0;return!1}function Dd(e){let t={};for(let n of pt){let r=tt(e,n);if(Wt(r))try{let o=_s(r,"utf-8");for(let i of o.split(/\r?\n/)){let s=i.trim();if(!s||s.startsWith("#"))continue;let a=s.indexOf("=");if(a===-1)continue;let l=s.slice(0,a).trim();l&&!(l in t)&&(t[l]=n)}}catch{}}return t}function Fd(e,t){let n=tt(e,t.replace(/\/$/,""));if(!Wt(n))return!1;if(t.endsWith("/"))try{return Cs(n).isDirectory()}catch{return!1}return!0}function Md(e){let t=["content","_posts","posts",tt("src","content")];for(let n of t){let r=tt(e,n);if(Wt(r))try{if(!Cs(r).isDirectory())continue;let o=[r],i=0;for(;o.length>0&&i<50;){let s=o.pop();for(let a of Od(s,{withFileTypes:!0}))if(i++,!a.name.startsWith(".")){if(a.isDirectory()&&o.length<5){o.push(tt(s,a.name));continue}if(a.isFile()&&/\.(md|mdx)$/i.test(a.name))return{type:"directory",detail:n,source:`${n}/${a.name}`}}}}catch{}}return null}function Ud(e){let t=[{marker:"app/blog/page.tsx",path:"/blog"},{marker:"app/blog/page.jsx",path:"/blog"},{marker:"app/blog/page.js",path:"/blog"},{marker:"src/app/blog/page.tsx",path:"/blog"},{marker:"src/app/blog/page.jsx",path:"/blog"},{marker:"pages/blog/index.tsx",path:"/blog"},{marker:"pages/blog/index.jsx",path:"/blog"},{marker:"pages/blog/index.js",path:"/blog"},{marker:"src/pages/blog/index.tsx",path:"/blog"},{marker:"app/(blog)/page.tsx",path:"/blog"},{marker:"app/articles/page.tsx",path:"/articles"},{marker:"pages/articles/index.tsx",path:"/articles"},{marker:"app/posts/page.tsx",path:"/posts"},{marker:"pages/posts/index.tsx",path:"/posts"},{marker:"app/learn/page.tsx",path:"/learn"},{marker:"app/resources/page.tsx",path:"/resources"}],n=[""],r=Hr(e);r&&n.push(r);for(let o of n)for(let i of t){let s=o?`${o}/${i.marker}`:i.marker;if(Wt(tt(e,s)))return i.path}return null}function Rs(e){let t=[],{deps:n}=Nd(e),r=Dd(e),o="none";for(let s of Ld){let a=[];for(let l of Object.keys(n))if(jd(l,s.deps)){a.push({type:"dep",detail:l,source:"package.json"});break}for(let l of s.envKeys)if(r[l]){a.push({type:"env",detail:l,source:r[l]});break}for(let l of s.fsMarkers)if(Fd(e,l)){let c=l.endsWith("/")?"directory":"file";a.push({type:c,detail:l,source:l});break}if(a.length>0){o=s.cms,t.push(...a);break}}if(o==="none"){let s=Md(e);s&&(o="mdx-local",t.push(s))}let i=Ud(e);return{cms:o,blog_path:i,evidence:t}}import{existsSync as Bd,readFileSync as Gd}from"fs";import{join as Hd}from"path";var Es=["openai","fal","replicate"],bt={openai:["OPENAI_API_KEY"],fal:["FAL_KEY","FAL_API_KEY"],replicate:["REPLICATE_API_TOKEN","REPLICATE_API_KEY"]};function Wd(e){let t={};if(!Bd(e))return t;let n=Gd(e,"utf-8");for(let r of n.split(/\r?\n/)){let o=r.trim();if(!o||o.startsWith("#"))continue;let i=o.indexOf("=");if(i===-1)continue;let s=o.slice(0,i).trim(),a=o.slice(i+1).trim();(a.startsWith('"')&&a.endsWith('"')||a.startsWith("'")&&a.endsWith("'"))&&(a=a.slice(1,-1)),t[s]=a}return t}function As(e,t){let n={};for(let r of t){let o=process.env[r];o&&o.trim().length>0&&(n[r]={value:o,source:"process"})}for(let r of pt){let o=Hd(e,r),i=Wd(o);for(let s of t){if(n[s])continue;let a=i[s];a&&a.length>0&&(n[s]={value:a,source:r})}}return{found:n}}function Be(e){let t=[].concat(...Es.map(a=>bt[a])),n=As(e,t),r=[];for(let a of Es)bt[a].some(l=>n.found[l])&&r.push(a);if(r.length===0)return{provider:"none",matched_key:null,source:null,available_providers:[]};let o=r[0],i=bt[o].find(a=>n.found[a])??null,s=i?n.found[i].source:null;return{provider:o,matched_key:i,source:s,available_providers:r}}function Ps(e,t){let n=As(e,bt[t]);for(let r of bt[t])if(n.found[r])return{key:n.found[r].value,envName:r};return null}function Is(){return bt}import{existsSync as nt,readFileSync as io}from"fs";import{join as rt}from"path";function Ts(e){try{return new URL(e).hostname}catch{return e}}function Os(e){let t=e.toLowerCase();return!!(t==="github.com"||t.endsWith(".github.com")||t==="gitlab.com"||t.endsWith(".gitlab.com")||t==="bitbucket.org"||t.endsWith(".bitbucket.org")||t==="dev.azure.com"||t==="visualstudio.com"||t.endsWith(".visualstudio.com")||t==="npmjs.com"||t.endsWith(".npmjs.com"))}function Kd(e){for(let t of pt){let n=rt(e,t);if(!nt(n))continue;let r=io(n,"utf-8");for(let o of Oi){let i=r.match(new RegExp(`^${o}=(.+)$`,"m"));if(i){let s=i[1].trim().replace(/^["']|["']$/g,""),a=Ts(s);if(Os(a))continue;return{value:a,source:`${t} (${o})`}}}}return null}function qd(e){let t=rt(e,"package.json");if(!nt(t))return null;try{let n=JSON.parse(io(t,"utf-8"));if(!n.homepage)return null;let r=Ts(n.homepage);return Os(r)?null:{value:r,source:"package.json (homepage)"}}catch{return null}}function Ls(e,t){let n=[];t?.("Checking for monorepo / workspace layout\u2026"),nt(rt(e,"pnpm-workspace.yaml"))&&n.push({field:"context",detail:"Monorepo workspace detected",source:"pnpm-workspace.yaml \u2014 using this directory for project signals"});let r=null;t?.("Scanning .env files for public / site URLs\u2026");let o=Kd(e);if(o)r=o.value,n.push({field:"domain",detail:o.value,source:o.source});else{t?.("Reading package.json homepage (skipping GitHub/GitLab repo URLs)\u2026");let a=qd(e);a&&(r=a.value,n.push({field:"domain",detail:a.value,source:a.source}))}let i=rt(e,"package.json"),s=null;if(nt(i))try{t?.("Reading dependencies to infer site type\u2026");let a=JSON.parse(io(i,"utf-8")),l=Object.keys({...a.dependencies,...a.devDependencies}),c=(...d)=>d.some(f=>l.includes(f));if(c("@shopify/hydrogen","@shopify/polaris")||nt(rt(e,"shopify.config.js"))||nt(rt(e,"shopify.config.ts"))){s="product";let d=c("@shopify/hydrogen","@shopify/polaris")?"Shopify-related dependencies in package.json":nt(rt(e,"shopify.config.ts"))?"shopify.config.ts":"shopify.config.js";n.push({field:"site_type",detail:"product",source:d})}else{let d=c("stripe","@stripe/stripe-js","@stripe/react-stripe-js","paddle","@paddle/paddle-js"),f=c("next-auth","@auth/core","lucia","clerk","@clerk/nextjs","@clerk/clerk-react"),p=c("next");d&&(p||f)?(s="saas",n.push({field:"site_type",detail:"saas",source:p?"payment SDK + Next.js in package.json":"payment SDK + auth library in package.json"})):!d&&c("astro","gatsby","contentlayer","next-mdx-remote","mdx-bundler","vitepress","vuepress","@docusaurus/core","docusaurus","nextra")&&(s="content",n.push({field:"site_type",detail:"content",source:c("vitepress","vuepress","@docusaurus/core","docusaurus","nextra")?"documentation / site generator in package.json (VitePress, Docusaurus, Nextra, etc.)":"content-oriented dependencies in package.json (no payment SDKs detected)"}))}}catch{}return{domain:r,siteType:s,evidence:n}}import{existsSync as Zd,readFileSync as lo,readdirSync as Ds,statSync as ep}from"fs";import{join as se,sep as tp}from"path";var zd=[/^\/admin(\/|$)/i,/^\/account(s)?(\/|$)/i,/^\/dashboard(\/|$)/i,/^\/settings(\/|$)/i,/^\/login(\/|$)/i,/^\/logout(\/|$)/i,/^\/sign[-_]?in(\/|$)/i,/^\/sign[-_]?up(\/|$)/i,/^\/register(\/|$)/i,/^\/reset[-_]?password(\/|$)/i,/^\/verify(\/|$)/i,/^\/api(\/|$)/i,/^\/_next(\/|$)/i,/^\/checkout(\/|$)/i,/^\/cart(\/|$)/i,/-(?:onboarding|login|logout|signin|sign-in|signup|sign-up|dashboard|checkout|account)$/i];function Ie(e){let t=[],n=/<loc>\s*([^<\s][^<]*?)\s*<\/loc>/gi,r;for(;(r=n.exec(e))!==null;){let o=r[1].replace(/&amp;/g,"&").replace(/&lt;/g,"<").replace(/&gt;/g,">").replace(/&#39;/g,"'").replace(/&quot;/g,'"').trim();o&&t.push(o)}return t}function Yd(e){let t=[],n=/<lastmod>\s*([^<\s][^<]*?)\s*<\/lastmod>/gi,r;for(;(r=n.exec(e))!==null;)t.push(r[1].trim());return t}var Vd=10,Ns=.9;function Jd(e,t){if(t<Vd||e.length<t*Ns)return null;let n=new Map;for(let r of e)n.set(r,(n.get(r)||0)+1);for(let[r,o]of n)if(o>=t*Ns)return r;return null}function F(e){let t=e;try{t=new URL(e).pathname}catch{let n=t.indexOf("#");n!==-1&&(t=t.slice(0,n));let r=t.indexOf("?");r!==-1&&(t=t.slice(0,r))}return t.length>1&&t.endsWith("/")&&(t=t.slice(0,-1)),t||"/"}function we(e){return zd.some(t=>t.test(e))}function Xd(e){return e.filter(t=>we(F(t)))}function Qd(e,t){let n=new Set(e.map(F)),r=[],o=new Set;for(let i of t){let s=F(i);we(s)||n.has(s)||o.has(s)||(o.add(s),r.push(s))}return r}function Kt(e){return`https://${e.replace(/^https?:\/\//,"").replace(/\/+$/,"")}/sitemap.xml`}async function js(e){let t=e.sitemapUrl||Kt(e.domain),n=[],r={ok:!1,sitemapUrl:t,fetched:!1,status:null,urls:[],privateUrls:[],missing:[],hasLastmod:!1,lastmods:[],uniformLastmod:null,issues:n},o="";try{let i=await I(t,{headers:{"user-agent":"SEOAgent-CLI/sitemap-check"}},{attempts:2,timeoutMs:1e4,fetchImpl:e.fetchImpl});if(r.status=i.status,r.fetched=!0,!i.ok)return n.push({level:"error",message:`Sitemap returned HTTP ${i.status} \u2014 Google can't fetch it. Make sure ${t} serves 200.`}),r;o=await i.text()}catch(i){return n.push({level:"error",message:`Could not reach ${t} (${i instanceof Error?i.message:String(i)}). If the site isn't deployed yet, that's expected.`}),r}return/<urlset|<sitemapindex/i.test(o)?(r.urls=Ie(o),r.lastmods=Yd(o),r.hasLastmod=r.lastmods.length>0,r.urls.length===0?(n.push({level:"error",message:"Sitemap is valid XML but contains zero <loc> URLs."}),r):(/<sitemapindex/i.test(o)&&n.push({level:"info",message:`Sitemap index with ${r.urls.length} child sitemap(s). Child URLs are not expanded by this check.`}),r.privateUrls=Xd(r.urls),r.privateUrls.length>0&&n.push({level:"warning",message:`${r.privateUrls.length} private/noindex URL(s) leaked into the sitemap (e.g. ${r.privateUrls.slice(0,3).map(F).join(", ")}). Remove auth/admin/api routes from the generator.`}),r.uniformLastmod=Jd(r.lastmods,r.urls.length),r.uniformLastmod?n.push({level:"warning",message:`Every URL carries the same <lastmod> (${r.uniformLastmod}) \u2014 that is the sitemap's own build time, not a content date. Google discounts <lastmod> for the WHOLE sitemap when it looks generated, including pages whose dates are real. Emit a per-page date where you have one and omit <lastmod> everywhere else.`}):r.hasLastmod||n.push({level:"info",message:"No <lastmod> dates. That is fine \u2014 omitting them is honest. Add a per-page date only where you have a real one (CMS updatedAt, file mtime); a single bulk date on every URL is worse than none."}),e.knownUrls&&e.knownUrls.length>0&&(r.missing=Qd(r.urls,e.knownUrls),r.missing.length>0&&n.push({level:"warning",message:`${r.missing.length} public page(s) in the repo are missing from the sitemap (e.g. ${r.missing.slice(0,3).join(", ")}). Add them to the generator so Google can find them.`})),r.ok=!n.some(i=>i.level==="error"),r)):(n.push({level:"error",message:`Response is not a valid XML sitemap (no <urlset>/<sitemapindex>). Got ${o.length} bytes.`}),r)}var Fs=["tsx","jsx","js","mdx","md"],np=["astro","md","mdx"],rp=5e3;function be(e,t){let n=new Map,r=0,o=(i,s,a=!1)=>{let l=lp(i);if(l===null){r++;return}let c=n.get(l);if(c){c.in_sitemap=c.in_sitemap||a,c.source.includes(s)||(c.source+=`, ${s}`);return}n.set(l,{route:l,url:hp(t,l),in_sitemap:a,source:s})};for(let i of ip(e))op(i,o);return{pages:[...n.values()].filter(i=>!we(i.route)).sort((i,s)=>i.route.localeCompare(s.route)),dynamicCount:r}}async function Ms(e,t,n){let r=[];try{let s=await I(Kt(t),{headers:{"user-agent":"SEOAgent-CLI/sitemap-check"}},{attempts:1,timeoutMs:5e3,fetchImpl:n});if(!s.ok)return{checked:!1,liveCount:0,flipped:0};r=Ie(await s.text())}catch{return{checked:!1,liveCount:0,flipped:0}}if(r.length===0)return{checked:!1,liveCount:0,flipped:0};let o=new Set(r.map(F)),i=0;for(let s of e)!s.in_sitemap&&o.has(F(s.url||s.route))&&(s.in_sitemap=!0,i++,s.source.includes("live-sitemap")||(s.source+=", live-sitemap"));return{checked:!0,liveCount:r.length,flipped:i}}function op(e,t){for(let n of["app",se("src","app")]){let r=se(e,n);if(qt(r))for(let o of so(r)){let i=ao(r,o),s=i[i.length-1];fp(s)&&(i.some(a=>a.startsWith("@"))||t(cp(i.slice(0,-1)),"app router"))}}for(let n of["pages",se("src","pages")]){let r=se(e,n);if(qt(r))for(let o of so(r)){let i=ao(r,o);if(i[0]==="api")continue;let s=i[i.length-1],a=co(s),l=Fs.includes(a),c=np.includes(a);if(!l&&!c)continue;let d=Vn(s);gp(d)||t(up(i),c?"astro":"pages router")}}for(let n of[".","public"]){let r=se(e,n);if(qt(r))for(let o of so(r,n==="."?1:1/0)){let i=ao(r,o),s=i[i.length-1];co(s)==="html"&&(i.some(a=>a==="node_modules"||a.startsWith("."))||t(dp(i),"static html"))}}for(let n of["public/sitemap.xml","sitemap.xml"]){let r=se(e,...n.split("/"));if(Zd(r)){for(let o of mp(r)){let i=pp(o);i!==null&&t(i,"sitemap.xml",!0)}break}}}function ip(e){let t=[e],n=new Set([e]);for(let r of sp(e))for(let o of ap(e,r))n.has(o)||(n.add(o),t.push(o));return t}function sp(e){let t=[];try{let n=lo(se(e,"pnpm-workspace.yaml"),"utf-8"),r=!1;for(let o of n.split(/\r?\n/)){let i=o.replace(/#.*$/,"");if(/^\s*packages\s*:/.test(i)){r=!0;continue}if(r){let s=i.match(/^\s*-\s*['"]?([^'"\s]+)['"]?\s*$/);s?t.push(s[1]):/^\S/.test(i)&&(r=!1)}}}catch{}try{let r=JSON.parse(lo(se(e,"package.json"),"utf-8")).workspaces,o=Array.isArray(r)?r:Array.isArray(r?.packages)?r.packages:[];for(let i of o??[])typeof i=="string"&&t.push(i)}catch{}return t}function ap(e,t){if(!t||t.startsWith("!"))return[];let n=t.replace(/\/\*\*?$/,"/*"),r=n.indexOf("*");if(r===-1){let a=se(e,...n.split("/").filter(Boolean));return qt(a)?[a]:[]}let o=n.slice(0,r).replace(/\/$/,""),i=se(e,...o.split("/").filter(Boolean));if(!qt(i))return[];let s=[];try{for(let a of Ds(i,{withFileTypes:!0}))if(a.isDirectory()&&!a.name.startsWith(".")&&!Us.has(a.name)&&(s.push(se(i,a.name)),s.length>=200))break}catch{}return s}function lp(e){let t=e.trim();return t===""||t==="/"||(t.startsWith("/")||(t=`/${t}`),t=t.replace(/\/+$/,""),t==="")?"/":/\[.*\]/.test(t)?null:t}function cp(e){return"/"+e.filter(n=>!(n.startsWith("(")&&n.endsWith(")"))).join("/")}function up(e){let t=[...e],n=Vn(t[t.length-1]);return n==="index"?t.pop():t[t.length-1]=n,"/"+t.join("/")}function dp(e){let t=[...e],n=Vn(t[t.length-1]);return n==="index"?t.pop():t[t.length-1]=n,"/"+t.join("/")}function pp(e){try{return new URL(e).pathname||"/"}catch{return e.startsWith("/")?e:null}}function fp(e){return Vn(e)==="page"&&Fs.includes(co(e))}function gp(e){return["_app","_document","_error","404","500","middleware"].includes(e)}function mp(e){try{let t=lo(e,"utf-8"),n=[],r=/<loc>\s*([^<\s]+)\s*<\/loc>/gi,o;for(;(o=r.exec(t))!==null;)n.push(o[1].trim());return n}catch{return[]}}var Us=new Set(["node_modules",".git",".next","dist","build",".vercel","out"]);function qt(e){try{return ep(e).isDirectory()}catch{return!1}}function*so(e,t=1/0){let n=0;function*r(o,i){if(i>t)return;let s;try{s=Ds(o,{withFileTypes:!0})}catch{return}for(let a of s){if(n>=rp)return;if(a.name.startsWith("."))continue;let l=se(o,a.name);if(a.isDirectory()){if(Us.has(a.name))continue;yield*r(l,i+1)}else a.isFile()&&(n++,yield l)}}yield*r(e,1)}function ao(e,t){return t.slice(e.length).replace(/^[/\\]+/,"").split(tp).filter(Boolean)}function co(e){let t=e.lastIndexOf(".");return t===-1?"":e.slice(t+1).toLowerCase()}function Vn(e){let t=e.lastIndexOf(".");return t===-1?e:e.slice(0,t)}function hp(e,t){return e?`https://${e.replace(/^https?:\/\//,"").replace(/\/$/,"")}${t==="/"?"/":t}`:t}import{existsSync as yp,mkdirSync as wp,readFileSync as bp,writeFileSync as kp}from"fs";import{join as Bs}from"path";function Gs(e,t){let n=!1,r=t.status!==null?String(t.status):"\u2014";e.status!==r&&(e.status=r,n=!0),e.rendered!==t.rendered&&(e.rendered=t.rendered,n=!0);let o=t.rendered==="error"?"\u2014":String(t.wordCount);return e.word_count!==o&&(e.word_count=o,n=!0),n}function Jn(e,t,n){let r=Bs(e,b,"pages.md"),o=yp(r),i=o?po(r):new Map;if(t.length===0&&!o)return{wrote:!1,added:0,total:0,merged:!1};let s=new Map(i),a=0,l=!1;for(let d of t){let f=Ws(d.url),p=n?.get(d.url),g=s.get(f);if(g)d.in_sitemap&&g.in_sitemap!=="yes"&&(g.in_sitemap="yes",l=!0),p&&Gs(g,p)&&(l=!0);else{let y={url:f,in_sitemap:d.in_sitemap?"yes":"no",in_nav:"\u2014",status:"\u2014",rendered:"\u2014",word_count:"\u2014",notes:Ws(d.source)};p&&Gs(y,p),s.set(f,y),a++,l=!0}}if(o&&!l)return{wrote:!1,added:0,total:s.size,merged:!0};let c=[...s.values()];return wp(Bs(e,b),{recursive:!0}),kp(r,vp(c),"utf-8"),{wrote:!0,added:a,total:c.length,merged:o}}function vp(e){let t=["---","generated: false",`discovered_at: ${new Date().toISOString()}`,`row_count: ${e.length}`,"---"].join(`
48
48
  `),n=["# Pages","","Inventory of pages discovered in your codebase. Re-running `seoagent init`","or `seoagent refresh` re-scans and merges in newly-added pages without","touching rows you or the audit have edited. `In sitemap` is refreshed from","the live sitemap; `Status` / `Rendered` / `Word count` are filled by","`seoagent refresh --crawl` (a live fetch of each page). This file syncs to","the cloud on login."].join(`
49
49
  `),r="| URL | In sitemap | In nav | Status | Rendered | Word count | Notes |",o="|---|---|---|---|---|---|---|",i=e.map(s=>`| ${s.url} | ${s.in_sitemap} | ${s.in_nav} | ${s.status} | ${s.rendered} | ${s.word_count} | ${s.notes} |`);return[t,"",n,"",r,o,...i,""].join(`
50
- `)}function po(e){let t=new Map,n;try{n=bp(e,"utf-8")}catch{return t}let r=n.split(/\r?\n/);for(let o=0;o<r.length-1;o++){if(!uo(r[o])||!$p(r[o+1]))continue;let i=Hs(r[o]).map(a=>a.toLowerCase()),s={url:i.findIndex(a=>a.includes("url")),in_sitemap:i.findIndex(a=>a.includes("sitemap")),in_nav:i.findIndex(a=>a.includes("nav")),status:i.findIndex(a=>a.includes("status")||a.includes("http")),rendered:i.findIndex(a=>a.includes("render")),word_count:i.findIndex(a=>a.includes("word")),notes:i.findIndex(a=>a.includes("note"))};if(s.url===-1)return t;for(let a=o+2;a<r.length&&uo(r[a]);a++){let l=Hs(r[a]),c=(l[s.url]??"").trim();!c||c==="\u2014"||c==="-"||t.set(c,{url:c,in_sitemap:kt(l,s.in_sitemap),in_nav:kt(l,s.in_nav),status:kt(l,s.status),rendered:kt(l,s.rendered),word_count:kt(l,s.word_count),notes:kt(l,s.notes)})}break}return t}function kt(e,t){let n=t>=0?(e[t]??"").trim():"";return n===""?"\u2014":n}function uo(e){let t=e.trim();return t.startsWith("|")&&t.endsWith("|")&&t.length>1}function $p(e){let t=e.trim();return uo(t)&&/^\|(?:\s*:?-+:?\s*\|)+$/.test(t)}function Hs(e){return e.trim().slice(1,-1).split("|").map(t=>t.trim())}function Ws(e){return e.replace(/\r?\n/g," ").replace(/\|/g,"/").trim()}import{existsSync as xp,readFileSync as Sp,statSync as _p}from"fs";import{dirname as fo,isAbsolute as Cp,join as Rp,resolve as go,sep as zt}from"path";var Ep={isWorktree:!1,mainRoot:null,reason:null};function Ks(e){let t=Rp(e,".git");try{if(xp(t)&&_p(t).isFile()){let i=Sp(t,"utf-8").match(/^gitdir:\s*(.+)\s*$/m),s=null;if(i){let a=Cp(i[1])?i[1]:go(e,i[1]),l=fo(a),c=fo(l);l.endsWith(`${zt}worktrees`)&&c.endsWith(`${zt}.git`)&&(s=fo(c))}return{isWorktree:!0,mainRoot:s,reason:"gitfile"}}}catch{}let n=`${zt}.claude${zt}worktrees${zt}`,r=go(e).indexOf(n);return r>=0?{isWorktree:!0,mainRoot:go(e).slice(0,r),reason:"agent-worktree-path"}:Ep}import{existsSync as Ge,mkdirSync as Xn,readdirSync as Ap,readFileSync as zs,statSync as Jv,writeFileSync as qs}from"fs";import{dirname as Pp,join as Te,relative as Ys,resolve as Ip,sep as Tp}from"path";var vt="okf";function $t(e){return Te(e,b,vt)}var Op=/^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}:\d{2})?)?$/;function Qn(e){let t=[];for(let n of Ap(e,{withFileTypes:!0})){let r=Te(e,n.name);n.isDirectory()?t.push(...Qn(r)):n.isFile()&&n.name.toLowerCase().endsWith(".md")&&t.push(r)}return t}function Lp(e){let t=[],n=/\[[^\]]*\]\(([^)]+)\)/g,r;for(;(r=n.exec(e))!==null;){let o=r[1].trim().split(/\s+/)[0];o&&(/^[a-z][a-z0-9+.-]*:/i.test(o)||o.startsWith("#")||t.push(o))}return t}function Zn(e){let t=[],n=[];if(!Ge(e))return{ok:!1,fileCount:0,errors:[{file:vt,message:"No OKF bundle found. Run `seoagent okf scaffold` first."}],warnings:[]};let r=Qn(e);if(r.length===0)return{ok:!1,fileCount:0,errors:[{file:vt,message:"OKF directory exists but contains no markdown files."}],warnings:[]};Ge(Te(e,"index.md"))||n.push({file:vt,message:"No top-level index.md \u2014 agents use it for progressive disclosure."});for(let i of r){let s=Ys(e,i),a;try{a=le(zs(i,"utf-8"))}catch{t.push({file:s,message:"Could not read or parse file."});continue}let l=a.data.type;(l==null||String(l).trim()==="")&&t.push({file:s,message:"Missing required frontmatter field `type`."});let c=a.data.timestamp;c!=null&&c!==""&&!Op.test(String(c))&&t.push({file:s,message:`\`timestamp\` is not ISO-8601: "${String(c)}"`});let d=a.data.description;typeof d=="string"&&d.length>200&&n.push({file:s,message:`\`description\` is ${d.length} chars (keep \u2264 200).`});for(let f of Lp(a.body)){let p=f.split("#")[0];if(!p)continue;let g=Ip(Pp(i),p);Ge(g)||t.push({file:s,message:`Broken link \u2192 "${f}" (target does not exist).`})}}return{ok:t.length===0,fileCount:r.length,errors:t,warnings:n}}function er(e){let t=$t(e);if(!Ge(t))return{exists:!1,fileCount:0};try{return{exists:!0,fileCount:Qn(t).length}}catch{return{exists:!0,fileCount:0}}}var Np=["_Describe the business in","_Link concept files here","_Link FAQ files here","_Link article files here"];function Vs(e){let t=Te(e,"index.md");if(!Ge(t))return!0;let n;try{n=zs(t,"utf-8")}catch{return!0}return Np.some(r=>n.includes(r))}function Js(e){if(!Ge(e))return[];try{return Qn(e).map(t=>Ys(e,t).split(Tp).join("/")).sort()}catch{return[]}}function tr(e,t){let n=$t(e);Xn(n,{recursive:!0}),Xn(Te(n,"concepts"),{recursive:!0}),Xn(Te(n,"faqs"),{recursive:!0}),Xn(Te(n,"articles"),{recursive:!0});let r=[],o=t&&t!=="unknown"?t:"your site",i=Te(n,"index.md");Ge(i)||(qs(i,`---
50
+ `)}function po(e){let t=new Map,n;try{n=bp(e,"utf-8")}catch{return t}let r=n.split(/\r?\n/);for(let o=0;o<r.length-1;o++){if(!uo(r[o])||!$p(r[o+1]))continue;let i=Hs(r[o]).map(a=>a.toLowerCase()),s={url:i.findIndex(a=>a.includes("url")),in_sitemap:i.findIndex(a=>a.includes("sitemap")),in_nav:i.findIndex(a=>a.includes("nav")),status:i.findIndex(a=>a.includes("status")||a.includes("http")),rendered:i.findIndex(a=>a.includes("render")),word_count:i.findIndex(a=>a.includes("word")),notes:i.findIndex(a=>a.includes("note"))};if(s.url===-1)return t;for(let a=o+2;a<r.length&&uo(r[a]);a++){let l=Hs(r[a]),c=(l[s.url]??"").trim();!c||c==="\u2014"||c==="-"||t.set(c,{url:c,in_sitemap:kt(l,s.in_sitemap),in_nav:kt(l,s.in_nav),status:kt(l,s.status),rendered:kt(l,s.rendered),word_count:kt(l,s.word_count),notes:kt(l,s.notes)})}break}return t}function kt(e,t){let n=t>=0?(e[t]??"").trim():"";return n===""?"\u2014":n}function uo(e){let t=e.trim();return t.startsWith("|")&&t.endsWith("|")&&t.length>1}function $p(e){let t=e.trim();return uo(t)&&/^\|(?:\s*:?-+:?\s*\|)+$/.test(t)}function Hs(e){return e.trim().slice(1,-1).split("|").map(t=>t.trim())}function Ws(e){return e.replace(/\r?\n/g," ").replace(/\|/g,"/").trim()}import{existsSync as xp,readFileSync as Sp,statSync as _p}from"fs";import{dirname as fo,isAbsolute as Cp,join as Rp,resolve as go,sep as zt}from"path";var Ep={isWorktree:!1,mainRoot:null,reason:null};function Ks(e){let t=Rp(e,".git");try{if(xp(t)&&_p(t).isFile()){let i=Sp(t,"utf-8").match(/^gitdir:\s*(.+)\s*$/m),s=null;if(i){let a=Cp(i[1])?i[1]:go(e,i[1]),l=fo(a),c=fo(l);l.endsWith(`${zt}worktrees`)&&c.endsWith(`${zt}.git`)&&(s=fo(c))}return{isWorktree:!0,mainRoot:s,reason:"gitfile"}}}catch{}let n=`${zt}.claude${zt}worktrees${zt}`,r=go(e).indexOf(n);return r>=0?{isWorktree:!0,mainRoot:go(e).slice(0,r),reason:"agent-worktree-path"}:Ep}import{existsSync as Ge,mkdirSync as Xn,readdirSync as Ap,readFileSync as zs,statSync as Qv,writeFileSync as qs}from"fs";import{dirname as Pp,join as Te,relative as Ys,resolve as Ip,sep as Tp}from"path";var vt="okf";function $t(e){return Te(e,b,vt)}var Op=/^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}:\d{2})?)?$/;function Qn(e){let t=[];for(let n of Ap(e,{withFileTypes:!0})){let r=Te(e,n.name);n.isDirectory()?t.push(...Qn(r)):n.isFile()&&n.name.toLowerCase().endsWith(".md")&&t.push(r)}return t}function Lp(e){let t=[],n=/\[[^\]]*\]\(([^)]+)\)/g,r;for(;(r=n.exec(e))!==null;){let o=r[1].trim().split(/\s+/)[0];o&&(/^[a-z][a-z0-9+.-]*:/i.test(o)||o.startsWith("#")||t.push(o))}return t}function Zn(e){let t=[],n=[];if(!Ge(e))return{ok:!1,fileCount:0,errors:[{file:vt,message:"No OKF bundle found. Run `seoagent okf scaffold` first."}],warnings:[]};let r=Qn(e);if(r.length===0)return{ok:!1,fileCount:0,errors:[{file:vt,message:"OKF directory exists but contains no markdown files."}],warnings:[]};Ge(Te(e,"index.md"))||n.push({file:vt,message:"No top-level index.md \u2014 agents use it for progressive disclosure."});for(let i of r){let s=Ys(e,i),a;try{a=le(zs(i,"utf-8"))}catch{t.push({file:s,message:"Could not read or parse file."});continue}let l=a.data.type;(l==null||String(l).trim()==="")&&t.push({file:s,message:"Missing required frontmatter field `type`."});let c=a.data.timestamp;c!=null&&c!==""&&!Op.test(String(c))&&t.push({file:s,message:`\`timestamp\` is not ISO-8601: "${String(c)}"`});let d=a.data.description;typeof d=="string"&&d.length>200&&n.push({file:s,message:`\`description\` is ${d.length} chars (keep \u2264 200).`});for(let f of Lp(a.body)){let p=f.split("#")[0];if(!p)continue;let g=Ip(Pp(i),p);Ge(g)||t.push({file:s,message:`Broken link \u2192 "${f}" (target does not exist).`})}}return{ok:t.length===0,fileCount:r.length,errors:t,warnings:n}}function er(e){let t=$t(e);if(!Ge(t))return{exists:!1,fileCount:0};try{return{exists:!0,fileCount:Qn(t).length}}catch{return{exists:!0,fileCount:0}}}var Np=["_Describe the business in","_Link concept files here","_Link FAQ files here","_Link article files here"];function Vs(e){let t=Te(e,"index.md");if(!Ge(t))return!0;let n;try{n=zs(t,"utf-8")}catch{return!0}return Np.some(r=>n.includes(r))}function Js(e){if(!Ge(e))return[];try{return Qn(e).map(t=>Ys(e,t).split(Tp).join("/")).sort()}catch{return[]}}function tr(e,t){let n=$t(e);Xn(n,{recursive:!0}),Xn(Te(n,"concepts"),{recursive:!0}),Xn(Te(n,"faqs"),{recursive:!0}),Xn(Te(n,"articles"),{recursive:!0});let r=[],o=t&&t!=="unknown"?t:"your site",i=Te(n,"index.md");Ge(i)||(qs(i,`---
51
51
  type: Organization
52
52
  title: ${o}
53
53
  description: Knowledge bundle describing ${o} for AI agents and answer engines.
@@ -825,7 +825,7 @@ skill knows what to do.
825
825
  `,"utf-8")}catch{}return l}var dn="context.md",im="context.md: the cloud copy is owner-authored and your local version does not derive from it \u2014 NOT pushed, both copies kept. Compare with `seoagent pull --print context.md`, merge into the local file, then sync; or take the cloud copy with `seoagent sync --force context.md`.",sm="context.md: replaced the local `seoagent init` scaffold with the owner-authored cloud context (the scaffold was written by `init`, not by you).",am="context.md: took the cloud copy (`--force context.md`) \u2014 the local version was replaced.";function lm(e){return ye(e.localBody)?!0:!e.prevStateHash||e.prevStateHash!==de(e.localBody)}function cm(e){let{localBody:t,prevStateHash:n,cloudBody:r}=e;return r===null?{kind:"none"}:de(t)===de(r)?{kind:"none"}:ye(r)?{kind:"none"}:ye(t)?{kind:"restore",body:r,notice:sm}:n&&n===de(r)?{kind:"none"}:e.forceTakeCloud?{kind:"restore",body:r,notice:am}:e.forcePushLocal?{kind:"none"}:{kind:"block-push",notice:im}}var pn={restored:!1,blockPush:!1,notices:[]};async function rl(e){let t=om(e.seoagentDir,dn);if(!tm(t))return pn;let n;try{n=nm(t,"utf-8")}catch{return pn}let r=e.stateFiles[dn]?.hash??null;if(!lm({localBody:n,prevStateHash:r}))return pn;let o=await Po({apiBase:e.apiBase,authHeader:e.authHeader,path:dn,fetchImpl:e.fetchImpl});if(!o.ok)return{restored:!1,blockPush:!ye(n)&&(!r||r!==de(n)),notices:[]};let i=cm({localBody:n,prevStateHash:r,cloudBody:o.artifact?.body_md??null,forceTakeCloud:e.forcePaths?.includes(dn)===!0,forcePushLocal:e.force===!0});switch(i.kind){case"none":return pn;case"restore":try{rm(t,i.body,"utf-8")}catch{return pn}return e.stateFiles[dn]={hash:de(i.body)},{restored:!0,blockPush:!1,notices:[i.notice]};case"block-push":return{restored:!1,blockPush:!0,notices:[i.notice]}}}import{existsSync as il,mkdirSync as sl,readFileSync as um,writeFileSync as Io}from"fs";import{dirname as al,join as dm}from"path";var To=1,ol=500;function Oo(e){return dm(e,b,Ni)}function ur(e){let t=Oo(e);if(!il(t))return[];try{let n=um(t,"utf-8"),r=JSON.parse(n);if(!r||typeof r!="object")return[];let o=r;return o.version!==To||!Array.isArray(o.entries)?[]:o.entries.filter(pm)}catch{return[]}}function ll(e,t){let n=Oo(e),o=ur(e).filter(a=>a.action_id!==t.action_id);o.push({action_id:t.action_id,status:t.status,reason:t.reason,queued_at:new Date().toISOString()});let i=o.length>ol?o.slice(-ol):o,s={version:To,entries:i};try{sl(al(n),{recursive:!0}),Io(n,JSON.stringify(s,null,2),"utf-8")}catch{}}function cl(e,t){if(t.length===0)return;let n=Oo(e),r=ur(e).filter(i=>!t.includes(i.action_id)),o={version:To,entries:r};try{if(r.length===0&&il(n)){Io(n,JSON.stringify(o,null,2),"utf-8");return}sl(al(n),{recursive:!0}),Io(n,JSON.stringify(o,null,2),"utf-8")}catch{}}function pm(e){if(!e||typeof e!="object")return!1;let t=e;return typeof t.action_id=="number"&&(t.status==="completed"||t.status==="failed")&&(t.reason===null||typeof t.reason=="string")&&typeof t.queued_at=="string"}import{existsSync as ul,readFileSync as fm,unlinkSync as gm,writeFileSync as mm,openSync as hm,closeSync as ym,mkdirSync as wm}from"fs";import{dirname as bm,join as km}from"path";var vm=6e4;function dl(e){return km(e,b,ji)}function pl(e,t={}){let n=t.now??Date.now,r=t.pid??process.pid,o=t.staleMs??vm,i=dl(e);if(!ul(i))return dr(i,r,n());let s=gl(i);return!s||n()-s.started_at>o||!$m(s.pid)?dr(i,r,n()):{acquired:!1,reason:"held-by-live-process",held_by_pid:s.pid}}function fl(e,t={}){let n=t.pid??process.pid,r=dl(e);if(!ul(r))return;let o=gl(r);if(!(o&&o.pid!==n))try{gm(r)}catch{}}function dr(e,t,n){let r={pid:t,started_at:n};try{wm(bm(e),{recursive:!0});let o=hm(e,"w");try{mm(o,JSON.stringify(r))}finally{ym(o)}return{acquired:!0}}catch{return{acquired:!1,reason:"held-but-pid-unknown"}}}function gl(e){try{let t=fm(e,"utf-8"),n=JSON.parse(t);return n&&typeof n=="object"&&typeof n.pid=="number"&&typeof n.started_at=="number"?n:null}catch{return null}}function $m(e){if(!Number.isInteger(e)||e<=0)return!1;try{return process.kill(e,0),!0}catch(t){return t.code==="EPERM"}}import{existsSync as Sl,mkdirSync as Im,readFileSync as _l,writeFileSync as Tm}from"fs";import{dirname as Om,join as fn}from"path";import{existsSync as Lo,readFileSync as wl,readdirSync as xm}from"fs";import{join as pr}from"path";var gr="llms.md",Sm=160,ml=100;function fr(e,t=Sm){let n=e.replace(/\s+/g," ").replace(/[[\]]/g,"").trim();return n.length<=t?n:n.slice(0,t-1).trimEnd()+"\u2026"}function No(e){let t=F(e);if(t==="/")return"Home";let r=(t.split("/").filter(Boolean).pop()||"Home").replace(/[-_]+/g," ").replace(/\.(html?|md|php)$/i,"").trim();return r?r.split(" ").map(o=>o.length>0?o[0].toUpperCase()+o.slice(1):o).join(" "):"Home"}var _m=new Set(["readme","changelog","license","licence","contributing","contributors","code_of_conduct","code-of-conduct","security","authors","notice","agents","claude","cursorrules","codeowners","copying","maintainers"]);function bl(e){let t=F(e).split("/").filter(Boolean).pop();return t?_m.has(t.replace(/\.(md|mdx|txt|html?)$/i,"").toLowerCase()):!1}function hl(e){let t=fr(e.label,80)||No(e.url),n=e.description?fr(e.description):"";return`- [${t}](${e.url})${n?`: ${n}`:""}`}function kl(e){let t=[`# ${fr(e.siteName,100)}`,""];if(e.summary&&t.push(`> ${fr(e.summary,300)}`,""),e.pages.length>0){t.push("## Pages","");for(let n of e.pages)t.push(hl(n));t.push("")}if(e.articles.length>0){t.push("## Articles","");for(let n of e.articles)t.push(hl(n));t.push("")}return t.join(`
826
826
  `).replace(/\n+$/,`
827
827
  `)}function Cm(e){let t=new Map,n=null;for(let r of e.split(/\r?\n/)){let o=/^##\s+(https?:\/\/\S+)\s*$/.exec(r);if(o){n=F(o[1]),t.has(n)||t.set(n,{});continue}if(!n)continue;if(/^##\s/.test(r)){n=null;continue}let i=/^-\s+\*\*title:\*\*\s+(.*)$/.exec(r);if(i&&i[1]!=="_(none)_"){t.get(n).title=i[1].trim();continue}let s=/^-\s+\*\*meta description:\*\*\s+(.*)$/.exec(r);s&&s[1]!=="_(none)_"&&(t.get(n).description=s[1].trim())}return t}function Rm(e){let t=pr(e,b,"audit","evidence.md");if(!Lo(t))return new Map;try{return Cm(wl(t,"utf-8"))}catch{return new Map}}function Em(e,t){let n=pr(e,b,"content");if(!Lo(n))return[];let r=new Map,o;try{o=xm(n)}catch{return[]}for(let i of o.sort()){if(!i.toLowerCase().endsWith(".md"))continue;let s;try{s=le(wl(pr(n,i),"utf-8")).data}catch{continue}let a=typeof s.canonical=="string"?s.canonical.trim():"",l=typeof s.status=="string"?s.status.toLowerCase():"";if(!a||l==="draft"||bl(a))continue;let c=typeof s.title=="string"&&s.title.trim()?s.title.trim():No(a),d=t.get(F(a)),f={url:a,label:d?.title||c,...d?.description?{description:d.description}:{}},p=typeof s.slug=="string"?s.slug.trim():i.slice(0,-3),g=F(a).split("/").filter(Boolean).pop()??"",y=p===g,h=r.get(a);(!h||y&&!h.slugMatchesUrl)&&r.set(a,{link:f,slugMatchesUrl:y})}return Array.from(r.values(),i=>i.link)}function Am(e){try{return new URL(e).host.toLowerCase().replace(/^www\./,"")}catch{return""}}function Pm(e,t){let n=pr(e,b,"pages.md");if(!Lo(n))return[];let r=t.toLowerCase().replace(/^https?:\/\//,"").replace(/\/.*$/,""),o=new Map;for(let i of po(n).keys()){if(!/^https?:\/\//i.test(i))continue;let s=F(i);if(we(s)||bl(i))continue;let l=`${Am(i)}${s}`;if(!o.get(l)){o.set(l,i);continue}let d="";try{d=new URL(i).hostname.toLowerCase()}catch{d=""}d===r&&o.set(l,i)}return Array.from(o.values())}function yl(e){return[...e].sort((t,n)=>{let r=F(t.url),o=F(n.url);return r==="/"?o==="/"?0:-1:o==="/"?1:r.localeCompare(o)})}function vl(e,t){let n=t.domain.replace(/^https?:\/\//,"").replace(/\/+$/,""),r=(()=>{try{return wt(e)}catch{return null}})(),o=Rm(e),i=Em(e,o),s=new Set(i.map(d=>F(d.url))),a=[];for(let d of Pm(e,n)){let f=F(d);if(s.has(f))continue;let p=o.get(f);a.push({url:d,label:p?.title||No(d),...p?.description?{description:p.description}:{}})}if(a.length===0&&i.length===0)return null;let l=r?.business?.name?.trim()||o.get("/")?.title||n,c=r?.business?.description?.trim()||o.get("/")?.description||null;return{siteName:l,summary:c,pages:yl(a).slice(0,ml),articles:yl(i).slice(0,ml)}}var mr=".well-known/okf",$l="llms.txt",xl={publicDir:null,publicDirSource:null,llmsSourceWritten:!1,llmsPublished:null,okfPublished:0,okfPublishedDir:null,llmsRoute:null,llmsRouteDrift:!1,skipped:[]};function jo(e,t){try{return Sl(e)&&_l(e,"utf-8")===t?!1:(Im(Om(e),{recursive:!0}),Tm(e,t,"utf-8"),!0)}catch{return!1}}function Ot(e,t){if(!t.domain||t.domain==="unknown")return{...xl,skipped:["no-domain"]};let n=Bn(e,t.public_dir??null),r={...xl,publicDir:n.dir,publicDirSource:n.source,skipped:[]},o=null;try{let s=vl(e,t);o=s?kl(s):null}catch{o=null}if(o===null)r.skipped.push("llms-no-content");else if(r.llmsSourceWritten=jo(fn(e,b,gr),o),r.llmsRoute=Wi(e,n.dir),r.llmsRoute)r.skipped.push("llms-route-owned"),r.llmsRouteDrift=r.llmsSourceWritten&&t.llms_owner!=="route";else if(n.dir){let s=fn(e,n.dir,$l);jo(s,o)&&(r.llmsPublished=`${n.dir}/${$l}`)}let i=$t(e);if(!Sl(i))r.skipped.push("okf-missing");else if(Vs(i))r.skipped.push("okf-placeholder");else if(!Zn(i).ok)r.skipped.push("okf-invalid");else if(n.dir){let s=fn(e,n.dir,mr),a=0;for(let l of Js(i)){let c;try{c=_l(fn(i,l),"utf-8")}catch{continue}jo(fn(s,l),c)&&a++}a>0&&(r.okfPublished=a,r.okfPublishedDir=`${n.dir}/${mr}`)}return n.dir||r.skipped.push("no-public-dir"),r}function Cl(e,t){if(!e)return[];let n=[],r=[];return e.llmsPublished&&r.push(e.llmsPublished),e.okfPublishedDir&&r.push(`${e.okfPublishedDir}/ (${e.okfPublished} file${e.okfPublished===1?"":"s"})`),r.length>0&&n.push({text:`ai-files: published ${r.join(" and ")} \u2014 commit these so answer engines can read them.`,tone:"dim",always:!0}),e.skipped.includes("okf-placeholder")&&n.push({text:"ai-files: .seoagent/okf/ is still the scaffold. Fill it (see references/open-knowledge-format.md) and it publishes itself on the next sync.",tone:"dim",always:!1}),e.skipped.includes("okf-invalid")&&n.push({text:`ai-files: .seoagent/okf/ has validation errors, so it was NOT published. Run \`${t("okf validate")}\` and fix them.`,tone:"warn",always:!1}),e.llmsRouteDrift&&n.push({text:`ai-files: .seoagent/llms.md changed, but ${e.llmsRoute} already serves /llms.txt, so nothing was published \u2014 a file in your static dir would shadow that route. Compare the two, or set \`llms_owner: route\` in .seoagent/project.md to own the file yourself and stop this notice.`,tone:"dim",always:!1}),e.skipped.includes("no-public-dir")&&n.push({text:"ai-files: couldn't tell which directory this project serves as static files. Set `public_dir:` in .seoagent/project.md (`public`, `static`, or a nested path like `apps/web/public` in a monorepo) so llms.txt and the OKF bundle actually get served.",tone:"dim",always:!1}),n}function Mm(e){try{let t=Tt(e);return t.evidenceFound?t:void 0}catch{return}}var Um=new Set([".md"]),Bm=new Set(["inbox"]);function Gm(e){let t=[];function n(r,o){if(Do(r))for(let i of Nm(r,{withFileTypes:!0})){if(i.name.startsWith(".")||o===""&&Bm.has(i.name))continue;let s=Fo(r,i.name);if(i.isDirectory())n(s,o?`${o}/${i.name}`:i.name);else if(i.isFile()){let a=i.name.lastIndexOf("."),l=a===-1?"":i.name.slice(a);Um.has(l)&&t.push(s)}}}return n(e,""),t}function El(e){let t=e.replace(/[^a-zA-Z0-9._-]/g,"_");return Fo(Mr,`${t}.json`)}var Hm=14400*1e3;function Wm(e){let t=El(e);if(!Do(t))return{files:{},last_synced_at:null};try{return JSON.parse(Rl(t,"utf-8"))}catch{return{files:{},last_synced_at:null}}}function Km(e,t){Lm(Mr,{recursive:!0}),jm(El(e),JSON.stringify(t,null,2)+`
828
- `,"utf-8")}function qm(e,t){return Dm(e,t).split(Fm).join("/")}async function zm(e,t){try{let n=await I(`${e}/api/cli/actions/fetch`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:t},body:JSON.stringify({})});if(!n.ok){let o=await n.text().catch(()=>"");return{ok:!1,actions:[],error:`${n.status} ${o.slice(0,200)}`}}let r=await n.json().catch(()=>null);return!r||r.status!=="ok"||!Array.isArray(r.actions)?{ok:!1,actions:[],error:"Malformed response"}:{ok:!0,actions:r.actions}}catch(n){return{ok:!1,actions:[],error:n.message}}}async function Ym(e,t,n){try{let r=await I(`${e}/api/cli/sync`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:n},body:JSON.stringify(t)});if(!r.ok){let i=await r.text().catch(()=>"");return{ok:!1,status:r.status,error:i.slice(0,200)}}let o=typeof r.json=="function"?await r.json().catch(()=>null):null;return{ok:!0,status:r.status,...o?.agent_notice?{agentNotice:o.agent_notice}:{}}}catch(r){return{ok:!1,status:0,error:r.message}}}async function Al(e,t={}){let n=Mm(e),r=c=>{let d=n?{...c,verifyRecs:n}:{...c};return i?{...d,aiFiles:i}:d},o=_(e),i=null;if(o)try{i=Ot(e,o)}catch{}let s=D(o?.domain);if(!s)return r({ok:!0,reason:"no-auth",synced:0,failed:0,errors:[]});if(!o)return r({ok:!0,reason:"no-project",synced:0,failed:0,errors:[]});if(o.domain===De)return r({ok:!0,reason:"domain-unknown",synced:0,failed:0,errors:[]});let a=Fo(e,b);if(!Do(a))return r({ok:!0,reason:"no-project",synced:0,failed:0,errors:[]});if(!pl(e).acquired)return r({ok:!0,reason:"busy",synced:0,failed:0,errors:[]});try{return r(await Vm(e,t,s,o,a))}finally{fl(e)}}async function Vm(e,t,n,r,o){let i=n.api_base||N.BASE,s=`Bearer ${n.user_token}:${n.website_token}`,a=await Me({apiBase:i,authHeader:s,localDomain:r.domain});if(a.verdict==="mismatch")return{ok:!0,reason:"project-mismatch",synced:0,failed:0,errors:[],cloudDomain:a.cloudDomain,localDomain:r.domain};if(a.verdict==="unverified")return{ok:!0,reason:"unverified",synced:0,failed:0,errors:[`could not verify cloud project binding: ${a.detail}`]};let l=Wm(r.domain);try{xa(e,r)}catch{}let c=Gm(o),d=0,f=0,p=[],g=[],y=0,h=await Jm(e,i,s),w={restored:!1,blockPush:!1,notices:[]};if((!t.pathFilter||"context.md".endsWith(t.pathFilter))&&(w=await rl({seoagentDir:o,apiBase:i,authHeader:s,stateFiles:l.files,force:t.force,forcePaths:t.forcePaths}),p.push(...w.notices)),!t.pullOnly)for(let q of c){let V=qm(o,q);if(t.pathFilter&&!V.endsWith(t.pathFilter)||V==="context.md"&&w.blockPush)continue;let ze=Rl(q,"utf-8"),ut=de(ze),In=l.files[V];if(!(!In||In.hash!==ut)&&!t.force)continue;y++;let Ye=await Ym(i,{path:V,contents:ze,domain:r.domain},s);Ye.ok?(l.files[V]={hash:ut},d++,Ye.agentNotice&&p.push(`${V}: ${Ye.agentNotice}`)):(f++,g.push(`${V}: ${Ye.status} ${Ye.error??""}`.trim()))}let $=await zm(i,s),C=0,U=0,P=[],T=0,Y=0;!$.ok&&$.error&&g.push(`pull actions: ${$.error}`);let W,j=[],Q=new Map,_e=!1,ct=l.last_synced_at;if(!t.pushOnly){let q=await nl({projectDir:e,apiBase:i,authHeader:s,since:l.last_synced_at,stateFiles:l.files,force:t.force,forcePaths:t.forcePaths});q.ok?ct=q.cursor:(_e=!0,q.error&&g.push(`pull: ${q.error}`)),W={written:q.written,overwritten:q.overwritten,skipped:q.skipped,conflicts:q.conflicts,deleted:q.deleted,warnings:q.warnings},j=q.conflictPaths;for(let[V,ze]of Object.entries(q.pointerOverwrites)){let ut=V.match(/^content\/(.+)\.md$/)?.[1];ut&&Q.set(ut,ze)}}if($.ok){let q=Co(e);for(let[Ce,dt]of Q)q.set(Ce,dt);let V=Ea(e,$.actions,{conflictedPaths:new Set(j),localPointers:q});C=V.wrote,U=V.removed,T=V.skippedUnknown;let ze=l.announced_at?Date.parse(l.announced_at):NaN,In=V.pendingIds.length>0&&(Number.isNaN(ze)||Date.now()-ze>=Hm)?new Set:new Set(l.announced_action_ids??[]),Tn=new Set(V.pendingIds.filter(Ce=>!In.has(Ce)));P=V.pending.filter(Ce=>Tn.has(Ce.id)),Y=Tn.size-P.length,l.announced_action_ids=V.pendingIds,Tn.size>0&&(l.announced_at=new Date().toISOString());let Ye=_a($.actions);W&&j.length>0&&(W.warnings=W.warnings.map(Ce=>{let dt=j.find(Dr=>Ce.startsWith(`${Dr}:`)),jr=dt?Ye.get(dt):void 0;return!dt||!jr?.length?Ce:`${Ce} \u2014 blocks inbox action${jr.length===1?"":"s"} ${jr.map(Dr=>`#${Dr}`).join(", ")}; take the cloud copy with \`seoagent sync --force ${dt}\``}))}let qe=!!W&&W.written+W.overwritten+W.deleted+W.conflicts>0;if(l.last_synced_at=ct,Km(r.domain,l),y===0&&C===0&&U===0&&!qe&&!w.restored&&w.notices.length===0&&h.flushed===0&&h.dropped===0&&!_e&&f===0)return{ok:!0,reason:"no-changes",synced:0,failed:0,errors:g,actionsPulled:0,actionsRemoved:0,actionsNew:[],actionsUnknown:T,actionsUnknownNew:Y,acksFlushed:0,acksDropped:0,pull:W};let Pi=f===0&&!_e;return{ok:Pi,reason:Pi?void 0:"error",synced:d,failed:f,errors:g,...p.length>0?{notices:p}:{},actionsPulled:C,actionsRemoved:U,actionsNew:P,actionsUnknown:T,actionsUnknownNew:Y,acksFlushed:h.flushed,acksDropped:h.dropped,pull:W}}async function Jm(e,t,n){let r=ur(e);if(r.length===0)return{flushed:0,dropped:0};let o=0,i=0,s=[];for(let a of r){let l=await Xm(a,t,n);l==="flushed"?(o++,s.push(a.action_id)):l==="already-settled"&&(i++,s.push(a.action_id))}return s.length>0&&cl(e,s),{flushed:o,dropped:i}}async function Xm(e,t,n){try{let r=await I(`${t}/api/cli/actions/${e.action_id}/ack`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:n},body:JSON.stringify({status:e.status,error_message:e.status==="failed"?e.reason:null,result:e.status==="failed"?{declined:!0,reason:e.reason}:{applied:!0}})});return r.ok?"flushed":r.status===409||r.status>=400&&r.status<500?"already-settled":"transient"}catch{return"transient"}}function Pl(e,t=3e4){let n=setTimeout(e,t);return n.unref?.(),{cancel:()=>clearTimeout(n)}}function Il(e){let t;try{t=_(e)}catch{return{refreshed:!1,reason:"error"}}if(!t)return{refreshed:!1,reason:"no-project"};let n=t.skill_version||t.seoagent_version||"0.0.0";if(We(A,n)<=0){if(!t.skill_version)try{J(e,{skill_version:n})}catch{}return{refreshed:!1,reason:"up-to-date"}}try{return yt(e),J(e,{skill_version:A}),{refreshed:!0,from:n,to:A,reason:"refreshed"}}catch{return{refreshed:!1,reason:"error"}}}function Mo(e){let t;try{t=_(e)}catch{return{refreshed:!1,from:null,to:A,reason:"error"}}if(!t)return{refreshed:!1,from:null,to:A,reason:"no-project"};let n=t.skill_version||t.seoagent_version||null;try{return yt(e),J(e,{skill_version:A}),{refreshed:!0,from:n,to:A,reason:"refreshed"}}catch{return{refreshed:!1,from:n,to:A,reason:"error"}}}import{createHash as Qm}from"crypto";import{existsSync as n_,mkdirSync as Zm,readFileSync as eh,writeFileSync as th}from"fs";import{join as nh}from"path";var rh="/cli-skill/manifest.json",oh=360*60*1e3,ih=4e3,sh=64,ah=4*1024*1024,Tl=nh(K,"skill-check.json"),lh=e=>Qm("sha256").update(e).digest("hex");function ch(e){if(typeof e!="object"||e===null)return null;let t=e;if(typeof t.hash!="string"||!/^[0-9a-f]{64}$/.test(t.hash)||typeof t.min_cli!="string"||!/^\d+\.\d+\.\d+/.test(t.min_cli)||!Array.isArray(t.files)||t.files.length===0||t.files.length>sh)return null;let n=[],r=0;for(let o of t.files){if(typeof o!="object"||o===null)return null;let i=o;if(typeof i.path!="string"||typeof i.sha256!="string"||!/^[A-Za-z0-9._/-]+$/.test(i.path)||i.path.startsWith("/")||i.path.split("/").includes("..")||!i.path.endsWith(".md")||!/^[0-9a-f]{64}$/.test(i.sha256))return null;let s=typeof i.bytes=="number"?i.bytes:0;r+=s,n.push({path:i.path,sha256:i.sha256,bytes:s})}return r>ah||!n.some(o=>o.path==="SKILL.md")?null:{hash:t.hash,min_cli:t.min_cli,files:n}}function uh(e,t,n=oh){if(!e)return!0;let r=Date.parse(e);return Number.isNaN(r)?!0:t.getTime()-r>=n}function dh(){try{return JSON.parse(eh(Tl,"utf-8"))}catch{return{}}}function ph(e){try{Zm(K,{recursive:!0}),th(Tl,JSON.stringify({checkedAt:e}),"utf-8")}catch{}}async function fh(e){try{let t=await fetch(e,{signal:AbortSignal.timeout(ih),headers:{"User-Agent":`seoagent-cli/${A}`}});return t.ok?await t.text():null}catch{return null}}async function Ol(e,t={}){try{let n=_(e);if(!n)return{updated:!1,reason:"no-project"};let r=t.now??new Date,o=dh();if(!t.force&&!uh(o.checkedAt,r))return{updated:!1,reason:"throttled"};let i=(t.baseUrl??N.BASE).replace(/\/$/,""),s=t.fetchText??fh,a=await s(`${i}${rh}`);if(ph(r.toISOString()),a===null)return{updated:!1,reason:"unreachable"};let l;try{l=ch(JSON.parse(a))}catch{l=null}if(!l)return{updated:!1,reason:"malformed"};if(n.skill_hash===l.hash)return{updated:!1,reason:"up-to-date"};if(We(A,l.min_cli)<0)return{updated:!1,reason:"cli-too-old",minCli:l.min_cli};let c={};for(let d of l.files){let f=await s(`${i}/cli-skill/${d.path}`);if(f===null)return{updated:!1,reason:"unreachable"};if(lh(f)!==d.sha256)return{updated:!1,reason:"verify-failed"};c[d.path]=f}return as(e,c),J(e,{skill_hash:l.hash}),{updated:!0,reason:"updated",fileCount:l.files.length}}catch{return{updated:!1,reason:"error"}}}import{existsSync as gh,mkdirSync as mh,readFileSync as hh,writeFileSync as yh}from"fs";var wh="https://registry.npmjs.org/@seoagent-official/seoagent/latest",bh=1440*60*1e3,kh=720*60*1e3,vh=2e3;function $h(e,t,n=bh){if(!e.checkedAt)return!0;let r=Date.parse(e.checkedAt);return Number.isNaN(r)?!0:t.getTime()-r>=n}function xh(e){let{current:t,latest:n,cache:r,now:o}=e,i=e.notifyIntervalMs??kh;if(!n||We(n,t)<=0)return null;if(r.notifiedVersion===n&&r.notifiedAt){let s=Date.parse(r.notifiedAt);if(!Number.isNaN(s)&&o.getTime()-s<i)return null}return`\u2191 SEOAgent ${n} is available (you have ${t}). Ask me to run \`seoagent update-cli\`, or run it yourself, to upgrade the CLI + all your projects.`}async function Sh(e){let t=e.readCache();if($h(t,e.now,e.checkIntervalMs)){let r=await e.fetchLatest();t={...t,checkedAt:e.now.toISOString(),...r?{latest:r}:{}},e.writeCache(t)}let n=xh({current:e.current,latest:t.latest,cache:t,now:e.now,notifyIntervalMs:e.notifyIntervalMs});return n&&t.latest&&e.writeCache({...t,notifiedVersion:t.latest,notifiedAt:e.now.toISOString()}),n}function _h(){try{return gh(Nn)?JSON.parse(hh(Nn,"utf-8")):{}}catch{return{}}}function Ch(e){try{mh(K,{recursive:!0}),yh(Nn,JSON.stringify(e,null,2)+`
828
+ `,"utf-8")}function qm(e,t){return Dm(e,t).split(Fm).join("/")}async function zm(e,t){try{let n=await I(`${e}/api/cli/actions/fetch`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:t},body:JSON.stringify({})});if(!n.ok){let o=await n.text().catch(()=>"");return{ok:!1,actions:[],error:`${n.status} ${o.slice(0,200)}`}}let r=await n.json().catch(()=>null);return!r||r.status!=="ok"||!Array.isArray(r.actions)?{ok:!1,actions:[],error:"Malformed response"}:{ok:!0,actions:r.actions}}catch(n){return{ok:!1,actions:[],error:n.message}}}async function Ym(e,t,n){try{let r=await I(`${e}/api/cli/sync`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:n},body:JSON.stringify(t)});if(!r.ok){let i=await r.text().catch(()=>"");return{ok:!1,status:r.status,error:i.slice(0,200)}}let o=typeof r.json=="function"?await r.json().catch(()=>null):null;return{ok:!0,status:r.status,...o?.agent_notice?{agentNotice:o.agent_notice}:{}}}catch(r){return{ok:!1,status:0,error:r.message}}}async function Al(e,t={}){let n=Mm(e),r=c=>{let d=n?{...c,verifyRecs:n}:{...c};return i?{...d,aiFiles:i}:d},o=_(e),i=null;if(o)try{i=Ot(e,o)}catch{}let s=D(o?.domain);if(!s)return r({ok:!0,reason:"no-auth",synced:0,failed:0,errors:[]});if(!o)return r({ok:!0,reason:"no-project",synced:0,failed:0,errors:[]});if(o.domain===De)return r({ok:!0,reason:"domain-unknown",synced:0,failed:0,errors:[]});let a=Fo(e,b);if(!Do(a))return r({ok:!0,reason:"no-project",synced:0,failed:0,errors:[]});if(!pl(e).acquired)return r({ok:!0,reason:"busy",synced:0,failed:0,errors:[]});try{return r(await Vm(e,t,s,o,a))}finally{fl(e)}}async function Vm(e,t,n,r,o){let i=n.api_base||N.BASE,s=`Bearer ${n.user_token}:${n.website_token}`,a=await Me({apiBase:i,authHeader:s,localDomain:r.domain});if(a.verdict==="mismatch")return{ok:!0,reason:"project-mismatch",synced:0,failed:0,errors:[],cloudDomain:a.cloudDomain,localDomain:r.domain};if(a.verdict==="unverified")return{ok:!0,reason:"unverified",synced:0,failed:0,errors:[`could not verify cloud project binding: ${a.detail}`]};let l=Wm(r.domain);try{xa(e,r)}catch{}let c=Gm(o),d=0,f=0,p=[],g=[],y=0,h=await Jm(e,i,s),w={restored:!1,blockPush:!1,notices:[]};if((!t.pathFilter||"context.md".endsWith(t.pathFilter))&&(w=await rl({seoagentDir:o,apiBase:i,authHeader:s,stateFiles:l.files,force:t.force,forcePaths:t.forcePaths}),p.push(...w.notices)),!t.pullOnly)for(let q of c){let V=qm(o,q);if(t.pathFilter&&!V.endsWith(t.pathFilter)||V==="context.md"&&w.blockPush)continue;let ze=Rl(q,"utf-8"),ut=de(ze),In=l.files[V];if(!(!In||In.hash!==ut)&&!t.force)continue;y++;let Ye=await Ym(i,{path:V,contents:ze,domain:r.domain},s);Ye.ok?(l.files[V]={hash:ut},d++,Ye.agentNotice&&p.push(`${V}: ${Ye.agentNotice}`)):(f++,g.push(`${V}: ${Ye.status} ${Ye.error??""}`.trim()))}let $=await zm(i,s),C=0,U=0,P=[],T=0,Y=0;!$.ok&&$.error&&g.push(`pull actions: ${$.error}`);let W,j=[],Q=new Map,_e=!1,ct=l.last_synced_at;if(!t.pushOnly){let q=await nl({projectDir:e,apiBase:i,authHeader:s,since:l.last_synced_at,stateFiles:l.files,force:t.force,forcePaths:t.forcePaths});q.ok?ct=q.cursor:(_e=!0,q.error&&g.push(`pull: ${q.error}`)),W={written:q.written,overwritten:q.overwritten,skipped:q.skipped,conflicts:q.conflicts,deleted:q.deleted,warnings:q.warnings},j=q.conflictPaths;for(let[V,ze]of Object.entries(q.pointerOverwrites)){let ut=V.match(/^content\/(.+)\.md$/)?.[1];ut&&Q.set(ut,ze)}}if($.ok){let q=Co(e);for(let[Ce,dt]of Q)q.set(Ce,dt);let V=Ea(e,$.actions,{conflictedPaths:new Set(j),localPointers:q});C=V.wrote,U=V.removed,T=V.skippedUnknown;let ze=l.announced_at?Date.parse(l.announced_at):NaN,In=V.pendingIds.length>0&&(Number.isNaN(ze)||Date.now()-ze>=Hm)?new Set:new Set(l.announced_action_ids??[]),Tn=new Set(V.pendingIds.filter(Ce=>!In.has(Ce)));P=V.pending.filter(Ce=>Tn.has(Ce.id)),Y=Tn.size-P.length,l.announced_action_ids=V.pendingIds,Tn.size>0&&(l.announced_at=new Date().toISOString());let Ye=_a($.actions);W&&j.length>0&&(W.warnings=W.warnings.map(Ce=>{let dt=j.find(Dr=>Ce.startsWith(`${Dr}:`)),jr=dt?Ye.get(dt):void 0;return!dt||!jr?.length?Ce:`${Ce} \u2014 blocks inbox action${jr.length===1?"":"s"} ${jr.map(Dr=>`#${Dr}`).join(", ")}; take the cloud copy with \`seoagent sync --force ${dt}\``}))}let qe=!!W&&W.written+W.overwritten+W.deleted+W.conflicts>0;if(l.last_synced_at=ct,Km(r.domain,l),y===0&&C===0&&U===0&&!qe&&!w.restored&&w.notices.length===0&&h.flushed===0&&h.dropped===0&&!_e&&f===0)return{ok:!0,reason:"no-changes",synced:0,failed:0,errors:g,actionsPulled:0,actionsRemoved:0,actionsNew:[],actionsUnknown:T,actionsUnknownNew:Y,acksFlushed:0,acksDropped:0,pull:W};let Pi=f===0&&!_e;return{ok:Pi,reason:Pi?void 0:"error",synced:d,failed:f,errors:g,...p.length>0?{notices:p}:{},actionsPulled:C,actionsRemoved:U,actionsNew:P,actionsUnknown:T,actionsUnknownNew:Y,acksFlushed:h.flushed,acksDropped:h.dropped,pull:W}}async function Jm(e,t,n){let r=ur(e);if(r.length===0)return{flushed:0,dropped:0};let o=0,i=0,s=[];for(let a of r){let l=await Xm(a,t,n);l==="flushed"?(o++,s.push(a.action_id)):l==="already-settled"&&(i++,s.push(a.action_id))}return s.length>0&&cl(e,s),{flushed:o,dropped:i}}async function Xm(e,t,n){try{let r=await I(`${t}/api/cli/actions/${e.action_id}/ack`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:n},body:JSON.stringify({status:e.status,error_message:e.status==="failed"?e.reason:null,result:e.status==="failed"?{declined:!0,reason:e.reason}:{applied:!0}})});return r.ok?"flushed":r.status===409||r.status>=400&&r.status<500?"already-settled":"transient"}catch{return"transient"}}function Pl(e,t=3e4){let n=setTimeout(e,t);return n.unref?.(),{cancel:()=>clearTimeout(n)}}function Il(e){let t;try{t=_(e)}catch{return{refreshed:!1,reason:"error"}}if(!t)return{refreshed:!1,reason:"no-project"};let n=t.skill_version||t.seoagent_version||"0.0.0";if(We(A,n)<=0){if(!t.skill_version)try{J(e,{skill_version:n})}catch{}return{refreshed:!1,reason:"up-to-date"}}try{return yt(e),J(e,{skill_version:A}),{refreshed:!0,from:n,to:A,reason:"refreshed"}}catch{return{refreshed:!1,reason:"error"}}}function Mo(e){let t;try{t=_(e)}catch{return{refreshed:!1,from:null,to:A,reason:"error"}}if(!t)return{refreshed:!1,from:null,to:A,reason:"no-project"};let n=t.skill_version||t.seoagent_version||null;try{return yt(e),J(e,{skill_version:A}),{refreshed:!0,from:n,to:A,reason:"refreshed"}}catch{return{refreshed:!1,from:n,to:A,reason:"error"}}}import{createHash as Qm}from"crypto";import{existsSync as o_,mkdirSync as Zm,readFileSync as eh,writeFileSync as th}from"fs";import{join as nh}from"path";var rh="/cli-skill/manifest.json",oh=360*60*1e3,ih=4e3,sh=64,ah=4*1024*1024,Tl=nh(K,"skill-check.json"),lh=e=>Qm("sha256").update(e).digest("hex");function ch(e){if(typeof e!="object"||e===null)return null;let t=e;if(typeof t.hash!="string"||!/^[0-9a-f]{64}$/.test(t.hash)||typeof t.min_cli!="string"||!/^\d+\.\d+\.\d+/.test(t.min_cli)||!Array.isArray(t.files)||t.files.length===0||t.files.length>sh)return null;let n=[],r=0;for(let o of t.files){if(typeof o!="object"||o===null)return null;let i=o;if(typeof i.path!="string"||typeof i.sha256!="string"||!/^[A-Za-z0-9._/-]+$/.test(i.path)||i.path.startsWith("/")||i.path.split("/").includes("..")||!i.path.endsWith(".md")||!/^[0-9a-f]{64}$/.test(i.sha256))return null;let s=typeof i.bytes=="number"?i.bytes:0;r+=s,n.push({path:i.path,sha256:i.sha256,bytes:s})}return r>ah||!n.some(o=>o.path==="SKILL.md")?null:{hash:t.hash,min_cli:t.min_cli,files:n}}function uh(e,t,n=oh){if(!e)return!0;let r=Date.parse(e);return Number.isNaN(r)?!0:t.getTime()-r>=n}function dh(){try{return JSON.parse(eh(Tl,"utf-8"))}catch{return{}}}function ph(e){try{Zm(K,{recursive:!0}),th(Tl,JSON.stringify({checkedAt:e}),"utf-8")}catch{}}async function fh(e){try{let t=await fetch(e,{signal:AbortSignal.timeout(ih),headers:{"User-Agent":`seoagent-cli/${A}`}});return t.ok?await t.text():null}catch{return null}}async function Ol(e,t={}){try{let n=_(e);if(!n)return{updated:!1,reason:"no-project"};let r=t.now??new Date,o=dh();if(!t.force&&!uh(o.checkedAt,r))return{updated:!1,reason:"throttled"};let i=(t.baseUrl??N.BASE).replace(/\/$/,""),s=t.fetchText??fh,a=await s(`${i}${rh}`);if(ph(r.toISOString()),a===null)return{updated:!1,reason:"unreachable"};let l;try{l=ch(JSON.parse(a))}catch{l=null}if(!l)return{updated:!1,reason:"malformed"};if(n.skill_hash===l.hash)return{updated:!1,reason:"up-to-date"};if(We(A,l.min_cli)<0)return{updated:!1,reason:"cli-too-old",minCli:l.min_cli};let c={};for(let d of l.files){let f=await s(`${i}/cli-skill/${d.path}`);if(f===null)return{updated:!1,reason:"unreachable"};if(lh(f)!==d.sha256)return{updated:!1,reason:"verify-failed"};c[d.path]=f}return as(e,c),J(e,{skill_hash:l.hash}),{updated:!0,reason:"updated",fileCount:l.files.length}}catch{return{updated:!1,reason:"error"}}}import{existsSync as gh,mkdirSync as mh,readFileSync as hh,writeFileSync as yh}from"fs";var wh="https://registry.npmjs.org/@seoagent-official/seoagent/latest",bh=1440*60*1e3,kh=720*60*1e3,vh=2e3;function $h(e,t,n=bh){if(!e.checkedAt)return!0;let r=Date.parse(e.checkedAt);return Number.isNaN(r)?!0:t.getTime()-r>=n}function xh(e){let{current:t,latest:n,cache:r,now:o}=e,i=e.notifyIntervalMs??kh;if(!n||We(n,t)<=0)return null;if(r.notifiedVersion===n&&r.notifiedAt){let s=Date.parse(r.notifiedAt);if(!Number.isNaN(s)&&o.getTime()-s<i)return null}return`\u2191 SEOAgent ${n} is available (you have ${t}). Ask me to run \`seoagent update-cli\`, or run it yourself, to upgrade the CLI + all your projects.`}async function Sh(e){let t=e.readCache();if($h(t,e.now,e.checkIntervalMs)){let r=await e.fetchLatest();t={...t,checkedAt:e.now.toISOString(),...r?{latest:r}:{}},e.writeCache(t)}let n=xh({current:e.current,latest:t.latest,cache:t,now:e.now,notifyIntervalMs:e.notifyIntervalMs});return n&&t.latest&&e.writeCache({...t,notifiedVersion:t.latest,notifiedAt:e.now.toISOString()}),n}function _h(){try{return gh(Nn)?JSON.parse(hh(Nn,"utf-8")):{}}catch{return{}}}function Ch(e){try{mh(K,{recursive:!0}),yh(Nn,JSON.stringify(e,null,2)+`
829
829
  `,"utf-8")}catch{}}async function Rh(){try{let e=await fetch(wh,{headers:{Accept:"application/json"},signal:AbortSignal.timeout(vh)});if(!e.ok)return null;let t=await e.json();return typeof t.version=="string"?t.version:null}catch{return null}}async function Ll(e){try{return await Sh({current:e,now:new Date,fetchLatest:Rh,readCache:_h,writeCache:Ch})}catch{return null}}import{existsSync as Nl,readdirSync as jl}from"fs";import{join as Uo}from"path";var Dl={PILLAR:0,SUB_PILLAR:1,LONG_TAIL:2},Fl={high:0,medium:1,low:2},Eh=new Set(["drafted","published","dismissed","superseded","done"]);function Ah(e){let t=e.filter(n=>!Eh.has((n.status??"").toLowerCase()));return t.length===0?null:[...t].sort((n,r)=>{let o=Dl[(n.role??"").toUpperCase()]??3,i=Dl[(r.role??"").toUpperCase()]??3;if(o!==i)return o-i;let s=Fl[(n.priority??"").toLowerCase()]??3,a=Fl[(r.priority??"").toLowerCase()]??3;return s!==a?s-a:n.slug.localeCompare(r.slug)})[0]}function Ml(e,t,n){if(t>0)return null;let r=Ah(e);if(!r)return null;let o=r.role?` (${r.role})`:"";return`Next: no article yet \u2014 write .seoagent/briefs/${r.slug}.md${o} first, then run \`${n("content track")} <path-to-article>\` so the board advances. ${e.length} brief${e.length===1?"":"s"} waiting.`}function Ul(e){let t={briefs:[],articleCount:0};try{let n=Uo(e,"briefs");if(Nl(n))for(let o of jl(n).filter(i=>i.endsWith(".md"))){let s=mt(Uo(n,o))?.data??{};t.briefs.push({slug:o.replace(/\.md$/,""),role:typeof s.role=="string"?s.role:null,priority:typeof s.priority=="string"?s.priority:null,status:typeof s.status=="string"?s.status:null})}let r=Uo(e,"content");Nl(r)&&(t.articleCount=jl(r).filter(o=>o.endsWith(".md")).length)}catch{}return t}async function re(e={}){let t=Pl(()=>{e.silent||u.warn(`Sync gave up after ${Math.round(3e4/1e3)}s (network too slow or stalled). Sync is best-effort \u2014 nothing is lost; it will retry on the next run.`),process.exit(0)});try{await Th(e)}finally{t.cancel()}}async function Th(e){let t=Il(process.cwd());if(t.refreshed){try{Ze(process.cwd())}catch{}e.silent||u.info(H.dim(`\u21BB Updated SEOAgent skill ${t.from} \u2192 ${t.to}.`))}let n=await Ol(process.cwd());n.updated&&!e.silent&&u.info(H.dim("\u21BB Updated the SEOAgent skill to the latest published version.")),n.reason==="cli-too-old"&&!e.silent&&u.message(H.yellow(`A newer SEOAgent skill is available but needs CLI ${n.minCli} or later (you have ${A}). Run \`${m("update-cli")}\` to get it.`)),et(process.cwd());let r=await Ll(A);r&&u.message(H.yellow(r));let o=!e.silent&&!!process.stdout.isTTY,i=e.pullOnly?"Pulling":e.pushOnly?"Pushing":"Syncing",s=o?Ih():null;s?.start(`${i} ${H.dim(".seoagent/")} with your dashboard\u2026`);let{force:a,forcePaths:l}=Qa(e.force),c=await Al(process.cwd(),{pathFilter:e.path,force:a,forcePaths:l,pushOnly:e.pushOnly,pullOnly:e.pullOnly}),d=c.verifyRecs;if(d){d.sourceRender&&u.message(H.yellow("verify-recs: evidence.md is a SOURCE RENDER (undesignated local dev-server crawl) \u2014 live-state claims cannot be verified. Re-run `seoagent crawl --url <live origin>`."));for(let g of d.corrections)u.message(H.yellow(`verify-recs: corrected ${g.file}:${g.line} \u2014 the live page (${g.pageUrl}) already serves ${g.entityLabel}; reflect this correction in your summary.`))}for(let g of Cl(c.aiFiles??null,m))!g.always&&e.silent||u.message(g.tone==="warn"?H.yellow(g.text):H.dim(g.text));if(c.actionsNew&&c.actionsNew.length>0){let g=c.actionsNew.slice(0,5).map(h=>` - ${h.title??h.action_type} (id ${h.id})`).join(`
830
830
  `),y=c.actionsNew.length>5?`
831
831
  \u2026 and ${c.actionsNew.length-5} more`:"";u.message(H.yellow(`inbox: ${c.actionsNew.length} pending action${c.actionsNew.length===1?"":"s"} from your SEOAgent dashboard await processing:
@@ -1023,11 +1023,11 @@ ${t}
1023
1023
  `),!(s.subtype==="success"&&!s.is_error))return u.error(" Agent did not complete the citation audit (auth, rate limit, or network)."),cu(),null;i=String(s.result??"")}return i}catch(o){return process.stdout.write(`
1024
1024
  `),u.error(` Agent error: ${o.message}`),cu(),null}}function cu(){u.message(ql.map(e=>L.dim(` ${e}`)).join(`
1025
1025
  `))}function Ob(e){if(!e||typeof e!="object")return;let t=e.type;if(t==="text"){let n=e.text;typeof n=="string"&&n&&process.stdout.write(br(n));return}if(t==="tool_use"){let n=String(e.name??"tool"),r=e.input,o=n==="WebSearch"?String(r?.query??""):n==="WebFetch"?String(r?.url??""):"";process.stdout.write(L.dim(` ${L.magenta("\u{1F50E}")} ${n}${o?` ${Lb(o,70)}`:""}
1026
- `))}}function Lb(e,t){return e.length<=t?e:e.slice(0,t-1)+"\u2026"}function Nb(e){return typeof e!="string"?[]:e.split(",").map(t=>t.trim()).filter(Boolean)}function jb(e){try{if(!Pb(e))return null;let t=Ab(e).filter(o=>o.endsWith(".json")).sort();if(t.length===0)return null;let n=t[t.length-1],r=JSON.parse(Eb(uu(e,n),"utf-8"));return r&&typeof r=="object"&&Array.isArray(r.results)&&typeof r.generated_at=="string"?r:null}catch{return null}}import Dt from"picocolors";var Db=30,Fb=/<(script|style|noscript|template|svg|nav|header|footer)\b[^>]*>[\s\S]*?<\/\1>/gi;function Mb(e){let t=e,n=/<body\b[^>]*>([\s\S]*?)<\/body>/i.exec(t);n&&(t=n[1]),t=t.replace(Fb," ").replace(/<!--[\s\S]*?-->/g," ").replace(/<[^>]+>/g," ").replace(/&nbsp;/gi," ").replace(/&[a-z]+;/gi," ").replace(/\s+/g," ").trim();let r=t===""?0:t.split(" ").filter(Boolean).length;return{wordCount:r,rendered:r<Db?"empty":"yes"}}async function Ub(e,t){try{let n=await I(e,{headers:{"user-agent":"Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html) SEOAgent-CLI"},redirect:"follow"},{attempts:2,timeoutMs:1e4,fetchImpl:t});if(!n.ok)return{status:n.status,rendered:"error",wordCount:0};let r=await n.text(),{wordCount:o,rendered:i}=Mb(r);return{status:n.status,rendered:i,wordCount:o}}catch{return{status:null,rendered:"error",wordCount:0}}}async function du(e,t={}){let n=Math.max(1,t.concurrency??5),r=new Map,o=[...e],i=0;async function s(){for(;;){let a=o.shift();if(a===void 0)return;r.set(a,await Ub(a,t.fetchImpl)),i+=1,t.onProgress?.(i,e.length)}}return await Promise.all(Array.from({length:Math.min(n,e.length)},()=>s())),r}var xi=300;async function _n(e={}){if(e.all){await Bb();return}let t=process.cwd();if(!O(t))return;et(t);let n=Mo(t);fu(t);let r=await pu(t,{enrichSitemap:!0,crawl:e.crawl});if(n.reason==="refreshed"?(u.success(n.from&&n.from!==n.to?`Refreshed the SEOAgent skill ${n.from} \u2192 ${n.to}.`:`Refreshed the SEOAgent skill (now ${n.to}).`),u.info("Your `.seoagent/` knowledge was left untouched.")):(u.error(`Could not refresh the skill (${n.reason}).`),process.exitCode=1),r.added>0?u.success(`Re-scanned the codebase: +${r.added} new page${r.added===1?"":"s"} merged into .seoagent/pages.md (${r.total} total).`):r.total>0&&u.info(`Page inventory already current (${r.total} pages).`),r.crawl){let{crawled:o,empty:i,errors:s}=r.crawl;u.success(`Crawled ${o} page${o===1?"":"s"} \u2192 filled Status / Rendered / Word count`+(i>0?`, ${i} render empty`:"")+(s>0?`, ${s} unreachable`:"")+".")}}async function pu(e,t={}){try{let n=_(e),r=n?.domain&&n.domain!=="unknown"?n.domain:null,{pages:o}=be(e,r);t.enrichSitemap&&r&&await Ms(o,r);let i,s;if(t.crawl&&r){let l=o.map(d=>d.url).filter(d=>/^https?:\/\//.test(d)),c=l.slice(0,xi);if(l.length>xi&&u.info(`Crawling the first ${xi} of ${l.length} pages (cap).`),c.length>0){u.info(`Crawling ${c.length} page${c.length===1?"":"s"}\u2026`),s=await du(c);let d=0,f=0;for(let p of s.values())p.rendered==="empty"?d++:p.rendered==="error"&&f++;i={crawled:s.size,empty:d,errors:f}}}let a=Jn(e,o,s);return{added:a.added,total:a.total,crawl:i}}catch{return{added:0,total:0}}}async function Bb(){let e=ks();if(e.length===0){u.info("No SEOAgent projects are registered on this machine yet. They register themselves on `init` / `sync`.");return}u.message(`Refreshing ${e.length} project${e.length===1?"":"s"} to SEOAgent ${Dt.bold(A)}\u2026`);let t=0,n=0;for(let r of e){let o=Mo(r);fu(r);let i=await pu(r),s=i.added>0?Dt.cyan(` +${i.added} page${i.added===1?"":"s"}`):"";if(o.reason==="refreshed"){t++;let a=o.from&&o.from!==o.to?Dt.dim(` (${o.from} \u2192 ${o.to})`):"";u.message(` ${Dt.green("\u2713")} ${r}${a}${s}`)}else n++,u.message(` ${Dt.yellow("!")} ${r} ${Dt.dim("\u2014 "+o.reason)}${s}`)}u.success(`Refreshed ${t}/${e.length} project${e.length===1?"":"s"} to ${A}.`),n>0&&u.info(`${n} could not be refreshed (see above).`)}function fu(e){try{Ze(e)}catch{}}import{spinner as _i,confirm as Gb,isCancel as Hb}from"@clack/prompts";import v from"picocolors";var mu={easy_win:"easy win",striking_distance:"striking distance",competitor_gap:"competitor gap",defend:"defend",low_priority:"low priority"};async function Wb(e){let t=process.cwd(),n=Hn(t);if(!n){u.error(`No .seoagent/project.md found. Run \`${m("init")}\` first.`),process.exitCode=1;return}let r=_(t),o=r?.domain&&r.domain!=="unknown"?r.domain:void 0,i=(e.apiBase||N.BASE).replace(/\/$/,""),a=!!process.stdout.isTTY&&!e.json?_i():null;a?.start(`Looking up ${v.bold(`"${e.keyword}"`)} ${v.dim("via DataForSEO (free peek)\u2026")}`);let l;try{l=await I(`${i}/api/cli/keywords/peek`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({install_id:n,keyword:e.keyword,...o?{domain:o}:{}})})}catch(d){x(a,"error",`Network error: ${d.message}`,v.red("Network error.")),process.exitCode=1;return}let c=null;try{c=await l.json()}catch{c=null}if(e.json){process.stdout.write(JSON.stringify({status:c?.status??"error",http_status:l.status,body:c??null})+`
1027
- `);return}if(l.status===429||c?.skipped?.startsWith("rate-limited")){let d=c?.skipped==="rate-limited:ip"?"this network":"this install";x(a,"warn",`Daily peek quota reached for ${d}.`,v.yellow(`Daily peek quota reached for ${d}.`)),u.message(gu()),process.exitCode=1;return}if(!l.ok||!c){x(a,"error",`Peek failed (${l.status})${c?.error?`: ${c.error}`:""}`,v.red(`Peek failed (${l.status}).`)),process.exitCode=1;return}if(c.skipped==="provider-quota"){x(a,"warn","Real keyword data is temporarily unavailable. Try again later.",v.yellow("Real keyword data is temporarily unavailable. Try again later.")),u.message(v.dim("No action needed on your side \u2014 the maintainer has been notified."));return}if(c.skipped==="no-creds"||c.skipped==="no-table"){x(a,"warn","Real keyword data isn\u2019t enabled on the server yet \u2014 try again later.",v.yellow("Real keyword data is not enabled on the server yet."));return}if(!c.opportunity){x(a,"warn",`No data returned for "${e.keyword}".`,v.yellow(`No data returned for "${e.keyword}".`));return}a?.stop(v.green("Got it.")),u.message(Kb(c.opportunity)),c.remaining&&u.message(`${v.dim("Peeks remaining today:")} ${v.bold(String(c.remaining.install))} ${v.dim("\u2014 "+gu())}`)}function Kb(e){let n=c=>v.dim(c.padEnd(14)),r=e.volume!=null?v.bold(String(e.volume)):v.dim("n/a"),o=e.difficulty!=null?`${v.bold(String(e.difficulty))} ${v.dim(qb(e.difficulty))}`:v.dim("n/a"),i=mu[e.opportunity]??e.opportunity,s=e.opportunity==="easy_win"?v.bold(v.green(i)):e.opportunity==="striking_distance"?v.bold(v.cyan(i)):e.opportunity==="competitor_gap"?v.bold(v.yellow(i)):v.dim(i),a=[{content:`${v.green("\u25CF")} ${v.bold(e.keyword)}`},{sep:!0},{content:`${n("Volume:")}${r} ${v.dim("/mo")}`},{content:`${n("Difficulty:")}${o}`},{content:`${n("Opportunity:")}${s}`}],l=`${v.bold(v.cyan("Keyword peek"))}`;return M(a,{width:55,title:l})}function qb(e){return e<=20?"easy":e<=40?"moderate":e<=60?"hard":"very hard"}function gu(e=D()){return e?`You're logged in \u2014 run \`${m("keywords")}\` for full enrichment (no quota). Pro unlocks the full inventory beyond the ~25 cap plus \`${m("keywords --discover")}\` (new targets) + \`${m("keywords --competitors")}\` (competitor gaps): ${m("upgrade")}.`:`Log in for the full top-25 enrichment, no quota: ${m("login")}`}function Si(e){let t=e.filter(n=>n.opportunity!=="low_priority");if(t.length!==0){u.message("Top opportunities:");for(let n of t){let r=n.volume!=null?`vol ${n.volume}`:"vol n/a",o=n.difficulty!=null?`KD ${n.difficulty}`:"KD n/a",i=n.our_position!=null?`, you're #${n.our_position}`:", not ranking";u.message(` \u2022 [${mu[n.opportunity]??n.opportunity}] ${n.keyword} \u2014 ${r}, ${o}${i}`)}}}function zb(e){if(e.length!==0){u.message("Top competitor gaps:");for(let t of e){let n=t.volume!=null?`vol ${t.volume}`:"vol n/a",r=t.difficulty!=null?`KD ${t.difficulty}`:"KD n/a",o=t.their_position!=null?`#${t.their_position}`:"top 10";u.message(` \u2022 ${t.keyword} \u2014 ${t.competitor_domain} ranks ${o}, ${n}, ${r}`)}}}async function Yb(e){let t=e.all?"all":"suggested";if(t==="all"&&process.stdin.isTTY){let a=await Gb({message:"Delete ALL keywords for this site from the cloud? This cannot be undone."});if(Hb(a)||!a){u.info("No changes.");return}}let n=e.apiBase||e.auth.api_base||N.BASE,r=process.stdout.isTTY?_i():null;r?.start(t==="all"?"Purging all keywords\u2026":"Purging suggested keywords\u2026");let o;try{o=await I(`${n}/api/cli/keywords/purge`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e.auth.user_token}:${e.auth.website_token}`},body:JSON.stringify({scope:t})})}catch(a){x(r,"error",`Network error: ${a.message}`,v.red("Network error.")),process.exitCode=1;return}let i=null;try{i=await o.json()}catch{i=null}if(i?.skipped==="no-table"||i?.skipped==="no-columns"){x(r,"warn","Keyword storage isn\u2019t enabled on the server yet.",v.yellow("Nothing to purge yet."));return}if(!o.ok||i?.status!=="ok"){x(r,"error",`Purge failed (${o.status})${i?.error?`: ${i.error}`:""}`,v.red(`Purge failed (${o.status}).`)),process.exitCode=1;return}let s=i.purged??0;x(r,"success",`Purged ${s} ${t==="all"?"":"suggested "}keyword${s===1?"":"s"}.`,v.green(`Purged ${s} keyword${s===1?"":"s"}.`)),u.message(v.dim(t==="all"?"Cleared the cloud inventory. Re-run `seoagent keywords --seed` / strategy to rebuild it.":"Cleared discovery noise. Your clustered keywords were kept."))}async function Ke(e={}){if(e.peek&&e.peek.trim().length>0){await Wb({keyword:e.peek.trim(),apiBase:e.apiBase,json:e.json});return}let t=_(process.cwd()),n=D(t?.domain);if(!n){u.error(`Not logged in. Run \`${m("login")}\` first.`),process.exitCode=1;return}if(e.purge){await Yb({all:e.all,apiBase:e.apiBase,auth:n});return}let r=e.apiBase||n.api_base||N.BASE,o=`Bearer ${n.user_token}:${n.website_token}`,i=e.competitors?"competitors":e.discover?"discover":e.seed?"seed":"enrich",s=i==="competitors"?"/api/cli/keywords/competitors":i==="discover"?"/api/cli/keywords/discover":i==="seed"?"/api/cli/keywords/seed":"/api/cli/keywords/enrich",a=i==="competitors"?"Finding keywords your competitors rank for\u2026":i==="discover"?"Discovering new keywords to target\u2026":i==="seed"?"Seeding keywords from your Search Console data\u2026":"Enriching keywords with real volume + difficulty\u2026",c=!!process.stdout.isTTY?_i():null;c?.start(a);let d=i==="competitors"||i==="discover"||i==="seed",f;try{f=await I(`${r}${s}`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:o},body:"{}"},d?{timeoutMs:6e4,attempts:1}:{})}catch(y){x(c,"error",`Network error: ${y.message}`,v.red("Network error.")),process.exitCode=1;return}let p=null;try{p=await f.json()}catch{p=null}if(f.status===402||p?.code==="upgrade_required"){x(c,"warn",i==="competitors"?"Competitor gap analysis is a paid feature.":"Keyword discovery is a paid feature.",v.yellow("Paid feature \u2014 upgrade to continue.")),u.message(i==="competitors"?`Upgrade to see which keywords competitors rank for that you don't: ${v.cyan(he("cli-keywords-competitors"))}`:`Upgrade to find new keywords to target: ${v.cyan(he("cli-keywords-discover"))}`),process.exitCode=1;return}if(!f.ok||p?.status!=="ok"){x(c,"error",`Server rejected request (${f.status})${p?.error?`: ${p.error}`:""}`,v.red(`Server rejected request (${f.status}).`)),process.exitCode=1;return}if(p.skipped==="no-gsc-data"){x(c,"warn","No Search Console data to seed from yet. Connect Google Search Console (run `seoagent login` and link GSC) \u2014 or wait for impressions to accrue on a new site.",v.yellow("No Search Console data yet."));return}if(p.skipped==="no-keywords"||p.skipped==="no-seeds"){x(c,"warn","No keywords yet. Seed from Search Console with `seoagent keywords --seed`, or run the keyword strategy (Phase 2) first, then try again.",v.yellow("No keywords yet."));return}if(p.skipped==="no-competitors"){x(c,"warn","No competitors found yet. Add competitors or run strategy discovery first, then try again.",v.yellow("No competitors yet."));return}if(p.skipped==="no-creds"||p.skipped==="no-columns"||p.skipped==="no-table"){x(c,"warn","Real keyword data isn\u2019t enabled on the server yet \u2014 try again later.",v.yellow("Real keyword data is not enabled on the server yet."));return}if(p.skipped){x(c,"warn",`Skipped: ${p.skipped}`,v.yellow(`Skipped: ${p.skipped}`));return}if(i==="competitors"){let y=p.gaps??0;x(c,"success",`Found ${y} competitor gap${y===1?"":"s"} across ${p.competitors?.length??0} competitor${p.competitors?.length===1?"":"s"} (added as "suggested").`,v.green(`${y} competitor gap${y===1?"":"s"} found.`)),zb(p.topGaps??[]),u.message(`Review them in ${v.cyan(".seoagent/keywords.md")} (a read-only projection \u2014 don't hand-edit it). Keep the on-topic ones for your clusters; clear off-topic noise with \`${m("keywords --purge")}\`. The analyzed competitors are saved to your dashboard's Competitors section.`);return}if(i==="discover"){let y=p.discovered??0;x(c,"success",`Discovered ${y} new keyword${y===1?"":"s"} to target (added as "suggested").`,v.green(`${y} new keyword${y===1?"":"s"} discovered.`)),Si(p.opportunities??[]),u.message(`Review them in ${v.cyan(".seoagent/keywords.md")} (a read-only projection \u2014 don't hand-edit it). Relevance-check before adopting; clear off-topic noise with \`${m("keywords --purge")}\`.`);return}if(i==="seed"){let y=p.seeded??0;x(c,"success",`Seeded ${y} keyword${y===1?"":"s"} from Search Console (real impressed queries, added as "suggested").`,v.green(`${y} keyword${y===1?"":"s"} seeded from Search Console.`)),Si(p.opportunities??[]),u.message(`These are queries your site already gets impressions for \u2014 striking-distance wins first. Review in ${v.cyan(".seoagent/keywords.md")} and build clusters from them.`);return}let g=p.enriched??0;x(c,"success",`Enriched ${g} keyword${g===1?"":"s"}.`,v.green(`Enriched ${g} keyword${g===1?"":"s"}.`)),Si(p.opportunities??[]),p.paid||u.message(`${v.dim("Free plan:")} enriched up to ${v.bold(String(p.cap??25))} keywords. ${v.dim("Upgrade for full enrichment + discovery + competitor gap:")} ${v.cyan(he("cli-keywords-cap"))}`)}import B from"picocolors";function Cn(e={}){let t=process.cwd(),n=_(t),r=Be(t);if(e.json){process.stdout.write(JSON.stringify(r,null,2)+`
1028
- `);return}if(u.message(Vb(r)),!n){u.warn(`No SEOAgent project here. Run \`${m("init")}\` first to persist the provider.`);return}J(t,{image_provider:r.provider}),u.info(`Saved to ${B.cyan(".seoagent/project.md")} ${B.dim(`(image_provider: ${r.provider})`)}`)}function Vb(e){let n=i=>B.dim(i.padEnd(12)),r=[];if(e.provider==="none"){r.push({content:`${B.dim("\u25CB")} ${B.bold(B.yellow("No image provider detected"))}`}),r.push({sep:!0}),r.push({content:B.dim("Set ONE of these to enable image generation:")});let i=Is();for(let s of Object.keys(i))r.push({content:`${n(s+":")}${B.cyan(i[s].join(" or "))}`});r.push({sep:!0}),r.push({content:B.dim("Add to your shell, .env.local, or .env, then re-run:")}),r.push({content:` ${B.cyan(m("env-check"))}`})}else{r.push({content:`${B.green("\u2713")} ${B.dim("Image provider:")} ${B.bold(B.green(e.provider))}`}),r.push({sep:!0}),e.matched_key&&r.push({content:`${n("Key:")}${B.bold(e.matched_key)}`}),e.source&&r.push({content:`${n("Source:")}${B.cyan(e.source)}`});let i=e.available_providers.filter(s=>s!==e.provider);i.length>0&&r.push({content:`${n("Available:")}${B.dim(i.join(", "))}`})}let o=`${B.bold(B.cyan("SEOAgent"))} ${B.dim("\xB7")} ${B.dim("env-check")}`;return M(r,{width:64,title:o})}import{existsSync as Ei,mkdirSync as wu,writeFileSync as bu}from"fs";import{dirname as Or,isAbsolute as nk,join as Tr,resolve as rk}from"path";import{tmpdir as ok}from"os";import{spinner as ik}from"@clack/prompts";import z from"picocolors";import{Buffer as hu}from"buffer";var Jb=6e4,Ci=18e4,Xb=15e3;async function Ri(e){let t=await fetch(e,{signal:AbortSignal.timeout(Jb)});if(!t.ok)throw new Error(`Image download failed: ${t.status} ${t.statusText}`);let n=await t.arrayBuffer();return hu.from(n)}async function Qb(e){let t=e.size??"1024x1024",n=await fetch("https://api.openai.com/v1/images/generations",{method:"POST",headers:{Authorization:`Bearer ${e.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify({model:"gpt-image-1",prompt:e.prompt,size:t,n:1}),signal:AbortSignal.timeout(Ci)});if(!n.ok){let i=await n.text().catch(()=>"");throw new Error(`OpenAI image API ${n.status}: ${i.slice(0,300)}`)}let o=(await n.json()).data?.[0];if(!o)throw new Error("OpenAI image API returned no data");if(o.b64_json)return{bytes:hu.from(o.b64_json,"base64"),contentType:"image/png"};if(o.url)return{bytes:await Ri(o.url),contentType:"image/png"};throw new Error("OpenAI image API returned neither b64_json nor url")}async function Zb(e){let t=process.env.FAL_MODEL||"fal-ai/flux/schnell",n=await fetch(`https://fal.run/${t}`,{method:"POST",headers:{Authorization:`Key ${e.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify({prompt:e.prompt,image_size:"landscape_16_9",num_images:1}),signal:AbortSignal.timeout(Ci)});if(!n.ok){let s=await n.text().catch(()=>"");throw new Error(`fal.ai API ${n.status}: ${s.slice(0,300)}`)}let o=(await n.json()).images?.[0];if(!o?.url)throw new Error("fal.ai API returned no image url");return{bytes:await Ri(o.url),contentType:o.content_type??"image/png"}}async function ek(e,t,n=9e4){let r=Date.now(),o=1e3;for(;Date.now()-r<n;){let i=await fetch(`https://api.replicate.com/v1/predictions/${e}`,{headers:{Authorization:`Token ${t}`},signal:AbortSignal.timeout(Xb)});if(!i.ok)throw new Error(`Replicate poll failed: ${i.status}`);let s=await i.json();if(s.status==="succeeded"||s.status==="failed"||s.status==="canceled")return s;await new Promise(a=>setTimeout(a,o)),o=Math.min(o*1.5,5e3)}throw new Error("Replicate prediction timed out")}async function tk(e){let t=process.env.REPLICATE_MODEL||"black-forest-labs/flux-schnell",n=await fetch(`https://api.replicate.com/v1/models/${t}/predictions`,{method:"POST",headers:{Authorization:`Token ${e.apiKey}`,"Content-Type":"application/json",Prefer:"wait"},body:JSON.stringify({input:{prompt:e.prompt,aspect_ratio:"16:9"}}),signal:AbortSignal.timeout(Ci)});if(!n.ok){let s=await n.text().catch(()=>"");throw new Error(`Replicate API ${n.status}: ${s.slice(0,300)}`)}let r=await n.json();if(r.status!=="succeeded"&&r.status!=="failed"&&(r=await ek(r.id,e.apiKey)),r.status!=="succeeded")throw new Error(`Replicate prediction ${r.status}: ${r.error??""}`);let o=Array.isArray(r.output)?r.output[0]:r.output;if(!o)throw new Error("Replicate prediction returned no output");return{bytes:await Ri(o),contentType:"image/png",providerImageId:r.id}}async function yu(e,t){switch(e){case"openai":return Qb(t);case"fal":return Zb(t);case"replicate":return tk(t);default:throw new Error(`Unknown image provider: ${e}`)}}function sk(e){return e==="openai"||e==="fal"||e==="replicate"}async function Rn(e={}){let t=process.cwd();if(!e.prompt){u.warn(`Missing --prompt. Example: ${m('generate-image --prompt "..." --out content/images/hero.png')}`),process.exitCode=1;return}if(!e.out){u.warn("Missing --out. Example: --out .seoagent/content/images/hero.png"),process.exitCode=1;return}let n=_(t),r=null;if(e.provider){if(!sk(e.provider)){u.warn(`Invalid --provider "${e.provider}". Use one of: openai, fal, replicate.`),process.exitCode=1;return}r=e.provider}else n?.image_provider&&n.image_provider!=="none"?r=n.image_provider:r=Be(t).provider;if(!r||r==="none"){u.warn(`No image generation provider available. Set OPENAI_API_KEY, FAL_KEY, or REPLICATE_API_TOKEN, then run \`${m("env-check")}\`.`),process.exitCode=1;return}let o=Ps(t,r);if(!o){u.warn(`Provider "${r}" selected but no API key found. Set the env var, then re-run.`),process.exitCode=1;return}let i=nk(e.out)?e.out:rk(t,e.out),a=!e.silent&&!!process.stdout.isTTY?ik():null;a?.start(`Generating image with ${z.bold(r)} ${z.dim(`(${o.envName})`)}\u2026`);try{let l=await yu(r,{prompt:e.prompt,apiKey:o.key,size:e.size});Ei(Or(i))||wu(Or(i),{recursive:!0}),bu(i,l.bytes),e.silent||x(a,"success",`Wrote ${i} (${l.bytes.length} bytes, ${l.contentType}).`,z.green(`Wrote ${i} ${z.dim(`(${l.bytes.length} bytes, ${l.contentType})`)}`))}catch(l){a?.stop(z.red("Image generation failed."));let c=lk(t,{provider:r,envName:o.envName,prompt:e.prompt,size:e.size,outPath:i,error:l});u.message(ak({provider:r,envName:o.envName,prompt:e.prompt,size:e.size,message:l.message})),c&&u.message(`${z.dim("Full error log:")} ${z.cyan(c)}`),process.exitCode=1}}function ak(e){let n=d=>z.dim(d.padEnd(10)),o=e.prompt.length>54?`${e.prompt.slice(0,53)}\u2026`:e.prompt,i=[{content:`${z.red("\u2717")} ${z.bold(z.red("Image generation failed"))}`},{sep:!0},{content:`${n("Provider:")}${z.bold(e.provider)}`},{content:`${n("Key:")}${e.envName}`},{content:`${n("Prompt:")}${z.dim(`"${o}"`)}`}];e.size&&i.push({content:`${n("Size:")}${e.size}`}),i.push({sep:!0});let s=e.message.replace(/\s+/g," ").trim(),a=64,l=s.length>a?`${s.slice(0,a-1)}\u2026`:s;i.push({content:z.red(l)});let c=`${z.bold(z.cyan("SEOAgent"))} ${z.dim("\xB7")} ${z.dim("generate-image")}`;return M(i,{width:70,title:c})}function lk(e,t){let r=`seoagent-generate-image-${new Date().toISOString().replace(/[:.]/g,"-")}.log`,o=Tr(e,b),i=Tr(o,".errors"),s=Ei(o)?Tr(i,r):Tr(ok(),r),a=["# SEOAgent generate-image error",`# ${new Date().toISOString()}`,"","## Request",`provider: ${t.provider}`,`env_var: ${t.envName}`,`prompt: ${JSON.stringify(t.prompt)}`,`size: ${t.size??"(default)"}`,`out_path: ${t.outPath}`,`cwd: ${e}`,"","## Error",`message: ${t.error.message}`,"","## Stack",t.error.stack??"(no stack)",""];t.error.cause&&a.push("## Cause",String(t.error.cause),"");try{return Ei(Or(s))||wu(Or(s),{recursive:!0}),bu(s,a.join(`
1026
+ `))}}function Lb(e,t){return e.length<=t?e:e.slice(0,t-1)+"\u2026"}function Nb(e){return typeof e!="string"?[]:e.split(",").map(t=>t.trim()).filter(Boolean)}function jb(e){try{if(!Pb(e))return null;let t=Ab(e).filter(o=>o.endsWith(".json")).sort();if(t.length===0)return null;let n=t[t.length-1],r=JSON.parse(Eb(uu(e,n),"utf-8"));return r&&typeof r=="object"&&Array.isArray(r.results)&&typeof r.generated_at=="string"?r:null}catch{return null}}import Dt from"picocolors";var Db=30,Fb=/<(script|style|noscript|template|svg|nav|header|footer)\b[^>]*>[\s\S]*?<\/\1>/gi;function Mb(e){let t=e,n=/<body\b[^>]*>([\s\S]*?)<\/body>/i.exec(t);n&&(t=n[1]),t=t.replace(Fb," ").replace(/<!--[\s\S]*?-->/g," ").replace(/<[^>]+>/g," ").replace(/&nbsp;/gi," ").replace(/&[a-z]+;/gi," ").replace(/\s+/g," ").trim();let r=t===""?0:t.split(" ").filter(Boolean).length;return{wordCount:r,rendered:r<Db?"empty":"yes"}}async function Ub(e,t){try{let n=await I(e,{headers:{"user-agent":"Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html) SEOAgent-CLI"},redirect:"follow"},{attempts:2,timeoutMs:1e4,fetchImpl:t});if(!n.ok)return{status:n.status,rendered:"error",wordCount:0};let r=await n.text(),{wordCount:o,rendered:i}=Mb(r);return{status:n.status,rendered:i,wordCount:o}}catch{return{status:null,rendered:"error",wordCount:0}}}async function du(e,t={}){let n=Math.max(1,t.concurrency??5),r=new Map,o=[...e],i=0;async function s(){for(;;){let a=o.shift();if(a===void 0)return;r.set(a,await Ub(a,t.fetchImpl)),i+=1,t.onProgress?.(i,e.length)}}return await Promise.all(Array.from({length:Math.min(n,e.length)},()=>s())),r}var xi=300;async function _n(e={}){if(e.all){await Bb();return}let t=process.cwd();if(!O(t))return;et(t);let n=Mo(t);fu(t);let r=await pu(t,{enrichSitemap:!0,crawl:e.crawl});if(n.reason==="refreshed"?(u.success(n.from&&n.from!==n.to?`Refreshed the SEOAgent skill ${n.from} \u2192 ${n.to}.`:`Refreshed the SEOAgent skill (now ${n.to}).`),u.info("Your `.seoagent/` knowledge was left untouched.")):(u.error(`Could not refresh the skill (${n.reason}).`),process.exitCode=1),r.added>0?u.success(`Re-scanned the codebase: +${r.added} new page${r.added===1?"":"s"} merged into .seoagent/pages.md (${r.total} total).`):r.total>0&&u.info(`Page inventory already current (${r.total} pages).`),r.crawl){let{crawled:o,empty:i,errors:s}=r.crawl;u.success(`Crawled ${o} page${o===1?"":"s"} \u2192 filled Status / Rendered / Word count`+(i>0?`, ${i} render empty`:"")+(s>0?`, ${s} unreachable`:"")+".")}}async function pu(e,t={}){try{let n=_(e),r=n?.domain&&n.domain!=="unknown"?n.domain:null,{pages:o}=be(e,r);t.enrichSitemap&&r&&await Ms(o,r);let i,s;if(t.crawl&&r){let l=o.map(d=>d.url).filter(d=>/^https?:\/\//.test(d)),c=l.slice(0,xi);if(l.length>xi&&u.info(`Crawling the first ${xi} of ${l.length} pages (cap).`),c.length>0){u.info(`Crawling ${c.length} page${c.length===1?"":"s"}\u2026`),s=await du(c);let d=0,f=0;for(let p of s.values())p.rendered==="empty"?d++:p.rendered==="error"&&f++;i={crawled:s.size,empty:d,errors:f}}}let a=Jn(e,o,s);return{added:a.added,total:a.total,crawl:i}}catch{return{added:0,total:0}}}async function Bb(){let e=ks();if(e.length===0){u.info("No SEOAgent projects are registered on this machine yet. They register themselves on `init` / `sync`.");return}u.message(`Refreshing ${e.length} project${e.length===1?"":"s"} to SEOAgent ${Dt.bold(A)}\u2026`);let t=0,n=0;for(let r of e){let o=Mo(r);fu(r);let i=await pu(r),s=i.added>0?Dt.cyan(` +${i.added} page${i.added===1?"":"s"}`):"";if(o.reason==="refreshed"){t++;let a=o.from&&o.from!==o.to?Dt.dim(` (${o.from} \u2192 ${o.to})`):"";u.message(` ${Dt.green("\u2713")} ${r}${a}${s}`)}else n++,u.message(` ${Dt.yellow("!")} ${r} ${Dt.dim("\u2014 "+o.reason)}${s}`)}u.success(`Refreshed ${t}/${e.length} project${e.length===1?"":"s"} to ${A}.`),n>0&&u.info(`${n} could not be refreshed (see above).`)}function fu(e){try{Ze(e)}catch{}}import{spinner as _i,confirm as Gb,isCancel as Hb}from"@clack/prompts";import v from"picocolors";var Wb=11e4,mu={easy_win:"easy win",striking_distance:"striking distance",competitor_gap:"competitor gap",defend:"defend",low_priority:"low priority"};async function Kb(e){let t=process.cwd(),n=Hn(t);if(!n){u.error(`No .seoagent/project.md found. Run \`${m("init")}\` first.`),process.exitCode=1;return}let r=_(t),o=r?.domain&&r.domain!=="unknown"?r.domain:void 0,i=(e.apiBase||N.BASE).replace(/\/$/,""),a=!!process.stdout.isTTY&&!e.json?_i():null;a?.start(`Looking up ${v.bold(`"${e.keyword}"`)} ${v.dim("via DataForSEO (free peek)\u2026")}`);let l;try{l=await I(`${i}/api/cli/keywords/peek`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({install_id:n,keyword:e.keyword,...o?{domain:o}:{}})})}catch(d){x(a,"error",`Network error: ${d.message}`,v.red("Network error.")),process.exitCode=1;return}let c=null;try{c=await l.json()}catch{c=null}if(e.json){process.stdout.write(JSON.stringify({status:c?.status??"error",http_status:l.status,body:c??null})+`
1027
+ `);return}if(l.status===429||c?.skipped?.startsWith("rate-limited")){let d=c?.skipped==="rate-limited:ip"?"this network":"this install";x(a,"warn",`Daily peek quota reached for ${d}.`,v.yellow(`Daily peek quota reached for ${d}.`)),u.message(gu()),process.exitCode=1;return}if(!l.ok||!c){x(a,"error",`Peek failed (${l.status})${c?.error?`: ${c.error}`:""}`,v.red(`Peek failed (${l.status}).`)),process.exitCode=1;return}if(c.skipped==="provider-quota"){x(a,"warn","Real keyword data is temporarily unavailable. Try again later.",v.yellow("Real keyword data is temporarily unavailable. Try again later.")),u.message(v.dim("No action needed on your side \u2014 the maintainer has been notified."));return}if(c.skipped==="no-creds"||c.skipped==="no-table"){x(a,"warn","Real keyword data isn\u2019t enabled on the server yet \u2014 try again later.",v.yellow("Real keyword data is not enabled on the server yet."));return}if(!c.opportunity){x(a,"warn",`No data returned for "${e.keyword}".`,v.yellow(`No data returned for "${e.keyword}".`));return}a?.stop(v.green("Got it.")),u.message(qb(c.opportunity)),c.remaining&&u.message(`${v.dim("Peeks remaining today:")} ${v.bold(String(c.remaining.install))} ${v.dim("\u2014 "+gu())}`)}function qb(e){let n=c=>v.dim(c.padEnd(14)),r=e.volume!=null?v.bold(String(e.volume)):v.dim("n/a"),o=e.difficulty!=null?`${v.bold(String(e.difficulty))} ${v.dim(zb(e.difficulty))}`:v.dim("n/a"),i=mu[e.opportunity]??e.opportunity,s=e.opportunity==="easy_win"?v.bold(v.green(i)):e.opportunity==="striking_distance"?v.bold(v.cyan(i)):e.opportunity==="competitor_gap"?v.bold(v.yellow(i)):v.dim(i),a=[{content:`${v.green("\u25CF")} ${v.bold(e.keyword)}`},{sep:!0},{content:`${n("Volume:")}${r} ${v.dim("/mo")}`},{content:`${n("Difficulty:")}${o}`},{content:`${n("Opportunity:")}${s}`}],l=`${v.bold(v.cyan("Keyword peek"))}`;return M(a,{width:55,title:l})}function zb(e){return e<=20?"easy":e<=40?"moderate":e<=60?"hard":"very hard"}function gu(e=D()){return e?`You're logged in \u2014 run \`${m("keywords")}\` for full enrichment (no quota). Pro unlocks the full inventory beyond the ~25 cap plus \`${m("keywords --discover")}\` (new targets) + \`${m("keywords --competitors")}\` (competitor gaps): ${m("upgrade")}.`:`Log in for the full top-25 enrichment, no quota: ${m("login")}`}function Si(e){let t=e.filter(n=>n.opportunity!=="low_priority");if(t.length!==0){u.message("Top opportunities:");for(let n of t){let r=n.volume!=null?`vol ${n.volume}`:"vol n/a",o=n.difficulty!=null?`KD ${n.difficulty}`:"KD n/a",i=n.our_position!=null?`, you're #${n.our_position}`:", not ranking";u.message(` \u2022 [${mu[n.opportunity]??n.opportunity}] ${n.keyword} \u2014 ${r}, ${o}${i}`)}}}function Yb(e){if(e.length!==0){u.message("Top competitor gaps:");for(let t of e){let n=t.volume!=null?`vol ${t.volume}`:"vol n/a",r=t.difficulty!=null?`KD ${t.difficulty}`:"KD n/a",o=t.their_position!=null?`#${t.their_position}`:"top 10";u.message(` \u2022 ${t.keyword} \u2014 ${t.competitor_domain} ranks ${o}, ${n}, ${r}`)}}}async function Vb(e){let t=e.all?"all":"suggested";if(t==="all"&&process.stdin.isTTY){let a=await Gb({message:"Delete ALL keywords for this site from the cloud? This cannot be undone."});if(Hb(a)||!a){u.info("No changes.");return}}let n=e.apiBase||e.auth.api_base||N.BASE,r=process.stdout.isTTY?_i():null;r?.start(t==="all"?"Purging all keywords\u2026":"Purging suggested keywords\u2026");let o;try{o=await I(`${n}/api/cli/keywords/purge`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e.auth.user_token}:${e.auth.website_token}`},body:JSON.stringify({scope:t})})}catch(a){x(r,"error",`Network error: ${a.message}`,v.red("Network error.")),process.exitCode=1;return}let i=null;try{i=await o.json()}catch{i=null}if(i?.skipped==="no-table"||i?.skipped==="no-columns"){x(r,"warn","Keyword storage isn\u2019t enabled on the server yet.",v.yellow("Nothing to purge yet."));return}if(!o.ok||i?.status!=="ok"){x(r,"error",`Purge failed (${o.status})${i?.error?`: ${i.error}`:""}`,v.red(`Purge failed (${o.status}).`)),process.exitCode=1;return}let s=i.purged??0;x(r,"success",`Purged ${s} ${t==="all"?"":"suggested "}keyword${s===1?"":"s"}.`,v.green(`Purged ${s} keyword${s===1?"":"s"}.`)),u.message(v.dim(t==="all"?"Cleared the cloud inventory. Re-run `seoagent keywords --seed` / strategy to rebuild it.":"Cleared discovery noise. Your clustered keywords were kept."))}async function Ke(e={}){if(e.peek&&e.peek.trim().length>0){await Kb({keyword:e.peek.trim(),apiBase:e.apiBase,json:e.json});return}let t=_(process.cwd()),n=D(t?.domain);if(!n){u.error(`Not logged in. Run \`${m("login")}\` first.`),process.exitCode=1;return}if(e.purge){await Vb({all:e.all,apiBase:e.apiBase,auth:n});return}let r=e.apiBase||n.api_base||N.BASE,o=`Bearer ${n.user_token}:${n.website_token}`,i=e.competitors?"competitors":e.discover?"discover":e.seed?"seed":"enrich",s=i==="competitors"?"/api/cli/keywords/competitors":i==="discover"?"/api/cli/keywords/discover":i==="seed"?"/api/cli/keywords/seed":"/api/cli/keywords/enrich",a=i==="competitors"?"Finding keywords your competitors rank for\u2026":i==="discover"?"Discovering new keywords to target\u2026":i==="seed"?"Seeding keywords from your Search Console data\u2026":"Enriching keywords with real volume + difficulty\u2026",c=!!process.stdout.isTTY?_i():null;c?.start(a);let d=i==="competitors"||i==="discover"||i==="seed",f;try{f=await I(`${r}${s}`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:o},body:"{}"},d?{timeoutMs:Wb,attempts:1}:{})}catch(y){x(c,"error",`Network error: ${y.message}`,v.red("Network error.")),process.exitCode=1;return}let p=null;try{p=await f.json()}catch{p=null}if(f.status===402||p?.code==="upgrade_required"){x(c,"warn",i==="competitors"?"Competitor gap analysis is a paid feature.":"Keyword discovery is a paid feature.",v.yellow("Paid feature \u2014 upgrade to continue.")),u.message(i==="competitors"?`Upgrade to see which keywords competitors rank for that you don't: ${v.cyan(he("cli-keywords-competitors"))}`:`Upgrade to find new keywords to target: ${v.cyan(he("cli-keywords-discover"))}`),process.exitCode=1;return}if(!f.ok||p?.status!=="ok"){x(c,"error",`Server rejected request (${f.status})${p?.error?`: ${p.error}`:""}`,v.red(`Server rejected request (${f.status}).`)),process.exitCode=1;return}if(p.skipped==="no-gsc-data"){x(c,"warn","No Search Console data to seed from yet. Connect Google Search Console (run `seoagent login` and link GSC) \u2014 or wait for impressions to accrue on a new site.",v.yellow("No Search Console data yet."));return}if(p.skipped==="no-keywords"||p.skipped==="no-seeds"){x(c,"warn","No keywords yet. Seed from Search Console with `seoagent keywords --seed`, or run the keyword strategy (Phase 2) first, then try again.",v.yellow("No keywords yet."));return}if(p.skipped==="no-competitors"){x(c,"warn","No competitors found yet. Add competitors or run strategy discovery first, then try again.",v.yellow("No competitors yet."));return}if(p.skipped==="no-creds"||p.skipped==="no-columns"||p.skipped==="no-table"){x(c,"warn","Real keyword data isn\u2019t enabled on the server yet \u2014 try again later.",v.yellow("Real keyword data is not enabled on the server yet."));return}if(p.skipped){x(c,"warn",`Skipped: ${p.skipped}`,v.yellow(`Skipped: ${p.skipped}`));return}if(i==="competitors"){let y=p.gaps??0;x(c,"success",`Found ${y} competitor gap${y===1?"":"s"} across ${p.competitors?.length??0} competitor${p.competitors?.length===1?"":"s"} (added as "suggested").`,v.green(`${y} competitor gap${y===1?"":"s"} found.`)),Yb(p.topGaps??[]),u.message(`Review them in ${v.cyan(".seoagent/keywords.md")} (a read-only projection \u2014 don't hand-edit it). Keep the on-topic ones for your clusters; clear off-topic noise with \`${m("keywords --purge")}\`. The analyzed competitors are saved to your dashboard's Competitors section.`);return}if(i==="discover"){let y=Jb(p);x(c,"success",y.headline,v.green(y.short)),Si(p.opportunities??[]),u.message(y.hint(m));return}if(i==="seed"){let y=p.seeded??0;x(c,"success",`Seeded ${y} keyword${y===1?"":"s"} from Search Console (real impressed queries, added as "suggested").`,v.green(`${y} keyword${y===1?"":"s"} seeded from Search Console.`)),Si(p.opportunities??[]),u.message(`These are queries your site already gets impressions for \u2014 striking-distance wins first. Review in ${v.cyan(".seoagent/keywords.md")} and build clusters from them.`);return}let g=p.enriched??0;x(c,"success",`Enriched ${g} keyword${g===1?"":"s"}.`,v.green(`Enriched ${g} keyword${g===1?"":"s"}.`)),Si(p.opportunities??[]),p.paid||u.message(`${v.dim("Free plan:")} enriched up to ${v.bold(String(p.cap??25))} keywords. ${v.dim("Upgrade for full enrichment + discovery + competitor gap:")} ${v.cyan(he("cli-keywords-cap"))}`)}function Jb(e){let t=e.discovered??0,n=e.ignored??0,r=(s,a)=>`${s} ${a}${s===1?"":"s"}`,o="Review them in .seoagent/keywords.md (a read-only projection \u2014 don't hand-edit it).";if(e.judgePending)return{headline:`Discovered ${r(t,"new keyword")} to target (added as "suggested", not yet relevance-judged).`,short:`${r(t,"new keyword")} discovered (unjudged).`,hint:s=>`${o} The relevance judge could not run for this site (it needs business context in .seoagent/context.md), so relevance-check before adopting; clear off-topic noise with \`${s("keywords --purge")}\`.`};if(t===0&&n>0)return{headline:`Found ${r(n,"candidate")}; the relevance judge ruled every one off-topic for this site, so nothing was added to target.`,short:`0 new keywords kept (${n} judged off-topic).`,hint:()=>"Nothing to review. Off-topic verdicts are stored, so the same junk is not proposed again."};let i=n>0?` Hid ${r(n,"off-topic candidate")} (judge verdict: ignore).`:"";return{headline:`Discovered ${r(t,"new keyword")} to target (judged relevant to your business, added as "suggested").${i}`,short:`${r(t,"new keyword")} discovered${n>0?` (${n} hidden as off-topic)`:""}.`,hint:s=>`${o} Keep the ones that fit your clusters; \`${s("keywords --purge")}\` clears anything the judge missed.`}}import B from"picocolors";function Cn(e={}){let t=process.cwd(),n=_(t),r=Be(t);if(e.json){process.stdout.write(JSON.stringify(r,null,2)+`
1028
+ `);return}if(u.message(Xb(r)),!n){u.warn(`No SEOAgent project here. Run \`${m("init")}\` first to persist the provider.`);return}J(t,{image_provider:r.provider}),u.info(`Saved to ${B.cyan(".seoagent/project.md")} ${B.dim(`(image_provider: ${r.provider})`)}`)}function Xb(e){let n=i=>B.dim(i.padEnd(12)),r=[];if(e.provider==="none"){r.push({content:`${B.dim("\u25CB")} ${B.bold(B.yellow("No image provider detected"))}`}),r.push({sep:!0}),r.push({content:B.dim("Set ONE of these to enable image generation:")});let i=Is();for(let s of Object.keys(i))r.push({content:`${n(s+":")}${B.cyan(i[s].join(" or "))}`});r.push({sep:!0}),r.push({content:B.dim("Add to your shell, .env.local, or .env, then re-run:")}),r.push({content:` ${B.cyan(m("env-check"))}`})}else{r.push({content:`${B.green("\u2713")} ${B.dim("Image provider:")} ${B.bold(B.green(e.provider))}`}),r.push({sep:!0}),e.matched_key&&r.push({content:`${n("Key:")}${B.bold(e.matched_key)}`}),e.source&&r.push({content:`${n("Source:")}${B.cyan(e.source)}`});let i=e.available_providers.filter(s=>s!==e.provider);i.length>0&&r.push({content:`${n("Available:")}${B.dim(i.join(", "))}`})}let o=`${B.bold(B.cyan("SEOAgent"))} ${B.dim("\xB7")} ${B.dim("env-check")}`;return M(r,{width:64,title:o})}import{existsSync as Ei,mkdirSync as wu,writeFileSync as bu}from"fs";import{dirname as Or,isAbsolute as ok,join as Tr,resolve as ik}from"path";import{tmpdir as sk}from"os";import{spinner as ak}from"@clack/prompts";import z from"picocolors";import{Buffer as hu}from"buffer";var Qb=6e4,Ci=18e4,Zb=15e3;async function Ri(e){let t=await fetch(e,{signal:AbortSignal.timeout(Qb)});if(!t.ok)throw new Error(`Image download failed: ${t.status} ${t.statusText}`);let n=await t.arrayBuffer();return hu.from(n)}async function ek(e){let t=e.size??"1024x1024",n=await fetch("https://api.openai.com/v1/images/generations",{method:"POST",headers:{Authorization:`Bearer ${e.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify({model:"gpt-image-1",prompt:e.prompt,size:t,n:1}),signal:AbortSignal.timeout(Ci)});if(!n.ok){let i=await n.text().catch(()=>"");throw new Error(`OpenAI image API ${n.status}: ${i.slice(0,300)}`)}let o=(await n.json()).data?.[0];if(!o)throw new Error("OpenAI image API returned no data");if(o.b64_json)return{bytes:hu.from(o.b64_json,"base64"),contentType:"image/png"};if(o.url)return{bytes:await Ri(o.url),contentType:"image/png"};throw new Error("OpenAI image API returned neither b64_json nor url")}async function tk(e){let t=process.env.FAL_MODEL||"fal-ai/flux/schnell",n=await fetch(`https://fal.run/${t}`,{method:"POST",headers:{Authorization:`Key ${e.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify({prompt:e.prompt,image_size:"landscape_16_9",num_images:1}),signal:AbortSignal.timeout(Ci)});if(!n.ok){let s=await n.text().catch(()=>"");throw new Error(`fal.ai API ${n.status}: ${s.slice(0,300)}`)}let o=(await n.json()).images?.[0];if(!o?.url)throw new Error("fal.ai API returned no image url");return{bytes:await Ri(o.url),contentType:o.content_type??"image/png"}}async function nk(e,t,n=9e4){let r=Date.now(),o=1e3;for(;Date.now()-r<n;){let i=await fetch(`https://api.replicate.com/v1/predictions/${e}`,{headers:{Authorization:`Token ${t}`},signal:AbortSignal.timeout(Zb)});if(!i.ok)throw new Error(`Replicate poll failed: ${i.status}`);let s=await i.json();if(s.status==="succeeded"||s.status==="failed"||s.status==="canceled")return s;await new Promise(a=>setTimeout(a,o)),o=Math.min(o*1.5,5e3)}throw new Error("Replicate prediction timed out")}async function rk(e){let t=process.env.REPLICATE_MODEL||"black-forest-labs/flux-schnell",n=await fetch(`https://api.replicate.com/v1/models/${t}/predictions`,{method:"POST",headers:{Authorization:`Token ${e.apiKey}`,"Content-Type":"application/json",Prefer:"wait"},body:JSON.stringify({input:{prompt:e.prompt,aspect_ratio:"16:9"}}),signal:AbortSignal.timeout(Ci)});if(!n.ok){let s=await n.text().catch(()=>"");throw new Error(`Replicate API ${n.status}: ${s.slice(0,300)}`)}let r=await n.json();if(r.status!=="succeeded"&&r.status!=="failed"&&(r=await nk(r.id,e.apiKey)),r.status!=="succeeded")throw new Error(`Replicate prediction ${r.status}: ${r.error??""}`);let o=Array.isArray(r.output)?r.output[0]:r.output;if(!o)throw new Error("Replicate prediction returned no output");return{bytes:await Ri(o),contentType:"image/png",providerImageId:r.id}}async function yu(e,t){switch(e){case"openai":return ek(t);case"fal":return tk(t);case"replicate":return rk(t);default:throw new Error(`Unknown image provider: ${e}`)}}function lk(e){return e==="openai"||e==="fal"||e==="replicate"}async function Rn(e={}){let t=process.cwd();if(!e.prompt){u.warn(`Missing --prompt. Example: ${m('generate-image --prompt "..." --out content/images/hero.png')}`),process.exitCode=1;return}if(!e.out){u.warn("Missing --out. Example: --out .seoagent/content/images/hero.png"),process.exitCode=1;return}let n=_(t),r=null;if(e.provider){if(!lk(e.provider)){u.warn(`Invalid --provider "${e.provider}". Use one of: openai, fal, replicate.`),process.exitCode=1;return}r=e.provider}else n?.image_provider&&n.image_provider!=="none"?r=n.image_provider:r=Be(t).provider;if(!r||r==="none"){u.warn(`No image generation provider available. Set OPENAI_API_KEY, FAL_KEY, or REPLICATE_API_TOKEN, then run \`${m("env-check")}\`.`),process.exitCode=1;return}let o=Ps(t,r);if(!o){u.warn(`Provider "${r}" selected but no API key found. Set the env var, then re-run.`),process.exitCode=1;return}let i=ok(e.out)?e.out:ik(t,e.out),a=!e.silent&&!!process.stdout.isTTY?ak():null;a?.start(`Generating image with ${z.bold(r)} ${z.dim(`(${o.envName})`)}\u2026`);try{let l=await yu(r,{prompt:e.prompt,apiKey:o.key,size:e.size});Ei(Or(i))||wu(Or(i),{recursive:!0}),bu(i,l.bytes),e.silent||x(a,"success",`Wrote ${i} (${l.bytes.length} bytes, ${l.contentType}).`,z.green(`Wrote ${i} ${z.dim(`(${l.bytes.length} bytes, ${l.contentType})`)}`))}catch(l){a?.stop(z.red("Image generation failed."));let c=uk(t,{provider:r,envName:o.envName,prompt:e.prompt,size:e.size,outPath:i,error:l});u.message(ck({provider:r,envName:o.envName,prompt:e.prompt,size:e.size,message:l.message})),c&&u.message(`${z.dim("Full error log:")} ${z.cyan(c)}`),process.exitCode=1}}function ck(e){let n=d=>z.dim(d.padEnd(10)),o=e.prompt.length>54?`${e.prompt.slice(0,53)}\u2026`:e.prompt,i=[{content:`${z.red("\u2717")} ${z.bold(z.red("Image generation failed"))}`},{sep:!0},{content:`${n("Provider:")}${z.bold(e.provider)}`},{content:`${n("Key:")}${e.envName}`},{content:`${n("Prompt:")}${z.dim(`"${o}"`)}`}];e.size&&i.push({content:`${n("Size:")}${e.size}`}),i.push({sep:!0});let s=e.message.replace(/\s+/g," ").trim(),a=64,l=s.length>a?`${s.slice(0,a-1)}\u2026`:s;i.push({content:z.red(l)});let c=`${z.bold(z.cyan("SEOAgent"))} ${z.dim("\xB7")} ${z.dim("generate-image")}`;return M(i,{width:70,title:c})}function uk(e,t){let r=`seoagent-generate-image-${new Date().toISOString().replace(/[:.]/g,"-")}.log`,o=Tr(e,b),i=Tr(o,".errors"),s=Ei(o)?Tr(i,r):Tr(sk(),r),a=["# SEOAgent generate-image error",`# ${new Date().toISOString()}`,"","## Request",`provider: ${t.provider}`,`env_var: ${t.envName}`,`prompt: ${JSON.stringify(t.prompt)}`,`size: ${t.size??"(default)"}`,`out_path: ${t.outPath}`,`cwd: ${e}`,"","## Error",`message: ${t.error.message}`,"","## Stack",t.error.stack??"(no stack)",""];t.error.cause&&a.push("## Cause",String(t.error.cause),"");try{return Ei(Or(s))||wu(Or(s),{recursive:!0}),bu(s,a.join(`
1029
1029
  `)),s}catch{return null}}import te from"picocolors";async function En(e={}){let t=_(process.cwd()),n=D(t?.domain);if(!n){let l=Ue();e.json?process.stdout.write(`${JSON.stringify({logged_in:!1,other_sites:l})}
1030
1030
  `):(u.error(`Not logged in. Run \`${m("login")}\` first.`),l.length>0&&u.info(`(Logged in to: ${l.join(", ")} \u2014 not this project's site.)`)),process.exitCode=1;return}let r=(e.apiBase||n.api_base||N.BASE).replace(/\/$/,""),o=`Bearer ${n.user_token}:${n.website_token}`,i;try{i=await fetch(`${r}/api/cli/whoami`,{method:"GET",headers:{Authorization:o,Accept:"application/json"},signal:AbortSignal.timeout(1e4)})}catch(l){u.error(`Network error: ${l.message}`),process.exitCode=1;return}if(i.status===404){if(e.json){process.stdout.write(`${JSON.stringify({logged_in:!0,server_supports_whoami:!1,api_base:r})}
1031
1031
  `);return}u.warn(`Logged in, but the server at this API base does not support \`/api/cli/whoami\` yet. Update with \`${m("update-cli")}\` (or re-deploy your server).`),u.message(`API base: ${r}`);return}let s=null;try{s=await i.json()}catch{s=null}if(!i.ok||!s||s.status!=="ok"){e.json?process.stdout.write(`${JSON.stringify({logged_in:!0,error:s?.error??`HTTP ${i.status}`})}
1032
1032
  `):u.error(`Could not fetch your account (HTTP ${i.status})${s?.error?`: ${s.error}`:""}`),process.exitCode=1;return}let a=Ue().filter(l=>l!==s.domain);if(e.json){process.stdout.write(`${JSON.stringify({logged_in:!0,...s,other_sites:a},null,2)}
1033
- `);return}u.message(ck(s)),a.length>0&&u.message(te.dim(`Also logged in to: ${a.join(", ")}.`)),s.paid===!1&&u.message(`${te.dim("Upgrade for autopilot:")} ${te.cyan(he("cli-whoami"))}`)}function ck(e){let n=i=>te.dim(i.padEnd(10)),r=[];if(e.email&&(r.push({content:`${te.green("\u2713")} ${te.dim("Logged in as")} ${te.bold(e.email)}`}),r.push({sep:!0})),e.domain&&r.push({content:`${n("Website:")}${te.cyan(e.domain)}`}),e.plan){let i=e.paid?te.bold(te.green(e.plan)):te.bold(e.plan);r.push({content:`${n("Plan:")}${i}`})}e.api_base&&r.push({content:`${n("API:")}${te.dim(e.api_base)}`});let o=`${te.bold(te.cyan("SEOAgent"))} ${te.dim("\xB7")} ${te.dim("whoami")}`;return M(r,{width:55,title:o})}import{select as Pn,text as An,confirm as $u,isCancel as ae,intro as uk,outro as dk}from"@clack/prompts";import Lr from"picocolors";function pk(){return[{command:"status",label:"Show project status",hint:"Audit, strategy, briefs, content, roadmap \u2014 everything at a glance.",run:async()=>Zt()},{command:"sync",label:"Sync with the cloud dashboard",hint:"Push local .seoagent/ changes + pull dashboard edits.",run:async()=>await re()},{command:"init",label:"Initialize a SEOAgent project",hint:"Scaffold .seoagent/ + the skill file in this directory.",run:async()=>{await Vt()}},{command:"login",label:"Connect this CLI to seoagent.com",hint:"Opens the browser to bind your account.",run:async()=>await rn()},{command:"whoami",label:"Show logged-in account",hint:"Email, bound website, plan, API base.",run:async()=>await En()},{command:"keywords",label:"Keyword research",hint:"Peek (free, one keyword) \xB7 enrich \xB7 discover \xB7 competitor gaps.",run:async()=>{let e=await Pn({message:"Which mode?",options:[{value:"peek",label:"peek",hint:"Free, one keyword, no login required"},{value:"enrich",label:"enrich (default)",hint:"Enrich your full strategy (requires login)"},{value:"discover",label:"discover",hint:"Find new keywords to target (paid)"},{value:"competitors",label:"competitors",hint:"Find competitor keyword gaps (paid)"}]});if(!ae(e))if(e==="peek"){let t=await An({message:"Keyword to peek:",placeholder:"saas seo",validate:n=>String(n??"").trim()?void 0:"A keyword is required."});if(ae(t))return;await Ke({peek:String(t)})}else e==="discover"?await Ke({discover:!0}):e==="competitors"?await Ke({competitors:!0}):await Ke()}},{command:"internal-links",label:"Analyze internal links",hint:"Find orphan pages \u2192 .seoagent/internal-links.md",run:async()=>hn({})},{command:"okf",label:"Open Knowledge Format bundle",hint:"Make your site AI-readable (AEO/GEO) \u2192 .seoagent/okf/",run:async()=>{let e=await Pn({message:"Which OKF action?",options:[{value:"status",label:"status",hint:"Is a bundle present?"},{value:"scaffold",label:"scaffold",hint:"Create the starter bundle"},{value:"validate",label:"validate",hint:"Check type fields, timestamps, links"}]});ae(e)||yn(e==="status"?void 0:String(e))}},{command:"citations",label:"Measure AI citations (AEO/GEO)",hint:"Are answer engines citing you? Web-grounded \u2192 .seoagent/citations/scorecard.md",run:async()=>await Sn()},{command:"generate-image",label:"Generate an image",hint:"Uses the detected (or explicit) provider.",run:async()=>{let e=await An({message:"Image prompt:",placeholder:"a minimalist cyan box logo",validate:n=>String(n??"").trim()?void 0:"A prompt is required."});if(ae(e))return;let t=await An({message:"Output file path:",placeholder:".seoagent/content/images/hero.png",validate:n=>String(n??"").trim()?void 0:"An output path is required."});ae(t)||await Rn({prompt:String(e),out:String(t)})}},{command:"env-check",label:"Check image-generation provider",hint:"Detect openai / fal / replicate from your env.",run:async()=>Cn({})},{command:"autopilot",label:"Toggle CLI autopilot",hint:"on / off / status \u2014 paid feature for queueing SEO fixes.",run:async()=>{let e=await Pn({message:"Autopilot action:",options:[{value:"status",label:"status",hint:"Show current state"},{value:"on",label:"on",hint:"Turn autopilot ON (paid)"},{value:"off",label:"off",hint:"Turn autopilot OFF"}]});ae(e)||await mn(String(e))}},{command:"pull",label:"Pull dashboard changes only",hint:"Cloud \u2192 local. Same as the second half of `sync`.",run:async()=>await at()},{command:"ack",label:"Close out a pending inbox action",hint:"Mark a .seoagent/inbox/ action completed or failed.",run:async()=>{let e=await An({message:"Action ID to close out:",placeholder:"action-abc123",validate:r=>String(r??"").trim()?void 0:"Action ID is required."});if(ae(e))return;let t=await $u({message:"Mark as failed instead of completed?",initialValue:!1});if(ae(t))return;let n;if(t===!0){let r=await An({message:"Reason for failure (optional):",placeholder:"couldn't apply because\u2026"});if(ae(r))return;String(r).trim()&&(n=String(r))}await ke(String(e),{failed:t===!0,reason:n})}},{command:"upgrade",label:"Upgrade your SEOAgent plan",hint:"Open the pricing page; contextual prompt if logged in.",run:async()=>await tn()},{command:"update-cli",label:"Update the CLI to the latest version",hint:"npm install -g @seoagent-official/seoagent@latest + refresh your projects",run:async()=>await nn({})},{command:"refresh",label:"Refresh the skill to your CLI version",hint:"This project \u2014 or every project on the machine via `refresh --all`.",run:async()=>{let e=await Pn({message:"Refresh which?",options:[{value:"this",label:"This project",hint:"Just the current directory"},{value:"all",label:"All projects",hint:"Every SEOAgent project on this machine"}]});ae(e)||_n({all:e==="all"})}},{command:"logout",label:"Log out",hint:"Clear stored credentials for seoagent.com.",run:async()=>on()},{command:"uninstall",label:"Uninstall SEOAgent from this project",hint:"Remove .seoagent/, the skill bundle, and the sync hook.",run:async()=>await Jt({})}]}var ku="__exit__";async function vu(e=pk()){let t=e.map(o=>({value:o.command,label:o.label,hint:o.hint}));t.push({value:ku,label:"Exit",hint:"Close the menu"});let n=await Pn({message:"What would you like to do?",options:t});if(ae(n)||n===ku)return!0;let r=e.find(o=>o.command===n);if(!r)return u.warn(`Unknown menu pick "${String(n)}" \u2014 returning to menu.`),!1;try{await r.run()}catch(o){u.error(`\`${r.command}\` failed: ${o.message??String(o)}`)}return!1}async function Ai(){if(!process.stdin.isTTY){u.info("The menu needs an interactive terminal. Use the regular commands directly, e.g. `seoagent --help`.");return}uk(`${Lr.bold(Lr.cyan("SEOAgent"))} ${Lr.dim("\xB7 menu")}`);let e=await vu();for(;!e;){let t=await $u({message:"Do another?",initialValue:!0});if(ae(t)||t===!1){e=!0;break}e=await vu()}dk(Lr.dim("See you next time."))}var R=new fk;R.name("seoagent").description("AI SEO agent for Claude Code").version(A).showSuggestionAfterError(!0).showHelpAfterError(!0);R.command("init").description("Initialize SEOAgent project \u2014 creates .seoagent/ and installs the skill file").option("-y, --yes","Non-interactive: use inferred/env/flag values only (requires domain if not inferable)").option("--domain <domain>","Website domain (non-interactive or override)").option("--site-type <type>","Site type: saas, service, product, content, etc.").option("--allow-worktree","Scaffold .seoagent/ inside a temporary git worktree anyway (not recommended \u2014 it is deleted with the worktree)").action(e=>{Vt({yes:e.yes,domain:e.domain,siteType:e.siteType,allowWorktree:e.allowWorktree})});R.command("uninstall").description("Remove SEOAgent from this project \u2014 deletes .seoagent/, the skill bundle, and the sync hook").option("-y, --yes","Skip the confirmation prompt (also implied in a non-TTY shell)").option("--global","Also wipe ~/.config/seoagent (login + sync state, all projects)").action(e=>{Jt({yes:e.yes,global:e.global})});R.command("status").description("Show current SEO project state").action(Zt);R.command("doctor").description("Session-start health check \u2014 surfaces untriaged pull receipts, unknown domain/site_type, and an image key that needs enabling").option("--json","Output findings as JSON (for scripting / the agent)").action(e=>{Xo({json:e.json})});R.command("login").description("Connect this CLI to your seoagent.com account (browser flow, or --code for headless setup)").option("--api-base <url>","Override API base URL (for testing)").option("--code <code>","One-time setup code from seoagent.com/grok \u2014 binds headlessly, no browser (also read from SEOAGENT_SETUP_CODE)").action(e=>{rn({apiBase:e.apiBase,code:e.code})});R.command("logout").description("Remove stored credentials for this project's site (--all for every logged-in site)").option("--all","Log out of every stored site, not just this project's").action(e=>{on({all:e.all})});R.command("sync").description("Sync .seoagent/ with your dashboard \u2014 push local changes then pull cloud changes (no-op when not logged in)").option("--silent","Suppress output (used by the Claude Code hook)").option("--force [relpath]","Re-send every artifact on push; take cloud on every pull conflict. Give a path (`--force content/x.md`) to take cloud for that ONE artifact and leave the other conflicts alone.").option("--path <relpath>","Push only files matching this path suffix").option("--push-only","Skip the cloud \u2192 local pull pass").option("--pull-only","Skip the local \u2192 cloud push pass (same as `seoagent pull`)").action(e=>{re({silent:e.silent,force:e.force,path:e.path,pushOnly:e.pushOnly,pullOnly:e.pullOnly})});R.command("pull").description("Pull cloud changes into .seoagent/ (dashboard / autopilot / chat edits)").option("--silent","Suppress output").option("--force [relpath]","Take the cloud version on every conflict (discards local edits). Give a path to force that ONE artifact.").option("--print <path>","Read-only: print the current cloud body of one artifact to stdout (writes nothing). For conflict diffs.").action(e=>{at({force:e.force,silent:e.silent,print:e.print})});R.command("ack [action_id]").description("Close out a pending action from .seoagent/inbox/ (omit id for an interactive picker)").option("--failed","Mark as failed instead of completed").option("--reason <text>","Reason for failure (used with --failed)").action((e,t)=>{ke(e,{failed:t.failed,reason:t.reason})});R.command("inbox").description("List pending actions queued by autopilot in .seoagent/inbox/").option("--json","Output as JSON (handy for scripting / `jq`)").action(e=>{Go({json:e.json})});R.command("process").description("Sync the inbox, pick which actions to apply, and run them via the Claude Agent SDK (uses your `claude login` session or ANTHROPIC_API_KEY)").option("--yes, -y","Skip the sync + selection prompts; process every pending action (for CI)").option("--model <name>","Override the model the Agent SDK uses (e.g. claude-sonnet-4-5-20250929)").action(e=>{Ko({yes:e.yes,model:e.model})});R.command("autopilot <action> [value]").description("Turn CLI autopilot on|off, show status, or toggle `auto-approve on|off` (paid; queues fixes for your agent)").option("--api-base <url>","Override API base URL (for testing)").action((e,t,n)=>{mn(e,t,{apiBase:n.apiBase})});R.command("keywords").description("Enrich keywords with real volume + difficulty + opportunity labels (requires login)").option("--peek <keyword>","Free one-keyword DataForSEO lookup (no login required; daily quota)").option("--seed","Add the queries your site already ranks for (Google Search Console) to the inventory \u2014 additive, keeps existing keywords (requires login + GSC)").option("--discover","Find NEW keywords to target from your existing strategy (paid)").option("--competitors","Find keywords competitors rank for that you don\u2019t (paid)").option("--purge","Delete suggested keywords (discover/competitors noise) from the cloud inventory").option("--all","With --purge: clear EVERY keyword for this site, not just suggested").option("--json","Output as JSON (handy for scripting / `jq` \u2014 currently --peek only)").option("--api-base <url>","Override API base URL (for testing)").action(e=>{Ke({peek:e.peek,seed:e.seed,discover:e.discover,competitors:e.competitors,purge:e.purge,all:e.all,json:e.json,apiBase:e.apiBase})});R.command("internal-links").description("Analyze the repo for orphan pages (no inbound internal links) \u2192 .seoagent/internal-links.md").option("--json","Print the analysis as JSON instead of writing the report").action(e=>{hn({json:e.json})});R.command("content [action]").description("Track published articles so the dashboard sees them \u2014 `content track` (one) or `content reconcile` (backfill all)").option("--slug <slug>","Article slug (or derive from --file)").option("--url <url>","Live/canonical URL (else built from project domain + blog_path + slug)").option("--file <path>","Repo/CMS source file that renders this article (the body source of truth)").option("--title <title>","Article title (else derived from the slug)").option("--type <page_type>","page_type: landing | pillar | sub_pillar | long_tail | programmatic | blog").option("--status <status>","published (default) | draft").option("--primary-keyword <kw>","Primary keyword for the article").option("--brief <brief-slug>","Brief this article was generated from (preserved automatically if the record already has one)").option("--supersedes <slug>","The cloud-delivered draft slug this article shipped as, when you renamed it \u2014 marks that draft superseded instead of leaving a duplicate").option("--action <id>","The cli_draft_ready action this track closes out (acks it for you)").option("--dir <dirs>","reconcile: content dir(s) to scan, comma-separated (else inferred from tracked articles)").option("--prune","reconcile: delete stale pointers whose source file is gone").action((e,t)=>{zo(e,{slug:t.slug,url:t.url,file:t.file,title:t.title,type:t.type,status:t.status,primaryKeyword:t.primaryKeyword,brief:t.brief,supersedes:t.supersedes,action:t.action,dir:t.dir,prune:t.prune})});R.command("okf [action]").description("Manage the Open Knowledge Format bundle for AI agents \u2014 [validate|scaffold|publish] \u2192 .seoagent/okf/ (publish copies it into your static dir so /.well-known/okf/ is actually served)").option("--json","Output result as JSON").action((e,t)=>{yn(e,{json:t.json})});R.command("llms").description("Generate and publish llms.txt \u2014 the markdown map answer engines read. Built from .seoagent/pages.md, content/, evidence.md and context.md, written to .seoagent/llms.md and published to <public_dir>/llms.txt. Runs automatically on every `seoagent sync`.").option("--json","Output result as JSON").action(e=>{Qo({json:e.json})});R.command("sitemap").description("Validate the site\u2019s live sitemap \u2014 reachability, private-route leakage, freshness, and which public repo pages are missing").option("--url <url>","Override the sitemap URL (default https://{domain}/sitemap.xml)").option("--json","Output result as JSON").action(e=>{Zo({url:e.url,json:e.json})});R.command("crawl").description("Capture the live-crawl evidence base (title, meta, all H1s, canonical, JSON-LD @types, OG/Twitter, img-alt gaps, robots.txt, sitemap counts, client-render detection) for the homepage + subpages discovered from the live sitemap + nav links, with a site-wide rollup \u2192 .seoagent/audit/evidence.md. Grounds every audit claim so nothing is invented. The crawled origin is EXPLICIT: --url wins (and is persisted as live_url: in project.md), else live_url:/domain: from project.md, else it errors \u2014 never a guess. An undesignated loopback origin is labeled SOURCE RENDER, not live evidence.").option("--url <origin>","The live site origin to crawl (e.g. https://example.com or http://127.0.0.1:3000) \u2014 persisted as live_url: in project.md for later runs").option("--max <n>","Max pages to crawl (default: every discovered page, up to 500)",e=>parseInt(e,10)).option("--json","Output the full evidence bundle as JSON").action(e=>{ci({url:e.url,max:e.max,json:e.json})});R.command("indexing").description("GSC indexing coverage: which sitemap URLs Google has actually indexed (authoritative URL Inspection verdicts) \u2192 .seoagent/audit/indexing.md. Requires the free `seoagent login` (connects Search Console). Origin resolution mirrors crawl: --url wins, else live_url:/domain: from project.md, else it errors \u2014 never a guess.").option("--url <origin>","The live site origin (e.g. https://example.com)").option("--max <n>","Max sitemap URLs to inspect (default 30, max 200 \u2014 URL Inspection has a daily quota)",e=>parseInt(e,10)).option("--json","Output the results as JSON").action(e=>{pi({url:e.url,max:e.max,json:e.json})});R.command("verify-recs [files...]").description('Mechanically verify authored recommendations against the crawl evidence (.seoagent/audit/evidence.md) \u2014 rewrites false "added X / none existed / missing X" claims about head-level entities (title, meta description, canonical, OG, twitter:card, JSON-LD) the live page already serves, across .seoagent/**/*.md plus any files passed. Mandatory final step of an audit/optimization session. Always exits 0.').option("--json","Output the machine-readable correction report as JSON").option("--quiet","Print only actionable output (corrections / source-render warning) \u2014 for hooks").action((e,t)=>{fi({files:e??[],json:t.json,quiet:t.quiet})});R.command("summary [files...]").description("Compose the ready-to-deliver session summary FROM the corrected on-disk .seoagent state \u2014 top audit findings quoted verbatim (confidence labels + Evidence citations), every verify-recs CORRECTION (so corrected framing is what gets relayed), the migration-plan harvest/redirect/sunset table, and top roadmap items. Runs the verify-recs pass first. The final message of an audit/optimization session must be built from this output, not from memory. Flags live-state claims as UNVERIFIED when the crawl evidence is missing or a source render. Plain markdown to stdout; always exits 0.").option("--json","Output the structured summary as JSON").action((e,t)=>{gi({files:e??[],json:t.json})});R.command("migrate").description("Plan the migration of legacy Google Search Console authority when the site has repositioned \u2014 classifies each URL/query as harvest | redirect | sunset \u2192 .seoagent/strategy/migration-plan.md").option("--csv <path>","Search Console Pages/Queries CSV export to plan from (no login required)").option("--direction <text>","Override the new-direction summary (else inferred from project.md + context.md)").option("--json","Output the full plan + decisions as JSON").action(e=>{wi({csv:e.csv,direction:e.direction,json:e.json})});R.command("citations").description("Track whether answer engines cite you (AEO/GEO) \u2014 web-grounded via the Claude Agent SDK, with history, share-of-voice + source-attribution \u2192 .seoagent/citations/scorecard.md").option("--queries <n>","How many answer-engine queries to test (1\u201312, default 6)",e=>parseInt(e,10)).option("--competitors <list>","Comma-separated competitor brands to track for share-of-voice").option("--model <name>","Override the model the Agent SDK uses").option("--json","Also print the parsed report as JSON").action(e=>{Sn({queries:e.queries,competitors:typeof e.competitors=="string"?e.competitors.split(",").map(t=>t.trim()).filter(Boolean):void 0,model:e.model,json:e.json})});R.command("env-check").description("Detect which image generation provider is available (OPENAI / FAL / REPLICATE)").option("--json","Output detection result as JSON").action(e=>{Cn({json:e.json})});R.command("generate-image").description("Generate an image via the detected (or explicit) provider").requiredOption("--prompt <text>","Image prompt").requiredOption("--out <path>","Output file path (relative to cwd or absolute)").option("--provider <name>","Force provider: openai | fal | replicate").option("--size <wxh>","Pixel size hint, e.g. 1024x1024").option("--silent","Suppress progress output").action(e=>{Rn({prompt:e.prompt,out:e.out,provider:e.provider,size:e.size,silent:e.silent})});R.command("whoami").description("Show the logged-in account: email, bound website, plan, and API base").option("--json","Output as JSON (handy for scripting / `jq`)").option("--api-base <url>","Override API base URL (for testing)").action(e=>{En({json:e.json,apiBase:e.apiBase})});R.command("upgrade").description("Open the SEOAgent Cloud pricing page in your browser (plan upgrade)").action(tn);R.command("update-cli").description("Update the SEOAgent CLI to the latest npm version, then refresh every project on this machine").option("--dry-run","Show what would happen without running the install").option("--no-projects","Skip refreshing your other projects after the update").action(e=>{nn({dryRun:e.dryRun,projects:e.projects})});R.command("refresh").description("Refresh the installed skill + re-scan the codebase for new pages. `--all` sweeps every project on this machine.").option("--all","Refresh every registered project, not just this one").option("--crawl","Also fetch each page to fill Status / Rendered / Word count in pages.md (slower)").action(e=>{_n({all:e.all,crawl:e.crawl})});R.command("menu").description("Interactive menu \u2014 pick a command from a list (no flags required)").action(()=>{Ai()});process.argv.length===2&&(R.outputHelp(),process.exit(0));R.parse();
1033
+ `);return}u.message(dk(s)),a.length>0&&u.message(te.dim(`Also logged in to: ${a.join(", ")}.`)),s.paid===!1&&u.message(`${te.dim("Upgrade for autopilot:")} ${te.cyan(he("cli-whoami"))}`)}function dk(e){let n=i=>te.dim(i.padEnd(10)),r=[];if(e.email&&(r.push({content:`${te.green("\u2713")} ${te.dim("Logged in as")} ${te.bold(e.email)}`}),r.push({sep:!0})),e.domain&&r.push({content:`${n("Website:")}${te.cyan(e.domain)}`}),e.plan){let i=e.paid?te.bold(te.green(e.plan)):te.bold(e.plan);r.push({content:`${n("Plan:")}${i}`})}e.api_base&&r.push({content:`${n("API:")}${te.dim(e.api_base)}`});let o=`${te.bold(te.cyan("SEOAgent"))} ${te.dim("\xB7")} ${te.dim("whoami")}`;return M(r,{width:55,title:o})}import{select as Pn,text as An,confirm as $u,isCancel as ae,intro as pk,outro as fk}from"@clack/prompts";import Lr from"picocolors";function gk(){return[{command:"status",label:"Show project status",hint:"Audit, strategy, briefs, content, roadmap \u2014 everything at a glance.",run:async()=>Zt()},{command:"sync",label:"Sync with the cloud dashboard",hint:"Push local .seoagent/ changes + pull dashboard edits.",run:async()=>await re()},{command:"init",label:"Initialize a SEOAgent project",hint:"Scaffold .seoagent/ + the skill file in this directory.",run:async()=>{await Vt()}},{command:"login",label:"Connect this CLI to seoagent.com",hint:"Opens the browser to bind your account.",run:async()=>await rn()},{command:"whoami",label:"Show logged-in account",hint:"Email, bound website, plan, API base.",run:async()=>await En()},{command:"keywords",label:"Keyword research",hint:"Peek (free, one keyword) \xB7 enrich \xB7 discover \xB7 competitor gaps.",run:async()=>{let e=await Pn({message:"Which mode?",options:[{value:"peek",label:"peek",hint:"Free, one keyword, no login required"},{value:"enrich",label:"enrich (default)",hint:"Enrich your full strategy (requires login)"},{value:"discover",label:"discover",hint:"Find new keywords to target (paid)"},{value:"competitors",label:"competitors",hint:"Find competitor keyword gaps (paid)"}]});if(!ae(e))if(e==="peek"){let t=await An({message:"Keyword to peek:",placeholder:"saas seo",validate:n=>String(n??"").trim()?void 0:"A keyword is required."});if(ae(t))return;await Ke({peek:String(t)})}else e==="discover"?await Ke({discover:!0}):e==="competitors"?await Ke({competitors:!0}):await Ke()}},{command:"internal-links",label:"Analyze internal links",hint:"Find orphan pages \u2192 .seoagent/internal-links.md",run:async()=>hn({})},{command:"okf",label:"Open Knowledge Format bundle",hint:"Make your site AI-readable (AEO/GEO) \u2192 .seoagent/okf/",run:async()=>{let e=await Pn({message:"Which OKF action?",options:[{value:"status",label:"status",hint:"Is a bundle present?"},{value:"scaffold",label:"scaffold",hint:"Create the starter bundle"},{value:"validate",label:"validate",hint:"Check type fields, timestamps, links"}]});ae(e)||yn(e==="status"?void 0:String(e))}},{command:"citations",label:"Measure AI citations (AEO/GEO)",hint:"Are answer engines citing you? Web-grounded \u2192 .seoagent/citations/scorecard.md",run:async()=>await Sn()},{command:"generate-image",label:"Generate an image",hint:"Uses the detected (or explicit) provider.",run:async()=>{let e=await An({message:"Image prompt:",placeholder:"a minimalist cyan box logo",validate:n=>String(n??"").trim()?void 0:"A prompt is required."});if(ae(e))return;let t=await An({message:"Output file path:",placeholder:".seoagent/content/images/hero.png",validate:n=>String(n??"").trim()?void 0:"An output path is required."});ae(t)||await Rn({prompt:String(e),out:String(t)})}},{command:"env-check",label:"Check image-generation provider",hint:"Detect openai / fal / replicate from your env.",run:async()=>Cn({})},{command:"autopilot",label:"Toggle CLI autopilot",hint:"on / off / status \u2014 paid feature for queueing SEO fixes.",run:async()=>{let e=await Pn({message:"Autopilot action:",options:[{value:"status",label:"status",hint:"Show current state"},{value:"on",label:"on",hint:"Turn autopilot ON (paid)"},{value:"off",label:"off",hint:"Turn autopilot OFF"}]});ae(e)||await mn(String(e))}},{command:"pull",label:"Pull dashboard changes only",hint:"Cloud \u2192 local. Same as the second half of `sync`.",run:async()=>await at()},{command:"ack",label:"Close out a pending inbox action",hint:"Mark a .seoagent/inbox/ action completed or failed.",run:async()=>{let e=await An({message:"Action ID to close out:",placeholder:"action-abc123",validate:r=>String(r??"").trim()?void 0:"Action ID is required."});if(ae(e))return;let t=await $u({message:"Mark as failed instead of completed?",initialValue:!1});if(ae(t))return;let n;if(t===!0){let r=await An({message:"Reason for failure (optional):",placeholder:"couldn't apply because\u2026"});if(ae(r))return;String(r).trim()&&(n=String(r))}await ke(String(e),{failed:t===!0,reason:n})}},{command:"upgrade",label:"Upgrade your SEOAgent plan",hint:"Open the pricing page; contextual prompt if logged in.",run:async()=>await tn()},{command:"update-cli",label:"Update the CLI to the latest version",hint:"npm install -g @seoagent-official/seoagent@latest + refresh your projects",run:async()=>await nn({})},{command:"refresh",label:"Refresh the skill to your CLI version",hint:"This project \u2014 or every project on the machine via `refresh --all`.",run:async()=>{let e=await Pn({message:"Refresh which?",options:[{value:"this",label:"This project",hint:"Just the current directory"},{value:"all",label:"All projects",hint:"Every SEOAgent project on this machine"}]});ae(e)||_n({all:e==="all"})}},{command:"logout",label:"Log out",hint:"Clear stored credentials for seoagent.com.",run:async()=>on()},{command:"uninstall",label:"Uninstall SEOAgent from this project",hint:"Remove .seoagent/, the skill bundle, and the sync hook.",run:async()=>await Jt({})}]}var ku="__exit__";async function vu(e=gk()){let t=e.map(o=>({value:o.command,label:o.label,hint:o.hint}));t.push({value:ku,label:"Exit",hint:"Close the menu"});let n=await Pn({message:"What would you like to do?",options:t});if(ae(n)||n===ku)return!0;let r=e.find(o=>o.command===n);if(!r)return u.warn(`Unknown menu pick "${String(n)}" \u2014 returning to menu.`),!1;try{await r.run()}catch(o){u.error(`\`${r.command}\` failed: ${o.message??String(o)}`)}return!1}async function Ai(){if(!process.stdin.isTTY){u.info("The menu needs an interactive terminal. Use the regular commands directly, e.g. `seoagent --help`.");return}pk(`${Lr.bold(Lr.cyan("SEOAgent"))} ${Lr.dim("\xB7 menu")}`);let e=await vu();for(;!e;){let t=await $u({message:"Do another?",initialValue:!0});if(ae(t)||t===!1){e=!0;break}e=await vu()}fk(Lr.dim("See you next time."))}var R=new mk;R.name("seoagent").description("AI SEO agent for Claude Code").version(A).showSuggestionAfterError(!0).showHelpAfterError(!0);R.command("init").description("Initialize SEOAgent project \u2014 creates .seoagent/ and installs the skill file").option("-y, --yes","Non-interactive: use inferred/env/flag values only (requires domain if not inferable)").option("--domain <domain>","Website domain (non-interactive or override)").option("--site-type <type>","Site type: saas, service, product, content, etc.").option("--allow-worktree","Scaffold .seoagent/ inside a temporary git worktree anyway (not recommended \u2014 it is deleted with the worktree)").action(e=>{Vt({yes:e.yes,domain:e.domain,siteType:e.siteType,allowWorktree:e.allowWorktree})});R.command("uninstall").description("Remove SEOAgent from this project \u2014 deletes .seoagent/, the skill bundle, and the sync hook").option("-y, --yes","Skip the confirmation prompt (also implied in a non-TTY shell)").option("--global","Also wipe ~/.config/seoagent (login + sync state, all projects)").action(e=>{Jt({yes:e.yes,global:e.global})});R.command("status").description("Show current SEO project state").action(Zt);R.command("doctor").description("Session-start health check \u2014 surfaces untriaged pull receipts, unknown domain/site_type, and an image key that needs enabling").option("--json","Output findings as JSON (for scripting / the agent)").action(e=>{Xo({json:e.json})});R.command("login").description("Connect this CLI to your seoagent.com account (browser flow, or --code for headless setup)").option("--api-base <url>","Override API base URL (for testing)").option("--code <code>","One-time setup code from seoagent.com/grok \u2014 binds headlessly, no browser (also read from SEOAGENT_SETUP_CODE)").action(e=>{rn({apiBase:e.apiBase,code:e.code})});R.command("logout").description("Remove stored credentials for this project's site (--all for every logged-in site)").option("--all","Log out of every stored site, not just this project's").action(e=>{on({all:e.all})});R.command("sync").description("Sync .seoagent/ with your dashboard \u2014 push local changes then pull cloud changes (no-op when not logged in)").option("--silent","Suppress output (used by the Claude Code hook)").option("--force [relpath]","Re-send every artifact on push; take cloud on every pull conflict. Give a path (`--force content/x.md`) to take cloud for that ONE artifact and leave the other conflicts alone.").option("--path <relpath>","Push only files matching this path suffix").option("--push-only","Skip the cloud \u2192 local pull pass").option("--pull-only","Skip the local \u2192 cloud push pass (same as `seoagent pull`)").action(e=>{re({silent:e.silent,force:e.force,path:e.path,pushOnly:e.pushOnly,pullOnly:e.pullOnly})});R.command("pull").description("Pull cloud changes into .seoagent/ (dashboard / autopilot / chat edits)").option("--silent","Suppress output").option("--force [relpath]","Take the cloud version on every conflict (discards local edits). Give a path to force that ONE artifact.").option("--print <path>","Read-only: print the current cloud body of one artifact to stdout (writes nothing). For conflict diffs.").action(e=>{at({force:e.force,silent:e.silent,print:e.print})});R.command("ack [action_id]").description("Close out a pending action from .seoagent/inbox/ (omit id for an interactive picker)").option("--failed","Mark as failed instead of completed").option("--reason <text>","Reason for failure (used with --failed)").action((e,t)=>{ke(e,{failed:t.failed,reason:t.reason})});R.command("inbox").description("List pending actions queued by autopilot in .seoagent/inbox/").option("--json","Output as JSON (handy for scripting / `jq`)").action(e=>{Go({json:e.json})});R.command("process").description("Sync the inbox, pick which actions to apply, and run them via the Claude Agent SDK (uses your `claude login` session or ANTHROPIC_API_KEY)").option("--yes, -y","Skip the sync + selection prompts; process every pending action (for CI)").option("--model <name>","Override the model the Agent SDK uses (e.g. claude-sonnet-4-5-20250929)").action(e=>{Ko({yes:e.yes,model:e.model})});R.command("autopilot <action> [value]").description("Turn CLI autopilot on|off, show status, or toggle `auto-approve on|off` (paid; queues fixes for your agent)").option("--api-base <url>","Override API base URL (for testing)").action((e,t,n)=>{mn(e,t,{apiBase:n.apiBase})});R.command("keywords").description("Enrich keywords with real volume + difficulty + opportunity labels (requires login)").option("--peek <keyword>","Free one-keyword DataForSEO lookup (no login required; daily quota)").option("--seed","Add the queries your site already ranks for (Google Search Console) to the inventory \u2014 additive, keeps existing keywords (requires login + GSC)").option("--discover","Find NEW keywords to target from your existing strategy (paid)").option("--competitors","Find keywords competitors rank for that you don\u2019t (paid)").option("--purge","Delete suggested keywords (discover/competitors noise) from the cloud inventory").option("--all","With --purge: clear EVERY keyword for this site, not just suggested").option("--json","Output as JSON (handy for scripting / `jq` \u2014 currently --peek only)").option("--api-base <url>","Override API base URL (for testing)").action(e=>{Ke({peek:e.peek,seed:e.seed,discover:e.discover,competitors:e.competitors,purge:e.purge,all:e.all,json:e.json,apiBase:e.apiBase})});R.command("internal-links").description("Analyze the repo for orphan pages (no inbound internal links) \u2192 .seoagent/internal-links.md").option("--json","Print the analysis as JSON instead of writing the report").action(e=>{hn({json:e.json})});R.command("content [action]").description("Track published articles so the dashboard sees them \u2014 `content track` (one) or `content reconcile` (backfill all)").option("--slug <slug>","Article slug (or derive from --file)").option("--url <url>","Live/canonical URL (else built from project domain + blog_path + slug)").option("--file <path>","Repo/CMS source file that renders this article (the body source of truth)").option("--title <title>","Article title (else derived from the slug)").option("--type <page_type>","page_type: landing | pillar | sub_pillar | long_tail | programmatic | blog").option("--status <status>","published (default) | draft").option("--primary-keyword <kw>","Primary keyword for the article").option("--brief <brief-slug>","Brief this article was generated from (preserved automatically if the record already has one)").option("--supersedes <slug>","The cloud-delivered draft slug this article shipped as, when you renamed it \u2014 marks that draft superseded instead of leaving a duplicate").option("--action <id>","The cli_draft_ready action this track closes out (acks it for you)").option("--dir <dirs>","reconcile: content dir(s) to scan, comma-separated (else inferred from tracked articles)").option("--prune","reconcile: delete stale pointers whose source file is gone").action((e,t)=>{zo(e,{slug:t.slug,url:t.url,file:t.file,title:t.title,type:t.type,status:t.status,primaryKeyword:t.primaryKeyword,brief:t.brief,supersedes:t.supersedes,action:t.action,dir:t.dir,prune:t.prune})});R.command("okf [action]").description("Manage the Open Knowledge Format bundle for AI agents \u2014 [validate|scaffold|publish] \u2192 .seoagent/okf/ (publish copies it into your static dir so /.well-known/okf/ is actually served)").option("--json","Output result as JSON").action((e,t)=>{yn(e,{json:t.json})});R.command("llms").description("Generate and publish llms.txt \u2014 the markdown map answer engines read. Built from .seoagent/pages.md, content/, evidence.md and context.md, written to .seoagent/llms.md and published to <public_dir>/llms.txt. Runs automatically on every `seoagent sync`.").option("--json","Output result as JSON").action(e=>{Qo({json:e.json})});R.command("sitemap").description("Validate the site\u2019s live sitemap \u2014 reachability, private-route leakage, freshness, and which public repo pages are missing").option("--url <url>","Override the sitemap URL (default https://{domain}/sitemap.xml)").option("--json","Output result as JSON").action(e=>{Zo({url:e.url,json:e.json})});R.command("crawl").description("Capture the live-crawl evidence base (title, meta, all H1s, canonical, JSON-LD @types, OG/Twitter, img-alt gaps, robots.txt, sitemap counts, client-render detection) for the homepage + subpages discovered from the live sitemap + nav links, with a site-wide rollup \u2192 .seoagent/audit/evidence.md. Grounds every audit claim so nothing is invented. The crawled origin is EXPLICIT: --url wins (and is persisted as live_url: in project.md), else live_url:/domain: from project.md, else it errors \u2014 never a guess. An undesignated loopback origin is labeled SOURCE RENDER, not live evidence.").option("--url <origin>","The live site origin to crawl (e.g. https://example.com or http://127.0.0.1:3000) \u2014 persisted as live_url: in project.md for later runs").option("--max <n>","Max pages to crawl (default: every discovered page, up to 500)",e=>parseInt(e,10)).option("--json","Output the full evidence bundle as JSON").action(e=>{ci({url:e.url,max:e.max,json:e.json})});R.command("indexing").description("GSC indexing coverage: which sitemap URLs Google has actually indexed (authoritative URL Inspection verdicts) \u2192 .seoagent/audit/indexing.md. Requires the free `seoagent login` (connects Search Console). Origin resolution mirrors crawl: --url wins, else live_url:/domain: from project.md, else it errors \u2014 never a guess.").option("--url <origin>","The live site origin (e.g. https://example.com)").option("--max <n>","Max sitemap URLs to inspect (default 30, max 200 \u2014 URL Inspection has a daily quota)",e=>parseInt(e,10)).option("--json","Output the results as JSON").action(e=>{pi({url:e.url,max:e.max,json:e.json})});R.command("verify-recs [files...]").description('Mechanically verify authored recommendations against the crawl evidence (.seoagent/audit/evidence.md) \u2014 rewrites false "added X / none existed / missing X" claims about head-level entities (title, meta description, canonical, OG, twitter:card, JSON-LD) the live page already serves, across .seoagent/**/*.md plus any files passed. Mandatory final step of an audit/optimization session. Always exits 0.').option("--json","Output the machine-readable correction report as JSON").option("--quiet","Print only actionable output (corrections / source-render warning) \u2014 for hooks").action((e,t)=>{fi({files:e??[],json:t.json,quiet:t.quiet})});R.command("summary [files...]").description("Compose the ready-to-deliver session summary FROM the corrected on-disk .seoagent state \u2014 top audit findings quoted verbatim (confidence labels + Evidence citations), every verify-recs CORRECTION (so corrected framing is what gets relayed), the migration-plan harvest/redirect/sunset table, and top roadmap items. Runs the verify-recs pass first. The final message of an audit/optimization session must be built from this output, not from memory. Flags live-state claims as UNVERIFIED when the crawl evidence is missing or a source render. Plain markdown to stdout; always exits 0.").option("--json","Output the structured summary as JSON").action((e,t)=>{gi({files:e??[],json:t.json})});R.command("migrate").description("Plan the migration of legacy Google Search Console authority when the site has repositioned \u2014 classifies each URL/query as harvest | redirect | sunset \u2192 .seoagent/strategy/migration-plan.md").option("--csv <path>","Search Console Pages/Queries CSV export to plan from (no login required)").option("--direction <text>","Override the new-direction summary (else inferred from project.md + context.md)").option("--json","Output the full plan + decisions as JSON").action(e=>{wi({csv:e.csv,direction:e.direction,json:e.json})});R.command("citations").description("Track whether answer engines cite you (AEO/GEO) \u2014 web-grounded via the Claude Agent SDK, with history, share-of-voice + source-attribution \u2192 .seoagent/citations/scorecard.md").option("--queries <n>","How many answer-engine queries to test (1\u201312, default 6)",e=>parseInt(e,10)).option("--competitors <list>","Comma-separated competitor brands to track for share-of-voice").option("--model <name>","Override the model the Agent SDK uses").option("--json","Also print the parsed report as JSON").action(e=>{Sn({queries:e.queries,competitors:typeof e.competitors=="string"?e.competitors.split(",").map(t=>t.trim()).filter(Boolean):void 0,model:e.model,json:e.json})});R.command("env-check").description("Detect which image generation provider is available (OPENAI / FAL / REPLICATE)").option("--json","Output detection result as JSON").action(e=>{Cn({json:e.json})});R.command("generate-image").description("Generate an image via the detected (or explicit) provider").requiredOption("--prompt <text>","Image prompt").requiredOption("--out <path>","Output file path (relative to cwd or absolute)").option("--provider <name>","Force provider: openai | fal | replicate").option("--size <wxh>","Pixel size hint, e.g. 1024x1024").option("--silent","Suppress progress output").action(e=>{Rn({prompt:e.prompt,out:e.out,provider:e.provider,size:e.size,silent:e.silent})});R.command("whoami").description("Show the logged-in account: email, bound website, plan, and API base").option("--json","Output as JSON (handy for scripting / `jq`)").option("--api-base <url>","Override API base URL (for testing)").action(e=>{En({json:e.json,apiBase:e.apiBase})});R.command("upgrade").description("Open the SEOAgent Cloud pricing page in your browser (plan upgrade)").action(tn);R.command("update-cli").description("Update the SEOAgent CLI to the latest npm version, then refresh every project on this machine").option("--dry-run","Show what would happen without running the install").option("--no-projects","Skip refreshing your other projects after the update").action(e=>{nn({dryRun:e.dryRun,projects:e.projects})});R.command("refresh").description("Refresh the installed skill + re-scan the codebase for new pages. `--all` sweeps every project on this machine.").option("--all","Refresh every registered project, not just this one").option("--crawl","Also fetch each page to fill Status / Rendered / Word count in pages.md (slower)").action(e=>{_n({all:e.all,crawl:e.crawl})});R.command("menu").description("Interactive menu \u2014 pick a command from a list (no flags required)").action(()=>{Ai()});process.argv.length===2&&(R.outputHelp(),process.exit(0));R.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seoagent-official/seoagent",
3
- "version": "1.95.2",
3
+ "version": "1.95.3",
4
4
  "description": "The persistent AI SEO agent for Claude Code. Audits, keyword strategy, briefs, articles, real product screenshots from your repo, and the autopilot loop (cloud detects → CLI executes → ack closes) — other SEO tools write the prompt, SEOAgent runs it.",
5
5
  "type": "module",
6
6
  "bin": {