@seoagent-official/seoagent 1.113.3 → 1.113.4
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.
- package/index.js +21 -21
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{Command as iS}from"commander";import{dirname as km,relative as Dt}from"path";var hp={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"},Cs={"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"},Eo="en";function yp(e){return e.trim().toLowerCase().replace(/^[a-z][a-z0-9+.-]*:\/\//,"").split(/[/?#]/)[0].split(":")[0].replace(/^www\./,"").replace(/\.$/,"")}function As(e){if(!e)return Eo;let t=yp(e).split(".").filter(Boolean);if(t.length<2)return Eo;if(t.length>=3){let n=`${t[t.length-2]}.${t[t.length-1]}`;if(Cs[n])return Cs[n]}return hp[t[t.length-1]]??Eo}import{intro as vl,outro as xm,text as $l,select as li,spinner as vm,isCancel as
|
|
2
|
+
import{Command as iS}from"commander";import{dirname as km,relative as Dt}from"path";var hp={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"},Cs={"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"},Eo="en";function yp(e){return e.trim().toLowerCase().replace(/^[a-z][a-z0-9+.-]*:\/\//,"").split(/[/?#]/)[0].split(":")[0].replace(/^www\./,"").replace(/\.$/,"")}function As(e){if(!e)return Eo;let t=yp(e).split(".").filter(Boolean);if(t.length<2)return Eo;if(t.length>=3){let n=`${t[t.length-2]}.${t[t.length-1]}`;if(Cs[n])return Cs[n]}return hp[t[t.length-1]]??Eo}import{intro as vl,outro as xm,text as $l,select as li,spinner as vm,isCancel as xt,cancel as vt,confirm as Sl}from"@clack/prompts";var Rt=[".env.local",".env.production",".env"],Is=["NEXT_PUBLIC_SITE_URL","SITE_URL","NEXT_PUBLIC_URL","NEXTAUTH_URL","VITE_SITE_URL"];var We="@seoagent-official/seoagent",nr=`${We}@latest`;import{homedir as wp}from"os";import{join as Et}from"path";var x=".seoagent",Ps=["audit","strategy/clusters","briefs","content","content/images","performance"],Ke={AGENTS:".agents/skills/seoagent",CLAUDE:".claude/skills/seoagent"},Zt=".claude/settings.json",en=".pull-receipt.json",Ts=".ack-queue.json",Os=".sync.lock",Ls=`# SEOAgent local files
|
|
3
3
|
.legacy/
|
|
4
4
|
content/images/*
|
|
5
5
|
!content/images/.gitkeep
|
|
@@ -12,18 +12,18 @@ content/images/*
|
|
|
12
12
|
${t}`;return n.join(`
|
|
13
13
|
`)+r+(r.endsWith(`
|
|
14
14
|
`)?"":`
|
|
15
|
-
`)}import{existsSync as ar,readdirSync as Pp,statSync as Tp}from"fs";import{join as Ve}from"path";var Us=["svelte.config.js","svelte.config.ts","svelte.config.mjs","svelte.config.cjs","gatsby-config.js","gatsby-config.ts","gatsby-config.mjs","docusaurus.config.js","docusaurus.config.ts","docusaurus.config.mjs","hugo.toml","hugo.yaml","hugo.json"],Fs=["next.config.js","next.config.ts","next.config.mjs","next.config.cjs","astro.config.js","astro.config.ts","astro.config.mjs","nuxt.config.js","nuxt.config.ts","nuxt.config.mjs","remix.config.js","remix.config.ts","vite.config.js","vite.config.ts","vite.config.mjs","vite.config.cjs"],Op=["apps","packages"];function Po(e){for(let t of Us)if(ar(Ve(e,t)))return"static";for(let t of Fs)if(ar(Ve(e,t)))return"public";return null}function Ms(e){for(let t of[...Us,...Fs])if(ar(Ve(e,t)))return t;return null}function To(e){let t=[];for(let n of Op){let r;try{r=Pp(Ve(e,n),{withFileTypes:!0}).filter(o=>o.isDirectory()).map(o=>o.name).sort()}catch{continue}for(let o of r){let i=`${n}/${o}`;Po(Ve(e,i))&&t.push(i)}}return t.length===1?t[0]:null}function lr(e){if(typeof e!="string")return null;let t=e.trim().replace(/^\.\//,"").replace(/\/+$/,"");return!t||t.startsWith("/")||/^[A-Za-z]:[\\/]/.test(t)||t.split(/[\\/]/).some(n=>n==="..")?null:t.replace(/\\/g,"/")}function sr(e,t){try{return Tp(Ve(e,t)).isDirectory()}catch{return!1}}function cr(e,t){let n=lr(t);if(n)return{dir:n,source:"configured",evidence:"public_dir in .seoagent/project.md",exists:sr(e,n)};let r=Po(e);if(r)return{dir:r,source:"framework",evidence:Ms(e),exists:sr(e,r)};let o=To(e);if(o){let i=Po(Ve(e,o));if(i){let s=`${o}/${i}`;return{dir:s,source:"framework",evidence:`${o}/${Ms(Ve(e,o))}`,exists:sr(e,s)}}}for(let i of["public","static"])if(sr(e,i))return{dir:i,source:"existing",evidence:`${i}/ exists`,exists:!0};return{dir:null,source:null,evidence:null,exists:!1}}var Lp=["app/llms.txt/route.ts","app/llms.txt/route.tsx","app/llms.txt/route.js","src/app/llms.txt/route.ts","src/app/llms.txt/route.tsx","src/app/llms.txt/route.js","pages/llms.txt.ts","pages/llms.txt.js","src/pages/llms.txt.ts","src/pages/llms.txt.js","src/routes/llms.txt/+server.ts","src/routes/llms.txt/+server.js","routes/llms.txt/+server.ts","routes/llms.txt/+server.js"];function Bs(e,t){let n=new Set([""]),r=lr(t);if(r){let o=r.split("/").slice(0,-1).join("/");o&&n.add(o)}for(let o of n)for(let i of Lp){let s=o?`${o}/${i}`:i;if(ar(Ve(e,s)))return s}return null}var Dp=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;function
|
|
16
|
-
`)}function Bp(e){let t={domain:e.domain,site_type:e.site_type,language:e.language,initialized_at:e.initialized_at,seoagent_version:e.seoagent_version};return e.skill_version&&(t.skill_version=e.skill_version),e.skill_hash&&(t.skill_hash=e.skill_hash),e.install_id&&(t.install_id=e.install_id),e.image_provider&&(t.image_provider=e.image_provider),e.cms&&(t.cms=e.cms),e.blog_path&&(t.blog_path=e.blog_path),e.content_dir&&(t.content_dir=e.content_dir),e.public_dir&&(t.public_dir=e.public_dir),e.llms_owner&&(t.llms_owner=e.llms_owner),e.live_url&&(t.live_url=e.live_url),t}function qs(e,t){let n=Ye(e);Hs(n,{recursive:!0});let r=Io(Bp(t),Fp(t));Ws(ur(e),r,"utf-8")}function X(e,t){let n=ur(e);if(!Gs(n))return!1;let r=me(jp(n,"utf-8")),o={...r.data,...t};return Ws(n,Io(o,r.body),"utf-8"),!0}function Vs(e){let t=Ye(e);for(let n of Ps)Hs(Oo(t,n),{recursive:!0})}var Lo="Sent an anonymous install ping (random id + CLI version, plus a partial setup-code prefix when --code is used \u2014 no domain, no paths). Opt out: SEOAGENT_TELEMETRY=0",Ys="Sent an anonymous ping naming this project's site so its owner can be told the run had no credential (random id + CLI version + domain \u2014 no paths, no secrets). Opt out: SEOAGENT_TELEMETRY=0",Gp=2500;function he(e=process.env){let t=(e.SEOAGENT_TELEMETRY??"").trim().toLowerCase();if(t==="0"||t==="false"||t==="off")return!0;let n=(e.DO_NOT_TRACK??"").trim().toLowerCase();return n==="1"||n==="true"}function Hp(e){let t=e.meta&&Object.values(e.meta).some(n=>n!==void 0)?e.meta:null;return{install_id:e.installId,event:e.event,cli_version:e.cliVersion,platform:process.platform,...e.codeHint?{code_hint:e.codeHint}:{},...t?{meta:t}:{}}}async function ze(e){let t=(e.apiBase||U.BASE).replace(/\/$/,""),n=e.fetchImpl??fetch;try{return(await n(`${t}/api/cli/telemetry`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(Hp(e)),signal:AbortSignal.timeout(e.timeoutMs??Gp)})).ok}catch{return!1}}var dr=/^sa_[0-9a-f]{24}$/;function pr(e){let t=(e??"").trim();return dr.test(t)?t.slice(3,11):null}function Wp(e){let t=(e??"").trim(),n=t.startsWith("sa_"),r=n?t.slice(3):t,o=r.length>0&&/^[0-9a-f]+$/.test(r),i=/^sa_x+$/i.test(t)||/^sa_[x0.…_-]+$/i.test(t)||/^<.*>$/.test(t)||/^\$\{?[A-Z_]+\}?$/.test(t)||/(your|paste|code|setup)/i.test(t);return{len:t.length,prefix_ok:n,hex_ok:o,well_formed:dr.test(t),placeholder:i}}function No(e){let t=(e??"").trim();return t?pr(t)?{with_code:!0}:{with_code:!0,code_shape:Wp(t)}:{with_code:!1}}function zs(e){switch(e){case"code_not_found":return"That setup code was not recognized: it was never issued. Codes cannot be guessed or retyped from memory; copy one exactly from seoagent.com/grok. A scheduled run on a fresh machine needs the SEOAGENT_AUTH secret (`seoagent whoami --env` on the bound machine), not a setup code.";case"code_already_used":return"That setup code has already been used \u2014 each code works exactly once. Generate a new one at seoagent.com/grok.";case"code_revoked":return"That setup code was replaced by a newer one. Use the latest code, or generate a new one at seoagent.com/grok.";case"code_expired":return"That setup code has expired. Generate a new one at seoagent.com/grok.";case"setup_codes_unavailable":return"Setup codes are not enabled on this server yet. Run `seoagent login` (browser flow) instead.";case"domain_required":return"This code has no site attached and the project has no domain. Your code has NOT been used \u2014 run `seoagent init` in the project root (or pass `--domain example.com`) and retry with the same code.";default:return typeof e=="string"&&e?`Login failed: ${e}`:"Login failed."}}function Js(e,t){let n=t&&typeof t=="object"?t:null;if(e>=500)return e===503&&n?.error==="setup_codes_unavailable"?{status:"error",terminal:!0,message:zs(n.error)}:{status:"error",terminal:!1,message:`HTTP ${e} (server error)`};if(e>=400){let r=typeof n?.error=="string"&&n.error?n.error:void 0;return{status:"error",terminal:!0,message:zs(n?.error),...r?{code:r}:{}}}if(!n)return{status:"error",terminal:!0,message:`Malformed response: expected an object, got ${typeof t}`};if(n.status==="ready"){if(typeof n.user_token=="string"&&typeof n.website_token=="string"){let r=typeof n.email=="string"&&n.email?n.email:void 0,o=typeof n.domain=="string"&&n.domain?n.domain:void 0;return{status:"ready",user_token:n.user_token,website_token:n.website_token,...r?{email:r}:{},...o?{domain:o}:{}}}return{status:"error",terminal:!0,message:"Malformed response: status=ready without user_token+website_token"}}return{status:"error",terminal:!0,message:`Malformed response: unknown status=${JSON.stringify(n.status)}`}}import{existsSync as Jp,mkdirSync as Zs,readFileSync as Xp,writeFileSync as Qp,chmodSync as Zp,unlinkSync as ea}from"fs";import{dirname as ef}from"path";var Kp=new Set([502,503,504]),qp=1e4;async function j(e,t,n={}){let r=Math.max(1,n.attempts??3),o=Math.max(0,n.baseDelayMs??500),i=n.timeoutMs??qp,s=n.fetchImpl??fetch,a=n.sleep??Vp,l=n.transientStatuses??Kp,c=null;for(let d=0;d<r;d++){let{init:p,cleanup:g}=Yp(t,i);try{let f=await s(e,p);if(l.has(f.status)&&d<r-1){await a(Xs(o,d));continue}return f}catch(f){if(c=f,d<r-1){await a(Xs(o,d));continue}}finally{g()}}throw c instanceof Error?c:new Error(String(c))}function Xs(e,t){return e*Math.pow(3,t)}function Vp(e){return new Promise(t=>setTimeout(t,e))}function Yp(e,t){if(t<=0)return{init:e,cleanup:()=>{}};let n=new AbortController,r=setTimeout(()=>n.abort(new Error(`Request timed out after ${t}ms`)),t),o=e?.signal??void 0,i;o&&(o.aborted?n.abort(o.reason):(i=()=>n.abort(o.reason),o.addEventListener("abort",i,{once:!0})));let s=()=>{clearTimeout(r),o&&i&&o.removeEventListener("abort",i)};return{init:{...e,signal:n.signal},cleanup:s}}var Ze="unknown";function Te(e){return e?e.trim().toLowerCase().replace(/^sc-domain:/,"").replace(/^https?:\/\//,"").replace(/^www\./,"").replace(/\/.*$/,""):""}function zp(e,t){let n=Te(e),r=Te(t);return!n||!r||n===Ze||r===Ze?!1:n===r}async function et(e){let t;try{t=await j(`${e.apiBase}/api/cli/whoami`,{method:"GET",headers:{Authorization:e.authHeader,Accept:"application/json"}},e.fetchImpl?{fetchImpl:e.fetchImpl}:{})}catch(o){return{verdict:"unverified",detail:o.message||"network error"}}if(t.status===404)return{verdict:"unverified",detail:"server does not support /api/cli/whoami"};if(!t.ok)return{verdict:"unverified",detail:`whoami returned HTTP ${t.status}`};let n=null;try{n=await t.json()}catch{n=null}if(!n||n.status!=="ok")return{verdict:"unverified",detail:"malformed whoami response"};let r=typeof n.domain=="string"?n.domain.trim():"";return r?zp(r,e.localDomain)?{verdict:"match",cloudDomain:r}:{verdict:"mismatch",cloudDomain:r}:{verdict:"unverified",detail:"whoami did not return a bound domain"}}function Qs(e,t){return`Cloud project is ${Te(e)||e} but this workspace is ${Te(t)||t} \u2014 not syncing. Run \`seoagent login\` from this directory to connect the right site \u2014 the CLI can stay logged into both sites at once, no need to log out first.`}var jo="SEOAGENT_AUTH";function ta(e,t=qe){let n=(e??"").trim();if(!n)return null;let r=n.indexOf(":");if(r<=0||r===n.length-1)return null;let o=n.slice(0,r).trim(),i=n.slice(r+1).trim();return!o||!i?null:{user_token:o,website_token:i,api_base:t}}function na(e){return`${jo}=${e.user_token}:${e.website_token}`}function fr(e=process.env){return Do(e)?"env":"file"}function gr(e=process.env){let t=(e[jo]??"").trim();return t?ta(t)?"ok":"malformed":"absent"}function Do(e=process.env){let t=(e.SEOAGENT_API_BASE??"").trim()||qe;return ta(e[jo],t)}function mr(e){if(!e)return"";let t=Te(e);return t&&t!==Ze?t:""}function tn(){if(!Jp(Xe))return null;try{return JSON.parse(Xp(Xe,"utf-8"))}catch{return null}}function
|
|
17
|
-
`,"utf-8");try{Zp(Xe,384)}catch{}}function B(e){let t=Do();if(t)return t;let n=tn();if(!n)return null;if(!e)return
|
|
15
|
+
`)}import{existsSync as ar,readdirSync as Pp,statSync as Tp}from"fs";import{join as Ve}from"path";var Us=["svelte.config.js","svelte.config.ts","svelte.config.mjs","svelte.config.cjs","gatsby-config.js","gatsby-config.ts","gatsby-config.mjs","docusaurus.config.js","docusaurus.config.ts","docusaurus.config.mjs","hugo.toml","hugo.yaml","hugo.json"],Fs=["next.config.js","next.config.ts","next.config.mjs","next.config.cjs","astro.config.js","astro.config.ts","astro.config.mjs","nuxt.config.js","nuxt.config.ts","nuxt.config.mjs","remix.config.js","remix.config.ts","vite.config.js","vite.config.ts","vite.config.mjs","vite.config.cjs"],Op=["apps","packages"];function Po(e){for(let t of Us)if(ar(Ve(e,t)))return"static";for(let t of Fs)if(ar(Ve(e,t)))return"public";return null}function Ms(e){for(let t of[...Us,...Fs])if(ar(Ve(e,t)))return t;return null}function To(e){let t=[];for(let n of Op){let r;try{r=Pp(Ve(e,n),{withFileTypes:!0}).filter(o=>o.isDirectory()).map(o=>o.name).sort()}catch{continue}for(let o of r){let i=`${n}/${o}`;Po(Ve(e,i))&&t.push(i)}}return t.length===1?t[0]:null}function lr(e){if(typeof e!="string")return null;let t=e.trim().replace(/^\.\//,"").replace(/\/+$/,"");return!t||t.startsWith("/")||/^[A-Za-z]:[\\/]/.test(t)||t.split(/[\\/]/).some(n=>n==="..")?null:t.replace(/\\/g,"/")}function sr(e,t){try{return Tp(Ve(e,t)).isDirectory()}catch{return!1}}function cr(e,t){let n=lr(t);if(n)return{dir:n,source:"configured",evidence:"public_dir in .seoagent/project.md",exists:sr(e,n)};let r=Po(e);if(r)return{dir:r,source:"framework",evidence:Ms(e),exists:sr(e,r)};let o=To(e);if(o){let i=Po(Ve(e,o));if(i){let s=`${o}/${i}`;return{dir:s,source:"framework",evidence:`${o}/${Ms(Ve(e,o))}`,exists:sr(e,s)}}}for(let i of["public","static"])if(sr(e,i))return{dir:i,source:"existing",evidence:`${i}/ exists`,exists:!0};return{dir:null,source:null,evidence:null,exists:!1}}var Lp=["app/llms.txt/route.ts","app/llms.txt/route.tsx","app/llms.txt/route.js","src/app/llms.txt/route.ts","src/app/llms.txt/route.tsx","src/app/llms.txt/route.js","pages/llms.txt.ts","pages/llms.txt.js","src/pages/llms.txt.ts","src/pages/llms.txt.js","src/routes/llms.txt/+server.ts","src/routes/llms.txt/+server.js","routes/llms.txt/+server.ts","routes/llms.txt/+server.js"];function Bs(e,t){let n=new Set([""]),r=lr(t);if(r){let o=r.split("/").slice(0,-1).join("/");o&&n.add(o)}for(let o of n)for(let i of Lp){let s=o?`${o}/${i}`:i;if(ar(Ve(e,s)))return s}return null}var Dp=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;function dt(){return Np()}var Mp=new Set(["strapi","wordpress","sanity","contentful","ghost","webflow","shopify","payload","directus","wix","squarespace","blogger","mdx-local","none"]),Up="project.md";function Ye(e){return Oo(e,x)}function ur(e){return Oo(Ye(e),Up)}function Ks(e){return Gs(ur(e))}function C(e){let t=Qe(ur(e));if(!t)return null;let n=t.data;if(typeof n.domain!="string"||!n.domain)return null;let r=typeof n.image_provider=="string"?n.image_provider:void 0,o=typeof n.cms=="string"?n.cms:void 0,i=o&&Mp.has(o)?o:void 0,s=typeof n.install_id=="string"?n.install_id:void 0,a=typeof n.publishing=="object"&&n.publishing!==null?n.publishing:void 0,l=typeof n.content_dir=="string"&&n.content_dir?n.content_dir:a&&typeof a.content_dir=="string"&&a.content_dir?a.content_dir:void 0,c=typeof n.public_dir=="string"&&n.public_dir?n.public_dir:a&&typeof a.public_dir=="string"&&a.public_dir?a.public_dir:void 0;return{domain:n.domain,site_type:typeof n.site_type=="string"?n.site_type:"unknown",language:typeof n.language=="string"?n.language:"en",initialized_at:typeof n.initialized_at=="string"?n.initialized_at:"",seoagent_version:typeof n.seoagent_version=="string"?n.seoagent_version:"",skill_version:typeof n.skill_version=="string"&&n.skill_version?n.skill_version:void 0,skill_hash:typeof n.skill_hash=="string"&&/^[0-9a-f]{64}$/.test(n.skill_hash)?n.skill_hash:void 0,install_id:s&&Dp.test(s)?s:void 0,schedule:typeof n.schedule=="string"&&n.schedule.trim()?n.schedule.trim():void 0,image_provider:r==="openai"||r==="fal"||r==="replicate"||r==="none"?r:void 0,cms:i,blog_path:typeof n.blog_path=="string"&&n.blog_path?n.blog_path:void 0,content_dir:l?l.replace(/\/+$/,""):void 0,public_dir:lr(c)??void 0,llms_owner:n.llms_owner==="route"?"route":void 0,live_url:typeof n.live_url=="string"&&n.live_url?n.live_url:void 0}}function $e(e){let t=C(e);if(!t)return null;if(t.install_id)return t.install_id;let n=dt();return X(e,{install_id:n}),n}function Fp(e){return["",`# SEOAgent Project \u2014 ${e.domain}`,"","This file holds project-level configuration for the SEOAgent skill.","The skill reads it on every session to know which domain it is working on.","",`Initialized ${e.initialized_at} with seoagent ${e.seoagent_version}.`,""].join(`
|
|
16
|
+
`)}function Bp(e){let t={domain:e.domain,site_type:e.site_type,language:e.language,initialized_at:e.initialized_at,seoagent_version:e.seoagent_version};return e.skill_version&&(t.skill_version=e.skill_version),e.skill_hash&&(t.skill_hash=e.skill_hash),e.install_id&&(t.install_id=e.install_id),e.image_provider&&(t.image_provider=e.image_provider),e.cms&&(t.cms=e.cms),e.blog_path&&(t.blog_path=e.blog_path),e.content_dir&&(t.content_dir=e.content_dir),e.public_dir&&(t.public_dir=e.public_dir),e.llms_owner&&(t.llms_owner=e.llms_owner),e.live_url&&(t.live_url=e.live_url),t}function qs(e,t){let n=Ye(e);Hs(n,{recursive:!0});let r=Io(Bp(t),Fp(t));Ws(ur(e),r,"utf-8")}function X(e,t){let n=ur(e);if(!Gs(n))return!1;let r=me(jp(n,"utf-8")),o={...r.data,...t};return Ws(n,Io(o,r.body),"utf-8"),!0}function Vs(e){let t=Ye(e);for(let n of Ps)Hs(Oo(t,n),{recursive:!0})}var Lo="Sent an anonymous install ping (random id + CLI version, plus a partial setup-code prefix when --code is used \u2014 no domain, no paths). Opt out: SEOAGENT_TELEMETRY=0",Ys="Sent an anonymous ping naming this project's site so its owner can be told the run had no credential (random id + CLI version + domain \u2014 no paths, no secrets). Opt out: SEOAGENT_TELEMETRY=0",Gp=2500;function he(e=process.env){let t=(e.SEOAGENT_TELEMETRY??"").trim().toLowerCase();if(t==="0"||t==="false"||t==="off")return!0;let n=(e.DO_NOT_TRACK??"").trim().toLowerCase();return n==="1"||n==="true"}function Hp(e){let t=e.meta&&Object.values(e.meta).some(n=>n!==void 0)?e.meta:null;return{install_id:e.installId,event:e.event,cli_version:e.cliVersion,platform:process.platform,...e.codeHint?{code_hint:e.codeHint}:{},...t?{meta:t}:{}}}async function ze(e){let t=(e.apiBase||U.BASE).replace(/\/$/,""),n=e.fetchImpl??fetch;try{return(await n(`${t}/api/cli/telemetry`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(Hp(e)),signal:AbortSignal.timeout(e.timeoutMs??Gp)})).ok}catch{return!1}}var dr=/^sa_[0-9a-f]{24}$/;function pr(e){let t=(e??"").trim();return dr.test(t)?t.slice(3,11):null}function Wp(e){let t=(e??"").trim(),n=t.startsWith("sa_"),r=n?t.slice(3):t,o=r.length>0&&/^[0-9a-f]+$/.test(r),i=/^sa_x+$/i.test(t)||/^sa_[x0.…_-]+$/i.test(t)||/^<.*>$/.test(t)||/^\$\{?[A-Z_]+\}?$/.test(t)||/(your|paste|code|setup)/i.test(t);return{len:t.length,prefix_ok:n,hex_ok:o,well_formed:dr.test(t),placeholder:i}}function No(e){let t=(e??"").trim();return t?pr(t)?{with_code:!0}:{with_code:!0,code_shape:Wp(t)}:{with_code:!1}}function zs(e){switch(e){case"code_not_found":return"That setup code was not recognized: it was never issued. Codes cannot be guessed or retyped from memory; copy one exactly from seoagent.com/grok. A scheduled run on a fresh machine needs the SEOAGENT_AUTH secret (`seoagent whoami --env` on the bound machine), not a setup code.";case"code_already_used":return"That setup code has already been used \u2014 each code works exactly once. Generate a new one at seoagent.com/grok.";case"code_revoked":return"That setup code was replaced by a newer one. Use the latest code, or generate a new one at seoagent.com/grok.";case"code_expired":return"That setup code has expired. Generate a new one at seoagent.com/grok.";case"setup_codes_unavailable":return"Setup codes are not enabled on this server yet. Run `seoagent login` (browser flow) instead.";case"domain_required":return"This code has no site attached and the project has no domain. Your code has NOT been used \u2014 run `seoagent init` in the project root (or pass `--domain example.com`) and retry with the same code.";default:return typeof e=="string"&&e?`Login failed: ${e}`:"Login failed."}}function Js(e,t){let n=t&&typeof t=="object"?t:null;if(e>=500)return e===503&&n?.error==="setup_codes_unavailable"?{status:"error",terminal:!0,message:zs(n.error)}:{status:"error",terminal:!1,message:`HTTP ${e} (server error)`};if(e>=400){let r=typeof n?.error=="string"&&n.error?n.error:void 0;return{status:"error",terminal:!0,message:zs(n?.error),...r?{code:r}:{}}}if(!n)return{status:"error",terminal:!0,message:`Malformed response: expected an object, got ${typeof t}`};if(n.status==="ready"){if(typeof n.user_token=="string"&&typeof n.website_token=="string"){let r=typeof n.email=="string"&&n.email?n.email:void 0,o=typeof n.domain=="string"&&n.domain?n.domain:void 0;return{status:"ready",user_token:n.user_token,website_token:n.website_token,...r?{email:r}:{},...o?{domain:o}:{}}}return{status:"error",terminal:!0,message:"Malformed response: status=ready without user_token+website_token"}}return{status:"error",terminal:!0,message:`Malformed response: unknown status=${JSON.stringify(n.status)}`}}import{existsSync as Jp,mkdirSync as Zs,readFileSync as Xp,writeFileSync as Qp,chmodSync as Zp,unlinkSync as ea}from"fs";import{dirname as ef}from"path";var Kp=new Set([502,503,504]),qp=1e4;async function j(e,t,n={}){let r=Math.max(1,n.attempts??3),o=Math.max(0,n.baseDelayMs??500),i=n.timeoutMs??qp,s=n.fetchImpl??fetch,a=n.sleep??Vp,l=n.transientStatuses??Kp,c=null;for(let d=0;d<r;d++){let{init:p,cleanup:g}=Yp(t,i);try{let f=await s(e,p);if(l.has(f.status)&&d<r-1){await a(Xs(o,d));continue}return f}catch(f){if(c=f,d<r-1){await a(Xs(o,d));continue}}finally{g()}}throw c instanceof Error?c:new Error(String(c))}function Xs(e,t){return e*Math.pow(3,t)}function Vp(e){return new Promise(t=>setTimeout(t,e))}function Yp(e,t){if(t<=0)return{init:e,cleanup:()=>{}};let n=new AbortController,r=setTimeout(()=>n.abort(new Error(`Request timed out after ${t}ms`)),t),o=e?.signal??void 0,i;o&&(o.aborted?n.abort(o.reason):(i=()=>n.abort(o.reason),o.addEventListener("abort",i,{once:!0})));let s=()=>{clearTimeout(r),o&&i&&o.removeEventListener("abort",i)};return{init:{...e,signal:n.signal},cleanup:s}}var Ze="unknown";function Te(e){return e?e.trim().toLowerCase().replace(/^sc-domain:/,"").replace(/^https?:\/\//,"").replace(/^www\./,"").replace(/\/.*$/,""):""}function zp(e,t){let n=Te(e),r=Te(t);return!n||!r||n===Ze||r===Ze?!1:n===r}async function et(e){let t;try{t=await j(`${e.apiBase}/api/cli/whoami`,{method:"GET",headers:{Authorization:e.authHeader,Accept:"application/json"}},e.fetchImpl?{fetchImpl:e.fetchImpl}:{})}catch(o){return{verdict:"unverified",detail:o.message||"network error"}}if(t.status===404)return{verdict:"unverified",detail:"server does not support /api/cli/whoami"};if(!t.ok)return{verdict:"unverified",detail:`whoami returned HTTP ${t.status}`};let n=null;try{n=await t.json()}catch{n=null}if(!n||n.status!=="ok")return{verdict:"unverified",detail:"malformed whoami response"};let r=typeof n.domain=="string"?n.domain.trim():"";return r?zp(r,e.localDomain)?{verdict:"match",cloudDomain:r}:{verdict:"mismatch",cloudDomain:r}:{verdict:"unverified",detail:"whoami did not return a bound domain"}}function Qs(e,t){return`Cloud project is ${Te(e)||e} but this workspace is ${Te(t)||t} \u2014 not syncing. Run \`seoagent login\` from this directory to connect the right site \u2014 the CLI can stay logged into both sites at once, no need to log out first.`}var jo="SEOAGENT_AUTH";function ta(e,t=qe){let n=(e??"").trim();if(!n)return null;let r=n.indexOf(":");if(r<=0||r===n.length-1)return null;let o=n.slice(0,r).trim(),i=n.slice(r+1).trim();return!o||!i?null:{user_token:o,website_token:i,api_base:t}}function na(e){return`${jo}=${e.user_token}:${e.website_token}`}function fr(e=process.env){return Do(e)?"env":"file"}function gr(e=process.env){let t=(e[jo]??"").trim();return t?ta(t)?"ok":"malformed":"absent"}function Do(e=process.env){let t=(e.SEOAGENT_API_BASE??"").trim()||qe;return ta(e[jo],t)}function mr(e){if(!e)return"";let t=Te(e);return t&&t!==Ze?t:""}function tn(){if(!Jp(Xe))return null;try{return JSON.parse(Xp(Xe,"utf-8"))}catch{return null}}function pt(e){return!e||!e.user_token||!e.website_token?null:{user_token:e.user_token,website_token:e.website_token,api_base:e.api_base||qe}}function ra(e){Zs(z,{recursive:!0}),Zs(ef(Xe),{recursive:!0}),Qp(Xe,JSON.stringify(e,null,2)+`
|
|
17
|
+
`,"utf-8");try{Zp(Xe,384)}catch{}}function B(e){let t=Do();if(t)return t;let n=tn();if(!n)return null;if(!e)return pt(n);let r=mr(e);return r&&Object.keys(n.sites??{}).length>0?pt(n.sites?.[r]):pt(n)}function hr(e){if(Do())return!0;let t=tn();if(!t)return!1;let n=mr(e);return n?pt(t.sites?.[n])!==null:!1}function Mo(e,t){let n=tn()??{},r=mr(t),o={...n.sites??{}};r&&(o[r]=e),ra({...e,sites:Object.keys(o).length>0?o:void 0})}function oa(e){let t=tn();if(!t)return!1;let n=mr(e),r=Object.keys(t.sites??{}).length>0;if(!n||!r)try{return ea(Xe),!0}catch{return!1}let o=t.sites?.[n];if(!o)return!1;let i={...t.sites};delete i[n];let s=Object.keys(i),a=t.website_token===o.website_token,l=pt(a?i[s[0]]:t);if(!l&&s.length===0)try{return ea(Xe),!0}catch{return!1}return ra({...l??{},sites:s.length>0?i:void 0}),!0}function tt(){let e=tn();return e?.sites?Object.keys(e.sites).sort():[]}var tf=new Set(["co","com","net","org","ac","gov","edu"]);function nf(e){let t=e.trim().toLowerCase().replace(/^[a-z][a-z0-9+.-]*:\/\//,"").replace(/[/?#].*$/,"").replace(/:\d+$/,"").replace(/^www\./,"");if(!t)return null;let n=t.split(".").filter(Boolean);if(n.length===0)return null;if(n.length===1)return n[0];let r=n.length>=3&&tf.has(n[n.length-2])?n.length-3:n.length-2;return n[Math.max(0,r)]||null}async function ia(e){let t=nf(e.domain);if(!t)return{status:"unavailable"};let n=(e.apiBase||U.BASE).replace(/\/$/,"");try{let r=await j(`${n}/api/cli/keywords/peek`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({install_id:e.installId,keyword:t,domain:e.domain})},{attempts:2,timeoutMs:6e3,fetchImpl:e.fetchImpl});if(!r.ok)return{status:"unavailable"};let o=await r.json();return!o?.ok||!o.opportunity?{status:"unavailable"}:o.opportunity.volume==null?{status:"no-data",keyword:t}:{status:"data",keyword:t,volume:o.opportunity.volume,difficulty:o.opportunity.difficulty,intent:o.opportunity.intent}}catch{return{status:"unavailable"}}}import{existsSync as nt,lstatSync as rf,mkdirSync as ft,readFileSync as Uo,readdirSync as of,rmSync as sf,statSync as Fo,symlinkSync as af,writeFileSync as nn}from"fs";import{dirname as At,join as ne,relative as Bo,sep as lf}from"path";import{fileURLToPath as cf}from"url";var uf=At(cf(import.meta.url));function sa(){return ne(uf,"skills")}function df(){let e=ne(sa(),"seoagent.md");if(nt(e))return Uo(e,"utf-8");throw new Error(`Could not find skill file at: ${e}`)}function aa(e,t){let n=[];if(!nt(e))return n;for(let r of of(e,{withFileTypes:!0})){let o=ne(e,r.name),i=ne(t,r.name);r.isDirectory()?(ft(i,{recursive:!0}),n.push(...aa(o,i))):r.isFile()&&(ft(At(i),{recursive:!0}),nn(i,Uo(o)),n.push(i))}return n}function gt(e){return nt(ne(e,"apps/skills-cli/skills/seoagent.md"))}function pf(e){let t=nt(ne(e,".agents"));return ne(e,t?Ke.AGENTS:Ke.CLAUDE)}function la(e,t){if(gt(e))return[];let n=pf(e),r=[];for(let[o,i]of Object.entries(t)){let s=ne(n,o);ft(At(s),{recursive:!0}),nn(s,i,"utf-8"),r.push(s)}return r}function It(e,t){let n=nt(ne(e,".agents")),r=ne(e,n?Ke.AGENTS:Ke.CLAUDE),o=ne(r,"SKILL.md");if(gt(e))return{skillFile:o,referenceFiles:[]};ft(r,{recursive:!0}),nn(o,t??df(),"utf-8");let i=ne(sa(),"references"),s=ne(r,"references"),a=[];try{nt(i)&&Fo(i).isDirectory()&&(ft(s,{recursive:!0}),a=aa(i,s).map(p=>Bo(r,p)))}catch{}let l=n?ff(e,r):void 0,c=mf(e,o),d=hf(e,o);return{skillFile:o,referenceFiles:a,claudeSymlink:l,agentsMdPath:c,cursorRulePath:d}}function ff(e,t){let n=ne(e,Ke.CLAUDE),r=null;try{r=rf(n)}catch{}if(r){if(!r.isSymbolicLink())return;try{sf(n,{force:!0})}catch{return}}try{ft(At(n),{recursive:!0});let o=Bo(At(n),t);return af(o,n,"dir"),n}catch{return}}var gf="<!-- seoagent:skill -->";function ca(e,t){return Bo(e,t).split(lf).join("/")}function mf(e,t){let n=ne(e,"AGENTS.md"),r=ca(e,t);try{let o="";if(nt(n)&&(!Fo(n).isFile()||(o=Uo(n,"utf-8"),/seoagent/i.test(o))))return;let i=[gf,"","## SEOAgent","","This repo uses SEOAgent \u2014 a persistent AI SEO agent. Its state lives in `.seoagent/`.","",`- At session start, read \`${r}\` \u2014 the working contract for all SEO tasks.`,"- At the start of each session, run `seoagent sync`. If it lists pending inbox actions, work them unless the user asked for something else first: read `.seoagent/inbox/README.md`, do each one, then `seoagent ack <id>`.",""].join(`
|
|
18
18
|
`),s=o.length===0?i:o.endsWith(`
|
|
19
19
|
`)?`${o}
|
|
20
20
|
${i}`:`${o}
|
|
21
21
|
|
|
22
22
|
${i}`;return nn(n,s,"utf-8"),n}catch{return}}function hf(e,t){let n=ca(e,t);try{let r=ne(e,".cursor");if(!nt(r)||!Fo(r).isDirectory())return;let o=ne(r,"rules","seoagent.mdc"),i=["---","description: SEOAgent \u2014 persistent AI SEO agent for audits, keywords, content, and technical SEO; state in .seoagent/","alwaysApply: true","---","","# SEOAgent","","This repo uses SEOAgent. Its state lives in `.seoagent/`.","","- At the start of each session, run `seoagent sync`. If it lists pending inbox actions, work them unless the user asked for something else first: read `.seoagent/inbox/README.md`, do each one, then `seoagent ack <id>`.",`- For that work and any SEO, content, or search-ranking task, first read \`${n}\` \u2014 it is the working contract.`,""].join(`
|
|
23
|
-
`);return
|
|
24
|
-
`,"utf-8"),{file:t,added:i||a,migrated:r}}function ga(e){let t=
|
|
23
|
+
`);return ft(At(o),{recursive:!0}),nn(o,i,"utf-8"),o}catch{return}}import{existsSync as rn,mkdirSync as Go,readFileSync as yf,writeFileSync as yr}from"fs";import{dirname as da,join as mt}from"path";var Ho=`command -v seoagent >/dev/null 2>&1 && seoagent sync --silent || npx -y ${nr} sync --silent`,pa=60;function fa(e){let t=mt(e,x);Go(t,{recursive:!0});let n=mt(t,".gitignore");yr(n,Ls,"utf-8");let r=mt(t,"content","images",".gitkeep");return Go(da(r),{recursive:!0}),rn(r)||yr(r,"","utf-8"),n}var wr=["PostToolUse","SessionStart"];function br(e){if(!rn(e))return{};try{let t=yf(e,"utf-8");return JSON.parse(t)}catch{return{}}}function on(e){return e.command.includes("@seoagent-official/seoagent")&&e.command.includes("sync")}function ua(e,t,n){let r=e.find(t);return r||(r=n(),e.push(r)),r.hooks.some(on)?!1:(r.hooks.push({type:"command",command:Ho,timeout:pa}),!0)}function wf(e){let t=!1;for(let n of wr){let r=e[n];if(Array.isArray(r))for(let o of r)for(let i of o.hooks??[])on(i)&&(i.command!==Ho&&(i.command=Ho,t=!0),typeof i.timeout!="number"&&(i.timeout=pa,t=!0))}return t}function ht(e){let t=mt(e,Zt);Go(da(t),{recursive:!0});let n=br(t);n.hooks=n.hooks??{};let r=wf(n.hooks),o=n.hooks.PostToolUse=n.hooks.PostToolUse??[],i=ua(o,l=>(l.matcher??"").includes("Write"),()=>({matcher:"Write|Edit",hooks:[]})),s=n.hooks.SessionStart=n.hooks.SessionStart??[],a=ua(s,l=>(l.matcher??"")==="",()=>({hooks:[]}));return yr(t,JSON.stringify(n,null,2)+`
|
|
24
|
+
`,"utf-8"),{file:t,added:i||a,migrated:r}}function ga(e){let t=mt(e,Zt);if(!rn(t))return 0;let n=br(t).hooks;if(!n)return 0;let r=0;for(let o of wr){let i=n[o];if(Array.isArray(i))for(let s of i)for(let a of s.hooks??[])on(a)&&typeof a.timeout!="number"&&r++}return r}function ma(e){let t=mt(e,Zt);if(!rn(t))return!1;let n=br(t).hooks;return n?wr.some(r=>{let o=n[r];return Array.isArray(o)&&o.some(i=>i.hooks?.some(on))}):!1}function ha(e){let t=mt(e,Zt);if(!rn(t))return{file:t,removed:!1};let n=br(t),r=n.hooks;if(!r)return{file:t,removed:!1};let o=!1;for(let i of wr){let s=r[i];if(!Array.isArray(s))continue;for(let l of s){let c=l.hooks.length;l.hooks=l.hooks.filter(d=>!on(d)),l.hooks.length!==c&&(o=!0)}let a=s.filter(l=>l.hooks.length>0);a.length===0?delete r[i]:r[i]=a}return o?(Object.keys(r).length===0&&delete n.hooks,yr(t,JSON.stringify(n,null,2)+`
|
|
25
25
|
`,"utf-8"),{file:t,removed:!0}):{file:t,removed:!1}}import{existsSync as ya,mkdirSync as bf,readFileSync as kf,writeFileSync as xf}from"fs";import{join as vf}from"path";function wa(e){return ya(vf(e,x,"project.md"))}function ba(){try{if(!ya(or))return[];let e=JSON.parse(kf(or,"utf-8"));return Array.isArray(e.projects)?e.projects.filter(t=>typeof t=="string"&&t.length>0):[]}catch{return[]}}function ka(e){try{bf(z,{recursive:!0}),xf(or,JSON.stringify({projects:e},null,2)+`
|
|
26
|
-
`,"utf-8")}catch{}}function
|
|
26
|
+
`,"utf-8")}catch{}}function yt(e){try{if(!wa(e))return;let t=ba();if(t.includes(e))return;t.push(e),ka(t)}catch{}}function xa(){let e=ba(),t=e.filter(wa);return t.length!==e.length&&ka(t),t}import{readFileSync as Sa,writeFileSync as Ef,existsSync as Ko}from"fs";import{join as qo}from"path";function Wo(e){let t={business:{},writingInstructions:[],referenceUrls:[],topicsToAvoid:[],contentTone:null,additionalNotes:null},n=$f(e);for(let[r,o]of n){let i=r.toLowerCase().trim();i==="business context"?t.business=Sf(o):i==="writing instructions"?t.writingInstructions=va(o):i==="reference urls"?t.referenceUrls=_f(o):i==="topics to avoid"?t.topicsToAvoid=va(o):i==="content tone"?t.contentTone=o.trim()||null:i==="additional notes"&&(t.additionalNotes=o.trim()||null)}return t}function $a(e){let t=[];t.push("# Business Context"),t.push("");let n=[["Name","name"],["Type","type"],["Audience","audience"],["Industry","industry"],["Location","location"],["Description","description"]];for(let[o,i]of n){let s=e.business[i];t.push(`- **${o}:** ${s||""}`)}let r=new Set(n.map(([,o])=>o));for(let[o,i]of Object.entries(e.business))if(!r.has(o)&&i){let s=o.charAt(0).toUpperCase()+o.slice(1);t.push(`- **${s}:** ${i}`)}if(t.push(""),t.push("# Writing Instructions"),t.push(""),e.writingInstructions.length>0)for(let o of e.writingInstructions)t.push(`- ${o}`);else t.push("- (Add your content writing guidelines here)");if(t.push(""),t.push("# Reference URLs"),t.push(""),e.referenceUrls.length>0)for(let o of e.referenceUrls)o.description?t.push(`- ${o.url} \u2014 ${o.description}`):t.push(`- ${o.url}`);else t.push("- (Add URLs the agent should reference for tone/style)");if(t.push(""),t.push("# Topics to Avoid"),t.push(""),e.topicsToAvoid.length>0)for(let o of e.topicsToAvoid)t.push(`- ${o}`);else t.push("- (Add topics the agent should never write about)");return t.push(""),t.push("# Content Tone"),t.push(""),t.push(e.contentTone||"professional"),t.push(""),t.push("# Additional Notes"),t.push(""),t.push(e.additionalNotes||"(Any other context you want the agent to know about your business, products, or audience.)"),t.push(""),t.join(`
|
|
27
27
|
`)}function $f(e){let t=e.split(`
|
|
28
28
|
`),n=[],r="",o=[];for(let i of t){let s=i.match(/^#\s+(.+)$/);s?(r&&n.push([r,o.join(`
|
|
29
29
|
`)]),r=s[1],o=[]):o.push(i)}return r&&n.push([r,o.join(`
|
|
@@ -44,7 +44,7 @@ page), fill in:
|
|
|
44
44
|
|
|
45
45
|
Then show the owner the draft for review, and delete this comment.
|
|
46
46
|
-->
|
|
47
|
-
`;function rt(e){let t=qo(e,".seoagent",Vo);if(!Ko(t))return null;let n=Sa(t,"utf-8");return Wo(n)}function _a(e,t){let n=qo(e,".seoagent",Vo);return Ko(n)?!1:(Ef(n,Cf+$a({business:{type:t||""},writingInstructions:[],referenceUrls:[],topicsToAvoid:[],contentTone:null,additionalNotes:null}),"utf-8"),!0)}function sn(e){let t=qo(e,".seoagent",Vo);if(!Ko(t))return!0;try{return Oe(Sa(t,"utf-8"))}catch{return!1}}import{existsSync as an,readFileSync as Ra,readdirSync as Af,statSync as Ea}from"fs";import{join as yt}from"path";var If=[{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 Pf(e){let t=yt(e,"package.json");if(!an(t))return{deps:{},source:"package.json"};try{let n=JSON.parse(Ra(t,"utf-8"));return{deps:{...n.dependencies,...n.devDependencies,...n.peerDependencies},source:"package.json"}}catch{return{deps:{},source:"package.json"}}}function Tf(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 Of(e){let t={};for(let n of Rt){let r=yt(e,n);if(an(r))try{let o=Ra(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 Lf(e,t){let n=yt(e,t.replace(/\/$/,""));if(!an(n))return!1;if(t.endsWith("/"))try{return Ea(n).isDirectory()}catch{return!1}return!0}function Nf(e){let t=["content","_posts","posts",yt("src","content")];for(let n of t){let r=yt(e,n);if(an(r))try{if(!Ea(r).isDirectory())continue;let o=[r],i=0;for(;o.length>0&&i<50;){let s=o.pop();for(let a of Af(s,{withFileTypes:!0}))if(i++,!a.name.startsWith(".")){if(a.isDirectory()&&o.length<5){o.push(yt(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 jf(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=To(e);r&&n.push(r);for(let o of n)for(let i of t){let s=o?`${o}/${i.marker}`:i.marker;if(an(yt(e,s)))return i.path}return null}function Ca(e){let t=[],{deps:n}=Pf(e),r=Of(e),o="none";for(let s of If){let a=[];for(let l of Object.keys(n))if(Tf(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(Lf(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=Nf(e);s&&(o="mdx-local",t.push(s))}let i=jf(e);return{cms:o,blog_path:i,evidence:t}}import{existsSync as Df,readFileSync as Mf}from"fs";import{join as Uf}from"path";var Aa=["openai","fal","replicate"],Pt={openai:["OPENAI_API_KEY"],fal:["FAL_KEY","FAL_API_KEY"],replicate:["REPLICATE_API_TOKEN","REPLICATE_API_KEY"]};function Ff(e){let t={};if(!Df(e))return t;let n=Mf(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 Ia(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 Rt){let o=Uf(e,r),i=Ff(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 ot(e){let t=[].concat(...Aa.map(a=>Pt[a])),n=Ia(e,t),r=[];for(let a of Aa)Pt[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=Pt[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 Pa(e,t){let n=Ia(e,Pt[t]);for(let r of Pt[t])if(n.found[r])return{key:n.found[r].value,envName:r};return null}function Ta(){return Pt}import{existsSync as wt,readFileSync as Yo}from"fs";import{join as bt}from"path";function Oa(e){try{return new URL(e).hostname}catch{return e}}function La(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 Na(e){let t=e.trim().toLowerCase().replace(/^\[([^\]]+)\](?::\d{1,5})?$/,"$1").replace(/^((?:\d{1,3}\.){3}\d{1,3}|localhost|[a-z0-9.-]*\.localhost)(?::\d{1,5})?$/,"$1");return t==="localhost"||t.endsWith(".localhost")||t==="::1"||t==="0.0.0.0"?!0:/^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(t)}function Bf(e){for(let t of Rt){let n=bt(e,t);if(!wt(n))continue;let r=Yo(n,"utf-8");for(let o of Is){let i=r.match(new RegExp(`^${o}=(.+)$`,"m"));if(i){let s=i[1].trim().replace(/^["']|["']$/g,""),a=Oa(s);if(La(a)||Na(a))continue;return{value:a,source:`${t} (${o})`}}}}return null}function Gf(e){let t=bt(e,"package.json");if(!wt(t))return null;try{let n=JSON.parse(Yo(t,"utf-8"));if(!n.homepage)return null;let r=Oa(n.homepage);return La(r)||Na(r)?null:{value:r,source:"package.json (homepage)"}}catch{return null}}function ja(e,t){let n=[];t?.("Checking for monorepo / workspace layout\u2026"),wt(bt(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=Bf(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=Gf(e);a&&(r=a.value,n.push({field:"domain",detail:a.value,source:a.source}))}let i=bt(e,"package.json"),s=null;if(wt(i))try{t?.("Reading dependencies to infer site type\u2026");let a=JSON.parse(Yo(i,"utf-8")),l=Object.keys({...a.dependencies,...a.devDependencies}),c=(...d)=>d.some(p=>l.includes(p));if(c("@shopify/hydrogen","@shopify/polaris")||wt(bt(e,"shopify.config.js"))||wt(bt(e,"shopify.config.ts"))){s="product";let d=c("@shopify/hydrogen","@shopify/polaris")?"Shopify-related dependencies in package.json":wt(bt(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"),p=c("next-auth","@auth/core","lucia","clerk","@clerk/nextjs","@clerk/clerk-react"),g=c("next");d&&(g||p)?(s="saas",n.push({field:"site_type",detail:"saas",source:g?"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 zf,readFileSync as kr,readdirSync as Ga,statSync as Jf}from"fs";import{join as ue,sep as Xf}from"path";var Ma=[/^\/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],Hf=[/^\/[a-z0-9]+-(?:onboarding|login|logout|signin|sign-in|signup|sign-up|dashboard|checkout|account)$/i];function Le(e){let t=[],n=/<loc>\s*([^<\s][^<]*?)\s*<\/loc>/gi,r;for(;(r=n.exec(e))!==null;){let o=r[1].replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/'/g,"'").replace(/"/g,'"').trim();o&&t.push(o)}return t}function Wf(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 Kf=10,Da=.9;function qf(e,t){if(t<Kf||e.length<t*Da)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*Da)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 Ne(e){return Ma.some(t=>t.test(e))||Hf.some(t=>t.test(e))}function Ua(e){return Ma.some(t=>t.test(e))}function Vf(e){return e.filter(t=>Ne(F(t)))}function Yf(e,t){let n=new Set(e.map(F)),r=[],o=new Set;for(let i of t){let s=F(i);Ne(s)||n.has(s)||o.has(s)||(o.add(s),r.push(s))}return r}function ln(e){return`https://${e.replace(/^https?:\/\//,"").replace(/\/+$/,"")}/sitemap.xml`}async function Fa(e){let t=e.sitemapUrl||ln(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 j(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=Le(o),r.lastmods=Wf(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=Vf(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=qf(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=Yf(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 Ha=["tsx","jsx","js","mdx","md"],Qf=["astro","md","mdx"],Zf=5e3,eg=2e3;function je(e,t){let n=new Map,r=0,o=(l,c,d=!1)=>{let p=Va(l);if(p===null){r++;return}let g=n.get(p);if(g){g.in_sitemap=g.in_sitemap||d,g.source.includes(c)||(g.source+=`, ${c}`);return}n.set(p,{route:p,url:za(t,p),in_sitemap:d,source:c})};for(let l of qa(e))ig(l,o);let i=tg(e,t),s=l=>i.some(c=>c.test(l)),a=[...n.values()].filter(l=>!Ne(l.route));return{pages:a.filter(l=>!s(l.route)).sort((l,c)=>l.route.localeCompare(c.route)),dynamicCount:r,offOriginCount:a.filter(l=>s(l.route)).length}}function tg(e,t){let n=Ba(t);if(!n)return[];let r=[];for(let o of qa(e)){let i;try{i=JSON.parse(kr(ue(o,"vercel.json"),"utf-8"))}catch{continue}if(Array.isArray(i.redirects))for(let s of i.redirects){if(!s||typeof s.source!="string"||typeof s.destination!="string")continue;let a=Ba(s.destination);if(!a||Wa(a,n)||!ng(s,n))continue;let l=rg(s.source);l&&r.push(l)}}return r}function Ba(e){if(!e)return null;let t=e.trim();if(!t||t.startsWith("/"))return null;try{return new URL(/^https?:\/\//i.test(t)?t:`https://${t}`).hostname.toLowerCase()}catch{return null}}function Wa(e,t){let n=r=>r.replace(/^www\./,"");return n(e)===n(t)}function ng(e,t){if(Array.isArray(e.missing)&&e.missing.length>0)return!1;let n=e.has;return!Array.isArray(n)||n.length===0?!0:n.every(r=>r&&r.type==="host"&&typeof r.value=="string"&&Wa(r.value.toLowerCase(),t))}function rg(e){if(!e.startsWith("/")||/[(){}[\]?+*]/.test(e.replace(/:[A-Za-z0-9_]+(?:\([^)]*\))?[*+?]?/g,"")))return null;let t="",n=0;for(;n<e.length;){let r=e[n];if(r!==":"){t+=r.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),n++;continue}let o=/^:([A-Za-z0-9_]+)(\(([^)]*)\))?([*+?])?/.exec(e.slice(n));if(!o)return null;let[,,,i,s]=o;i!==void 0?t+=`(?:${i})`:s==="*"?t+=".*":s==="+"?t+=".+":s==="?"?t+="[^/]*":t+="[^/]+",n+=o[0].length}try{return new RegExp(`^${t}$`,"i")}catch{return null}}async function Ka(e,t,n){let r={checked:!1,liveCount:0,flipped:0,added:0},o=[];try{let c=await j(ln(t),{headers:{"user-agent":"SEOAgent-CLI/sitemap-check"}},{attempts:1,timeoutMs:5e3,fetchImpl:n});if(!c.ok)return r;o=Le(await c.text())}catch{return r}if(o.length===0)return r;let i=new Set(o.map(F)),s=0;for(let c of e)!c.in_sitemap&&i.has(F(c.url||c.route))&&(c.in_sitemap=!0,s++,c.source.includes("live-sitemap")||(c.source+=", live-sitemap"));let a=new Set(e.map(c=>F(c.url||c.route))),l=0;for(let c of o){if(l>=eg)break;let d=og(c,t);d===null||a.has(d)||(a.add(d),e.push({route:d,url:za(t,d),in_sitemap:!0,source:"live-sitemap"}),l++)}return l>0&&e.sort((c,d)=>c.route.localeCompare(d.route)),{checked:!0,liveCount:o.length,flipped:s,added:l}}function og(e,t){let n=t.replace(/^https?:\/\//,"").replace(/\/.*$/,"").toLowerCase().replace(/^www\./,""),r;try{r=new URL(e)}catch{return null}if(r.protocol!=="https:"&&r.protocol!=="http:"||r.hostname.toLowerCase().replace(/^www\./,"")!==n)return null;let o=F(e);return/\.xml$/i.test(o)||Ua(o)?null:Va(o)}function ig(e,t){for(let n of["app",ue("src","app")]){let r=ue(e,n);if(cn(r))for(let o of zo(r)){let i=Jo(r,o),s=i[i.length-1];pg(s)&&(i.some(a=>a.startsWith("@"))||t(lg(i.slice(0,-1)),"app router"))}}for(let n of["pages",ue("src","pages")]){let r=ue(e,n);if(cn(r))for(let o of zo(r)){let i=Jo(r,o);if(i[0]==="api")continue;let s=i[i.length-1],a=Xo(s),l=Ha.includes(a),c=Qf.includes(a);if(!l&&!c)continue;let d=xr(s);fg(d)||t(cg(i),c?"astro":"pages router")}}for(let n of[".","public"]){let r=ue(e,n);if(cn(r))for(let o of zo(r,n==="."?1:1/0)){let i=Jo(r,o),s=i[i.length-1];Xo(s)==="html"&&(i.some(a=>a==="node_modules"||a.startsWith("."))||t(ug(i),"static html"))}}for(let n of["public/sitemap.xml","sitemap.xml"]){let r=ue(e,...n.split("/"));if(zf(r)){for(let o of gg(r)){let i=dg(o);i!==null&&t(i,"sitemap.xml",!0)}break}}}function qa(e){let t=[e],n=new Set([e]);for(let r of sg(e))for(let o of ag(e,r))n.has(o)||(n.add(o),t.push(o));return t}function sg(e){let t=[];try{let n=kr(ue(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(kr(ue(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 ag(e,t){if(!t||t.startsWith("!"))return[];let n=t.replace(/\/\*\*?$/,"/*"),r=n.indexOf("*");if(r===-1){let a=ue(e,...n.split("/").filter(Boolean));return cn(a)?[a]:[]}let o=n.slice(0,r).replace(/\/$/,""),i=ue(e,...o.split("/").filter(Boolean));if(!cn(i))return[];let s=[];try{for(let a of Ga(i,{withFileTypes:!0}))if(a.isDirectory()&&!a.name.startsWith(".")&&!Ya.has(a.name)&&(s.push(ue(i,a.name)),s.length>=200))break}catch{}return s}function Va(e){let t=e.trim();return t===""||t==="/"||(t.startsWith("/")||(t=`/${t}`),t=t.replace(/\/+$/,""),t==="")?"/":/\[.*\]/.test(t)?null:t}function lg(e){return"/"+e.filter(n=>!(n.startsWith("(")&&n.endsWith(")"))).join("/")}function cg(e){let t=[...e],n=xr(t[t.length-1]);return n==="index"?t.pop():t[t.length-1]=n,"/"+t.join("/")}function ug(e){let t=[...e],n=xr(t[t.length-1]);return n==="index"?t.pop():t[t.length-1]=n,"/"+t.join("/")}function dg(e){try{return new URL(e).pathname||"/"}catch{return e.startsWith("/")?e:null}}function pg(e){return xr(e)==="page"&&Ha.includes(Xo(e))}function fg(e){return["_app","_document","_error","404","500","middleware"].includes(e)}function gg(e){try{let t=kr(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 Ya=new Set(["node_modules",".git",".next","dist","build",".vercel","out"]);function cn(e){try{return Jf(e).isDirectory()}catch{return!1}}function*zo(e,t=1/0){let n=0;function*r(o,i){if(i>t)return;let s;try{s=Ga(o,{withFileTypes:!0})}catch{return}for(let a of s){if(n>=Zf)return;if(a.name.startsWith("."))continue;let l=ue(o,a.name);if(a.isDirectory()){if(Ya.has(a.name))continue;yield*r(l,i+1)}else a.isFile()&&(n++,yield l)}}yield*r(e,1)}function Jo(e,t){return t.slice(e.length).replace(/^[/\\]+/,"").split(Xf).filter(Boolean)}function Xo(e){let t=e.lastIndexOf(".");return t===-1?"":e.slice(t+1).toLowerCase()}function xr(e){let t=e.lastIndexOf(".");return t===-1?e:e.slice(0,t)}function za(e,t){return e?`https://${e.replace(/^https?:\/\//,"").replace(/\/$/,"")}${t==="/"?"/":t}`:t}import{existsSync as mg,mkdirSync as hg,readFileSync as yg,writeFileSync as wg}from"fs";import{join as Qo}from"path";var un="pages.md",vr="pages-discovered.md";function bg(e){return Qe(Qo(e,x,un))?.data?.generated===!0}function kg(e){let t=bg(e);return{rel:t?vr:un,cloudOwned:t}}function Ja(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 $r(e,t,n){let r=kg(e),o=Qo(e,x,r.rel),i=mg(o),s=i?Sr(o):new Map;if(t.length===0&&!i)return{wrote:!1,added:0,total:0,merged:!1,file:r.rel};let a=new Map(s),l=0,c=!1;for(let p of t){let g=Qa(p.url),f=n?.get(p.url),h=a.get(g);if(h)p.in_sitemap&&h.in_sitemap!=="yes"&&(h.in_sitemap="yes",c=!0),f&&Ja(h,f)&&(c=!0);else{let y={url:g,in_sitemap:p.in_sitemap?"yes":"no",in_nav:"\u2014",status:"\u2014",rendered:"\u2014",word_count:"\u2014",notes:Qa(p.source)};f&&Ja(y,f),a.set(g,y),l++,c=!0}}if(i&&!c)return{wrote:!1,added:0,total:a.size,merged:!0,file:r.rel};let d=[...a.values()];return hg(Qo(e,x),{recursive:!0}),wg(o,xg(d,r.cloudOwned),"utf-8"),{wrote:!0,added:l,total:d.length,merged:i,file:r.rel}}function xg(e,t){let n=["---","generated: false",`discovered_at: ${new Date().toISOString()}`,`row_count: ${e.length}`,"---"].join(`
|
|
47
|
+
`;function rt(e){let t=qo(e,".seoagent",Vo);if(!Ko(t))return null;let n=Sa(t,"utf-8");return Wo(n)}function _a(e,t){let n=qo(e,".seoagent",Vo);return Ko(n)?!1:(Ef(n,Cf+$a({business:{type:t||""},writingInstructions:[],referenceUrls:[],topicsToAvoid:[],contentTone:null,additionalNotes:null}),"utf-8"),!0)}function sn(e){let t=qo(e,".seoagent",Vo);if(!Ko(t))return!0;try{return Oe(Sa(t,"utf-8"))}catch{return!1}}import{existsSync as an,readFileSync as Ra,readdirSync as Af,statSync as Ea}from"fs";import{join as wt}from"path";var If=[{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 Pf(e){let t=wt(e,"package.json");if(!an(t))return{deps:{},source:"package.json"};try{let n=JSON.parse(Ra(t,"utf-8"));return{deps:{...n.dependencies,...n.devDependencies,...n.peerDependencies},source:"package.json"}}catch{return{deps:{},source:"package.json"}}}function Tf(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 Of(e){let t={};for(let n of Rt){let r=wt(e,n);if(an(r))try{let o=Ra(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 Lf(e,t){let n=wt(e,t.replace(/\/$/,""));if(!an(n))return!1;if(t.endsWith("/"))try{return Ea(n).isDirectory()}catch{return!1}return!0}function Nf(e){let t=["content","_posts","posts",wt("src","content")];for(let n of t){let r=wt(e,n);if(an(r))try{if(!Ea(r).isDirectory())continue;let o=[r],i=0;for(;o.length>0&&i<50;){let s=o.pop();for(let a of Af(s,{withFileTypes:!0}))if(i++,!a.name.startsWith(".")){if(a.isDirectory()&&o.length<5){o.push(wt(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 jf(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=To(e);r&&n.push(r);for(let o of n)for(let i of t){let s=o?`${o}/${i.marker}`:i.marker;if(an(wt(e,s)))return i.path}return null}function Ca(e){let t=[],{deps:n}=Pf(e),r=Of(e),o="none";for(let s of If){let a=[];for(let l of Object.keys(n))if(Tf(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(Lf(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=Nf(e);s&&(o="mdx-local",t.push(s))}let i=jf(e);return{cms:o,blog_path:i,evidence:t}}import{existsSync as Df,readFileSync as Mf}from"fs";import{join as Uf}from"path";var Aa=["openai","fal","replicate"],Pt={openai:["OPENAI_API_KEY"],fal:["FAL_KEY","FAL_API_KEY"],replicate:["REPLICATE_API_TOKEN","REPLICATE_API_KEY"]};function Ff(e){let t={};if(!Df(e))return t;let n=Mf(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 Ia(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 Rt){let o=Uf(e,r),i=Ff(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 ot(e){let t=[].concat(...Aa.map(a=>Pt[a])),n=Ia(e,t),r=[];for(let a of Aa)Pt[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=Pt[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 Pa(e,t){let n=Ia(e,Pt[t]);for(let r of Pt[t])if(n.found[r])return{key:n.found[r].value,envName:r};return null}function Ta(){return Pt}import{existsSync as bt,readFileSync as Yo}from"fs";import{join as kt}from"path";function Oa(e){try{return new URL(e).hostname}catch{return e}}function La(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 Na(e){let t=e.trim().toLowerCase().replace(/^\[([^\]]+)\](?::\d{1,5})?$/,"$1").replace(/^((?:\d{1,3}\.){3}\d{1,3}|localhost|[a-z0-9.-]*\.localhost)(?::\d{1,5})?$/,"$1");return t==="localhost"||t.endsWith(".localhost")||t==="::1"||t==="0.0.0.0"?!0:/^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(t)}function Bf(e){for(let t of Rt){let n=kt(e,t);if(!bt(n))continue;let r=Yo(n,"utf-8");for(let o of Is){let i=r.match(new RegExp(`^${o}=(.+)$`,"m"));if(i){let s=i[1].trim().replace(/^["']|["']$/g,""),a=Oa(s);if(La(a)||Na(a))continue;return{value:a,source:`${t} (${o})`}}}}return null}function Gf(e){let t=kt(e,"package.json");if(!bt(t))return null;try{let n=JSON.parse(Yo(t,"utf-8"));if(!n.homepage)return null;let r=Oa(n.homepage);return La(r)||Na(r)?null:{value:r,source:"package.json (homepage)"}}catch{return null}}function ja(e,t){let n=[];t?.("Checking for monorepo / workspace layout\u2026"),bt(kt(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=Bf(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=Gf(e);a&&(r=a.value,n.push({field:"domain",detail:a.value,source:a.source}))}let i=kt(e,"package.json"),s=null;if(bt(i))try{t?.("Reading dependencies to infer site type\u2026");let a=JSON.parse(Yo(i,"utf-8")),l=Object.keys({...a.dependencies,...a.devDependencies}),c=(...d)=>d.some(p=>l.includes(p));if(c("@shopify/hydrogen","@shopify/polaris")||bt(kt(e,"shopify.config.js"))||bt(kt(e,"shopify.config.ts"))){s="product";let d=c("@shopify/hydrogen","@shopify/polaris")?"Shopify-related dependencies in package.json":bt(kt(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"),p=c("next-auth","@auth/core","lucia","clerk","@clerk/nextjs","@clerk/clerk-react"),g=c("next");d&&(g||p)?(s="saas",n.push({field:"site_type",detail:"saas",source:g?"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 zf,readFileSync as kr,readdirSync as Ga,statSync as Jf}from"fs";import{join as ue,sep as Xf}from"path";var Ma=[/^\/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],Hf=[/^\/[a-z0-9]+-(?:onboarding|login|logout|signin|sign-in|signup|sign-up|dashboard|checkout|account)$/i];function Le(e){let t=[],n=/<loc>\s*([^<\s][^<]*?)\s*<\/loc>/gi,r;for(;(r=n.exec(e))!==null;){let o=r[1].replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/'/g,"'").replace(/"/g,'"').trim();o&&t.push(o)}return t}function Wf(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 Kf=10,Da=.9;function qf(e,t){if(t<Kf||e.length<t*Da)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*Da)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 Ne(e){return Ma.some(t=>t.test(e))||Hf.some(t=>t.test(e))}function Ua(e){return Ma.some(t=>t.test(e))}function Vf(e){return e.filter(t=>Ne(F(t)))}function Yf(e,t){let n=new Set(e.map(F)),r=[],o=new Set;for(let i of t){let s=F(i);Ne(s)||n.has(s)||o.has(s)||(o.add(s),r.push(s))}return r}function ln(e){return`https://${e.replace(/^https?:\/\//,"").replace(/\/+$/,"")}/sitemap.xml`}async function Fa(e){let t=e.sitemapUrl||ln(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 j(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=Le(o),r.lastmods=Wf(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=Vf(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=qf(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=Yf(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 Ha=["tsx","jsx","js","mdx","md"],Qf=["astro","md","mdx"],Zf=5e3,eg=2e3;function je(e,t){let n=new Map,r=0,o=(l,c,d=!1)=>{let p=Va(l);if(p===null){r++;return}let g=n.get(p);if(g){g.in_sitemap=g.in_sitemap||d,g.source.includes(c)||(g.source+=`, ${c}`);return}n.set(p,{route:p,url:za(t,p),in_sitemap:d,source:c})};for(let l of qa(e))ig(l,o);let i=tg(e,t),s=l=>i.some(c=>c.test(l)),a=[...n.values()].filter(l=>!Ne(l.route));return{pages:a.filter(l=>!s(l.route)).sort((l,c)=>l.route.localeCompare(c.route)),dynamicCount:r,offOriginCount:a.filter(l=>s(l.route)).length}}function tg(e,t){let n=Ba(t);if(!n)return[];let r=[];for(let o of qa(e)){let i;try{i=JSON.parse(kr(ue(o,"vercel.json"),"utf-8"))}catch{continue}if(Array.isArray(i.redirects))for(let s of i.redirects){if(!s||typeof s.source!="string"||typeof s.destination!="string")continue;let a=Ba(s.destination);if(!a||Wa(a,n)||!ng(s,n))continue;let l=rg(s.source);l&&r.push(l)}}return r}function Ba(e){if(!e)return null;let t=e.trim();if(!t||t.startsWith("/"))return null;try{return new URL(/^https?:\/\//i.test(t)?t:`https://${t}`).hostname.toLowerCase()}catch{return null}}function Wa(e,t){let n=r=>r.replace(/^www\./,"");return n(e)===n(t)}function ng(e,t){if(Array.isArray(e.missing)&&e.missing.length>0)return!1;let n=e.has;return!Array.isArray(n)||n.length===0?!0:n.every(r=>r&&r.type==="host"&&typeof r.value=="string"&&Wa(r.value.toLowerCase(),t))}function rg(e){if(!e.startsWith("/")||/[(){}[\]?+*]/.test(e.replace(/:[A-Za-z0-9_]+(?:\([^)]*\))?[*+?]?/g,"")))return null;let t="",n=0;for(;n<e.length;){let r=e[n];if(r!==":"){t+=r.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),n++;continue}let o=/^:([A-Za-z0-9_]+)(\(([^)]*)\))?([*+?])?/.exec(e.slice(n));if(!o)return null;let[,,,i,s]=o;i!==void 0?t+=`(?:${i})`:s==="*"?t+=".*":s==="+"?t+=".+":s==="?"?t+="[^/]*":t+="[^/]+",n+=o[0].length}try{return new RegExp(`^${t}$`,"i")}catch{return null}}async function Ka(e,t,n){let r={checked:!1,liveCount:0,flipped:0,added:0},o=[];try{let c=await j(ln(t),{headers:{"user-agent":"SEOAgent-CLI/sitemap-check"}},{attempts:1,timeoutMs:5e3,fetchImpl:n});if(!c.ok)return r;o=Le(await c.text())}catch{return r}if(o.length===0)return r;let i=new Set(o.map(F)),s=0;for(let c of e)!c.in_sitemap&&i.has(F(c.url||c.route))&&(c.in_sitemap=!0,s++,c.source.includes("live-sitemap")||(c.source+=", live-sitemap"));let a=new Set(e.map(c=>F(c.url||c.route))),l=0;for(let c of o){if(l>=eg)break;let d=og(c,t);d===null||a.has(d)||(a.add(d),e.push({route:d,url:za(t,d),in_sitemap:!0,source:"live-sitemap"}),l++)}return l>0&&e.sort((c,d)=>c.route.localeCompare(d.route)),{checked:!0,liveCount:o.length,flipped:s,added:l}}function og(e,t){let n=t.replace(/^https?:\/\//,"").replace(/\/.*$/,"").toLowerCase().replace(/^www\./,""),r;try{r=new URL(e)}catch{return null}if(r.protocol!=="https:"&&r.protocol!=="http:"||r.hostname.toLowerCase().replace(/^www\./,"")!==n)return null;let o=F(e);return/\.xml$/i.test(o)||Ua(o)?null:Va(o)}function ig(e,t){for(let n of["app",ue("src","app")]){let r=ue(e,n);if(cn(r))for(let o of zo(r)){let i=Jo(r,o),s=i[i.length-1];pg(s)&&(i.some(a=>a.startsWith("@"))||t(lg(i.slice(0,-1)),"app router"))}}for(let n of["pages",ue("src","pages")]){let r=ue(e,n);if(cn(r))for(let o of zo(r)){let i=Jo(r,o);if(i[0]==="api")continue;let s=i[i.length-1],a=Xo(s),l=Ha.includes(a),c=Qf.includes(a);if(!l&&!c)continue;let d=xr(s);fg(d)||t(cg(i),c?"astro":"pages router")}}for(let n of[".","public"]){let r=ue(e,n);if(cn(r))for(let o of zo(r,n==="."?1:1/0)){let i=Jo(r,o),s=i[i.length-1];Xo(s)==="html"&&(i.some(a=>a==="node_modules"||a.startsWith("."))||t(ug(i),"static html"))}}for(let n of["public/sitemap.xml","sitemap.xml"]){let r=ue(e,...n.split("/"));if(zf(r)){for(let o of gg(r)){let i=dg(o);i!==null&&t(i,"sitemap.xml",!0)}break}}}function qa(e){let t=[e],n=new Set([e]);for(let r of sg(e))for(let o of ag(e,r))n.has(o)||(n.add(o),t.push(o));return t}function sg(e){let t=[];try{let n=kr(ue(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(kr(ue(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 ag(e,t){if(!t||t.startsWith("!"))return[];let n=t.replace(/\/\*\*?$/,"/*"),r=n.indexOf("*");if(r===-1){let a=ue(e,...n.split("/").filter(Boolean));return cn(a)?[a]:[]}let o=n.slice(0,r).replace(/\/$/,""),i=ue(e,...o.split("/").filter(Boolean));if(!cn(i))return[];let s=[];try{for(let a of Ga(i,{withFileTypes:!0}))if(a.isDirectory()&&!a.name.startsWith(".")&&!Ya.has(a.name)&&(s.push(ue(i,a.name)),s.length>=200))break}catch{}return s}function Va(e){let t=e.trim();return t===""||t==="/"||(t.startsWith("/")||(t=`/${t}`),t=t.replace(/\/+$/,""),t==="")?"/":/\[.*\]/.test(t)?null:t}function lg(e){return"/"+e.filter(n=>!(n.startsWith("(")&&n.endsWith(")"))).join("/")}function cg(e){let t=[...e],n=xr(t[t.length-1]);return n==="index"?t.pop():t[t.length-1]=n,"/"+t.join("/")}function ug(e){let t=[...e],n=xr(t[t.length-1]);return n==="index"?t.pop():t[t.length-1]=n,"/"+t.join("/")}function dg(e){try{return new URL(e).pathname||"/"}catch{return e.startsWith("/")?e:null}}function pg(e){return xr(e)==="page"&&Ha.includes(Xo(e))}function fg(e){return["_app","_document","_error","404","500","middleware"].includes(e)}function gg(e){try{let t=kr(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 Ya=new Set(["node_modules",".git",".next","dist","build",".vercel","out"]);function cn(e){try{return Jf(e).isDirectory()}catch{return!1}}function*zo(e,t=1/0){let n=0;function*r(o,i){if(i>t)return;let s;try{s=Ga(o,{withFileTypes:!0})}catch{return}for(let a of s){if(n>=Zf)return;if(a.name.startsWith("."))continue;let l=ue(o,a.name);if(a.isDirectory()){if(Ya.has(a.name))continue;yield*r(l,i+1)}else a.isFile()&&(n++,yield l)}}yield*r(e,1)}function Jo(e,t){return t.slice(e.length).replace(/^[/\\]+/,"").split(Xf).filter(Boolean)}function Xo(e){let t=e.lastIndexOf(".");return t===-1?"":e.slice(t+1).toLowerCase()}function xr(e){let t=e.lastIndexOf(".");return t===-1?e:e.slice(0,t)}function za(e,t){return e?`https://${e.replace(/^https?:\/\//,"").replace(/\/$/,"")}${t==="/"?"/":t}`:t}import{existsSync as mg,mkdirSync as hg,readFileSync as yg,writeFileSync as wg}from"fs";import{join as Qo}from"path";var un="pages.md",vr="pages-discovered.md";function bg(e){return Qe(Qo(e,x,un))?.data?.generated===!0}function kg(e){let t=bg(e);return{rel:t?vr:un,cloudOwned:t}}function Ja(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 $r(e,t,n){let r=kg(e),o=Qo(e,x,r.rel),i=mg(o),s=i?Sr(o):new Map;if(t.length===0&&!i)return{wrote:!1,added:0,total:0,merged:!1,file:r.rel};let a=new Map(s),l=0,c=!1;for(let p of t){let g=Qa(p.url),f=n?.get(p.url),h=a.get(g);if(h)p.in_sitemap&&h.in_sitemap!=="yes"&&(h.in_sitemap="yes",c=!0),f&&Ja(h,f)&&(c=!0);else{let y={url:g,in_sitemap:p.in_sitemap?"yes":"no",in_nav:"\u2014",status:"\u2014",rendered:"\u2014",word_count:"\u2014",notes:Qa(p.source)};f&&Ja(y,f),a.set(g,y),l++,c=!0}}if(i&&!c)return{wrote:!1,added:0,total:a.size,merged:!0,file:r.rel};let d=[...a.values()];return hg(Qo(e,x),{recursive:!0}),wg(o,xg(d,r.cloudOwned),"utf-8"),{wrote:!0,added:l,total:d.length,merged:i,file:r.rel}}function xg(e,t){let n=["---","generated: false",`discovered_at: ${new Date().toISOString()}`,`row_count: ${e.length}`,"---"].join(`
|
|
48
48
|
`),r=[t?"# Pages discovered in the codebase":"# Pages","",t?"Inventory of pages the codebase scan found. `pages.md` is now a cloud":"Inventory of pages discovered in your codebase. Every `seoagent sync`,",t?"projection (GSC traffic per page, edited in the dashboard), so the scan":"`seoagent init` and `seoagent refresh` re-scan and merge in newly-added",t?"writes here instead; every `seoagent sync` re-scans, merges in new pages":"pages without touching rows you or the audit have edited. `In sitemap` is",t?"and pushes them so the cloud inventory stays current. `In sitemap` is":"refreshed from the live sitemap; `Status` / `Rendered` / `Word count` are",t?"refreshed from the live sitemap; `Status` / `Rendered` / `Word count` are":"filled by `seoagent refresh --crawl` (a live fetch of each page). This file",t?"filled by `seoagent refresh --crawl` (a live fetch of each page).":"syncs to the cloud on login."].join(`
|
|
49
49
|
`),o="| URL | In sitemap | In nav | Status | Rendered | Word count | Notes |",i="|---|---|---|---|---|---|---|",s=e.map(a=>`| ${a.url} | ${a.in_sitemap} | ${a.in_nav} | ${a.status} | ${a.rendered} | ${a.word_count} | ${a.notes} |`);return[n,"",r,"",o,i,...s,""].join(`
|
|
50
50
|
`)}function Sr(e){let t=new Map,n;try{n=yg(e,"utf-8")}catch{return t}let r=n.split(/\r?\n/);for(let o=0;o<r.length-1;o++){if(!Zo(r[o])||!vg(r[o+1]))continue;let i=Xa(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&&Zo(r[a]);a++){let l=Xa(r[a]),c=(l[s.url]??"").trim();!c||c==="\u2014"||c==="-"||t.set(c,{url:c,in_sitemap:Tt(l,s.in_sitemap),in_nav:Tt(l,s.in_nav),status:Tt(l,s.status),rendered:Tt(l,s.rendered),word_count:Tt(l,s.word_count),notes:Tt(l,s.notes)})}break}return t}function Tt(e,t){let n=t>=0?(e[t]??"").trim():"";return n===""?"\u2014":n}function Zo(e){let t=e.trim();return t.startsWith("|")&&t.endsWith("|")&&t.length>1}function vg(e){let t=e.trim();return Zo(t)&&/^\|(?:\s*:?-+:?\s*\|)+$/.test(t)}function Xa(e){return e.trim().slice(1,-1).split("|").map(t=>t.trim())}function Qa(e){return e.replace(/\r?\n/g," ").replace(/\|/g,"/").trim()}import{existsSync as $g,readFileSync as Sg,statSync as _g}from"fs";import{dirname as ei,isAbsolute as Rg,join as Eg,resolve as ti,sep as dn}from"path";var Cg={isWorktree:!1,mainRoot:null,reason:null};function Za(e){let t=Eg(e,".git");try{if($g(t)&&_g(t).isFile()){let i=Sg(t,"utf-8").match(/^gitdir:\s*(.+)\s*$/m),s=null;if(i){let a=Rg(i[1])?i[1]:ti(e,i[1]),l=ei(a),c=ei(l);l.endsWith(`${dn}worktrees`)&&c.endsWith(`${dn}.git`)&&(s=ei(c))}return{isWorktree:!0,mainRoot:s,reason:"gitfile"}}}catch{}let n=`${dn}.claude${dn}worktrees${dn}`,r=ti(e).indexOf(n);return r>=0?{isWorktree:!0,mainRoot:ti(e).slice(0,r),reason:"agent-worktree-path"}:Cg}import{existsSync as it,mkdirSync as _r,readdirSync as Ag,readFileSync as ni,statSync as W_,writeFileSync as el}from"fs";import{dirname as tl,join as Se,relative as ri,resolve as nl,sep as rl}from"path";var Ot="okf";function Lt(e){return Se(e,x,Ot)}var Ig=/^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}:\d{2})?)?$/;function Rr(e){let t=[];for(let n of Ag(e,{withFileTypes:!0})){let r=Se(e,n.name);n.isDirectory()?t.push(...Rr(r)):n.isFile()&&n.name.toLowerCase().endsWith(".md")&&t.push(r)}return t}function ol(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 Er(e){let t=[],n=[];if(!it(e))return{ok:!1,fileCount:0,errors:[{file:Ot,message:"No OKF bundle found. Run `seoagent okf scaffold` first."}],warnings:[]};let r=Rr(e);if(r.length===0)return{ok:!1,fileCount:0,errors:[{file:Ot,message:"OKF directory exists but contains no markdown files."}],warnings:[]};it(Se(e,"index.md"))||n.push({file:Ot,message:"No top-level index.md \u2014 agents use it for progressive disclosure."});for(let i of r){let s=ri(e,i),a;try{a=me(ni(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!==""&&!Ig.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 p of ol(a.body)){let g=p.split("#")[0];if(!g)continue;let f=nl(tl(i),g);it(f)||t.push({file:s,message:`Broken link \u2192 "${p}" (target does not exist).`})}}return{ok:t.length===0,fileCount:r.length,errors:t,warnings:n}}function Cr(e){let t=Lt(e);if(!it(t))return{exists:!1,fileCount:0};try{return{exists:!0,fileCount:Rr(t).length}}catch{return{exists:!0,fileCount:0}}}var il=["_Describe the business in","_Link concept files here","_Link FAQ files here","_Link article files here"];var Pg="- Bundle scaffolded.";function oi(e,t){if(e==="okf/index.md")return il.some(n=>t.includes(n));if(e==="okf/log.md"){let n=t.replace(/\r\n/g,`
|
|
@@ -87,8 +87,8 @@ description: Chronological history of changes to this OKF bundle.
|
|
|
87
87
|
- Bundle scaffolded.
|
|
88
88
|
`,"utf-8"),r.push("log.md")),{created:r,okfDir:n}}import{log as Nt}from"@clack/prompts";var pn=null;function jt(e,t){pn?pn.push(t):e(t)}var u={info:e=>jt(Nt.info,e),warn:e=>jt(Nt.warn,e),error:e=>jt(Nt.error,e),success:e=>jt(Nt.success,e),step:e=>jt(Nt.step,e),message:e=>jt(Nt.message,e)};function ll(){let e=[];pn=e;let t=!1;return()=>t?"":(t=!0,pn===e&&(pn=null),e.join(`
|
|
89
89
|
|
|
90
|
-
`))}function A(e,t,n,r=n){e?e.stop(r):u[t](n)}var cl=[{suffixes:[".wordpress.com"],platform:{id:"wordpress.com",label:"WordPress.com",api:"wordpress",enableHint:"an application password (Users \u2192 Profile \u2192 Application Passwords) or a WordPress.com OAuth token"}},{suffixes:[".wixsite.com",".wix.com"],platform:{id:"wix",label:"Wix",api:"wix",enableHint:"an API key from the Wix API Keys Manager with Blog permissions, plus the site ID"}},{suffixes:[".myshopify.com"],platform:{id:"shopify",label:"Shopify",api:"shopify",enableHint:"a Shopify Admin API access token with blog write scope"}},{suffixes:[".webflow.io"],platform:{id:"webflow",label:"Webflow",api:"webflow",enableHint:"a Webflow site API token with CMS write access"}},{suffixes:[".ghost.io"],platform:{id:"ghost",label:"Ghost",api:"ghost",enableHint:"a Ghost Admin API key"}},{suffixes:[".blogspot.com"],platform:{id:"blogger",label:"Blogger",api:"blogger",enableHint:"Blogger API access"}},{suffixes:[".squarespace.com"],platform:{id:"squarespace",label:"Squarespace",api:null,enableHint:""}},{suffixes:[".weebly.com"],platform:{id:"weebly",label:"Weebly",api:null,enableHint:""}},{suffixes:[".godaddysites.com"],platform:{id:"godaddy",label:"GoDaddy Website Builder",api:null,enableHint:""}},{suffixes:[".carrd.co"],platform:{id:"carrd",label:"Carrd",api:null,enableHint:""}},{suffixes:[".notion.site"],platform:{id:"notion",label:"Notion",api:null,enableHint:""}},{suffixes:[".substack.com"],platform:{id:"substack",label:"Substack",api:null,enableHint:""}},{suffixes:[".medium.com"],platform:{id:"medium",label:"Medium",api:null,enableHint:""}},{suffixes:[".tumblr.com"],platform:{id:"tumblr",label:"Tumblr",api:null,enableHint:""}},{suffixes:[".framer.website",".framer.app"],platform:{id:"framer",label:"Framer",api:null,enableHint:""}}];function ul(e){if(!e)return null;let t=e.trim().toLowerCase().replace(/^sc-domain:/,"").replace(/^https?:\/\//,"").replace(/^www\./,"").replace(/[/?#].*$/,"").replace(/:\d+$/,"");if(!t)return null;for(let{suffixes:n,platform:r}of cl)if(n.some(o=>t.endsWith(o)&&t.length>o.length))return r;return null}var X_=cl.flatMap(({suffixes:e,platform:t})=>e.map(n=>({suffix:n,id:t.id,api:t.api})));function dl(e,t){let n=[` \u{1F3E0} ${e} is hosted on ${t.label} \u2014 there is no repository here and`," none to find; this folder is the workspace. Audit, keywords, strategy"," and writing all work as normal. Delivery differs: read",' references/publishing.md \xA7 "No repo" (next to the skill) BEFORE the'," audit."];return t.api?n.push(` Ask your human once to set up the ${t.label} connector:`,` ${t.enableHint}. Credentials go in .env in this folder, never`," in a tracked file. Posts are created as drafts unless told otherwise."):n.push(` ${t.label} has no public API for posts, so the connector is the`," editor: write each article to .seoagent/content/<slug>.md with the exact"," title, slug, meta description and body, walk your human through pasting"," it, then `seoagent content track --slug <slug> --url <live-url>`."),n}import{randomBytes as em}from"crypto";import{isCancel as tm,spinner as bl,text as nm}from"@clack/prompts";import ye from"picocolors";import{exec as Og}from"child_process";function Ir(e){let t=process.platform==="darwin"?`open "${e}"`:process.platform==="win32"?`start "" "${e}"`:`xdg-open "${e}"`;Og(t,()=>{})}function pl(e,t){if(e>=500)return{status:"error",terminal:!1,message:`HTTP ${e} (server error)`};if(e>=400)return{status:"error",terminal:!0,message:`HTTP ${e} (client error)`};if(!t||typeof t!="object")return{status:"error",terminal:!0,message:`Malformed response: expected an object, got ${typeof t}`};let n=t;if(n.status==="ready"){if(typeof n.user_token=="string"&&typeof n.website_token=="string"){let r=typeof n.email=="string"&&n.email?n.email:void 0,o=typeof n.domain=="string"&&n.domain?n.domain:void 0;return{status:"ready",user_token:n.user_token,website_token:n.website_token,...r?{email:r}:{},...o?{domain:o}:{}}}return{status:"error",terminal:!0,message:"Malformed response: status=ready without user_token+website_token"}}return n.status==="pending"?{status:"pending"}:n.status==="expired"?{status:"expired"}:{status:"error",terminal:!0,message:`Malformed response: unknown status=${JSON.stringify(n.status)}`}}import{existsSync as Lg,mkdirSync as Ng,readFileSync as jg,writeFileSync as Dg}from"fs";import{join as Mg}from"path";var Ug="install-id",Fg=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;function fl(e=z){let t=Mg(e,Ug);try{if(Lg(t)){let r=jg(t,"utf-8").trim();if(Fg.test(r))return r}let n=
|
|
91
|
-
`,"utf-8"),n}catch{return null}}import{execFileSync as Bg}from"child_process";var Gg=2048,Hg=200,Wg=10,Kg=/^[A-Za-z0-9._-]+$/,qg=/^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/,Vg=new Set(["https:","http:","ssh:","git:","git+ssh:","ssh+git:"]),Yg=/^(?:[^@\s/]+@)?([^:/\s@]+):(?!\/\/)(.+)$/;function gl(e){let t=e.trim();t=t.replace(/[?#].*$/,""),t=t.replace(/^\/+/,"").replace(/\/+$/,""),t=t.replace(/\.git$/i,"");let n=t.split("/").filter(i=>i.length>0);if(n.length<2||n.length>Wg)return null;for(let i of n)if(!Kg.test(i)||i==="."||i==="..")return null;let r=n[n.length-1],o=n.slice(0,-1).join("/");return`${o}/${r}`.length>Hg?null:{owner:o,repo:r}}function ml(e){let t=e.trim().toLowerCase().replace(/\.$/,"");return t.length<2||t.length>253?null:qg.test(t)?t:null}function zg(e){if(typeof e!="string")return null;let t=e.trim();if(!t||t.length>Gg||/\s/.test(t))return null;if(/^[a-z][a-z0-9+.-]*:\/\//i.test(t)){let r;try{r=new URL(t)}catch{return null}if(!Vg.has(r.protocol.toLowerCase()))return null;let o=ml(r.hostname);if(!o)return null;let i=gl(Jg(r.pathname));return i?{host:o,...i}:null}let n=t.match(Yg);if(n){if(n[1].length<2)return null;let r=ml(n[1]);if(!r)return null;let o=gl(n[2]);return o?{host:r,...o}:null}return null}function Jg(e){try{return decodeURIComponent(e)}catch{return e}}function Xg(e){return e?{host:e.host,full_name:`${e.owner}/${e.repo}`}:null}var Qg=1500,Zg=e=>Bg("git",["-C",e,"remote","get-url","origin"],{encoding:"utf8",timeout:Qg,stdio:["ignore","pipe","ignore"],windowsHide:!0,env:{...process.env,GIT_TERMINAL_PROMPT:"0"}});function Pr(e,t=Zg){try{return Xg(zg(t(e)))}catch{return null}}var rm=1500,om=900*1e3,im=2700*1e3;function sm(e){return e?im:om}function am(e){return`Waiting for browser confirmation\u2026 (up to ${Math.round(e/6e4)} minutes \u2014 if you need longer, the page prints a \`seoagent login --code \u2026\` line to run here)`}var hl="Login timed out. If the browser page shows a `seoagent login --code \u2026` line, run that here; otherwise run `seoagent login` again.",lm=3,cm=2e3;function Tr(e,t){if(he())return;let n=$e(e);n&&ze({installId:n,event:t,cliVersion:P})}function um(){return em(16).toString("hex")}function dm(e,t,n){let r=new URLSearchParams({session:t});return n&&n!=="unknown"&&r.set("domain",n),`${e}/cli/auth?${r.toString()}`}function yl(e){let t=e.trim().replace(/^https?:\/\//i,"").replace(/^www\./i,"").replace(/\/.*$/,"");return!t||!/^[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/i.test(t)?null:t.toLowerCase()}async function pm(e,t,n){if(!e||e.website_token===t)return null;try{let r=await n(e);return r.verdict!=="unverified"?r.cloudDomain:null}catch{return null}}async function fm(e,t){try{let n=`${e}/api/cli/auth/poll?session=${encodeURIComponent(t)}`,r=await fetch(n,{headers:{Accept:"application/json"},signal:AbortSignal.timeout(1e4)}),o=null;try{o=await r.json()}catch{}return pl(r.status,o)}catch(n){return{status:"error",terminal:!1,message:n.message||"network error"}}}function kl(e){return new Promise(t=>setTimeout(t,e))}async function xl(e,t,n,r){let o=B(),i=await pm(o,e.website_token,c=>et({apiBase:c.api_base,authHeader:`Bearer ${c.user_token}:${c.website_token}`,localDomain:""}));i&&o&&Mo(o,i),Mo({user_token:e.user_token,website_token:e.website_token,api_base:t},e.domain||n);let s=e.email&&e.domain?`Logged in as ${e.email} (${e.domain}). Future SEO work in this repo will sync to your dashboard.`:e.email?`Logged in as ${e.email}. Future SEO work in this repo will sync to your dashboard.`:"Logged in. Future SEO work in this repo will sync to your dashboard.",a=e.email&&e.domain?`Logged in as ${ye.bold(e.email)} (${ye.cyan(e.domain)}).`:e.email?`Logged in as ${ye.bold(e.email)}.`:"Logged in.";A(r,"success",s,ye.green(a)),r&&u.info("Future SEO work in this repo will sync to your dashboard.");let l=tt().filter(c=>c!==Te(e.domain||n));l.length>0&&u.info(`Also logged in to: ${l.join(", ")}. Each project auto-selects its own site \u2014 no need to log out first.`)}function gm(e,t,n){return{code:e,...t&&t!=="unknown"?{domain:t}:{},...n?{install_id:n.installId,cli_version:n.cliVersion,platform:process.platform,...n.command?{command:n.command}:{},...n.installScope?{install_scope:n.installScope}:{},...n.repo?{repo:{host:n.repo.host,full_name:n.repo.full_name}}:{}}:{}}}function ii(e,t){if(he())return null;let n=$e(e);if(n){let o=Pr(e);return{installId:n,cliVersion:P,command:t,installScope:"project",...o?{repo:o}:{}}}let r=fl();return r?{installId:r,cliVersion:P,command:t,installScope:"machine"}:null}async function mm(e,t,n,r){try{let o=await fetch(`${e}/api/cli/auth/redeem`,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify(gm(t,n,r)),signal:AbortSignal.timeout(1e4)}),i=null;try{i=await o.json()}catch{}return Js(o.status,i)}catch(o){return{status:"error",terminal:!1,message:o.message||"network error"}}}var hm=new Set(["code_already_used","code_revoked","code_expired"]);function ym(e,t){return`That setup code was for ${e}, but this directory is the ${t} project \u2014 it stays ${t}, and running \`${m("sync")}\` here syncs ${t}. Credentials for ${e} are saved. One site per directory: run \`${m("init")} --domain ${e}\` in an empty directory of its own (or in the repo that builds ${e}) \u2014 it is already logged in there.`}var wm="Scheduled runs on a fresh machine: store the output of `seoagent whoami --env` as the task's SEOAGENT_AUTH secret. A setup code works once and cannot be guessed; never reuse or invent one.";function si(e){return e!=="ready"&&e!=="already"}function bm(e,t,n){if(!e||!n||t&&t!=="unknown")return!1;try{if(!X(e,{domain:n}))return!1}catch{return!1}return u.info(`Saved domain: ${n} \u2192 .seoagent/project.md (from the setup code).`),!0}async function ai(e,t,n,r,o,i={}){if(!dr.test(e))return u.warn("That does not look like a setup code (expected `sa_` + 24 hex characters). Generate one at seoagent.com/grok."),"invalid";let s=!!(i.siteAlreadyBound&&n),a=s?null:n,c=!!process.stdout.isTTY?bl():null;c?.start(ye.dim("Redeeming setup code\u2026"));let d="network error";for(let p=0;p<lm;p++){p>0&&await kl(cm);let g=await mm(t,e,a,r);if(g.status==="ready")return await xl(g,t,n,c),s&&g.domain&&Te(g.domain)!==Te(n)?(u.warn(ym(g.domain,n)),"other-site"):(bm(o,n,g.domain),u.info(wm),i.onBound?.({email:g.email,domain:g.domain}),"ready");if(g.terminal)return s&&g.code&&hm.has(g.code)?(A(c,"info",`Already logged in for ${n} \u2014 that setup code is spent (each works once); nothing to do.`,ye.dim(`Already logged in for ${n} \u2014 setup code spent, nothing to do.`)),"already"):(A(c,"warn",g.message,ye.red(g.message)),"rejected");d=g.message}return A(c,"warn",`Could not reach the server (${d}). Check your connection and run \`${m("login")} --code <code>\` again.`,ye.yellow("Could not reach the server.")),c&&u.warn(`Run \`${m("login")} --code <code>\` again to retry.`),"unreachable"}function Or(e){return e&&e!=="unknown"?hr(e):!!B()}function wl(){return`Next: run \`${m("sync")}\` in this repo \u2014 it pulls your strategy, briefs and inbox into .seoagent/. Nothing syncs until it runs.`}async function fn(e={}){let t=process.cwd(),n=C(t),r=(e.apiBase||U.BASE||qe).replace(/\/$/,""),o=n?.domain??null;if(o==="unknown"&&process.stdin.isTTY){let y=await nm({message:".seoagent/project.md has `domain: unknown`. What is your site's URL?",placeholder:"example.com",validate(k){if(!String(k??"").trim())return"A site URL is required.";if(!yl(String(k)))return"Please enter a valid domain like `example.com`."}});if(tm(y)){u.warn("Login cancelled.");return}let w=yl(String(y));if(w){o=w;try{X(t,{domain:w}),u.success(`Saved domain: ${w} \u2192 .seoagent/project.md`)}catch{}}}let i=(e.code??process.env.SEOAGENT_SETUP_CODE??"").trim(),s=o&&o!=="unknown";if(!i&&Or(o)){let y=s?` for ${o}`:"";u.info(`Already logged in${y}. To switch accounts${y}, run \`${m("logout")}\` first.`);return}if(i){let y=await ai(i,r,o,ii(t,"login"),t,{siteAlreadyBound:!!s&&Or(o)});si(y)?process.exitCode=1:y==="ready"&&u.info(wl());return}let a=um(),l=dm(r,a,o);u.message("Opening seoagent.com to connect this CLI to your account..."),u.info(`If the browser does not open, visit: ${l}`),Ir(l),u.info('In your browser: sign in (if needed) and click "Connect this CLI" to finish.');let c=!!process.stdout.isTTY,d=c?bl():null,p=sm(c),g=am(p);d?d.start(ye.dim(g)):u.info(g);let f=Date.now()+p,h=!0;for(;Date.now()<f;){h||await kl(rm),h=!1;let y=await fm(r,a);if(y.status==="ready"){await xl(y,r,o,d),Tr(t,"login_browser_ok"),u.info(wl());return}if(y.status==="expired"){Tr(t,"login_browser_expired"),A(d,"warn",`Session expired. Run \`${m("login")}\` again.`,ye.red("Session expired.")),d&&u.warn(`Run \`${m("login")}\` again.`);return}if(y.status==="error"){if(y.terminal){A(d,"warn",`Login failed: ${y.message}. Run \`${m("login")}\` again.`,ye.red(`Login failed: ${y.message}.`)),d&&u.warn(`Run \`${m("login")}\` again.`),Tr(t,"login_browser_failed");return}continue}}A(d,"warn",hl,ye.yellow("Login timed out.")),d&&u.warn(hl),Tr(t,"login_browser_timeout")}function gn(e={}){let t=e.all?null:C(process.cwd()),n=!e.all&&t?.domain&&t.domain!=="unknown"?t.domain:null;if(!oa(e.all?null:n)){u.info(n?`You were not logged in for ${n}.`:"You were not logged in.");return}u.success(n?`Logged out of ${n}.`:"Logged out.");let o=tt();o.length>0&&u.info(`Still logged in to: ${o.join(", ")}. Run \`${m("logout")} --all\` to log out of everything.`)}function Lr(e,t=process.env){return(e.code??t.SEOAGENT_SETUP_CODE??"").trim()}function $m(e,t=m("init")){return e?`${t} --code ${e}`:t}async function El(e,t,n){if(!n)return{outcome:"none",who:null};let r=t!==_e,o=(U.BASE||qe).replace(/\/$/,""),i=null;return{outcome:await ai(n,o,r?t:null,ii(e,"init"),e,{siteAlreadyBound:r&&Or(t),onBound:a=>{i=a}}),who:i}}function Cl(e,t,n){let r=t?.domain||(n!==_e?n:null);return e==="ready"?t?.email&&r?`\u2713 Logged in as ${t.email} (${r}) \u2014 this project is bound.`:t?.email?`\u2713 Logged in as ${t.email} \u2014 this project is bound.`:r?`\u2713 Logged in (${r}) \u2014 this project is bound.`:"\u2713 Logged in \u2014 this project is bound.":e==="already"?r?`\u2713 Already logged in for ${r} \u2014 this project is bound.`:"\u2713 Already logged in \u2014 this project is bound.":null}var _l=[{value:"saas",label:"SaaS / App"},{value:"service",label:"Service business"},{value:"product",label:"E-commerce / Product"},{value:"content",label:"Content / Blog"},{value:"marketplace",label:"Marketplace"},{value:"tool",label:"Tool / Utility"},{value:"nonprofit",label:"Nonprofit / Community"},{value:"unknown",label:"Not sure"}],_e="unknown",Sm=new Set(["saas","service","product","content","marketplace","tool","app","nonprofit","community","unknown"]);function _m(e){return e.field==="context"?`${e.detail} \u2014 ${e.source}`:e.field==="domain"?`Domain: ${e.detail} \u2014 ${e.source}`:`Site type: ${e.detail} \u2014 ${e.source}`}function Nr(e){if(!e?.trim())return null;let t=e.trim().toLowerCase();return Sm.has(t)?t:null}function Al(e,t){t.agentsMdPath&&u.info(`Added a SEOAgent section to ${Dt(e,t.agentsMdPath)} so agents that read it (Codex, others) find the skill.`),t.cursorRulePath&&u.info(`Wrote ${Dt(e,t.cursorRulePath)} so Cursor agents find the skill.`)}async function mn(e={}){let t=process.cwd();if(Ks(t)){let l=C(t),c=It(t);mt(t),X(t,{skill_version:P}),ht(t);let d=Dt(t,c.skillFile),p=c.claudeSymlink?` (symlinked at ${Dt(t,c.claudeSymlink)})`:"",g=l?.skill_version&&l.skill_version!==P?`${l.skill_version} \u2192 ${P}`:P;u.success(`Refreshed the SEOAgent skill (${g}).`),u.info("Your `.seoagent/` knowledge was left untouched."),u.info(`Skill at ${d}${p} \u2014 agents read this file at session start.`),Al(t,c),u.info(`Run \`${m("status")}\` to see your project, or \`${m("uninstall")}\` to start fresh.`);let f=Lr(e);if(!he()){let k=$e(t);k&&await ze({installId:k,event:"reinit",cliVersion:P,codeHint:pr(f),meta:No(f)})&&u.info(Lo)}let{outcome:h,who:y}=await El(t,l?.domain??_e,f);(h==="rejected"||h==="unreachable"||h==="invalid")&&u.warn(Rm()),h!=="none"&&si(h)&&(process.exitCode=1);let w=Cl(h,y,l?.domain??_e);w&&u.success(w);return}let n=!!e.yes||!process.stdin.isTTY,r=Za(t);if(r.isWorktree&&!e.allowWorktree){let l=$m(Lr(e)),c=r.mainRoot?`cd ${r.mainRoot} && ${l}`:`run ${l} from the main repository root`;n&&(u.warn("This directory is a temporary git worktree \u2014 a `.seoagent/` created here is deleted with the worktree, and images/un-synced edits cannot be recovered from the cloud."),u.info(`Init at the main checkout instead: ${c}`),u.info("To init here anyway (not recommended), pass --allow-worktree."),process.exit(1)),vl("SEOAgent \u2014 AI SEO Agent");let d=await Sl({message:"This looks like a temporary git worktree \u2014 .seoagent/ created here is lost when the worktree is removed. Create it here anyway?",initialValue:!1});(kt(d)||!d)&&(xt(`Good call. ${c}`),process.exit(0))}else n||vl("SEOAgent \u2014 AI SEO Agent");let o=ja(t,l=>u.step(l)),i=()=>{if(o.evidence.length!==0){u.info("Inferred from your project:");for(let l of o.evidence)u.info(` \u2022 ${_m(l)}`)}},s=e.domain?.trim()||process.env.SEOAGENT_DOMAIN?.trim()||o.domain||null,a=Nr(e.siteType)||Nr(process.env.SEOAGENT_SITE_TYPE)||o.siteType||null;if(n){e.siteType?.trim()&&Nr(e.siteType)===null&&(u.warn("Invalid --site-type. Use: saas, service, product, content, marketplace, tool, nonprofit, unknown, app, community."),process.exit(1)),process.env.SEOAGENT_SITE_TYPE?.trim()&&Nr(process.env.SEOAGENT_SITE_TYPE)===null&&(u.warn("Invalid SEOAGENT_SITE_TYPE environment value."),process.exit(1)),i(),s||(u.info("Couldn't detect your site URL from this repo \u2014 scaffolding anyway; you'll be prompted to supply it."),s=_e),a||(a="unknown"),await Rl(t,s,a,Lr(e));return}if(i(),!s){let l=await $l({message:"Website domain",placeholder:"example.com",validate:c=>c.trim()?void 0:"Domain is required"});kt(l)&&(xt("Cancelled"),process.exit(0)),s=String(l)}if(!a){let l=await li({message:"What kind of site is this?",options:[..._l]});kt(l)&&(xt("Cancelled"),process.exit(0)),a=l}for(;;){let l=await Sl({message:`Create .seoagent for ${s} (${a})?`});if(kt(l)&&(xt("Cancelled"),process.exit(0)),l)break;let c=await li({message:"What should we change?",options:[{value:"domain",label:"Domain"},{value:"site_type",label:"Site type"},{value:"abort",label:"Cancel setup"}]});if((kt(c)||c==="abort")&&(xt("Cancelled"),process.exit(0)),c==="domain"){let d=await $l({message:"Website domain",placeholder:"example.com",validate:p=>p.trim()?void 0:"Domain is required"});kt(d)&&(xt("Cancelled"),process.exit(0)),s=String(d)}else{let d=await li({message:"What kind of site is this?",options:[..._l]});kt(d)&&(xt("Cancelled"),process.exit(0)),a=d}}await Rl(t,s,a,Lr(e))}async function Rl(e,t,n,r){let o=vm();o.start("Setting up your SEO project");let i=Ca(e),s=ot(e),a=cr(e),l=ut(),c={domain:t,site_type:n,language:As(t),initialized_at:new Date().toISOString(),seoagent_version:P,skill_version:P,install_id:l,...i.cms!=="none"?{cms:i.cms}:{},...i.blog_path?{blog_path:i.blog_path}:{},...s.provider!=="none"?{image_provider:s.provider}:{},...a.dir?{public_dir:a.dir}:{}},d=he(),p=d?Promise.resolve(!1):ze({installId:l,event:"init",cliVersion:P,codeHint:pr(r),meta:No(r)}),g=d||t===_e?Promise.resolve(null):ia({domain:t,installId:l}),f=B(t),h=f&&t!==_e?et({apiBase:f.api_base,authHeader:`Bearer ${f.user_token}:${f.website_token}`,localDomain:t}).catch(()=>null):Promise.resolve(null);Vs(e),qs(e,c),ht(e),_a(e,n),fa(e);let y=Ar(e,t!==_e?t:null),w=It(e),k=Dt(e,w.skillFile),R=km(k),b=`${R}/references/audit-checks.md`,v=w.claudeSymlink?Dt(e,w.claudeSymlink):void 0;mt(e);let{outcome:S,who:O}=await El(e,t,r),L=S==="ready"||S==="already";if(S==="ready"&&t===_e){let Z=C(e)?.domain;Z&&Z!==_e&&(t=Z,c.domain=Z)}let I=t!==_e,D=S==="rejected"||S==="unreachable"||S==="invalid",K=S==="other-site";(D||K)&&(process.exitCode=1);let ee={pages:[],dynamicCount:0},Y={wrote:!1,added:0,total:0,merged:!1,file:"pages.md"};try{ee=je(e,I?t:null),Y=$r(e,ee.pages)}catch{}if(o.stop(I?`Created .seoagent/ project for ${c.domain}`:"Created .seoagent/ project (site URL not detected yet \u2014 see step 2)"),i.cms!=="none"){let Z=i.evidence.map(q=>`${q.detail} (${q.source})`).slice(0,2).join(", ");u.info(`CMS detected: ${i.cms} \u2014 ${Z}`)}if(i.blog_path&&u.info(`Blog route: ${i.blog_path}`),a.dir&&u.info(`Static files: ${a.dir}/ (${a.evidence}) \u2014 your llms.txt and OKF bundle publish there.`),y.created.length>0&&u.info("Scaffolded .seoagent/okf/ \u2014 the AI-readable knowledge bundle answer engines cite. The audit will offer to fill it."),s.provider!=="none"&&u.info(`Image generation: ${s.provider} (detected ${s.matched_key}) \u2014 articles can get AI hero images.`),ee.pages.length>0){let Z=Y.merged?Y.added>0?`Re-scanned your codebase: +${Y.added} new page${Y.added===1?"":"s"} merged into .seoagent/pages.md (${Y.total} total)`:`Re-scanned your codebase: .seoagent/pages.md already current (${Y.total} pages)`:`Found ${ee.pages.length} page${ee.pages.length===1?"":"s"} in your codebase \u2192 .seoagent/pages.md`;u.info(Z+(ee.dynamicCount>0?` (plus ${ee.dynamicCount} dynamic route template${ee.dynamicCount===1?"":"s"} \u2014 the audit will discover their instances)`:""))}Al(e,w);let[er,He,So]=await Promise.all([p,g,h]);if(So?.verdict==="mismatch"&&u.warn(`Heads up: the credentials bound to ${t} are for a SEOAgent cloud project on ${So.cloudDomain} instead. This workspace stays LOCAL-ONLY \u2014 nothing will be pushed to or pulled from that project. To connect ${t} to the cloud, run \`${m("login")}\` from this directory and pick (or create) ${t} \u2014 the CLI can stay logged into both sites at once, no need to log out first.`),He?.status==="data"){let Z=He.difficulty!=null?`, difficulty ${He.difficulty}/100`:"";u.info(`Free keyword peek \u2014 "${He.keyword}": ${He.volume.toLocaleString("en-US")} searches/mo${Z}. You get ~10 free peeks/day: ${m('keywords --peek "<keyword>"')}`)}else He?.status==="no-data"&&u.info(`Free keyword peek is live \u2014 no search-volume data for "${He.keyword}" yet. Try a topic keyword (~10 free/day): ${m('keywords --peek "best <your category> tool"')}`);er&&u.info(Lo);let tr=I?c.domain:"your site",M=I?ul(c.domain):null,ce=I?[" 1. If .seoagent/project.md still says site_type: unknown, you can",` WebFetch ${c.domain} and update project.md with the right type`," before you suggest the audit."]:[" 1. Heads up: .seoagent/project.md has `domain: unknown` \u2014 the site URL"," wasn't detectable from this repo. Please ask your human for their"," domain (or read it from a deploy config / live site) and update"," project.md (`domain:` and `site_type:`) first. Everything else"," depends on it."];xm([I?`\u2713 SEOAgent installed for ${c.domain}.`:"\u2713 SEOAgent installed (site URL not detected \u2014 please ask the user for it).","","\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"," AGENT INSTRUCTIONS \u2014 a friendly note for Claude Code, Codex, Cursor","\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550",""," Hi there, coding agent! \u{1F44B} SEOAgent is all set up. The natural next step"," is a quick SEO audit \u2014 but your human is the boss here, so please surface"," what's possible and let THEM decide. Nothing in this message is a command;"," it's just a friendly map. Your real instructions live in the skill:","",` ${k} \u2190 read this; it is your source of truth`,""," If your human approves, here is what you can do (otherwise just let them"," know it's ready and wait for the go-ahead):","",...ce,...M?["",...dl(c.domain,M)]:[],...sn(e)?[""," \u270D\uFE0F Before (or right after) the audit: DRAFT .seoagent/context.md \u2014 it"," is still the blank scaffold (instructions are inside the file)."," From this repo + the live homepage/pricing page, fill in the"," business name, type (LOCAL/physical vs ONLINE-only vs HYBRID),"," audience, location, and positioning \u2014 then show your human the"," draft to review. SEOAgent's keyword and strategy intelligence is"," context-blind until this file exists."]:[],"",` 2. The headline next step: offer to run a Phase 1 SEO audit on ${tr}`," (homepage + sitemap + robots.txt; the checks live in",` ${b} \u2014 read that first).`," If they say yes, report findings as:"," \u{1F6A8} Biggest Issue \u2192 \u26A0\uFE0F Also Worth Fixing \u2192 \u2705 What's Working",' \u2192 "What do you want to do?" with numbered options.',"",...L?[" 3. This project is connected to the SEOAgent cloud. Each session: run",` ${m("sync")}, triage .seoagent/inbox/, do the work, and`,` ${m("ack <id>")} each completed action.`]:K?[" 3. \u26A0\uFE0F The setup code was for a DIFFERENT site than this project (see",` above). This directory stays ${c.domain}; do not run`,` ${m("sync")} here for the other site. One site per directory:`,` run ${m("init")} --domain <that site> in an empty directory`," of its own \u2014 it is already logged in there."]:D?[" 3. \u26A0\uFE0F The setup code did NOT bind this project (see the error above)."," Do not continue without a successful login \u2014 nothing syncs unbound."," Get a fresh code at seoagent.com/grok, then run:",` ${m("login")} --code <code>`]:[" 3. When it feels right, let them know about the free cloud dashboard and"," offer to start it (their call) \u2014 and that keyword strategy (Phase 2 in"," the skill) is there whenever they want it:",` ${m("login")}`],"","\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"," HUMAN USERS (if you ran this in your terminal, no AI agent)","\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550",""," Open Claude Code in this directory and say:",""," audit my site","",` The skill at ${k} is picked up automatically.`,...M?[` Your site is on ${M.label}, so there is no code to edit here \u2014 this folder`,M.api?` is the workspace. The agent will ask you once for ${M.enableHint}.`:` is the workspace. The agent writes each article for you to paste into ${M.label}.`]:[],...v?[` Symlinked at ${v} \u2192 ${R} so Claude Code finds it too.`]:[],"",...L?[" Cloud dashboard: connected. Pull your first work with:","",` ${m("sync")}`]:K?[` \u26A0\uFE0F That setup code was for another site; this project stays ${c.domain}.`," Give the other site a directory of its own:","",` ${m("init")} --domain <that site>`]:D?[" \u26A0\uFE0F The setup code did not bind this project. Get a fresh code at"," seoagent.com/grok, then run:","",` ${m("login")} --code <code>`]:[" Want the cloud dashboard now? Free account, ~30 seconds:","",` ${m("login")}`],"",...L?[Cl(S,O,c.domain)]:[]].join(`
|
|
90
|
+
`))}function A(e,t,n,r=n){e?e.stop(r):u[t](n)}var cl=[{suffixes:[".wordpress.com"],platform:{id:"wordpress.com",label:"WordPress.com",api:"wordpress",enableHint:"an application password (Users \u2192 Profile \u2192 Application Passwords) or a WordPress.com OAuth token"}},{suffixes:[".wixsite.com",".wix.com"],platform:{id:"wix",label:"Wix",api:"wix",enableHint:"an API key from the Wix API Keys Manager with Blog permissions, plus the site ID"}},{suffixes:[".myshopify.com"],platform:{id:"shopify",label:"Shopify",api:"shopify",enableHint:"a Shopify Admin API access token with blog write scope"}},{suffixes:[".webflow.io"],platform:{id:"webflow",label:"Webflow",api:"webflow",enableHint:"a Webflow site API token with CMS write access"}},{suffixes:[".ghost.io"],platform:{id:"ghost",label:"Ghost",api:"ghost",enableHint:"a Ghost Admin API key"}},{suffixes:[".blogspot.com"],platform:{id:"blogger",label:"Blogger",api:"blogger",enableHint:"Blogger API access"}},{suffixes:[".squarespace.com"],platform:{id:"squarespace",label:"Squarespace",api:null,enableHint:""}},{suffixes:[".weebly.com"],platform:{id:"weebly",label:"Weebly",api:null,enableHint:""}},{suffixes:[".godaddysites.com"],platform:{id:"godaddy",label:"GoDaddy Website Builder",api:null,enableHint:""}},{suffixes:[".carrd.co"],platform:{id:"carrd",label:"Carrd",api:null,enableHint:""}},{suffixes:[".notion.site"],platform:{id:"notion",label:"Notion",api:null,enableHint:""}},{suffixes:[".substack.com"],platform:{id:"substack",label:"Substack",api:null,enableHint:""}},{suffixes:[".medium.com"],platform:{id:"medium",label:"Medium",api:null,enableHint:""}},{suffixes:[".tumblr.com"],platform:{id:"tumblr",label:"Tumblr",api:null,enableHint:""}},{suffixes:[".framer.website",".framer.app"],platform:{id:"framer",label:"Framer",api:null,enableHint:""}}];function ul(e){if(!e)return null;let t=e.trim().toLowerCase().replace(/^sc-domain:/,"").replace(/^https?:\/\//,"").replace(/^www\./,"").replace(/[/?#].*$/,"").replace(/:\d+$/,"");if(!t)return null;for(let{suffixes:n,platform:r}of cl)if(n.some(o=>t.endsWith(o)&&t.length>o.length))return r;return null}var X_=cl.flatMap(({suffixes:e,platform:t})=>e.map(n=>({suffix:n,id:t.id,api:t.api})));function dl(e,t){let n=[` \u{1F3E0} ${e} is hosted on ${t.label} \u2014 there is no repository here and`," none to find; this folder is the workspace. Audit, keywords, strategy"," and writing all work as normal. Delivery differs: read",' references/publishing.md \xA7 "No repo" (next to the skill) BEFORE the'," audit."];return t.api?n.push(` Ask your human once to set up the ${t.label} connector:`,` ${t.enableHint}. Credentials go in .env in this folder, never`," in a tracked file. Posts are created as drafts unless told otherwise."):n.push(` ${t.label} has no public API for posts, so the connector is the`," editor: write each article to .seoagent/content/<slug>.md with the exact"," title, slug, meta description and body, walk your human through pasting"," it, then `seoagent content track --slug <slug> --url <live-url>`."),n}import{randomBytes as em}from"crypto";import{isCancel as tm,spinner as bl,text as nm}from"@clack/prompts";import ye from"picocolors";import{exec as Og}from"child_process";function Ir(e){let t=process.platform==="darwin"?`open "${e}"`:process.platform==="win32"?`start "" "${e}"`:`xdg-open "${e}"`;Og(t,()=>{})}function pl(e,t){if(e>=500)return{status:"error",terminal:!1,message:`HTTP ${e} (server error)`};if(e>=400)return{status:"error",terminal:!0,message:`HTTP ${e} (client error)`};if(!t||typeof t!="object")return{status:"error",terminal:!0,message:`Malformed response: expected an object, got ${typeof t}`};let n=t;if(n.status==="ready"){if(typeof n.user_token=="string"&&typeof n.website_token=="string"){let r=typeof n.email=="string"&&n.email?n.email:void 0,o=typeof n.domain=="string"&&n.domain?n.domain:void 0;return{status:"ready",user_token:n.user_token,website_token:n.website_token,...r?{email:r}:{},...o?{domain:o}:{}}}return{status:"error",terminal:!0,message:"Malformed response: status=ready without user_token+website_token"}}return n.status==="pending"?{status:"pending"}:n.status==="expired"?{status:"expired"}:{status:"error",terminal:!0,message:`Malformed response: unknown status=${JSON.stringify(n.status)}`}}import{existsSync as Lg,mkdirSync as Ng,readFileSync as jg,writeFileSync as Dg}from"fs";import{join as Mg}from"path";var Ug="install-id",Fg=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;function fl(e=z){let t=Mg(e,Ug);try{if(Lg(t)){let r=jg(t,"utf-8").trim();if(Fg.test(r))return r}let n=dt();return Ng(e,{recursive:!0}),Dg(t,n+`
|
|
91
|
+
`,"utf-8"),n}catch{return null}}import{execFileSync as Bg}from"child_process";var Gg=2048,Hg=200,Wg=10,Kg=/^[A-Za-z0-9._-]+$/,qg=/^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/,Vg=new Set(["https:","http:","ssh:","git:","git+ssh:","ssh+git:"]),Yg=/^(?:[^@\s/]+@)?([^:/\s@]+):(?!\/\/)(.+)$/;function gl(e){let t=e.trim();t=t.replace(/[?#].*$/,""),t=t.replace(/^\/+/,"").replace(/\/+$/,""),t=t.replace(/\.git$/i,"");let n=t.split("/").filter(i=>i.length>0);if(n.length<2||n.length>Wg)return null;for(let i of n)if(!Kg.test(i)||i==="."||i==="..")return null;let r=n[n.length-1],o=n.slice(0,-1).join("/");return`${o}/${r}`.length>Hg?null:{owner:o,repo:r}}function ml(e){let t=e.trim().toLowerCase().replace(/\.$/,"");return t.length<2||t.length>253?null:qg.test(t)?t:null}function zg(e){if(typeof e!="string")return null;let t=e.trim();if(!t||t.length>Gg||/\s/.test(t))return null;if(/^[a-z][a-z0-9+.-]*:\/\//i.test(t)){let r;try{r=new URL(t)}catch{return null}if(!Vg.has(r.protocol.toLowerCase()))return null;let o=ml(r.hostname);if(!o)return null;let i=gl(Jg(r.pathname));return i?{host:o,...i}:null}let n=t.match(Yg);if(n){if(n[1].length<2)return null;let r=ml(n[1]);if(!r)return null;let o=gl(n[2]);return o?{host:r,...o}:null}return null}function Jg(e){try{return decodeURIComponent(e)}catch{return e}}function Xg(e){return e?{host:e.host,full_name:`${e.owner}/${e.repo}`}:null}var Qg=1500,Zg=e=>Bg("git",["-C",e,"remote","get-url","origin"],{encoding:"utf8",timeout:Qg,stdio:["ignore","pipe","ignore"],windowsHide:!0,env:{...process.env,GIT_TERMINAL_PROMPT:"0"}});function Pr(e,t=Zg){try{return Xg(zg(t(e)))}catch{return null}}var rm=1500,om=900*1e3,im=2700*1e3;function sm(e){return e?im:om}function am(e){return`Waiting for browser confirmation\u2026 (up to ${Math.round(e/6e4)} minutes \u2014 if you need longer, the page prints a \`seoagent login --code \u2026\` line to run here)`}var hl="Login timed out. If the browser page shows a `seoagent login --code \u2026` line, run that here; otherwise run `seoagent login` again.",lm=3,cm=2e3;function Tr(e,t){if(he())return;let n=$e(e);n&&ze({installId:n,event:t,cliVersion:P})}function um(){return em(16).toString("hex")}function dm(e,t,n){let r=new URLSearchParams({session:t});return n&&n!=="unknown"&&r.set("domain",n),`${e}/cli/auth?${r.toString()}`}function yl(e){let t=e.trim().replace(/^https?:\/\//i,"").replace(/^www\./i,"").replace(/\/.*$/,"");return!t||!/^[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/i.test(t)?null:t.toLowerCase()}async function pm(e,t,n){if(!e||e.website_token===t)return null;try{let r=await n(e);return r.verdict!=="unverified"?r.cloudDomain:null}catch{return null}}async function fm(e,t){try{let n=`${e}/api/cli/auth/poll?session=${encodeURIComponent(t)}`,r=await fetch(n,{headers:{Accept:"application/json"},signal:AbortSignal.timeout(1e4)}),o=null;try{o=await r.json()}catch{}return pl(r.status,o)}catch(n){return{status:"error",terminal:!1,message:n.message||"network error"}}}function kl(e){return new Promise(t=>setTimeout(t,e))}async function xl(e,t,n,r){let o=B(),i=await pm(o,e.website_token,c=>et({apiBase:c.api_base,authHeader:`Bearer ${c.user_token}:${c.website_token}`,localDomain:""}));i&&o&&Mo(o,i),Mo({user_token:e.user_token,website_token:e.website_token,api_base:t},e.domain||n);let s=e.email&&e.domain?`Logged in as ${e.email} (${e.domain}). Future SEO work in this repo will sync to your dashboard.`:e.email?`Logged in as ${e.email}. Future SEO work in this repo will sync to your dashboard.`:"Logged in. Future SEO work in this repo will sync to your dashboard.",a=e.email&&e.domain?`Logged in as ${ye.bold(e.email)} (${ye.cyan(e.domain)}).`:e.email?`Logged in as ${ye.bold(e.email)}.`:"Logged in.";A(r,"success",s,ye.green(a)),r&&u.info("Future SEO work in this repo will sync to your dashboard.");let l=tt().filter(c=>c!==Te(e.domain||n));l.length>0&&u.info(`Also logged in to: ${l.join(", ")}. Each project auto-selects its own site \u2014 no need to log out first.`)}function gm(e,t,n){return{code:e,...t&&t!=="unknown"?{domain:t}:{},...n?{install_id:n.installId,cli_version:n.cliVersion,platform:process.platform,...n.command?{command:n.command}:{},...n.installScope?{install_scope:n.installScope}:{},...n.repo?{repo:{host:n.repo.host,full_name:n.repo.full_name}}:{}}:{}}}function ii(e,t){if(he())return null;let n=$e(e);if(n){let o=Pr(e);return{installId:n,cliVersion:P,command:t,installScope:"project",...o?{repo:o}:{}}}let r=fl();return r?{installId:r,cliVersion:P,command:t,installScope:"machine"}:null}async function mm(e,t,n,r){try{let o=await fetch(`${e}/api/cli/auth/redeem`,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify(gm(t,n,r)),signal:AbortSignal.timeout(1e4)}),i=null;try{i=await o.json()}catch{}return Js(o.status,i)}catch(o){return{status:"error",terminal:!1,message:o.message||"network error"}}}var hm=new Set(["code_already_used","code_revoked","code_expired"]);function ym(e,t){return`That setup code was for ${e}, but this directory is the ${t} project \u2014 it stays ${t}, and running \`${m("sync")}\` here syncs ${t}. Credentials for ${e} are saved. One site per directory: run \`${m("init")} --domain ${e}\` in an empty directory of its own (or in the repo that builds ${e}) \u2014 it is already logged in there.`}var wm="Scheduled runs on a fresh machine: store the output of `seoagent whoami --env` as the task's SEOAGENT_AUTH secret. A setup code works once and cannot be guessed; never reuse or invent one.";function si(e){return e!=="ready"&&e!=="already"}function bm(e,t,n){if(!e||!n||t&&t!=="unknown")return!1;try{if(!X(e,{domain:n}))return!1}catch{return!1}return u.info(`Saved domain: ${n} \u2192 .seoagent/project.md (from the setup code).`),!0}async function ai(e,t,n,r,o,i={}){if(!dr.test(e))return u.warn("That does not look like a setup code (expected `sa_` + 24 hex characters). Generate one at seoagent.com/grok."),"invalid";let s=!!(i.siteAlreadyBound&&n),a=s?null:n,c=!!process.stdout.isTTY?bl():null;c?.start(ye.dim("Redeeming setup code\u2026"));let d="network error";for(let p=0;p<lm;p++){p>0&&await kl(cm);let g=await mm(t,e,a,r);if(g.status==="ready")return await xl(g,t,n,c),s&&g.domain&&Te(g.domain)!==Te(n)?(u.warn(ym(g.domain,n)),"other-site"):(bm(o,n,g.domain),u.info(wm),i.onBound?.({email:g.email,domain:g.domain}),"ready");if(g.terminal)return s&&g.code&&hm.has(g.code)?(A(c,"info",`Already logged in for ${n} \u2014 that setup code is spent (each works once); nothing to do.`,ye.dim(`Already logged in for ${n} \u2014 setup code spent, nothing to do.`)),"already"):(A(c,"warn",g.message,ye.red(g.message)),"rejected");d=g.message}return A(c,"warn",`Could not reach the server (${d}). Check your connection and run \`${m("login")} --code <code>\` again.`,ye.yellow("Could not reach the server.")),c&&u.warn(`Run \`${m("login")} --code <code>\` again to retry.`),"unreachable"}function Or(e){return e&&e!=="unknown"?hr(e):!!B()}function wl(){return`Next: run \`${m("sync")}\` in this repo \u2014 it pulls your strategy, briefs and inbox into .seoagent/. Nothing syncs until it runs.`}async function fn(e={}){let t=process.cwd(),n=C(t),r=(e.apiBase||U.BASE||qe).replace(/\/$/,""),o=n?.domain??null;if(o==="unknown"&&process.stdin.isTTY){let y=await nm({message:".seoagent/project.md has `domain: unknown`. What is your site's URL?",placeholder:"example.com",validate(k){if(!String(k??"").trim())return"A site URL is required.";if(!yl(String(k)))return"Please enter a valid domain like `example.com`."}});if(tm(y)){u.warn("Login cancelled.");return}let w=yl(String(y));if(w){o=w;try{X(t,{domain:w}),u.success(`Saved domain: ${w} \u2192 .seoagent/project.md`)}catch{}}}let i=(e.code??process.env.SEOAGENT_SETUP_CODE??"").trim(),s=o&&o!=="unknown";if(!i&&Or(o)){let y=s?` for ${o}`:"";u.info(`Already logged in${y}. To switch accounts${y}, run \`${m("logout")}\` first.`);return}if(i){let y=await ai(i,r,o,ii(t,"login"),t,{siteAlreadyBound:!!s&&Or(o)});si(y)?process.exitCode=1:y==="ready"&&u.info(wl());return}let a=um(),l=dm(r,a,o);u.message("Opening seoagent.com to connect this CLI to your account..."),u.info(`If the browser does not open, visit: ${l}`),Ir(l),u.info('In your browser: sign in (if needed) and click "Connect this CLI" to finish.');let c=!!process.stdout.isTTY,d=c?bl():null,p=sm(c),g=am(p);d?d.start(ye.dim(g)):u.info(g);let f=Date.now()+p,h=!0;for(;Date.now()<f;){h||await kl(rm),h=!1;let y=await fm(r,a);if(y.status==="ready"){await xl(y,r,o,d),Tr(t,"login_browser_ok"),u.info(wl());return}if(y.status==="expired"){Tr(t,"login_browser_expired"),A(d,"warn",`Session expired. Run \`${m("login")}\` again.`,ye.red("Session expired.")),d&&u.warn(`Run \`${m("login")}\` again.`);return}if(y.status==="error"){if(y.terminal){A(d,"warn",`Login failed: ${y.message}. Run \`${m("login")}\` again.`,ye.red(`Login failed: ${y.message}.`)),d&&u.warn(`Run \`${m("login")}\` again.`),Tr(t,"login_browser_failed");return}continue}}A(d,"warn",hl,ye.yellow("Login timed out.")),d&&u.warn(hl),Tr(t,"login_browser_timeout")}function gn(e={}){let t=e.all?null:C(process.cwd()),n=!e.all&&t?.domain&&t.domain!=="unknown"?t.domain:null;if(!oa(e.all?null:n)){u.info(n?`You were not logged in for ${n}.`:"You were not logged in.");return}u.success(n?`Logged out of ${n}.`:"Logged out.");let o=tt();o.length>0&&u.info(`Still logged in to: ${o.join(", ")}. Run \`${m("logout")} --all\` to log out of everything.`)}function Lr(e,t=process.env){return(e.code??t.SEOAGENT_SETUP_CODE??"").trim()}function $m(e,t=m("init")){return e?`${t} --code ${e}`:t}async function El(e,t,n){if(!n)return{outcome:"none",who:null};let r=t!==_e,o=(U.BASE||qe).replace(/\/$/,""),i=null;return{outcome:await ai(n,o,r?t:null,ii(e,"init"),e,{siteAlreadyBound:r&&Or(t),onBound:a=>{i=a}}),who:i}}function Cl(e,t,n){let r=t?.domain||(n!==_e?n:null);return e==="ready"?t?.email&&r?`\u2713 Logged in as ${t.email} (${r}) \u2014 this project is bound.`:t?.email?`\u2713 Logged in as ${t.email} \u2014 this project is bound.`:r?`\u2713 Logged in (${r}) \u2014 this project is bound.`:"\u2713 Logged in \u2014 this project is bound.":e==="already"?r?`\u2713 Already logged in for ${r} \u2014 this project is bound.`:"\u2713 Already logged in \u2014 this project is bound.":null}var _l=[{value:"saas",label:"SaaS / App"},{value:"service",label:"Service business"},{value:"product",label:"E-commerce / Product"},{value:"content",label:"Content / Blog"},{value:"marketplace",label:"Marketplace"},{value:"tool",label:"Tool / Utility"},{value:"nonprofit",label:"Nonprofit / Community"},{value:"unknown",label:"Not sure"}],_e="unknown",Sm=new Set(["saas","service","product","content","marketplace","tool","app","nonprofit","community","unknown"]);function _m(e){return e.field==="context"?`${e.detail} \u2014 ${e.source}`:e.field==="domain"?`Domain: ${e.detail} \u2014 ${e.source}`:`Site type: ${e.detail} \u2014 ${e.source}`}function Nr(e){if(!e?.trim())return null;let t=e.trim().toLowerCase();return Sm.has(t)?t:null}function Al(e,t){t.agentsMdPath&&u.info(`Added a SEOAgent section to ${Dt(e,t.agentsMdPath)} so agents that read it (Codex, others) find the skill.`),t.cursorRulePath&&u.info(`Wrote ${Dt(e,t.cursorRulePath)} so Cursor agents find the skill.`)}async function mn(e={}){let t=process.cwd();if(Ks(t)){let l=C(t),c=It(t);ht(t),X(t,{skill_version:P}),yt(t);let d=Dt(t,c.skillFile),p=c.claudeSymlink?` (symlinked at ${Dt(t,c.claudeSymlink)})`:"",g=l?.skill_version&&l.skill_version!==P?`${l.skill_version} \u2192 ${P}`:P;u.success(`Refreshed the SEOAgent skill (${g}).`),u.info("Your `.seoagent/` knowledge was left untouched."),u.info(`Skill at ${d}${p} \u2014 agents read this file at session start.`),Al(t,c),u.info(`Run \`${m("status")}\` to see your project, or \`${m("uninstall")}\` to start fresh.`);let f=Lr(e);if(!he()){let k=$e(t);k&&await ze({installId:k,event:"reinit",cliVersion:P,codeHint:pr(f),meta:No(f)})&&u.info(Lo)}let{outcome:h,who:y}=await El(t,l?.domain??_e,f);(h==="rejected"||h==="unreachable"||h==="invalid")&&u.warn(Rm()),h!=="none"&&si(h)&&(process.exitCode=1);let w=Cl(h,y,l?.domain??_e);w&&u.success(w);return}let n=!!e.yes||!process.stdin.isTTY,r=Za(t);if(r.isWorktree&&!e.allowWorktree){let l=$m(Lr(e)),c=r.mainRoot?`cd ${r.mainRoot} && ${l}`:`run ${l} from the main repository root`;n&&(u.warn("This directory is a temporary git worktree \u2014 a `.seoagent/` created here is deleted with the worktree, and images/un-synced edits cannot be recovered from the cloud."),u.info(`Init at the main checkout instead: ${c}`),u.info("To init here anyway (not recommended), pass --allow-worktree."),process.exit(1)),vl("SEOAgent \u2014 AI SEO Agent");let d=await Sl({message:"This looks like a temporary git worktree \u2014 .seoagent/ created here is lost when the worktree is removed. Create it here anyway?",initialValue:!1});(xt(d)||!d)&&(vt(`Good call. ${c}`),process.exit(0))}else n||vl("SEOAgent \u2014 AI SEO Agent");let o=ja(t,l=>u.step(l)),i=()=>{if(o.evidence.length!==0){u.info("Inferred from your project:");for(let l of o.evidence)u.info(` \u2022 ${_m(l)}`)}},s=e.domain?.trim()||process.env.SEOAGENT_DOMAIN?.trim()||o.domain||null,a=Nr(e.siteType)||Nr(process.env.SEOAGENT_SITE_TYPE)||o.siteType||null;if(n){e.siteType?.trim()&&Nr(e.siteType)===null&&(u.warn("Invalid --site-type. Use: saas, service, product, content, marketplace, tool, nonprofit, unknown, app, community."),process.exit(1)),process.env.SEOAGENT_SITE_TYPE?.trim()&&Nr(process.env.SEOAGENT_SITE_TYPE)===null&&(u.warn("Invalid SEOAGENT_SITE_TYPE environment value."),process.exit(1)),i(),s||(u.info("Couldn't detect your site URL from this repo \u2014 scaffolding anyway; you'll be prompted to supply it."),s=_e),a||(a="unknown"),await Rl(t,s,a,Lr(e));return}if(i(),!s){let l=await $l({message:"Website domain",placeholder:"example.com",validate:c=>c.trim()?void 0:"Domain is required"});xt(l)&&(vt("Cancelled"),process.exit(0)),s=String(l)}if(!a){let l=await li({message:"What kind of site is this?",options:[..._l]});xt(l)&&(vt("Cancelled"),process.exit(0)),a=l}for(;;){let l=await Sl({message:`Create .seoagent for ${s} (${a})?`});if(xt(l)&&(vt("Cancelled"),process.exit(0)),l)break;let c=await li({message:"What should we change?",options:[{value:"domain",label:"Domain"},{value:"site_type",label:"Site type"},{value:"abort",label:"Cancel setup"}]});if((xt(c)||c==="abort")&&(vt("Cancelled"),process.exit(0)),c==="domain"){let d=await $l({message:"Website domain",placeholder:"example.com",validate:p=>p.trim()?void 0:"Domain is required"});xt(d)&&(vt("Cancelled"),process.exit(0)),s=String(d)}else{let d=await li({message:"What kind of site is this?",options:[..._l]});xt(d)&&(vt("Cancelled"),process.exit(0)),a=d}}await Rl(t,s,a,Lr(e))}async function Rl(e,t,n,r){let o=vm();o.start("Setting up your SEO project");let i=Ca(e),s=ot(e),a=cr(e),l=dt(),c={domain:t,site_type:n,language:As(t),initialized_at:new Date().toISOString(),seoagent_version:P,skill_version:P,install_id:l,...i.cms!=="none"?{cms:i.cms}:{},...i.blog_path?{blog_path:i.blog_path}:{},...s.provider!=="none"?{image_provider:s.provider}:{},...a.dir?{public_dir:a.dir}:{}},d=he(),p=d?Promise.resolve(!1):ze({installId:l,event:"init",cliVersion:P,codeHint:pr(r),meta:No(r)}),g=d||t===_e?Promise.resolve(null):ia({domain:t,installId:l}),f=B(t),h=f&&t!==_e?et({apiBase:f.api_base,authHeader:`Bearer ${f.user_token}:${f.website_token}`,localDomain:t}).catch(()=>null):Promise.resolve(null);Vs(e),qs(e,c),yt(e),_a(e,n),fa(e);let y=Ar(e,t!==_e?t:null),w=It(e),k=Dt(e,w.skillFile),R=km(k),b=`${R}/references/audit-checks.md`,v=w.claudeSymlink?Dt(e,w.claudeSymlink):void 0;ht(e);let{outcome:S,who:O}=await El(e,t,r),L=S==="ready"||S==="already";if(S==="ready"&&t===_e){let Z=C(e)?.domain;Z&&Z!==_e&&(t=Z,c.domain=Z)}let I=t!==_e,D=S==="rejected"||S==="unreachable"||S==="invalid",K=S==="other-site";(D||K)&&(process.exitCode=1);let ee={pages:[],dynamicCount:0},Y={wrote:!1,added:0,total:0,merged:!1,file:"pages.md"};try{ee=je(e,I?t:null),Y=$r(e,ee.pages)}catch{}if(o.stop(I?`Created .seoagent/ project for ${c.domain}`:"Created .seoagent/ project (site URL not detected yet \u2014 see step 2)"),i.cms!=="none"){let Z=i.evidence.map(q=>`${q.detail} (${q.source})`).slice(0,2).join(", ");u.info(`CMS detected: ${i.cms} \u2014 ${Z}`)}if(i.blog_path&&u.info(`Blog route: ${i.blog_path}`),a.dir&&u.info(`Static files: ${a.dir}/ (${a.evidence}) \u2014 your llms.txt and OKF bundle publish there.`),y.created.length>0&&u.info("Scaffolded .seoagent/okf/ \u2014 the AI-readable knowledge bundle answer engines cite. The audit will offer to fill it."),s.provider!=="none"&&u.info(`Image generation: ${s.provider} (detected ${s.matched_key}) \u2014 articles can get AI hero images.`),ee.pages.length>0){let Z=Y.merged?Y.added>0?`Re-scanned your codebase: +${Y.added} new page${Y.added===1?"":"s"} merged into .seoagent/pages.md (${Y.total} total)`:`Re-scanned your codebase: .seoagent/pages.md already current (${Y.total} pages)`:`Found ${ee.pages.length} page${ee.pages.length===1?"":"s"} in your codebase \u2192 .seoagent/pages.md`;u.info(Z+(ee.dynamicCount>0?` (plus ${ee.dynamicCount} dynamic route template${ee.dynamicCount===1?"":"s"} \u2014 the audit will discover their instances)`:""))}Al(e,w);let[er,He,So]=await Promise.all([p,g,h]);if(So?.verdict==="mismatch"&&u.warn(`Heads up: the credentials bound to ${t} are for a SEOAgent cloud project on ${So.cloudDomain} instead. This workspace stays LOCAL-ONLY \u2014 nothing will be pushed to or pulled from that project. To connect ${t} to the cloud, run \`${m("login")}\` from this directory and pick (or create) ${t} \u2014 the CLI can stay logged into both sites at once, no need to log out first.`),He?.status==="data"){let Z=He.difficulty!=null?`, difficulty ${He.difficulty}/100`:"";u.info(`Free keyword peek \u2014 "${He.keyword}": ${He.volume.toLocaleString("en-US")} searches/mo${Z}. You get ~10 free peeks/day: ${m('keywords --peek "<keyword>"')}`)}else He?.status==="no-data"&&u.info(`Free keyword peek is live \u2014 no search-volume data for "${He.keyword}" yet. Try a topic keyword (~10 free/day): ${m('keywords --peek "best <your category> tool"')}`);er&&u.info(Lo);let tr=I?c.domain:"your site",M=I?ul(c.domain):null,ce=I?[" 1. If .seoagent/project.md still says site_type: unknown, you can",` WebFetch ${c.domain} and update project.md with the right type`," before you suggest the audit."]:[" 1. Heads up: .seoagent/project.md has `domain: unknown` \u2014 the site URL"," wasn't detectable from this repo. Please ask your human for their"," domain (or read it from a deploy config / live site) and update"," project.md (`domain:` and `site_type:`) first. Everything else"," depends on it."];xm([I?`\u2713 SEOAgent installed for ${c.domain}.`:"\u2713 SEOAgent installed (site URL not detected \u2014 please ask the user for it).","","\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"," AGENT INSTRUCTIONS \u2014 a friendly note for Claude Code, Codex, Cursor","\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550",""," Hi there, coding agent! \u{1F44B} SEOAgent is all set up. The natural next step"," is a quick SEO audit \u2014 but your human is the boss here, so please surface"," what's possible and let THEM decide. Nothing in this message is a command;"," it's just a friendly map. Your real instructions live in the skill:","",` ${k} \u2190 read this; it is your source of truth`,""," If your human approves, here is what you can do (otherwise just let them"," know it's ready and wait for the go-ahead):","",...ce,...M?["",...dl(c.domain,M)]:[],...sn(e)?[""," \u270D\uFE0F Before (or right after) the audit: DRAFT .seoagent/context.md \u2014 it"," is still the blank scaffold (instructions are inside the file)."," From this repo + the live homepage/pricing page, fill in the"," business name, type (LOCAL/physical vs ONLINE-only vs HYBRID),"," audience, location, and positioning \u2014 then show your human the"," draft to review. SEOAgent's keyword and strategy intelligence is"," context-blind until this file exists."]:[],"",` 2. The headline next step: offer to run a Phase 1 SEO audit on ${tr}`," (homepage + sitemap + robots.txt; the checks live in",` ${b} \u2014 read that first).`," If they say yes, report findings as:"," \u{1F6A8} Biggest Issue \u2192 \u26A0\uFE0F Also Worth Fixing \u2192 \u2705 What's Working",' \u2192 "What do you want to do?" with numbered options.',"",...L?[" 3. This project is connected to the SEOAgent cloud. Each session: run",` ${m("sync")}, triage .seoagent/inbox/, do the work, and`,` ${m("ack <id>")} each completed action.`]:K?[" 3. \u26A0\uFE0F The setup code was for a DIFFERENT site than this project (see",` above). This directory stays ${c.domain}; do not run`,` ${m("sync")} here for the other site. One site per directory:`,` run ${m("init")} --domain <that site> in an empty directory`," of its own \u2014 it is already logged in there."]:D?[" 3. \u26A0\uFE0F The setup code did NOT bind this project (see the error above)."," Do not continue without a successful login \u2014 nothing syncs unbound."," Get a fresh code at seoagent.com/grok, then run:",` ${m("login")} --code <code>`]:[" 3. When it feels right, let them know about the free cloud dashboard and"," offer to start it (their call) \u2014 and that keyword strategy (Phase 2 in"," the skill) is there whenever they want it:",` ${m("login")}`],"","\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"," HUMAN USERS (if you ran this in your terminal, no AI agent)","\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550",""," Open Claude Code in this directory and say:",""," audit my site","",` The skill at ${k} is picked up automatically.`,...M?[` Your site is on ${M.label}, so there is no code to edit here \u2014 this folder`,M.api?` is the workspace. The agent will ask you once for ${M.enableHint}.`:` is the workspace. The agent writes each article for you to paste into ${M.label}.`]:[],...v?[` Symlinked at ${v} \u2192 ${R} so Claude Code finds it too.`]:[],"",...L?[" Cloud dashboard: connected. Pull your first work with:","",` ${m("sync")}`]:K?[` \u26A0\uFE0F That setup code was for another site; this project stays ${c.domain}.`," Give the other site a directory of its own:","",` ${m("init")} --domain <that site>`]:D?[" \u26A0\uFE0F The setup code did not bind this project. Get a fresh code at"," seoagent.com/grok, then run:","",` ${m("login")} --code <code>`]:[" Want the cloud dashboard now? Free account, ~30 seconds:","",` ${m("login")}`],"",...L?[Cl(S,O,c.domain)]:[]].join(`
|
|
92
92
|
`))}function Rm(){return`The setup code did not bind this project. Do not continue without a successful login \u2014 get a fresh code at seoagent.com/grok, then run \`${m("login")} --code <code>\`.`}import{existsSync as Tl}from"fs";import{intro as Cm,outro as Am,confirm as Im,isCancel as Pm,cancel as Tm}from"@clack/prompts";import{existsSync as jr,rmSync as Em}from"fs";import{join as Dr}from"path";function Il(e,t={}){let n=[],r=Dr(e,x);jr(r)&&n.push({label:".seoagent/ \u2014 project state + all generated work",path:r,kind:"dir"});let o=Dr(e,Ke.CLAUDE);jr(o)&&n.push({label:".claude/skills/seoagent/ \u2014 installed skill bundle",path:o,kind:"dir"});let i=Dr(e,Ke.AGENTS);return jr(i)&&n.push({label:".agents/skills/seoagent/ \u2014 installed skill bundle",path:i,kind:"dir"}),ma(e)&&n.push({label:".claude/settings.json \u2014 sync hook entry (other settings preserved)",path:Dr(e,".claude","settings.json"),kind:"hook"}),t.global&&jr(z)&&n.push({label:"~/.config/seoagent/ \u2014 login credentials + sync state (all projects)",path:z,kind:"global"}),n}function Pl(e,t){for(let n of t)n.kind==="hook"?ha(e):Em(n.path,{recursive:!0,force:!0})}async function hn(e={}){let t=process.cwd(),n=Il(t,{global:e.global});if(n.length===0){u.info("No SEOAgent install found in this directory \u2014 nothing to remove."),!e.global&&Tl(z)&&u.info("Your seoagent.com login is still stored at ~/.config/seoagent/. Re-run with --global to wipe it too.");return}let r=!!e.yes||!process.stdin.isTTY,o=n.map(s=>` \u2022 ${s.label}`).join(`
|
|
93
93
|
`);if(!r){Cm("SEOAgent \u2014 uninstall"),u.message(`This will remove:
|
|
94
94
|
${o}`);let s=await Im({message:"Remove these and start fresh?"});if(Pm(s)||!s){Tm("Cancelled \u2014 nothing was removed.");return}}Pl(t,n);let i=n.map(s=>` \u2713 ${s.label}`).join(`
|
|
@@ -110,7 +110,7 @@ ${r.join(`
|
|
|
110
110
|
${r.join(`
|
|
111
111
|
`)}
|
|
112
112
|
---${n[2]??`
|
|
113
|
-
`}`}function Wl(e,t){let n=Ut(e,x,"content"),r=$n(n),o=new Set;t.content_dir&&o.add(t.content_dir);for(let c of r)c.source==="repo"&&c.repo_path&&o.add(mh(c.repo_path));if(o.size===0)return{tracked:[],published:[]};let i=[...o].flatMap(c=>fi(e,c)),s=gi(i,r,mi(e)),a=[];for(let{pointer:c,article:d}of s.toPublish)try{let p=yh(pi(c.file,"utf-8"),{repoPath:d.path,canonical:Ft(c.slug,{domain:t.domain,blogPath:t.blog_path})});Ul(c.file,p,"utf-8"),a.push(c.slug)}catch{}if(s.toTrack.length===0)return{tracked:[],published:a};gh(n,{recursive:!0});let l=[];for(let c of s.toTrack){let d=Sn({slug:c.slug,title:c.title,canonical:Ft(c.slug,{domain:t.domain,blogPath:t.blog_path}),status:"published",page_type:c.page_type,source:"repo",repo_path:c.path,tracked_at:new Date().toISOString()});Ul(Ut(n,`${c.slug}.md`),d,"utf-8"),l.push(c.slug)}return{tracked:l,published:a}}import{existsSync as
|
|
113
|
+
`}`}function Wl(e,t){let n=Ut(e,x,"content"),r=$n(n),o=new Set;t.content_dir&&o.add(t.content_dir);for(let c of r)c.source==="repo"&&c.repo_path&&o.add(mh(c.repo_path));if(o.size===0)return{tracked:[],published:[]};let i=[...o].flatMap(c=>fi(e,c)),s=gi(i,r,mi(e)),a=[];for(let{pointer:c,article:d}of s.toPublish)try{let p=yh(pi(c.file,"utf-8"),{repoPath:d.path,canonical:Ft(c.slug,{domain:t.domain,blogPath:t.blog_path})});Ul(c.file,p,"utf-8"),a.push(c.slug)}catch{}if(s.toTrack.length===0)return{tracked:[],published:a};gh(n,{recursive:!0});let l=[];for(let c of s.toTrack){let d=Sn({slug:c.slug,title:c.title,canonical:Ft(c.slug,{domain:t.domain,blogPath:t.blog_path}),status:"published",page_type:c.page_type,source:"repo",repo_path:c.path,tracked_at:new Date().toISOString()});Ul(Ut(n,`${c.slug}.md`),d,"utf-8"),l.push(c.slug)}return{tracked:l,published:a}}import{existsSync as $t,mkdirSync as wh,readdirSync as hi,readFileSync as ql,unlinkSync as Vl,writeFileSync as Br}from"fs";import{join as we}from"path";import Q from"picocolors";var yi=".declined.json",bh=15,kh=5;function wi(e){if(!Array.isArray(e))return[];let t=[];for(let n of e){if(!n||typeof n!="object")continue;let r=n;if(typeof r.id!="number"||typeof r.action_type!="string"||typeof r.reason!="string"||r.reason.trim().length===0)continue;let o=i=>typeof i=="string"&&i.length>0?i:null;t.push({id:r.id,action_type:r.action_type,title:o(r.title),check_id:o(r.check_id),page_url:o(r.page_url),issue:o(r.issue),reason:r.reason,declined_at:o(r.declined_at)})}return t}function Kl(e){if(!e)return null;try{let t=new URL(e),n=t.pathname.replace(/\/+$/,"")||"/";return`${t.hostname.toLowerCase()}${n}${t.search}`}catch{return e.trim().toLowerCase().replace(/\/+$/,"")||null}}function xh(e){let t=e.payload??{},n=t.page_url??t.url??t.originalUrl;return typeof n=="string"?n:null}function vh(e){let t=e.payload?.check_id;return typeof t=="string"?t:null}function $h(e,t){if(t.length===0)return[];let n=Kl(xh(e)),r=vh(e),o=e.action_type!=="cli_technical_fix"&&n===null;return t.filter(s=>!!(n!==null&&Kl(s.page_url)===n||r!==null&&s.check_id===r||o&&s.action_type===e.action_type&&!s.page_url)).slice().sort((s,a)=>(a.declined_at??"").localeCompare(s.declined_at??"")).slice(0,kh)}function Yl(e){let t=e.declined_at?` (${e.declined_at.slice(0,10)})`:"",n=[e.action_type,e.check_id??e.issue].filter(Boolean).map(o=>`\`${o}\``).join(" "),r=e.page_url?` \u2014 ${e.page_url}`:"";return`- **#${e.id}** ${n}${r}${t}: "${e.reason.replace(/\s+/g," ").trim()}"`}function Sh(e,t){let n=$h(e,t);if(n.length===0)return"";let r=n[0];return`
|
|
114
114
|
|
|
115
115
|
## \u26A0\uFE0F Related past declines on this site \u2014 read before investigating
|
|
116
116
|
|
|
@@ -140,7 +140,7 @@ Each decline is remembered here, and the underlying issue is not proposed again.
|
|
|
140
140
|
|
|
141
141
|
${t.map(Yl).join(`
|
|
142
142
|
`)}${n}
|
|
143
|
-
`}function bi(e){let t=we(De(e),yi);if(
|
|
143
|
+
`}function bi(e){let t=we(De(e),yi);if(!$t(t))return[];try{return wi(JSON.parse(ql(t,"utf-8")))}catch{return[]}}var Gr={};function ki(e){let t=new Map;try{for(let n of $n(we(e,x,"content")))t.set(n.slug,{status:n.status,repo_path:n.repo_path})}catch{}return t}function _h(e){let t=we(e,x);try{let n=we(t,"strategy","keywords");if($t(n)){let r=hi(n).filter(o=>o.endsWith(".md")).sort().map(o=>`strategy/keywords/${o}`);if(r.length>0)return r}if($t(we(t,"keywords.md")))return["keywords.md"]}catch{}return[]}function Jl(e){let t=new Map;for(let n of e){if(n.action_type!=="cli_draft_ready")continue;let r=n.payload||{},o=r.article_slug??n.subject_id??null,i=r.path??(o?`content/${o}.md`:null);i&&t.set(i,[...t.get(i)??[],n.id])}return t}function De(e){return we(e,x,"inbox")}function Xl(e){return`${e.action_type}-${e.id}.md`}function Rh(e,t){let n=0;if(!$t(e))return 0;for(let r of hi(e)){if(!r.endsWith(".md")||r==="README.md")continue;let o=r.match(/^(?:.+)-(\d+)\.md$/);if(!o)continue;let i=Number.parseInt(o[1],10);if(!Number.isNaN(i)&&!t.has(i))try{Vl(we(e,r)),n++}catch{}}return n}function Fr(e,t=0){return typeof e=="number"&&Number.isFinite(e)?e.toFixed(t):"n/a"}function Eh(e){let t=e.payload||{},n=t.slug??"",r=t.originalTitle??null,o=t.originalUrl??null,i=t.rawPath??null,s=t.ageDays,a=t.metrics??{},l=(()=>{try{return o?new URL(o).pathname:null}catch{return null}})();return`---
|
|
144
144
|
action_id: ${e.id}
|
|
145
145
|
action_type: ${e.action_type}
|
|
146
146
|
source: repo
|
|
@@ -960,7 +960,7 @@ For each file, read it, take the action it describes, then run \`seoagent ack <a
|
|
|
960
960
|
|
|
961
961
|
If you're using Claude Code, just open this directory and ask "process the inbox" \u2014 the
|
|
962
962
|
skill knows what to do.
|
|
963
|
-
${r}`}function Ee(e){let t=De(e);if(
|
|
963
|
+
${r}`}function Ee(e){let t=De(e);if(!$t(t))return[];let n=[];for(let r of hi(t)){if(!r.endsWith(".md")||r==="README.md")continue;let o=r.match(/^(.+)-(\d+)\.md$/);if(!o)continue;let i=o[1],s=Number.parseInt(o[2],10);if(!Number.isFinite(s))continue;let a="no title",l=null,c=null,d=null,p=null,g=null,f=null;try{let h=ql(we(t,r),"utf-8"),y=h.match(/^---\n([\s\S]*?)\n---/);if(y){let R=y[1];l=Bt(R,"severity"),c=Bt(R,"issue"),d=Bt(R,"page_url");let b=Bt(R,"priority");(b==="high"||b==="medium"||b==="low")&&(p=b),g=Bt(R,"impact_estimate"),f=Bt(R,"effort_estimate")}let k=h.replace(/^---\n[\s\S]*?\n---\n+/,"").match(/^#\s+(.+)$/m);k&&(a=k[1].trim())}catch{}n.push({id:s,action_type:i,title:a,filename:r,severity:l,issue:c,page_url:d,priority:p,impact_estimate:g,effort_estimate:f})}return n.sort((r,o)=>r.id-o.id)}function Gt(e){let t=e.issue??e.action_type.replace(/^cli_/,"").replace(/_/g,"-"),n=Xh[e.severity??""]??Q.dim,r=e.severity??"unknown",o=[Q.dim("\u25B8"),Q.bold(`id ${e.id}`),Q.dim("\xB7"),Q.cyan(t),n(r)];if(e.priority){let i=Qh[e.priority];o.push(Q.dim("\xB7"),i(`p:${e.priority}`))}return e.impact_estimate&&o.push(Q.dim("\xB7"),Q.green(`\u21E7 ${e.impact_estimate}`)),e.effort_estimate&&o.push(Q.dim("\xB7"),Q.dim(`\u23F1 ${e.effort_estimate}`)),o.push(Q.dim("\u2014"),e.title),o.join(" ")}var Xh={critical:e=>Q.red(Q.bold(e)),high:e=>Q.red(e),medium:e=>Q.yellow(e),low:e=>Q.dim(e)},Qh={high:e=>Q.red(Q.bold(e)),medium:e=>Q.yellow(e),low:e=>Q.dim(e)};function Bt(e,t){let n=e.match(new RegExp(`^${t}:[ \\t]*([^\\n]*?)[ \\t]*$`,"m"));if(!n)return null;let r=n[1].replace(/^["']|["']$/g,"").trim();return!r||r==="null"||r==="~"?null:r}function Wr(e){let t=De(e);if(!$t(t))return;let n=Ee(e),r=n.length===0?`# SEOAgent Inbox
|
|
964
964
|
|
|
965
965
|
No pending actions. Run \`seoagent sync\` later to check for new ones.
|
|
966
966
|
`:`# SEOAgent Inbox
|
|
@@ -976,22 +976,22 @@ For each file, read it, take the action it describes, then run \`seoagent ack <a
|
|
|
976
976
|
If you're using Claude Code, just open this directory and ask "process the inbox" \u2014 the
|
|
977
977
|
skill knows what to do.
|
|
978
978
|
`;Br(we(t,"README.md"),r+zl(bi(e)),"utf-8")}function Zl(e,t,n=Gr){let r=De(e);wh(r,{recursive:!0});let o=new Set(t.map(p=>p.id)),i=Rh(r,o),s={...n,localPointers:n.localPointers??ki(e),keywordInventory:n.keywordInventory??_h(e)},a=[],l=0,c=n.declined??[];for(let p of t){let g=zh(p,s);if(g===null){l++;continue}Br(we(r,Xl(p)),g+Sh(p,c),"utf-8"),a.push(p)}Br(we(r,"README.md"),Jh(a,l,c),"utf-8");let d=we(r,yi);if(c.length>0)Br(d,JSON.stringify(c,null,2)+`
|
|
979
|
-
`,"utf-8");else if(
|
|
979
|
+
`,"utf-8");else if($t(d))try{Vl(d)}catch{}return{wrote:a.length,removed:i,inboxPath:r,skippedUnknown:l,pendingIds:t.map(p=>p.id),pending:a.map(p=>({id:p.id,action_type:p.action_type,title:p.title}))}}import{existsSync as Wy,readFileSync as Ky,writeFileSync as qy}from"fs";import{join as Vy}from"path";import{createHash as xc}from"crypto";import{existsSync as vc,mkdirSync as Oy,readFileSync as Ly,writeFileSync as bc,rmSync as Ny}from"fs";import{dirname as jy,join as $i,sep as Dy}from"path";function Zh(e){return e.replace(/^\/+/,"").replace(/\\/g,"/")}var ey=[{cls:"project",test:e=>e==="project.md"},{cls:"audit",test:e=>e==="audit/latest.md"||/^audit\/(critical|high|medium|low)\.md$/.test(e)},{cls:"brief",test:e=>/^briefs\/[^/]+\.md$/.test(e)},{cls:"article",test:e=>/^content\/[^/]+\.md$/.test(e)},{cls:"cluster",test:e=>/^strategy\/clusters\/[^/]+\.md$/.test(e)},{cls:"keywords",test:e=>e==="keywords.md"||/^strategy\/keywords\/[^/]+\.md$/.test(e)},{cls:"pages",test:e=>e==="pages.md"||e==="pages-discovered.md"||/^pages\/[^/]+\.md$/.test(e)},{cls:"competitors",test:e=>e==="competitors.md"},{cls:"changelog",test:e=>e==="changelog.md"}];function ec(e,t){let n=Zh(e);for(let{cls:r,test:o}of ey)if(o(n))return t&&(r==="keywords"||r==="pages")?"generated-index":r;return"other"}import{readdirSync as ty,readFileSync as tc,statSync as ny,writeFileSync as ry,existsSync as nc}from"fs";import{join as xi,relative as rc,resolve as oc,sep as gc,posix as lt}from"path";function Ht(e,t){let n=new RegExp(`^- \\*\\*${t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}\\*\\* *:?\\s*(.*)$`,"mi"),o=new RegExp(`^- \\*\\*${t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}:\\*\\*\\s*(.*)$`,"mi").exec(e)??n.exec(e);return o?o[1].trim():null}function Wt(e){return e!==null&&!e.startsWith("_(none")}function oy(e){try{let t=new URL(e).pathname.replace(/\/+$/,"");return t===""?"/":t}catch{return"/"}}function qr(e){let t=/^---\n([\s\S]*?)\n---/.exec(e);return t&&/^source_render:\s*true\s*$/m.test(t[1])?!0:/SOURCE RENDER \(local dev server\)/.test(e)}function iy(e){let t=[],n=e.split(/^## /m).slice(1);for(let r of n){let o=r.split(`
|
|
980
980
|
`,1)[0].trim();if(!/^https?:\/\//i.test(o))continue;let i=r.slice(o.length),s=!/^- Not fetched/m.test(i),a=Ht(i,"JSON-LD @types present")??"",l=Wt(a)?Array.from(a.matchAll(/`([^`]+)`/g)).map(c=>c[1].toLowerCase()):[];t.push({url:o,pathname:oy(o),fetched:s,titlePresent:Wt(Ht(i,"title")),metaDescriptionPresent:Wt(Ht(i,"meta description")),canonicalPresent:Wt(Ht(i,"canonical")),jsonLdTypes:l,openGraphPresent:Wt(Ht(i,"Open Graph")),twitterPresent:Wt(Ht(i,"Twitter"))})}return t}var Kr="(\\d{4}-\\d{2}-\\d{2})";function sy(e){let t=/^---\n([\s\S]*?)\n---/.exec(e),n=t?new RegExp(`^captured_at:\\s*["']?${Kr}`,"m").exec(t[1]):null;if(n)return n[1];let r=new RegExp(`^\\*\\*Crawled origin:\\*\\*.*\\bat ${Kr}`,"m").exec(e);return r?r[1]:null}var ay=["audited_at","generated_at","updated_at","created_at","last_updated","updated","date"];function ly(e){let t=/^---\n([\s\S]*?)\n---/.exec(e);if(!t)return null;let n=null;for(let r of ay){let o=new RegExp(`^${r}:\\s*["']?${Kr}`,"im").exec(t[1]);o&&(n===null||o[1]>n)&&(n=o[1])}return n}var ic=new RegExp(`^\\s*(?:(?:[-*+]|\\d+\\.)\\s+)?\\[?${Kr}\\]?(?:$|[\\s:\u2014\u2013\\-)\\]])`);function cy(e,t,n){let r=ic.exec(e);if(r)return r[1];for(let o=t.length-1;o>=0;o--){let i=ic.exec(t[o]);if(i)return i[1]}return n}var Rn="CORRECTION (verify-recs)",uy=/\b(add(?:ed|ing|s)?|implement(?:ed|ing|s)?|inject(?:ed|ing|s)?|creat(?:e|ed|ing|es)|introduc(?:e|ed|ing|es))\b/i,dy=/\b(none existed|never existed|there (?:was|is|were|are) no|not present|missing|absent|lack(?:s|ed|ing)?|ha[sd] no|have no|did ?not (?:previously )?(?:have|exist|serve)|didn['’]?t (?:previously )?(?:have|exist|serve)|does ?not (?:have|exist|serve)|doesn['’]?t (?:have|exist|serve)|no existing)\b/i,py=/\b(pages|posts|articles|subpages|templates|blog posts|every page|all pages)\b/i,sc=/\b(home ?page)\b/i,mc=["organization","softwareapplication","website","webpage","aboutpage","contactpage","collectionpage","profilepage","article","blogposting","newsarticle","faqpage","qapage","breadcrumblist","product","howto","itemlist","localbusiness","person","review","aggregaterating","videoobject","event","offer","service","recipe","course","jobposting"],fy=/\bjson[\s-]*ld\b|\bstructured\s+data\b|\bschema\b|@type|\bmarkup\b/i,gy=/\bjson[\s-]*ld\b|\bstructured\s+data\b|\bschema(?:\.org)?\b|@type|\bmarkup\b/.source,hc={faq:"faqpage",faqs:"faqpage",howto:"howto",breadcrumb:"breadcrumblist",breadcrumbs:"breadcrumblist"},my=new Set(["a","an","the","any","no","not","its","our","your","their","this","that","these","those","some","all","more","extra","additional","further","other","new","net-new","existing","current","valid","proper","correct","appropriate","relevant","basic","full","complete","rich","minimal","page","page-level","site","site-wide","sitewide","global","homepage","structured","json","ld","json-ld","schema","and","or","of","with","without","has","have","had","is","are","was","were","serve","serves","served","serving","include","includes","including","add","adds","added","adding","implement","implemented","implementing","inject","injected","injecting","create","created","creating","introduce","introduced","introducing","missing","absent","lacks","lacking","plus","use","uses","using","need","needs","needed","ship","ships","shipped","emit","emits","render","renders","output","outputs","in","on","for","to","from","per","via","but","still","already","currently","never","previously","only","just","e.g","i.e","etc"]);function hy(e){let t=[],n=new RegExp(gy,"gi"),r;for(;(r=n.exec(e))!==null;){let o=/([A-Za-z][A-Za-z0-9'\u2019.-]*)\s+$/.exec(e.slice(0,r.index));if(!o)continue;let i=o[1].toLowerCase().replace(/['\u2019]s$/,"").replace(/\.+$/,"");!i||my.has(i)||mc.includes(i)||hc[i]||t.push(i)}return t}function yy(e){let t=e.toLowerCase(),n=new Set;for(let r of mc)new RegExp(`\\b${r}\\b`,"i").test(t)&&n.add(r);for(let[r,o]of Object.entries(hc))new RegExp(`\\b${r}\\b`,"i").test(t)&&n.add(o);return Array.from(n)}var wy=[{id:"title",re:/\btitle\s+tag\b|<title>/i,present:e=>e.titlePresent,label:"a <title> tag"},{id:"meta_description",re:/\bmeta[\s-]*description\b/i,present:e=>e.metaDescriptionPresent,label:"a meta description"},{id:"canonical",re:/\bcanonical\b/i,present:e=>e.canonicalPresent,label:"a canonical tag"},{id:"open_graph",re:/\bopen[\s-]*graph\b|\bog:[a-z]+|\bog\s+tags?\b/i,present:e=>e.openGraphPresent,label:"Open Graph tags"},{id:"twitter_card",re:/\btwitter:[a-z_]+|\btwitter\s+(card|tags?)\b/i,present:e=>e.twitterPresent,label:"a Twitter card"}],by=["page_url","target_url","page","url","resource","canonical","permalink","live_url"],ky=/^[ \t]*(?:[-*+]\s+)?\*\*(?:page(?:\s+to\s+update)?|target\s+page|url|canonical)\s*:?\*\*:?\s*<?([^\s<>)]+)/im;function xy(e){return/(^|[\\/])(briefs|content|articles|pages|posts)[\\/]/.test(e)}function _n(e){return e.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")}function vy(e){let t="";try{t=new URL(e.url).host.replace(/^www\./,"")}catch{}let n=e.pathname==="/"?"":e.pathname;return(n?[_n(t+n),_n(n)]:[_n(t)]).filter(Boolean)}function ac(e,t){let n=_n(e);if(!n)return null;let r=null,o=0;for(let s of t)for(let a of vy(s))n!==a&&!n.endsWith(`-${a}`)||a.length>o&&(r=s,o=a.length);if(r)return r;let i=t.filter(s=>{let a=s.pathname.split("/").filter(Boolean).pop();return a!==void 0&&_n(a)===n});return i.length===1?i[0]:null}function lc(e,t){let n=e.trim().replace(/^[<("'`]+/,"").replace(/[>)"'`.,]+$/,"");if(!n)return null;let r=n.toLowerCase();for(let i of t)if(i.url.toLowerCase()===r)return i;let o=r;try{o=new URL(n).pathname.toLowerCase()}catch{}if(o=o.replace(/\/+$/,"")||"/",!o.startsWith("/"))return null;for(let i of t)if(i.pathname.toLowerCase()===o)return i;return null}function $y(e,t,n){let r=/^---\n([\s\S]*?)\n---/.exec(t);if(r)for(let i of by){let s=new RegExp(`^${i}:\\s*["']?([^"'\\n]+)`,"im").exec(r[1]),a=s?lc(s[1],n):null;if(a)return a}let o=ky.exec(t);if(o){let i=lc(o[1],n);if(i)return i}if(e&&xy(e)){let i=(e.split(/[\\/]/).pop()??"").replace(/\.md$/i,""),s=ac(i,n);if(s)return s}if(r){let i=/^slug:\s*["']?([^"'\n]+)/im.exec(r[1]),s=i?ac(i[1],n):null;if(s)return s}return null}function vi(e,t){let n=e.toLowerCase();return t.filter(r=>n.includes(r.url.toLowerCase())||r.pathname!=="/"&&n.includes(r.pathname.toLowerCase()))}function Sy(e){let t=[],n=[];for(let r of e){let o=/^(#{1,6})\s+(.*)$/.exec(r);if(o){let i=o[1].length;for(;n.length>0&&n[n.length-1].level>=i;)n.pop();n.push({level:i,text:o[2]})}t.push(n.map(i=>i.text))}return t}function _y(e,t,n,r){let o=vi(e,n);if(o.length>0)return{targets:o,basis:"line"};if(sc.test(e))return r.homepage?{targets:[r.homepage],basis:"line"}:null;for(let i=t.length-1;i>=0;i--){let s=vi(t[i],n);if(s.length>0)return{targets:s,basis:"heading"};if(sc.test(t[i]))return r.homepage?{targets:[r.homepage],basis:"heading"}:null}return r.fileBound?{targets:[r.fileBound],basis:"file"}:py.test(e)||r.namesOtherPages?null:r.homepage?{targets:[r.homepage],basis:"origin"}:null}function cc(e){return{organization:"Organization",softwareapplication:"SoftwareApplication",website:"WebSite",webpage:"WebPage",blogposting:"BlogPosting",newsarticle:"NewsArticle",faqpage:"FAQPage",breadcrumblist:"BreadcrumbList"}[e]??e.charAt(0).toUpperCase()+e.slice(1)}function Ry(e){let t=/^(\s*(?:(?:[-*+]|\d+\.)\s+(?:\[[ xX]\]\s+)?|>\s+|#{1,6}\s+)?)(.*)$/.exec(e);return t?{prefix:t[1],content:t[2]}:{prefix:"",content:e}}function Ey(e,t,n={}){if(qr(t))return{text:e,corrections:[],stale:[]};let r=iy(t).filter(g=>g.fetched);if(r.length===0)return{text:e,corrections:[],stale:[]};let o=r.find(g=>g.pathname==="/")??r[0],i=sy(t),s=ly(e),a=e.split(`
|
|
981
981
|
`),l=Sy(a),c=[],d=[],p={homepage:o,fileBound:$y(n.filePath,e,r),namesOtherPages:r.some(g=>g!==o&&vi(e,[g]).length>0)};for(let g=0;g<a.length;g++){let f=a[g];if(f.includes(Rn)||!uy.test(f)&&!dy.test(f)&&!/\bno\b/i.test(f))continue;let h=_y(f,l[g],r,p);if(!h||h.targets.length===0)continue;let y=h.targets,w=[];for(let I of wy){let D=I.re.exec(f);D&&dc(f,D.index,D[0])&&y.every(K=>I.present(K))&&w.push(I.label)}let k=fy.exec(f);if(k&&dc(f,k.index,k[0])){let I=yy(f);if(I.length>0){let D=I.filter(K=>y.every(ee=>ee.jsonLdTypes.includes(K)));D.length>0&&w.push(`${D.map(cc).join(", ")} JSON-LD`)}else hy(f).length===0&&y.every(D=>D.jsonLdTypes.length>0)&&w.push(`JSON-LD (${Array.from(new Set(y.flatMap(D=>D.jsonLdTypes))).map(cc).join(", ")})`)}if(w.length===0)continue;let R=w.join(" + "),b=y.map(I=>I.url).join(", "),v=i?cy(f,l[g],s):null;if(i&&v&&v<i){d.push({line:g+1,original:f,entityLabel:R,pageUrl:b,basis:h.basis,authoredAt:v,capturedAt:i});continue}let{prefix:S,content:O}=Ry(f),L=`${S}~~${O}~~ \u2192 ${Rn}: the live page (${b}) already serves ${R} per evidence.md \u2014 this was a repo-source reconciliation, not a net-new addition.`;a[g]=L,c.push({line:g+1,original:f,corrected:L,entityLabel:R,pageUrl:b,basis:h.basis})}return{text:a.join(`
|
|
982
982
|
`),corrections:c,stale:d}}var Cy=/^\s*(?:$|[()[\]{}.,;:!?'"’”…]|—|–|→|\btags?\b|\belements?\b|\bmeta\b|\bmarkup\b|\bstructured\b|\bdata\b|\bcards?\b|\blinks?\b|\burls?\b|\bdeclarations?\b|\bsnippets?\b|\battributes?\b|\bentr(?:y|ies)\b|\brel\b|\bhref\b|\bschema\b|\bjson[\s-]*ld\b|\bis\b|\bare\b|\bwas\b|\bwere\b|\bisn['’]?t\b|\baren['’]?t\b|\bwasn['’]?t\b|\bweren['’]?t\b|\bdid\b|\bdidn['’]?t\b|\bdoes\b|\bdoesn['’]?t\b|\bha[sd]\b|\bhasn['’]?t\b|\bhadn['’]?t\b|\bnot\b|\bnever\b|\bpresent\b|\bfound\b|\bexists?\b|\bexisted\b|\bset\b|\bdefined\b|\bconfigured\b|\bmissing\b|\babsent\b|\banywhere\b|\bat\s+all\b|\byet\b|\bto\b|\bon\b|\bfor\b|\bin\b|\binto\b|\bacross\b|\bat\b|\bvia\b|\bwith\b|\band\b|\bor\b|\bplus\b|\bper\b|https?:|\/|`)/i,Ay=/\b(?:add(?:ed|ing|s)?|implement(?:ed|ing|s)?|inject(?:ed|ing|s)?|creat(?:e|ed|ing|es)|introduc(?:e|ed|ing|es))\s+(?:[\w`'’"/-]+\s+){0,3}$/i,Iy=/\b(?:no|not|missing|absent|without|zero|lack(?:s|ed|ing)?|ha[sd]\s+no|have\s+no|never\s+(?:had|served|shipped)|(?:do|does|did)\s*(?:n['’]t|\s+not)?\s+(?:have|serve|include|ship|set|emit|render|output)|don['’]t\s+(?:have|serve|include|ship|set|emit|render|output))\s+(?:(?:a|an|the|any|its|valid|proper|correct|self-referencing)\s+){0,2}$/i,uc=/^\s*[:,(]?\s*(?:tags?|elements?|meta|markup|data|cards?|links?|urls?|declarations?|snippets?|attributes?)?\s*(?:(?:is|are|was|were|ha[sd])\s+)?(?:still\s+)?(?:missing|absent|nowhere|not\s+(?:previously\s+)?(?:present|set|defined|found|served|rendered|emitted)|never\s+(?:previously\s+)?existed|did\s*n(?:ot|['’]?t)\s+(?:previously\s+)?(?:exist|have)|does\s*n(?:ot|['’]?t)\s+(?:currently\s+)?(?:exist|have)|no\s+longer\s+exists?)\b/i,Py=/^\s*[:,(]?\s*(?:tags?|elements?|meta|markup|data|cards?|links?|schema)?\s*(?:is|are|was|were|do|does|did|would|will|can|could|should|must)\s*n(?:ot|['’]t)\b/i;function dc(e,t,n){let r=e.slice(t+n.length),o=e.slice(Math.max(0,t-64),t);return!Cy.test(r)||!uc.test(r)&&Py.test(r)?!1:Ay.test(o)||Iy.test(o)||uc.test(r)}function yc(e){let t=`~~ \u2192 ${Rn}:`;if(!e.includes(t))return{text:e,stripped:0};let n=e.split(`
|
|
983
983
|
`),r=0;for(let o=0;o<n.length;o++){let i=n[o].indexOf(t);if(i===-1)continue;let s=n[o].slice(0,i),a=s.indexOf("~~");a!==-1&&(n[o]=s.slice(0,a)+s.slice(a+2),r++)}return{text:n.join(`
|
|
984
|
-
`),stripped:r}}var Ty
|
|
984
|
+
`),stripped:r}}var Ty=lt.join(x,"audit","evidence.md");function pc(e){let t=e.split(gc).join("/");return t===Ty||t===lt.join(x,"audit","findings.md")||t===lt.join(x,"pages.md")||t===lt.join(x,"keywords.md")||t===lt.join(x,"llms.md")||t.startsWith(lt.join(x,"okf")+"/")||t.startsWith(lt.join(x,"content")+"/")||t.startsWith(lt.join(x,"inbox")+"/")}function fc(e){return e.split(gc).join("/").split("/").includes(".claude")}function wc(e,t=[]){let n;try{n=ty(e)}catch{return t}for(let r of n){let o=xi(e,r),i;try{i=ny(o)}catch{continue}i.isDirectory()?wc(o,t):r.endsWith(".md")&&t.push(o)}return t}function Kt(e,t={}){let n=xi(e,x,"audit","evidence.md"),r={evidenceFound:nc(n),evidencePath:n,sourceRender:!1,scanned:[],skipped:[],corrections:[],stale:[]};if(!r.evidenceFound)return r;let o=tc(n,"utf-8");if(qr(o))return r.sourceRender=!0,r;let i=new Map;for(let s of wc(xi(e,x))){let a=rc(e,s);pc(a)||fc(a)||i.set(oc(s),a)}for(let s of t.extraFiles??[]){let a=oc(e,s),l=rc(e,a);if(pc(l)||fc(l)){r.skipped.push(l);continue}nc(a)?i.set(a,l):r.skipped.push(l)}for(let[s,a]of i){let l;try{l=tc(s,"utf-8")}catch{continue}r.scanned.push(a);let{text:c,corrections:d,stale:p}=Ey(l,o,{filePath:a});if(d.length>0){ry(s,c,"utf-8");for(let g of d)r.corrections.push({...g,file:a})}for(let g of p)r.stale.push({...g,file:a})}return r}function My(e){if(!e)return{};let t={"X-SEOAgent-Agent":e.agent,"X-SEOAgent-Auth-Source":e.authSource,"X-SEOAgent-Silent":e.silent?"1":"0"};return e.cliVersion&&(t["X-SEOAgent-CLI-Version"]=e.cliVersion),e.installId&&(t["X-SEOAgent-Install-Id"]=e.installId),e.repo&&(t["X-SEOAgent-Repo-Host"]=e.repo.host,t["X-SEOAgent-Repo"]=e.repo.full_name),t}function Uy(e){let t=e.directives;if(!Array.isArray(t))return[];let n=[];for(let r of t){if(!r||typeof r!="object")continue;let{key:o,text:i,tone:s}=r;typeof o!="string"||!o||typeof i!="string"||!i.trim()||n.push({key:o,text:i.trim(),tone:s==="upsell"||s==="action"?s:"info"})}return n}function $c(e){if(typeof e!="string")return{force:e===!0};let t=e.trim().replace(/\\/g,"/").replace(/^\.?\/+/,"").replace(/^\.seoagent\//,"").replace(/^\/+/,"");return t?{force:!1,forcePaths:[t]}:{force:!0}}function kc(e){return"sha256:"+xc("sha256").update(e).digest("hex")}function Ce(e){return xc("sha256").update(e,"utf-8").digest("hex")}function Fy(e,t,n={}){let r=[],o=i=>n.force===!0||n.forcePaths?.includes(i)===!0;for(let i of e.artifacts){let s=t[i.path]??{exists:!1,locallyEdited:!1};if(!s.exists){r.push({path:i.path,kind:"write",body:i.body_md});continue}if(s.contentHash===i.content_hash){r.push({path:i.path,kind:"skip"});continue}if(s.verifyRecsStrippedHash===i.content_hash){r.push({path:i.path,kind:"skip",note:"local differs from cloud only by verify-recs corrections (written by the CLI, not by you)"});continue}if(i.generated){r.push({path:i.path,kind:"overwrite",body:i.body_md,note:s.locallyEdited?"discarded local edits \u2014 this is a cloud-generated file (edit it in the dashboard)":void 0});continue}if(s.trackingPointer&&s.locallyEdited){r.push({path:i.path,kind:"overwrite",body:i.body_md,note:"replaced the tracking-pointer stub with the cloud draft (the stub was written by `content track`, not by you)"});continue}if(i.path==="context.md"&&s.scaffoldContext&&!Oe(i.body_md)){r.push({path:i.path,kind:"overwrite",body:i.body_md,note:"replaced the `seoagent init` scaffold with the owner-authored cloud context (the scaffold was written by `init`, not by you)"});continue}if(s.scaffoldOkf&&!oi(i.path,i.body_md)){r.push({path:i.path,kind:"overwrite",body:i.body_md,note:"replaced the `seoagent init` OKF scaffold with the authored cloud bundle (the scaffold was written by `init`, not by you)"});continue}if(s.locallyEdited&&!o(i.path)){r.push({path:i.path,kind:"conflict",note:"local newer than cloud, keeping local \u2014 use --force to take cloud"});continue}r.push({path:i.path,kind:"overwrite",body:i.body_md})}for(let i of e.deleted){let s=t[i];if(!(!s||!s.exists)){if(s.locallyEdited&&!o(i)){r.push({path:i,kind:"delete-skipped",note:"server deleted this but it has local edits \u2014 keeping it (use --force to delete)"});continue}r.push({path:i,kind:"delete"})}}return r}function By(e,t,n,r){let o=new Map;for(let s of t.artifacts)o.set(s.path,s.generated);let i=[];for(let s of e)s.kind!=="skip"&&i.push({path:s.path,kind:s.kind,class:ec(s.path,o.get(s.path)??!1),...s.note?{note:s.note}:{}});return i.length===0?null:{pulled_at:r,cursor:n,changes:i}}function Gy(e,t,n,r){if(r!==null)return!1;let o=new Set(t.artifacts.filter(i=>i.generated).map(i=>i.path));return e.every(i=>{if(i.kind==="skip"||i.kind==="write")return!0;if(i.kind!=="overwrite")return!1;let s=n[i.path];return o.has(i.path)||s?.scaffoldContext===!0||s?.scaffoldOkf===!0})}async function Sc(e){let t=await Si(e);return t.ok?t.artifact?{ok:!0,body:t.artifact.body_md}:{ok:!1,error:`No cloud artifact found at "${e.path.replace(/^\/+/,"").replace(/\\/g,"/")}"`}:t}async function Si(e){let t=await Rc({apiBase:e.apiBase,authHeader:e.authHeader,since:null,fetchImpl:e.fetchImpl});if(!t.ok)return{ok:!1,error:t.error};let n=e.path.replace(/^\/+/,"").replace(/\\/g,"/");return{ok:!0,artifact:t.manifest.artifacts.find(r=>r.path===n)??null}}function _c(e,t){return $i(e,t.split("/").join(Dy))}function Hy(e,t,n){let r={};for(let o of t){let i=_c(e,o);if(!vc(i)){r[o]={exists:!1,locallyEdited:!1};continue}let s=n[o],a=Ly(i,"utf-8"),l=!s||s.hash!==Ce(a),c=Gl(a),d=yc(a);r[o]={exists:!0,contentHash:kc(a),locallyEdited:l,...o==="context.md"&&Oe(a)?{scaffoldContext:!0}:{},...oi(o,a)?{scaffoldOkf:!0}:{},...c?{trackingPointer:c}:{},...d.stripped>0?{verifyRecsStrippedHash:kc(d.text)}:{}}}return r}async function Rc(e){let t=e.fetchImpl??fetch,n=`${e.apiBase}/api/cli/sync`+(e.since?`?since=${encodeURIComponent(e.since)}`:"");try{let r=await t(n,{method:"GET",headers:{Authorization:e.authHeader,Accept:"application/json",...My(e.meta)}});if(!r.ok){let i=await r.text().catch(()=>"");return{ok:!1,error:`${r.status} ${i.slice(0,200)}`.trim()}}let o=await r.json().catch(()=>null);return!o||!Array.isArray(o.artifacts)||typeof o.now!="string"?{ok:!1,error:"Malformed manifest response"}:(Array.isArray(o.deleted)||(o.deleted=[]),{ok:!0,manifest:o})}catch(r){return{ok:!1,error:r.message}}}async function Ec(e){let t=$i(e.projectDir,x),n={ok:!0,written:0,overwritten:0,skipped:0,conflicts:0,conflictPaths:[],pointerOverwrites:{},deleted:0,warnings:[],cursor:e.since};if(!vc(t))return{...n,ok:!0};let r=await Rc({apiBase:e.apiBase,authHeader:e.authHeader,since:e.since,fetchImpl:e.fetchImpl,meta:e.meta});if(!r.ok)return{...n,ok:!1,error:r.error};let o=r.manifest,i=[...o.artifacts.map(d=>d.path),...o.deleted],s=Hy(t,i,e.stateFiles),a=Fy(o,s,{force:e.force,forcePaths:e.forcePaths}),l={...n,pointerOverwrites:{},cursor:o.now,directives:Uy(o)};for(let d of a){let p=_c(t,d.path);try{switch(d.kind){case"skip":l.skipped++;break;case"write":case"overwrite":{Oy(jy(p),{recursive:!0});let g=d.body??"";bc(p,g,"utf-8"),e.stateFiles[d.path]={hash:Ce(g)},d.kind==="write"?l.written++:l.overwritten++;let f=s[d.path]?.trackingPointer;d.kind==="overwrite"&&f&&(l.pointerOverwrites[d.path]=f),d.note&&l.warnings.push(`${d.path}: ${d.note}`);break}case"conflict":l.conflicts++,l.conflictPaths.push(d.path),l.warnings.push(`${d.path}: ${d.note??"conflict"}`);break;case"delete":Ny(p,{force:!0}),delete e.stateFiles[d.path],l.deleted++;break;case"delete-skipped":l.conflicts++,l.conflictPaths.push(d.path),l.warnings.push(`${d.path}: ${d.note??"delete skipped"}`);break}}catch(g){l.ok=!1,l.error=`${d.path}: ${g.message}`}}(l.conflicts>0||!l.ok)&&(l.cursor=e.since);let c=Gy(a,o,s,e.since)?null:By(a,o,l.cursor,new Date().toISOString());if(c)try{bc($i(t,en),JSON.stringify(c,null,2)+`
|
|
985
985
|
`,"utf-8")}catch{}return l}var En="context.md",Yy="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`.",zy="context.md: replaced the local `seoagent init` scaffold with the owner-authored cloud context (the scaffold was written by `init`, not by you).",Jy="context.md: took the cloud copy (`--force context.md`) \u2014 the local version was replaced.";function Xy(e){return Oe(e.localBody)?!0:!e.prevStateHash||e.prevStateHash!==Ce(e.localBody)}function Qy(e){let{localBody:t,prevStateHash:n,cloudBody:r}=e;return r===null?{kind:"none"}:Ce(t)===Ce(r)?{kind:"none"}:Oe(r)?{kind:"none"}:Oe(t)?{kind:"restore",body:r,notice:zy}:n&&n===Ce(r)?{kind:"none"}:e.forceTakeCloud?{kind:"restore",body:r,notice:Jy}:e.forcePushLocal?{kind:"none"}:{kind:"block-push",notice:Yy}}var Cn={restored:!1,blockPush:!1,notices:[]};async function Cc(e){let t=Vy(e.seoagentDir,En);if(!Wy(t))return Cn;let n;try{n=Ky(t,"utf-8")}catch{return Cn}let r=e.stateFiles[En]?.hash??null;if(!Xy({localBody:n,prevStateHash:r}))return Cn;let o=await Si({apiBase:e.apiBase,authHeader:e.authHeader,path:En,fetchImpl:e.fetchImpl});if(!o.ok)return{restored:!1,blockPush:!Oe(n)&&(!r||r!==Ce(n)),notices:[]};let i=Qy({localBody:n,prevStateHash:r,cloudBody:o.artifact?.body_md??null,forceTakeCloud:e.forcePaths?.includes(En)===!0,forcePushLocal:e.force===!0});switch(i.kind){case"none":return Cn;case"restore":try{qy(t,i.body,"utf-8")}catch{return Cn}return e.stateFiles[En]={hash:Ce(i.body)},{restored:!0,blockPush:!1,notices:[i.notice]};case"block-push":return{restored:!1,blockPush:!0,notices:[i.notice]}}}import{existsSync as Zy,mkdirSync as ew,readFileSync as tw,writeFileSync as nw}from"fs";import{join as rw}from"path";function Ac(e){let t=e.replace(/[^a-zA-Z0-9._-]/g,"_");return rw(Co,`${t}.json`)}function An(e){let t=Ac(e);if(!Zy(t))return{files:{},last_synced_at:null};try{return JSON.parse(tw(t,"utf-8"))}catch{return{files:{},last_synced_at:null}}}function Vr(e,t){ew(Co,{recursive:!0}),nw(Ac(e),JSON.stringify(t,null,2)+`
|
|
986
986
|
`,"utf-8")}function Ic(e){if(!e)return null;try{return An(e).inbox_pulled_at??null}catch{return null}}import{existsSync as Tc,mkdirSync as Oc,readFileSync as ow,writeFileSync as _i}from"fs";import{dirname as Lc,join as iw}from"path";var Ri=1,Pc=500;function Ei(e){return iw(e,x,Ts)}function Yr(e){let t=Ei(e);if(!Tc(t))return[];try{let n=ow(t,"utf-8"),r=JSON.parse(n);if(!r||typeof r!="object")return[];let o=r;return o.version!==Ri||!Array.isArray(o.entries)?[]:o.entries.filter(sw)}catch{return[]}}function Nc(e,t){let n=Ei(e),o=Yr(e).filter(a=>a.action_id!==t.action_id);o.push({action_id:t.action_id,status:t.status,reason:t.reason,...t.details?{details:t.details}:{},queued_at:new Date().toISOString()});let i=o.length>Pc?o.slice(-Pc):o,s={version:Ri,entries:i};try{Oc(Lc(n),{recursive:!0}),_i(n,JSON.stringify(s,null,2),"utf-8")}catch{}}function jc(e,t){if(t.length===0)return;let n=Ei(e),r=Yr(e).filter(i=>!t.includes(i.action_id)),o={version:Ri,entries:r};try{if(r.length===0&&Tc(n)){_i(n,JSON.stringify(o,null,2),"utf-8");return}Oc(Lc(n),{recursive:!0}),_i(n,JSON.stringify(o,null,2),"utf-8")}catch{}}function sw(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 Dc,readFileSync as aw,unlinkSync as lw,writeFileSync as cw,openSync as uw,closeSync as dw,mkdirSync as pw}from"fs";import{dirname as fw,join as gw}from"path";var mw=6e4;function Mc(e){return gw(e,x,Os)}function Uc(e,t={}){let n=t.now??Date.now,r=t.pid??process.pid,o=t.staleMs??mw,i=Mc(e);if(!Dc(i))return zr(i,r,n());let s=Fc(i);return!s||n()-s.started_at>o||!hw(s.pid)?zr(i,r,n()):{acquired:!1,reason:"held-by-live-process",held_by_pid:s.pid}}function Jr(e,t={}){let n=t.pid??process.pid,r=Mc(e);if(!Dc(r))return;let o=Fc(r);if(!(o&&o.pid!==n))try{lw(r)}catch{}}function zr(e,t,n){let r={pid:t,started_at:n};try{pw(fw(e),{recursive:!0});let o=uw(e,"w");try{cw(o,JSON.stringify(r))}finally{dw(o)}return{acquired:!0}}catch{return{acquired:!1,reason:"held-but-pid-unknown"}}}function Fc(e){try{let t=aw(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 hw(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 Xc,mkdirSync as Ew,readFileSync as Qc,writeFileSync as Cw}from"fs";import{dirname as Aw,join as Tn}from"path";import{existsSync as Pn,readFileSync as Kc,readdirSync as yw}from"fs";import{join as qt}from"path";var Qr="llms.md",ww=160,Bc=100;function Xr(e,t=ww){let n=e.replace(/\s+/g," ").replace(/[[\]]/g,"").trim();return n.length<=t?n:n.slice(0,t-1).trimEnd()+"\u2026"}function Ci(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 bw=new Set(["readme","changelog","license","licence","contributing","contributors","code_of_conduct","code-of-conduct","security","authors","notice","agents","claude","cursorrules","codeowners","copying","maintainers"]);function kw(e){let t=Number.parseInt(String(e).trim(),10);return Number.isFinite(t)?t>=400&&t<=599:!1}function qc(e){let t=F(e).split("/").filter(Boolean).pop();return t?bw.has(t.replace(/\.(md|mdx|txt|html?)$/i,"").toLowerCase()):!1}function Gc(e){let t=Xr(e.label,80)||Ci(e.url),n=e.description?Xr(e.description):"";return`- [${t}](${e.url})${n?`: ${n}`:""}`}function Vc(e){let t=[`# ${Xr(e.siteName,100)}`,""];if(e.summary&&t.push(`> ${Xr(e.summary,300)}`,""),e.pages.length>0){t.push("## Pages","");for(let n of e.pages)t.push(Gc(n));t.push("")}if(e.articles.length>0){t.push("## Articles","");for(let n of e.articles)t.push(Gc(n));t.push("")}return t.join(`
|
|
987
987
|
`).replace(/\n+$/,`
|
|
988
|
-
`)}function xw(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);if(s&&s[1]!=="_(none)_"){t.get(n).description=s[1].trim();continue}let a=/^-\s+\*\*robots:\*\*\s+(.*)$/.exec(r);a&&/\bnoindex\b/i.test(a[1])&&(t.get(n).noindex=!0)}return t}function vw(e){let t=qt(e,x,"audit","evidence.md");if(!Pn(t))return new Map;try{return xw(Kc(t,"utf-8"))}catch{return new Map}}function $w(e,t,n,r){let o=qt(e,x,"content");if(!Pn(o))return[];let i=new Map,s;try{s=yw(o)}catch{return[]}for(let a of s.sort()){if(!a.toLowerCase().endsWith(".md"))continue;let l;try{l=me(Kc(qt(o,a),"utf-8")).data}catch{continue}let c=typeof l.canonical=="string"?l.canonical.trim():"",d=typeof l.status=="string"?l.status.toLowerCase():"";if(!c||d==="draft"||qc(c)||n?.has(F(c)))continue;let p=typeof l.source=="string"?l.source.trim().toLowerCase():"",g=typeof l.repo_path=="string"?l.repo_path.trim():"";if(p==="repo"&&g&&!Pn(qt(e,g))&&!r?.has(F(c)))continue;let f=typeof l.title=="string"&&l.title.trim()?l.title.trim():Ci(c),h=t.get(F(c)),y={url:c,label:h?.title||f,...h?.description?{description:h.description}:{}},w=typeof l.slug=="string"?l.slug.trim():a.slice(0,-3),k=F(c).split("/").filter(Boolean).pop()??"",R=w===k,b=i.get(c);(!b||R&&!b.slugMatchesUrl)&&i.set(c,{link:y,slugMatchesUrl:R})}return Array.from(i.values(),a=>a.link)}function Hc(e){try{return new URL(e).host.toLowerCase().replace(/^www\./,"")}catch{return""}}function In(e){return/^http:\/\//i.test(e)}function Sw(e,t,n,r){let o=[un,vr].map(a=>qt(e,x,a)).filter(a=>Pn(a));if(o.length===0)return[];let i=t.toLowerCase().replace(/^https?:\/\//,"").replace(/\/.*$/,""),s=new Map;for(let a of o.flatMap(l=>[...Sr(l).keys()])){if(!/^https?:\/\//i.test(a))continue;let l=F(a);if(Ne(l)||qc(a)||r?.has(l))continue;let d=`${Hc(a)}${l}`,p=s.get(d);if(!p){s.set(d,a);continue}if(In(p)!==In(a)){In(p)&&s.set(d,a);continue}let g="";try{g=new URL(a).hostname.toLowerCase()}catch{g=""}g===i&&s.set(d,a)}return n?Array.from(s.values()):Array.from(s.values(),a=>In(a)&&Hc(a)===i.replace(/^www\./,"")?a.replace(/^http:\/\//i,"https://"):a)}function _w(e){let t=new Set,n=new Set;for(let r of[un,vr]){let o=qt(e,x,r);if(Pn(o))for(let i of Sr(o).values()){let s=F(i.url);t.add(s),kw(i.status)&&n.add(s)}}return{listed:t,dead:n}}function Rw(e){let t=new Set;for(let[n,r]of e)r.noindex&&t.add(n);return t}function Wc(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 Yc(e,t){let n=t.domain.replace(/^https?:\/\//,"").replace(/\/+$/,""),r=(()=>{try{return rt(e)}catch{return null}})(),o=vw(e),i=_w(e),s=new Set([...i.dead,...Rw(o)]),a=$w(e,o,s,i.listed),l=new Set(a.map(g=>F(g.url))),c=[];for(let g of Sw(e,n,In(t.domain),s)){let f=F(g);if(l.has(f))continue;let h=o.get(f);c.push({url:g,label:h?.title||Ci(g),...h?.description?{description:h.description}:{}})}if(c.length===0&&a.length===0)return null;let d=r?.business?.name?.trim()||o.get("/")?.title||n,p=r?.business?.description?.trim()||o.get("/")?.description||null;return{siteName:d,summary:p,pages:Wc(c).slice(0,Bc),articles:Wc(a).slice(0,Bc)}}var Zr=".well-known/okf",zc="llms.txt",Jc={publicDir:null,publicDirSource:null,llmsSourceWritten:!1,llmsPublished:null,okfPublished:0,okfPublishedDir:null,llmsRoute:null,llmsRouteDrift:!1,skipped:[]};function Ai(e,t){try{return Xc(e)&&Qc(e,"utf-8")===t?!1:(Ew(Aw(e),{recursive:!0}),Cw(e,t,"utf-8"),!0)}catch{return!1}}function Vt(e,t){if(!t.domain||t.domain==="unknown")return{...Jc,skipped:["no-domain"]};let n=cr(e,t.public_dir??null),r={...Jc,publicDir:n.dir,publicDirSource:n.source,skipped:[]},o=null;try{let s=Yc(e,t);o=s?Vc(s):null}catch{o=null}if(o===null)r.skipped.push("llms-no-content");else if(r.llmsSourceWritten=Ai(Tn(e,x,Qr),o),r.llmsRoute=Bs(e,n.dir),r.llmsRoute){if(r.skipped.push("llms-route-owned"),r.llmsRouteDrift=r.llmsSourceWritten&&t.llms_owner!=="route",t.llms_owner!=="route")try{X(e,{llms_owner:"route"})&&(t.llms_owner="route")}catch{}}else if(n.dir){let s=Tn(e,n.dir,zc);Ai(s,o)&&(r.llmsPublished=`${n.dir}/${zc}`)}let i=Lt(e);if(!Xc(i))r.skipped.push("okf-missing");else if(sl(i))r.skipped.push("okf-placeholder");else if(!Er(i).ok)r.skipped.push("okf-invalid");else if(n.dir){let s=Tn(e,n.dir,Zr),a=0;for(let l of al(i)){let c;try{c=Qc(Tn(i,l),"utf-8")}catch{continue}Ai(Tn(s,l),c)&&a++}a>0&&(r.okfPublished=a,r.okfPublishedDir=`${n.dir}/${Zr}`)}return n.dir||r.skipped.push("no-public-dir"),r}function Zc(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}var Iw=30,Pw=/<(script|style|noscript|template|svg|nav|header|footer)\b[^>]*>[\s\S]*?<\/\1>/gi;function Tw(e){let t=e,n=/<body\b[^>]*>([\s\S]*?)<\/body>/i.exec(t);n&&(t=n[1]),t=t.replace(Pw," ").replace(/<!--[\s\S]*?-->/g," ").replace(/<[^>]+>/g," ").replace(/ /gi," ").replace(/&[a-z]+;/gi," ").replace(/\s+/g," ").trim();let r=t===""?0:t.split(" ").filter(Boolean).length;return{wordCount:r,rendered:r<Iw?"empty":"yes"}}async function Ow(e,t){try{let n=await j(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}=Tw(r);return{status:n.status,rendered:i,wordCount:o}}catch{return{status:null,rendered:"error",wordCount:0}}}async function eu(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 Ow(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 Ii=300;async function On(e,t={}){try{let n=C(e),r=n?.domain&&n.domain!=="unknown"?n.domain:null,{pages:o}=je(e,r);t.enrichSitemap&&r&&await Ka(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,Ii);if(l.length>Ii&&u.info(`Crawling the first ${Ii} of ${l.length} pages (cap).`),c.length>0){u.info(`Crawling ${c.length} page${c.length===1?"":"s"}\u2026`),s=await eu(c);let d=0,p=0;for(let g of s.values())g.rendered==="empty"?d++:g.rendered==="error"&&p++;i={crawled:s.size,empty:d,errors:p}}}let a=$r(e,o,s);return{added:a.added,total:a.total,file:a.file,crawl:i}}catch{return{added:0,total:0,file:"pages.md"}}}function Mw(e){try{let t=Kt(e);return t.evidenceFound?t:void 0}catch{return}}var Uw=new Set([".md"]),Fw=new Set(["inbox"]);function Bw(e){let t=[];function n(r,o){if(nu(r))for(let i of Nw(r,{withFileTypes:!0})){if(i.name.startsWith(".")||o===""&&Fw.has(i.name))continue;let s=ru(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);Uw.has(l)&&t.push(s)}}}return n(e,""),t}var Gw=14400*1e3;function Hw(e,t){return jw(e,t).split(Dw).join("/")}async function Ww(e,t){try{let n=await j(`${e}/api/cli/actions/fetch`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:t,...Ml()},body:JSON.stringify({})});if(!n.ok){let o=await n.text().catch(()=>"");return{ok:!1,actions:[],declined:[],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:[],declined:[],error:"Malformed response"}:{ok:!0,actions:r.actions,declined:wi(r.declined)}}catch(n){return{ok:!1,actions:[],declined:[],error:n.message}}}async function tu(e,t,n){try{let r=await j(`${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}}}var Kw=12e3;function qw(e,t){return e===void 0?!1:t>=e-Kw}var Vw=6,Yw=new Set(["briefs","content","okf"]);function zw(e){let t=[],n=null;for(let r of e){let o=r.rel.indexOf("/"),i=o===-1?null:r.rel.slice(0,o),s=i!==null&&Yw.has(i);s&&i===n?t[t.length-1].push(r):(t.push([r]),n=s?i:null)}return t}async function eo(e,t={}){let n=Mw(e),r=d=>{let p=n?{...d,verifyRecs:n}:{...d};return s&&(p={...p,aiFiles:s}),i&&i.added>0&&(p={...p,pagesRescan:{added:i.added,total:i.total,file:i.file}}),p},o=C(e),i=null;if(o&&!t.pullOnly)try{i=await On(e)}catch{}let s=null;if(o)try{s=Vt(e,o)}catch{}let a=B(o?.domain);if(!a)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===Ze)return r({ok:!0,reason:"domain-unknown",synced:0,failed:0,errors:[]});let l=ru(e,x);if(!nu(l))return r({ok:!0,reason:"no-project",synced:0,failed:0,errors:[]});if(!Uc(e).acquired)return r({ok:!0,reason:"busy",synced:0,failed:0,errors:[]});try{return r(await Jw(e,t,a,o,l))}finally{Jr(e)}}async function Jw(e,t,n,r,o){let i=n.api_base||U.BASE,s=`Bearer ${n.user_token}:${n.website_token}`;t.onPhase?.("binding");let a=await et({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=An(r.domain);try{Wl(e,r)}catch{}let c=Bw(o),d=0,p=0,g=[],f=[],h=0;t.onPhase?.("ack-flush");let y=await Zw(e,i,s),w={restored:!1,blockPush:!1,notices:[]};(!t.pathFilter||"context.md".endsWith(t.pathFilter))&&(w=await Cc({seoagentDir:o,apiBase:i,authHeader:s,stateFiles:l.files,force:t.force,forcePaths:t.forcePaths}),g.push(...w.notices));let k=0;if(!t.pullOnly){t.onPhase?.("push");for(let q of t.deletedPaths??[]){h++;let oe=await tu(i,{path:q,domain:r.domain,deleted:!0},s);oe.ok?(delete l.files[q],d++,Vr(r.domain,l)):(p++,f.push(`${q}: ${oe.status} ${oe.error??""}`.trim()))}let M=[];for(let q of c){let oe=Hw(o,q);if(t.pathFilter&&!oe.endsWith(t.pathFilter)||oe==="context.md"&&w.blockPush)continue;let Pe=Lw(q,"utf-8"),fe=Ce(Pe),ge=l.files[oe];!(!ge||ge.hash!==fe)&&!t.force||M.push({rel:oe,contents:Pe,hash:fe})}let ce=async({rel:q,contents:oe,hash:Pe})=>{h++;let fe=await tu(i,{path:q,contents:oe,domain:r.domain},s);fe.ok?(l.files[q]={hash:Pe},d++,fe.agentNotice&&g.push(`${q}: ${fe.agentNotice}`),Vr(r.domain,l)):(p++,f.push(`${q}: ${fe.status} ${fe.error??""}`.trim()))},Z=0;for(let q of zw(M)){let oe=0,Pe=async()=>{for(;oe<q.length&&!qw(t.deadlineAt,Date.now());){let fe=q[oe++];Z++,await ce(fe)}};if(await Promise.all(Array.from({length:Math.min(Vw,q.length)},Pe)),oe<q.length)break}Z<M.length&&(k=M.length-Z)}t.onPhase?.("inbox-pull");let R=await Ww(i,s),b=0,v=0,S=[],O=0,L=0;!R.ok&&R.error&&f.push(`pull actions: ${R.error}`);let I,D=[],K=new Map,ee=!1,Y=[],er=l.last_synced_at;if(!t.pushOnly){t.onPhase?.("artifact-pull");let M=await Ec({projectDir:e,apiBase:i,authHeader:s,since:l.last_synced_at,stateFiles:l.files,force:t.force,forcePaths:t.forcePaths,meta:Xw(e,!!t.silent)});Y=M.directives??[],M.ok?er=M.cursor:(ee=!0,M.error&&f.push(`pull: ${M.error}`)),I={written:M.written,overwritten:M.overwritten,skipped:M.skipped,conflicts:M.conflicts,deleted:M.deleted,warnings:M.warnings},D=M.conflictPaths;for(let[ce,Z]of Object.entries(M.pointerOverwrites)){let q=ce.match(/^content\/(.+)\.md$/)?.[1];q&&K.set(q,Z)}}if(R.ok){t.onPhase?.("inbox-write");let M=ki(e);for(let[ge,ct]of K)M.set(ge,ct);let ce=Zl(e,R.actions,{conflictedPaths:new Set(D),localPointers:M,declined:R.declined});b=ce.wrote,v=ce.removed,O=ce.skippedUnknown;let Z=l.announced_at?Date.parse(l.announced_at):NaN,oe=ce.pendingIds.length>0&&(Number.isNaN(Z)||Date.now()-Z>=Gw)?new Set:new Set(l.announced_action_ids??[]),Pe=new Set(ce.pendingIds.filter(ge=>!oe.has(ge)));S=ce.pending.filter(ge=>Pe.has(ge.id)),L=Pe.size-S.length,l.announced_action_ids=ce.pendingIds,Pe.size>0&&(l.announced_at=new Date().toISOString()),l.inbox_pulled_at=new Date().toISOString();let fe=Jl(R.actions);I&&D.length>0&&(I.warnings=I.warnings.map(ge=>{let ct=D.find(Ro=>ge.startsWith(`${Ro}:`)),_o=ct?fe.get(ct):void 0;return!ct||!_o?.length?ge:`${ge} \u2014 blocks inbox action${_o.length===1?"":"s"} ${_o.map(Ro=>`#${Ro}`).join(", ")}; take the cloud copy with \`seoagent sync --force ${ct}\``}))}let He=!!I&&I.written+I.overwritten+I.deleted+I.conflicts>0;if(l.last_synced_at=er,l.last_run_at=new Date().toISOString(),Vr(r.domain,l),t.onPhase?.("done"),h===0&&k===0&&b===0&&v===0&&!He&&!w.restored&&w.notices.length===0&&y.flushed===0&&y.dropped===0&&!ee&&p===0)return{ok:!0,reason:"no-changes",synced:0,failed:0,errors:f,actionsPulled:0,actionsRemoved:0,actionsNew:[],actionsUnknown:O,actionsUnknownNew:L,acksFlushed:0,acksDropped:0,pull:I,directives:Y};let tr=p===0&&!ee;return{ok:tr,reason:tr?void 0:"error",synced:d,failed:p,errors:f,...g.length>0?{notices:g}:{},...k>0?{pushDeferred:k}:{},actionsPulled:b,actionsRemoved:v,actionsNew:S,actionsUnknown:O,actionsUnknownNew:L,acksFlushed:y.flushed,acksDropped:y.dropped,pull:I,directives:Y}}function Xw(e,t){let n=he(),r=n?null:C(e)?.install_id??null;return{cliVersion:n?null:P,agent:Dl(),authSource:fr(),installId:r||null,silent:t,...n?{}:Qw(e)}}function Qw(e){let t=Pr(e);return t?{repo:t}:{}}async function Zw(e,t,n){let r=Yr(e);if(r.length===0)return{flushed:0,dropped:0};let o=0,i=0,s=[];for(let a of r){let l=await eb(a,t,n);l==="flushed"?(o++,s.push(a.action_id)):l==="already-settled"&&(i++,s.push(a.action_id))}return s.length>0&&jc(e,s),{flushed:o,dropped:i}}async function eb(e,t,n){try{let r=await j(`${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,...e.details??{}}})});return r.ok?"flushed":r.status===409||r.status>=400&&r.status<500?"already-settled":"transient"}catch{return"transient"}}function ou(e,t=3e4){let n=Date.now()+t,r=setTimeout(e,t);return r.unref?.(),{cancel:()=>clearTimeout(r),deadlineAt:n}}function iu(e){let t;try{t=C(e)}catch{return{refreshed:!1,reason:"error"}}if(!t)return{refreshed:!1,reason:"no-project"};if(ft(e))return{refreshed:!1,reason:"source-repo"};let n=t.skill_version||t.seoagent_version||"0.0.0";if(at(P,n)<=0){if(!t.skill_version)try{X(e,{skill_version:n})}catch{}return{refreshed:!1,reason:"up-to-date"}}try{return It(e),X(e,{skill_version:P}),{refreshed:!0,from:n,to:P,reason:"refreshed"}}catch{return{refreshed:!1,reason:"error"}}}function Pi(e){let t;try{t=C(e)}catch{return{refreshed:!1,from:null,to:P,reason:"error"}}if(!t)return{refreshed:!1,from:null,to:P,reason:"no-project"};if(ft(e))return{refreshed:!1,from:null,to:P,reason:"source-repo"};let n=t.skill_version||t.seoagent_version||null;try{return It(e),X(e,{skill_version:P}),{refreshed:!0,from:n,to:P,reason:"refreshed"}}catch{return{refreshed:!1,from:n,to:P,reason:"error"}}}import{createHash as tb}from"crypto";import{existsSync as jA,mkdirSync as nb,readFileSync as rb,writeFileSync as ob}from"fs";import{join as ib}from"path";var sb="/cli-skill/manifest.json",ab=360*60*1e3,lb=4e3,cb=64,ub=4*1024*1024,su=ib(z,"skill-check.json"),db=e=>tb("sha256").update(e).digest("hex");function pb(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>cb)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>ub||!n.some(o=>o.path==="SKILL.md")?null:{hash:t.hash,min_cli:t.min_cli,files:n}}function fb(e,t,n=ab){if(!e)return!0;let r=Date.parse(e);return Number.isNaN(r)?!0:t.getTime()-r>=n}function gb(){try{return JSON.parse(rb(su,"utf-8"))}catch{return{}}}function mb(e){try{nb(z,{recursive:!0}),ob(su,JSON.stringify({checkedAt:e}),"utf-8")}catch{}}async function hb(e){try{let t=await fetch(e,{signal:AbortSignal.timeout(lb),headers:{"User-Agent":`seoagent-cli/${P}`}});return t.ok?await t.text():null}catch{return null}}async function au(e,t={}){try{let n=C(e);if(!n)return{updated:!1,reason:"no-project"};if(ft(e))return{updated:!1,reason:"source-repo"};let r=t.now??new Date,o=gb();if(!t.force&&!fb(o.checkedAt,r))return{updated:!1,reason:"throttled"};let i=(t.baseUrl??U.BASE).replace(/\/$/,""),s=t.fetchText??hb,a=await s(`${i}${sb}`);if(mb(r.toISOString()),a===null)return{updated:!1,reason:"unreachable"};let l;try{l=pb(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(at(P,l.min_cli)<0)return{updated:!1,reason:"cli-too-old",minCli:l.min_cli};let c={};for(let d of l.files){let p=await s(`${i}/cli-skill/${d.path}`);if(p===null)return{updated:!1,reason:"unreachable"};if(db(p)!==d.sha256)return{updated:!1,reason:"verify-failed"};c[d.path]=p}return la(e,c),X(e,{skill_hash:l.hash}),{updated:!0,reason:"updated",fileCount:l.files.length}}catch{return{updated:!1,reason:"error"}}}import{existsSync as yb,mkdirSync as wb,readFileSync as bb,writeFileSync as kb}from"fs";var xb="https://registry.npmjs.org/@seoagent-official/seoagent/latest",vb=1440*60*1e3,$b=720*60*1e3,Sb=2e3;function _b(e,t,n=vb){if(!e.checkedAt)return!0;let r=Date.parse(e.checkedAt);return Number.isNaN(r)?!0:t.getTime()-r>=n}function Rb(e){let{current:t,latest:n,cache:r,now:o}=e,i=e.notifyIntervalMs??$b;if(!n||at(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 Eb(e){let t=e.readCache();if(_b(t,e.now,e.checkIntervalMs)){let r=await e.fetchLatest();t={...t,checkedAt:e.now.toISOString(),...r?{latest:r}:{}},e.writeCache(t)}let n=Rb({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 Cb(){try{return yb(rr)?JSON.parse(bb(rr,"utf-8")):{}}catch{return{}}}function Ab(e){try{wb(z,{recursive:!0}),kb(rr,JSON.stringify(e,null,2)+`
|
|
989
|
-
`,"utf-8")}catch{}}async function Ib(){try{let e=await fetch(xb,{headers:{Accept:"application/json"},signal:AbortSignal.timeout(Sb)});if(!e.ok)return null;let t=await e.json();return typeof t.version=="string"?t.version:null}catch{return null}}async function lu(e){try{return await Eb({current:e,now:new Date,fetchLatest:Ib,readCache:Cb,writeCache:Ab})}catch{return null}}import{existsSync as cu,readdirSync as uu}from"fs";import{join as Ti}from"path";var du={PILLAR:0,SUB_PILLAR:1,LONG_TAIL:2},pu={high:0,medium:1,low:2},Pb=new Set(["drafted","published","dismissed","superseded","done"]);function Tb(e){let t=e.filter(n=>!Pb.has((n.status??"").toLowerCase()));return t.length===0?null:[...t].sort((n,r)=>{let o=du[(n.role??"").toUpperCase()]??3,i=du[(r.role??"").toUpperCase()]??3;if(o!==i)return o-i;let s=pu[(n.priority??"").toLowerCase()]??3,a=pu[(r.priority??"").toLowerCase()]??3;return s!==a?s-a:n.slug.localeCompare(r.slug)})[0]}function fu(e,t,n){if(t>0)return null;let r=Tb(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 gu(e){let t={briefs:[],articleCount:0};try{let n=Ti(e,"briefs");if(cu(n))for(let o of uu(n).filter(i=>i.endsWith(".md"))){let s=Qe(Ti(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=Ti(e,"content");cu(r)&&(t.articleCount=uu(r).filter(o=>o.endsWith(".md")).length)}catch{}return t}var Ob=1500;function Lb(e=process.env){switch(gr(e)){case"absent":return"sync_unauthenticated";case"malformed":return"sync_unauthenticated_malformed";default:return null}}function Nb(e){try{let n=C(e)?.domain?.trim();return!n||n.toLowerCase()==="unknown"?null:n}catch{return null}}async function mu(e){let t=e.env??process.env,n=Lb(t);if(!n)return{sent:!1,event:null,skipped:"authenticated",throwawayInstallId:!1};if(he(t))return{sent:!1,event:n,skipped:"opted-out",throwawayInstallId:!1};let r=null;try{r=$e(e.cwd)}catch{r=null}let o=!r,i=Nb(e.cwd),s=i?{domain:i}:null;return{sent:await ze({installId:r??
|
|
988
|
+
`)}function xw(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);if(s&&s[1]!=="_(none)_"){t.get(n).description=s[1].trim();continue}let a=/^-\s+\*\*robots:\*\*\s+(.*)$/.exec(r);a&&/\bnoindex\b/i.test(a[1])&&(t.get(n).noindex=!0)}return t}function vw(e){let t=qt(e,x,"audit","evidence.md");if(!Pn(t))return new Map;try{return xw(Kc(t,"utf-8"))}catch{return new Map}}function $w(e,t,n,r){let o=qt(e,x,"content");if(!Pn(o))return[];let i=new Map,s;try{s=yw(o)}catch{return[]}for(let a of s.sort()){if(!a.toLowerCase().endsWith(".md"))continue;let l;try{l=me(Kc(qt(o,a),"utf-8")).data}catch{continue}let c=typeof l.canonical=="string"?l.canonical.trim():"",d=typeof l.status=="string"?l.status.toLowerCase():"";if(!c||d==="draft"||qc(c)||n?.has(F(c)))continue;let p=typeof l.source=="string"?l.source.trim().toLowerCase():"",g=typeof l.repo_path=="string"?l.repo_path.trim():"";if(p==="repo"&&g&&!Pn(qt(e,g))&&!r?.has(F(c)))continue;let f=typeof l.title=="string"&&l.title.trim()?l.title.trim():Ci(c),h=t.get(F(c)),y={url:c,label:h?.title||f,...h?.description?{description:h.description}:{}},w=typeof l.slug=="string"?l.slug.trim():a.slice(0,-3),k=F(c).split("/").filter(Boolean).pop()??"",R=w===k,b=i.get(c);(!b||R&&!b.slugMatchesUrl)&&i.set(c,{link:y,slugMatchesUrl:R})}return Array.from(i.values(),a=>a.link)}function Hc(e){try{return new URL(e).host.toLowerCase().replace(/^www\./,"")}catch{return""}}function In(e){return/^http:\/\//i.test(e)}function Sw(e,t,n,r){let o=[un,vr].map(a=>qt(e,x,a)).filter(a=>Pn(a));if(o.length===0)return[];let i=t.toLowerCase().replace(/^https?:\/\//,"").replace(/\/.*$/,""),s=new Map;for(let a of o.flatMap(l=>[...Sr(l).keys()])){if(!/^https?:\/\//i.test(a))continue;let l=F(a);if(Ne(l)||qc(a)||r?.has(l))continue;let d=`${Hc(a)}${l}`,p=s.get(d);if(!p){s.set(d,a);continue}if(In(p)!==In(a)){In(p)&&s.set(d,a);continue}let g="";try{g=new URL(a).hostname.toLowerCase()}catch{g=""}g===i&&s.set(d,a)}return n?Array.from(s.values()):Array.from(s.values(),a=>In(a)&&Hc(a)===i.replace(/^www\./,"")?a.replace(/^http:\/\//i,"https://"):a)}function _w(e){let t=new Set,n=new Set;for(let r of[un,vr]){let o=qt(e,x,r);if(Pn(o))for(let i of Sr(o).values()){let s=F(i.url);t.add(s),kw(i.status)&&n.add(s)}}return{listed:t,dead:n}}function Rw(e){let t=new Set;for(let[n,r]of e)r.noindex&&t.add(n);return t}function Wc(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 Yc(e,t){let n=t.domain.replace(/^https?:\/\//,"").replace(/\/+$/,""),r=(()=>{try{return rt(e)}catch{return null}})(),o=vw(e),i=_w(e),s=new Set([...i.dead,...Rw(o)]),a=$w(e,o,s,i.listed),l=new Set(a.map(g=>F(g.url))),c=[];for(let g of Sw(e,n,In(t.domain),s)){let f=F(g);if(l.has(f))continue;let h=o.get(f);c.push({url:g,label:h?.title||Ci(g),...h?.description?{description:h.description}:{}})}if(c.length===0&&a.length===0)return null;let d=r?.business?.name?.trim()||o.get("/")?.title||n,p=r?.business?.description?.trim()||o.get("/")?.description||null;return{siteName:d,summary:p,pages:Wc(c).slice(0,Bc),articles:Wc(a).slice(0,Bc)}}var Zr=".well-known/okf",zc="llms.txt",Jc={publicDir:null,publicDirSource:null,llmsSourceWritten:!1,llmsPublished:null,okfPublished:0,okfPublishedDir:null,llmsRoute:null,llmsRouteDrift:!1,skipped:[]};function Ai(e,t){try{return Xc(e)&&Qc(e,"utf-8")===t?!1:(Ew(Aw(e),{recursive:!0}),Cw(e,t,"utf-8"),!0)}catch{return!1}}function Vt(e,t){if(!t.domain||t.domain==="unknown")return{...Jc,skipped:["no-domain"]};let n=cr(e,t.public_dir??null),r={...Jc,publicDir:n.dir,publicDirSource:n.source,skipped:[]},o=null;try{let s=Yc(e,t);o=s?Vc(s):null}catch{o=null}if(o===null)r.skipped.push("llms-no-content");else if(r.llmsSourceWritten=Ai(Tn(e,x,Qr),o),r.llmsRoute=Bs(e,n.dir),r.llmsRoute){if(r.skipped.push("llms-route-owned"),r.llmsRouteDrift=r.llmsSourceWritten&&t.llms_owner!=="route",t.llms_owner!=="route")try{X(e,{llms_owner:"route"})&&(t.llms_owner="route")}catch{}}else if(n.dir){let s=Tn(e,n.dir,zc);Ai(s,o)&&(r.llmsPublished=`${n.dir}/${zc}`)}let i=Lt(e);if(!Xc(i))r.skipped.push("okf-missing");else if(sl(i))r.skipped.push("okf-placeholder");else if(!Er(i).ok)r.skipped.push("okf-invalid");else if(n.dir){let s=Tn(e,n.dir,Zr),a=0;for(let l of al(i)){let c;try{c=Qc(Tn(i,l),"utf-8")}catch{continue}Ai(Tn(s,l),c)&&a++}a>0&&(r.okfPublished=a,r.okfPublishedDir=`${n.dir}/${Zr}`)}return n.dir||r.skipped.push("no-public-dir"),r}function Zc(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}var Iw=30,Pw=/<(script|style|noscript|template|svg|nav|header|footer)\b[^>]*>[\s\S]*?<\/\1>/gi;function Tw(e){let t=e,n=/<body\b[^>]*>([\s\S]*?)<\/body>/i.exec(t);n&&(t=n[1]),t=t.replace(Pw," ").replace(/<!--[\s\S]*?-->/g," ").replace(/<[^>]+>/g," ").replace(/ /gi," ").replace(/&[a-z]+;/gi," ").replace(/\s+/g," ").trim();let r=t===""?0:t.split(" ").filter(Boolean).length;return{wordCount:r,rendered:r<Iw?"empty":"yes"}}async function Ow(e,t){try{let n=await j(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}=Tw(r);return{status:n.status,rendered:i,wordCount:o}}catch{return{status:null,rendered:"error",wordCount:0}}}async function eu(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 Ow(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 Ii=300;async function On(e,t={}){try{let n=C(e),r=n?.domain&&n.domain!=="unknown"?n.domain:null,{pages:o}=je(e,r);t.enrichSitemap&&r&&await Ka(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,Ii);if(l.length>Ii&&u.info(`Crawling the first ${Ii} of ${l.length} pages (cap).`),c.length>0){u.info(`Crawling ${c.length} page${c.length===1?"":"s"}\u2026`),s=await eu(c);let d=0,p=0;for(let g of s.values())g.rendered==="empty"?d++:g.rendered==="error"&&p++;i={crawled:s.size,empty:d,errors:p}}}let a=$r(e,o,s);return{added:a.added,total:a.total,file:a.file,crawl:i}}catch{return{added:0,total:0,file:"pages.md"}}}function Mw(e){try{let t=Kt(e);return t.evidenceFound?t:void 0}catch{return}}var Uw=new Set([".md"]),Fw=new Set(["inbox"]);function Bw(e){let t=[];function n(r,o){if(nu(r))for(let i of Nw(r,{withFileTypes:!0})){if(i.name.startsWith(".")||o===""&&Fw.has(i.name))continue;let s=ru(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);Uw.has(l)&&t.push(s)}}}return n(e,""),t}var Gw=14400*1e3;function Hw(e,t){return jw(e,t).split(Dw).join("/")}async function Ww(e,t){try{let n=await j(`${e}/api/cli/actions/fetch`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:t,...Ml()},body:JSON.stringify({})});if(!n.ok){let o=await n.text().catch(()=>"");return{ok:!1,actions:[],declined:[],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:[],declined:[],error:"Malformed response"}:{ok:!0,actions:r.actions,declined:wi(r.declined)}}catch(n){return{ok:!1,actions:[],declined:[],error:n.message}}}async function tu(e,t,n){try{let r=await j(`${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}}}var Kw=12e3;function qw(e,t){return e===void 0?!1:t>=e-Kw}var Vw=6,Yw=new Set(["briefs","content","okf"]);function zw(e){let t=[],n=null;for(let r of e){let o=r.rel.indexOf("/"),i=o===-1?null:r.rel.slice(0,o),s=i!==null&&Yw.has(i);s&&i===n?t[t.length-1].push(r):(t.push([r]),n=s?i:null)}return t}async function eo(e,t={}){let n=Mw(e),r=d=>{let p=n?{...d,verifyRecs:n}:{...d};return s&&(p={...p,aiFiles:s}),i&&i.added>0&&(p={...p,pagesRescan:{added:i.added,total:i.total,file:i.file}}),p},o=C(e),i=null;if(o&&!t.pullOnly)try{i=await On(e)}catch{}let s=null;if(o)try{s=Vt(e,o)}catch{}let a=B(o?.domain);if(!a)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===Ze)return r({ok:!0,reason:"domain-unknown",synced:0,failed:0,errors:[]});let l=ru(e,x);if(!nu(l))return r({ok:!0,reason:"no-project",synced:0,failed:0,errors:[]});if(!Uc(e).acquired)return r({ok:!0,reason:"busy",synced:0,failed:0,errors:[]});try{return r(await Jw(e,t,a,o,l))}finally{Jr(e)}}async function Jw(e,t,n,r,o){let i=n.api_base||U.BASE,s=`Bearer ${n.user_token}:${n.website_token}`;t.onPhase?.("binding");let a=await et({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=An(r.domain);try{Wl(e,r)}catch{}let c=Bw(o),d=0,p=0,g=[],f=[],h=0;t.onPhase?.("ack-flush");let y=await Zw(e,i,s),w={restored:!1,blockPush:!1,notices:[]};(!t.pathFilter||"context.md".endsWith(t.pathFilter))&&(w=await Cc({seoagentDir:o,apiBase:i,authHeader:s,stateFiles:l.files,force:t.force,forcePaths:t.forcePaths}),g.push(...w.notices));let k=0;if(!t.pullOnly){t.onPhase?.("push");for(let q of t.deletedPaths??[]){h++;let oe=await tu(i,{path:q,domain:r.domain,deleted:!0},s);oe.ok?(delete l.files[q],d++,Vr(r.domain,l)):(p++,f.push(`${q}: ${oe.status} ${oe.error??""}`.trim()))}let M=[];for(let q of c){let oe=Hw(o,q);if(t.pathFilter&&!oe.endsWith(t.pathFilter)||oe==="context.md"&&w.blockPush)continue;let Pe=Lw(q,"utf-8"),fe=Ce(Pe),ge=l.files[oe];!(!ge||ge.hash!==fe)&&!t.force||M.push({rel:oe,contents:Pe,hash:fe})}let ce=async({rel:q,contents:oe,hash:Pe})=>{h++;let fe=await tu(i,{path:q,contents:oe,domain:r.domain},s);fe.ok?(l.files[q]={hash:Pe},d++,fe.agentNotice&&g.push(`${q}: ${fe.agentNotice}`),Vr(r.domain,l)):(p++,f.push(`${q}: ${fe.status} ${fe.error??""}`.trim()))},Z=0;for(let q of zw(M)){let oe=0,Pe=async()=>{for(;oe<q.length&&!qw(t.deadlineAt,Date.now());){let fe=q[oe++];Z++,await ce(fe)}};if(await Promise.all(Array.from({length:Math.min(Vw,q.length)},Pe)),oe<q.length)break}Z<M.length&&(k=M.length-Z)}t.onPhase?.("inbox-pull");let R=await Ww(i,s),b=0,v=0,S=[],O=0,L=0;!R.ok&&R.error&&f.push(`pull actions: ${R.error}`);let I,D=[],K=new Map,ee=!1,Y=[],er=l.last_synced_at;if(!t.pushOnly){t.onPhase?.("artifact-pull");let M=await Ec({projectDir:e,apiBase:i,authHeader:s,since:l.last_synced_at,stateFiles:l.files,force:t.force,forcePaths:t.forcePaths,meta:Xw(e,!!t.silent)});Y=M.directives??[],M.ok?er=M.cursor:(ee=!0,M.error&&f.push(`pull: ${M.error}`)),I={written:M.written,overwritten:M.overwritten,skipped:M.skipped,conflicts:M.conflicts,deleted:M.deleted,warnings:M.warnings},D=M.conflictPaths;for(let[ce,Z]of Object.entries(M.pointerOverwrites)){let q=ce.match(/^content\/(.+)\.md$/)?.[1];q&&K.set(q,Z)}}if(R.ok){t.onPhase?.("inbox-write");let M=ki(e);for(let[ge,ut]of K)M.set(ge,ut);let ce=Zl(e,R.actions,{conflictedPaths:new Set(D),localPointers:M,declined:R.declined});b=ce.wrote,v=ce.removed,O=ce.skippedUnknown;let Z=l.announced_at?Date.parse(l.announced_at):NaN,oe=ce.pendingIds.length>0&&(Number.isNaN(Z)||Date.now()-Z>=Gw)?new Set:new Set(l.announced_action_ids??[]),Pe=new Set(ce.pendingIds.filter(ge=>!oe.has(ge)));S=ce.pending.filter(ge=>Pe.has(ge.id)),L=Pe.size-S.length,l.announced_action_ids=ce.pendingIds,Pe.size>0&&(l.announced_at=new Date().toISOString()),l.inbox_pulled_at=new Date().toISOString();let fe=Jl(R.actions);I&&D.length>0&&(I.warnings=I.warnings.map(ge=>{let ut=D.find(Ro=>ge.startsWith(`${Ro}:`)),_o=ut?fe.get(ut):void 0;return!ut||!_o?.length?ge:`${ge} \u2014 blocks inbox action${_o.length===1?"":"s"} ${_o.map(Ro=>`#${Ro}`).join(", ")}; take the cloud copy with \`seoagent sync --force ${ut}\``}))}let He=!!I&&I.written+I.overwritten+I.deleted+I.conflicts>0;if(l.last_synced_at=er,l.last_run_at=new Date().toISOString(),Vr(r.domain,l),t.onPhase?.("done"),h===0&&k===0&&b===0&&v===0&&!He&&!w.restored&&w.notices.length===0&&y.flushed===0&&y.dropped===0&&!ee&&p===0)return{ok:!0,reason:"no-changes",synced:0,failed:0,errors:f,actionsPulled:0,actionsRemoved:0,actionsNew:[],actionsUnknown:O,actionsUnknownNew:L,acksFlushed:0,acksDropped:0,pull:I,directives:Y};let tr=p===0&&!ee;return{ok:tr,reason:tr?void 0:"error",synced:d,failed:p,errors:f,...g.length>0?{notices:g}:{},...k>0?{pushDeferred:k}:{},actionsPulled:b,actionsRemoved:v,actionsNew:S,actionsUnknown:O,actionsUnknownNew:L,acksFlushed:y.flushed,acksDropped:y.dropped,pull:I,directives:Y}}function Xw(e,t){let n=he(),r=n?null:C(e)?.install_id??null;return{cliVersion:n?null:P,agent:Dl(),authSource:fr(),installId:r||null,silent:t,...n?{}:Qw(e)}}function Qw(e){let t=Pr(e);return t?{repo:t}:{}}async function Zw(e,t,n){let r=Yr(e);if(r.length===0)return{flushed:0,dropped:0};let o=0,i=0,s=[];for(let a of r){let l=await eb(a,t,n);l==="flushed"?(o++,s.push(a.action_id)):l==="already-settled"&&(i++,s.push(a.action_id))}return s.length>0&&jc(e,s),{flushed:o,dropped:i}}async function eb(e,t,n){try{let r=await j(`${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,...e.details??{}}})});return r.ok?"flushed":r.status===409||r.status>=400&&r.status<500?"already-settled":"transient"}catch{return"transient"}}function ou(e,t=3e4){let n=Date.now()+t,r=setTimeout(e,t);return r.unref?.(),{cancel:()=>clearTimeout(r),deadlineAt:n}}function iu(e){let t;try{t=C(e)}catch{return{refreshed:!1,reason:"error"}}if(!t)return{refreshed:!1,reason:"no-project"};if(gt(e))return{refreshed:!1,reason:"source-repo"};let n=t.skill_version||t.seoagent_version||"0.0.0";if(at(P,n)<=0){if(!t.skill_version)try{X(e,{skill_version:n})}catch{}return{refreshed:!1,reason:"up-to-date"}}try{return It(e),X(e,{skill_version:P}),{refreshed:!0,from:n,to:P,reason:"refreshed"}}catch{return{refreshed:!1,reason:"error"}}}function Pi(e){let t;try{t=C(e)}catch{return{refreshed:!1,from:null,to:P,reason:"error"}}if(!t)return{refreshed:!1,from:null,to:P,reason:"no-project"};if(gt(e))return{refreshed:!1,from:null,to:P,reason:"source-repo"};let n=t.skill_version||t.seoagent_version||null;try{return It(e),X(e,{skill_version:P}),{refreshed:!0,from:n,to:P,reason:"refreshed"}}catch{return{refreshed:!1,from:n,to:P,reason:"error"}}}import{createHash as tb}from"crypto";import{existsSync as jA,mkdirSync as nb,readFileSync as rb,writeFileSync as ob}from"fs";import{join as ib}from"path";var sb="/cli-skill/manifest.json",ab=360*60*1e3,lb=4e3,cb=64,ub=4*1024*1024,su=ib(z,"skill-check.json"),db=e=>tb("sha256").update(e).digest("hex");function pb(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>cb)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>ub||!n.some(o=>o.path==="SKILL.md")?null:{hash:t.hash,min_cli:t.min_cli,files:n}}function fb(e,t,n=ab){if(!e)return!0;let r=Date.parse(e);return Number.isNaN(r)?!0:t.getTime()-r>=n}function gb(){try{return JSON.parse(rb(su,"utf-8"))}catch{return{}}}function mb(e){try{nb(z,{recursive:!0}),ob(su,JSON.stringify({checkedAt:e}),"utf-8")}catch{}}async function hb(e){try{let t=await fetch(e,{signal:AbortSignal.timeout(lb),headers:{"User-Agent":`seoagent-cli/${P}`}});return t.ok?await t.text():null}catch{return null}}async function au(e,t={}){try{let n=C(e);if(!n)return{updated:!1,reason:"no-project"};if(gt(e))return{updated:!1,reason:"source-repo"};let r=t.now??new Date,o=gb();if(!t.force&&!fb(o.checkedAt,r))return{updated:!1,reason:"throttled"};let i=(t.baseUrl??U.BASE).replace(/\/$/,""),s=t.fetchText??hb,a=await s(`${i}${sb}`);if(mb(r.toISOString()),a===null)return{updated:!1,reason:"unreachable"};let l;try{l=pb(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(at(P,l.min_cli)<0)return{updated:!1,reason:"cli-too-old",minCli:l.min_cli};let c={};for(let d of l.files){let p=await s(`${i}/cli-skill/${d.path}`);if(p===null)return{updated:!1,reason:"unreachable"};if(db(p)!==d.sha256)return{updated:!1,reason:"verify-failed"};c[d.path]=p}return la(e,c),X(e,{skill_hash:l.hash}),{updated:!0,reason:"updated",fileCount:l.files.length}}catch{return{updated:!1,reason:"error"}}}import{existsSync as yb,mkdirSync as wb,readFileSync as bb,writeFileSync as kb}from"fs";var xb="https://registry.npmjs.org/@seoagent-official/seoagent/latest",vb=1440*60*1e3,$b=720*60*1e3,Sb=2e3;function _b(e,t,n=vb){if(!e.checkedAt)return!0;let r=Date.parse(e.checkedAt);return Number.isNaN(r)?!0:t.getTime()-r>=n}function Rb(e){let{current:t,latest:n,cache:r,now:o}=e,i=e.notifyIntervalMs??$b;if(!n||at(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 Eb(e){let t=e.readCache();if(_b(t,e.now,e.checkIntervalMs)){let r=await e.fetchLatest();t={...t,checkedAt:e.now.toISOString(),...r?{latest:r}:{}},e.writeCache(t)}let n=Rb({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 Cb(){try{return yb(rr)?JSON.parse(bb(rr,"utf-8")):{}}catch{return{}}}function Ab(e){try{wb(z,{recursive:!0}),kb(rr,JSON.stringify(e,null,2)+`
|
|
989
|
+
`,"utf-8")}catch{}}async function Ib(){try{let e=await fetch(xb,{headers:{Accept:"application/json"},signal:AbortSignal.timeout(Sb)});if(!e.ok)return null;let t=await e.json();return typeof t.version=="string"?t.version:null}catch{return null}}async function lu(e){try{return await Eb({current:e,now:new Date,fetchLatest:Ib,readCache:Cb,writeCache:Ab})}catch{return null}}import{existsSync as cu,readdirSync as uu}from"fs";import{join as Ti}from"path";var du={PILLAR:0,SUB_PILLAR:1,LONG_TAIL:2},pu={high:0,medium:1,low:2},Pb=new Set(["drafted","published","dismissed","superseded","done"]);function Tb(e){let t=e.filter(n=>!Pb.has((n.status??"").toLowerCase()));return t.length===0?null:[...t].sort((n,r)=>{let o=du[(n.role??"").toUpperCase()]??3,i=du[(r.role??"").toUpperCase()]??3;if(o!==i)return o-i;let s=pu[(n.priority??"").toLowerCase()]??3,a=pu[(r.priority??"").toLowerCase()]??3;return s!==a?s-a:n.slug.localeCompare(r.slug)})[0]}function fu(e,t,n){if(t>0)return null;let r=Tb(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 gu(e){let t={briefs:[],articleCount:0};try{let n=Ti(e,"briefs");if(cu(n))for(let o of uu(n).filter(i=>i.endsWith(".md"))){let s=Qe(Ti(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=Ti(e,"content");cu(r)&&(t.articleCount=uu(r).filter(o=>o.endsWith(".md")).length)}catch{}return t}var Ob=1500;function Lb(e=process.env){switch(gr(e)){case"absent":return"sync_unauthenticated";case"malformed":return"sync_unauthenticated_malformed";default:return null}}function Nb(e){try{let n=C(e)?.domain?.trim();return!n||n.toLowerCase()==="unknown"?null:n}catch{return null}}async function mu(e){let t=e.env??process.env,n=Lb(t);if(!n)return{sent:!1,event:null,skipped:"authenticated",throwawayInstallId:!1};if(he(t))return{sent:!1,event:n,skipped:"opted-out",throwawayInstallId:!1};let r=null;try{r=$e(e.cwd)}catch{r=null}let o=!r,i=Nb(e.cwd),s=i?{domain:i}:null;return{sent:await ze({installId:r??dt(),event:n,cliVersion:P,meta:s,apiBase:e.apiBase,fetchImpl:e.fetchImpl,timeoutMs:e.timeoutMs??Ob}),event:n,throwawayInstallId:o,domain:i}}import{isAbsolute as hu,relative as jb,resolve as yu,sep as Db}from"path";var Mb=new Set(["posttooluse","afterfileedit"]),Ub=120*1e3,Fb=250;function Bb(e){let t=e.trim();if(!t.startsWith("{"))return null;try{let n=JSON.parse(t),r=typeof n.hook_event_name=="string"?n.hook_event_name:null,o=n.tool_input??null,i=o&&typeof o.file_path=="string"&&o.file_path.length>0?o.file_path:typeof n.file_path=="string"&&n.file_path.length>0?n.file_path:null;return!r&&!i?null:{event:r,filePath:i}}catch{return null}}function Oi(e){return!!e?.event&&Mb.has(e.event.toLowerCase())}function wu(e){return!!e?.event&&e.event.toLowerCase()==="sessionstart"}function Gb(e,t){let n=hu(e)?e:yu(t,e),r=jb(yu(t,x),n);return r===""||!r.startsWith("..")&&!hu(r)&&!r.startsWith(`..${Db}`)}function bu(e){if(!Oi(e.input)||e.input?.filePath&&Gb(e.input.filePath,e.projectDir)||!e.lastRunAt)return!1;let t=Date.parse(e.lastRunAt);if(!Number.isFinite(t))return!1;let n=e.now??Date.now();return n-t>=0&&n-t<(e.minGapMs??Ub)}function Hb(e){return e.replace(/\u001b\[[0-9;]*m/g,"")}function ku(e){let t=Hb(e).trim();return t?JSON.stringify({hookSpecificOutput:{hookEventName:"PostToolUse",additionalContext:t}}):null}async function xu(e=process.stdin,t=Fb){return e.isTTY?null:new Promise(n=>{let r="",o=!1,i=()=>{if(!o){o=!0,clearTimeout(a),e.removeListener("data",s),e.removeListener("end",i),e.removeListener("error",i);try{e.pause(),e.unref?.()}catch{}n(Bb(r))}},s=l=>{r+=l.toString(),r.length>1e6&&i()},a=setTimeout(i,t);try{e.setEncoding?.("utf-8"),e.on("data",s),e.once("end",i),e.once("error",i),e.resume()}catch{i()}})}var qb=new Set(["no-auth","no-project","domain-unknown","project-mismatch","unverified"]);function Vb(e){return qb.has(e)}var Yb=new Set(["inbox-write","done"]);function zb(e,t,n){let r=Math.round(t/1e3),o=e!==null&&Yb.has(e),i=e?` during the ${e} pass`:"";return o?{text:`Sync gave up after ${r}s${i} (network too slow or stalled). Pushed files are remembered; the rest goes on the next run.`,inboxRefreshed:o}:{text:`Sync gave up after ${r}s${i} \u2014 pending dashboard actions were NOT pulled this run, so .seoagent/inbox/ and \`${n("inbox")}\` may be stale. Run \`${n("pull")}\` to refresh them; pushed files are remembered and the rest goes on the next sync.`,inboxRefreshed:o}}function Jb(e,t){return e==="malformed"?`SEOAGENT_AUTH is set but is not \`<user_token>:<website_token>\` - nothing was synced and no work was pulled. Run \`${t("whoami")} --env\` on the machine that is logged in, store its output as the secret exactly (no quotes, and without the \`SEOAGENT_AUTH=\` prefix inside the value), then run \`${t("sync")}\` again.`:`Not logged in - nothing was synced and no work was pulled. This directory is not connected to SEOAgent. Fix: run \`${t("login")} --code <code>\` here (get a code at seoagent.com/grok or from the dashboard install prompt), then \`${t("sync")}\` again. Scheduled run? Export the SEOAGENT_AUTH secret before this command (\`${t("whoami")} --env\` on the logged-in machine prints it).`}function vu(e,t=e.length){let n=e.slice(0,5).map(o=>` - ${o.title||o.action_type} (id ${o.id})`).join(`
|
|
990
990
|
`),r=t>5?`
|
|
991
991
|
\u2026 and ${t-5} more`:"";return`inbox: ${t} pending SEOAgent action${t===1?"":"s"} waiting in .seoagent/inbox/:
|
|
992
992
|
${n}${r}
|
|
993
993
|
Work them now, unless the user asked for something else first: read .seoagent/inbox/README.md, do each one (see references/inbox.md), then \`seoagent ack <id>\`. Each file says whether it needs the user's go-ahead.`}function Xb(e){try{let t=C(e);return t?.domain?An(t.domain).last_run_at??null:null}catch{return null}}async function be(e={}){let t=e.silent?e.hookInput!==void 0?e.hookInput:await xu():null;if(e.silent&&bu({input:t,projectDir:process.cwd(),lastRunAt:Xb(process.cwd())}))return;let n=e.announceAll??(!e.silent||wu(t)),r=e.silent&&Oi(t)&&t?.event==="PostToolUse"?ll():null,o=()=>{if(!r)return;let a=ku(r());a&&process.stdout.write(a+`
|
|
994
|
-
`)},i=null,s=ou(()=>{Jr(process.cwd());let a=zb(i,3e4,m);(!e.silent||!a.inboxRefreshed)&&u.warn(a.text),o(),process.exit(e.strict&&!a.inboxRefreshed?1:0)});try{await Qb({...e,announceAll:n},{deadlineAt:s.deadlineAt,onPhase:a=>{i=a}})}finally{s.cancel(),o()}}async function Qb(e,t={}){let n=iu(process.cwd());if(n.refreshed){try{
|
|
994
|
+
`)},i=null,s=ou(()=>{Jr(process.cwd());let a=zb(i,3e4,m);(!e.silent||!a.inboxRefreshed)&&u.warn(a.text),o(),process.exit(e.strict&&!a.inboxRefreshed?1:0)});try{await Qb({...e,announceAll:n},{deadlineAt:s.deadlineAt,onPhase:a=>{i=a}})}finally{s.cancel(),o()}}async function Qb(e,t={}){let n=iu(process.cwd());if(n.refreshed){try{ht(process.cwd())}catch{}e.silent||u.info(G.dim(`\u21BB Updated SEOAgent skill ${n.from} \u2192 ${n.to}.`))}let r=await au(process.cwd());r.updated&&!e.silent&&u.info(G.dim("\u21BB Updated the SEOAgent skill to the latest published version.")),r.reason==="cli-too-old"&&!e.silent&&u.message(G.yellow(`A newer SEOAgent skill is available but needs CLI ${r.minCli} or later (you have ${P}). Run \`${m("update-cli")}\` to get it.`)),yt(process.cwd());let o=await lu(P);o&&u.message(G.yellow(o));let i=!e.silent&&!!process.stdout.isTTY,s=e.pullOnly?"Pulling":e.pushOnly?"Pushing":"Syncing",a=i?Kb():null;a?.start(`${s} ${G.dim(".seoagent/")} with your dashboard\u2026`);let{force:l,forcePaths:c}=$c(e.force),d=await eo(process.cwd(),{pathFilter:e.path,silent:!!e.silent,force:l,forcePaths:c,pushOnly:e.pushOnly,pullOnly:e.pullOnly,deadlineAt:t.deadlineAt,onPhase:t.onPhase}),p=!!e.strict&&Vb(d.reason);p&&(process.exitCode=1,u.warn(`sync --strict: nothing was synced (${d.reason}) - exiting 1.`));let g=d.verifyRecs;if(g){g.sourceRender&&u.message(G.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 w of g.corrections)u.message(G.yellow(`verify-recs: corrected ${w.file}:${w.line} \u2014 the live page (${w.pageUrl}) already serves ${w.entityLabel}; reflect this correction in your summary.`))}for(let w of Zc(d.aiFiles??null,m))!w.always&&e.silent||u.message(w.tone==="warn"?G.yellow(w.text):G.dim(w.text));let f=d.pagesRescan;if(f&&!e.silent&&u.message(G.dim(`+${f.added} page${f.added===1?"":"s"} discovered in the codebase \u2192 .seoagent/${f.file} (${f.total} total).`)),e.announceAll){let w=[];try{w=Ee(process.cwd())}catch{w=[]}w.length>0&&u.message(G.yellow(vu(w)))}else d.actionsNew&&d.actionsNew.length>0&&u.message(G.yellow(vu(d.actionsNew)));d.actionsUnknownNew&&d.actionsUnknownNew>0&&u.message(G.yellow(`inbox: ${d.actionsUnknownNew} pending action${d.actionsUnknownNew===1?"":"s"} need a newer CLI to display \u2014 run \`${m("update-cli")}\` and sync again.`));for(let w of Zb(d.directives??[]))u.message(w);if(e.silent){!d.ok&&!p&&(process.exitCode=0);return}if(d.reason==="no-auth"){let w=Jb(gr(),m);A(a,"warn",w,G.yellow(w)),process.exitCode=1,(await mu({cwd:process.cwd()})).sent&&u.info(G.dim(Ys));return}if(d.reason==="no-project"){A(a,"info",`No SEOAgent project here. Run \`${m("init")}\` first.`,G.dim("No SEOAgent project \u2014 sync skipped.")),a&&u.info(`Run \`${m("init")}\` first.`);return}if(d.reason==="domain-unknown"){A(a,"info","`.seoagent/project.md` has `domain: unknown` \u2014 cloud sync skipped until you set your site's domain.",G.dim("domain: unknown \u2014 cloud sync skipped.")),a&&u.info("Set `domain:` in .seoagent/project.md, then re-run sync.");return}if(d.reason==="project-mismatch"){let w=`Cloud project is ${d.cloudDomain} but this workspace is ${d.localDomain} \u2014 not syncing. Run \`${m("login")}\` from this directory to connect ${d.localDomain} \u2014 the CLI can stay logged into both sites at once, no need to log out first.`;A(a,"warn",w,G.yellow(w));return}if(d.reason==="unverified"){A(a,"info","Could not verify which cloud site this login is connected to \u2014 sync skipped (nothing pushed or pulled).",G.dim("Cloud binding unverified \u2014 sync skipped.")),a&&d.errors[0]&&u.info(` \u2022 ${d.errors[0]}`);return}if(d.reason==="no-changes"){A(a,"info","Already in sync.",G.green("Already in sync."));return}if(d.reason==="busy"){A(a,"info","Another sync is in progress \u2014 skipping this run.",G.dim("Another sync is in progress \u2014 skipping this run."));return}let h=ek(d);A(a,"success",h??"Sync complete.",G.green(h??"Sync complete.")),d.synced>0&&u.success(`Synced ${d.synced} file${d.synced===1?"":"s"} to your dashboard.`),d.pushDeferred&&d.pushDeferred>0&&u.info(`${d.pushDeferred} changed file${d.pushDeferred===1?"":"s"} deferred to the next sync (this run stopped pushing to leave time to pull your inbox). Run \`${m("sync")}\` again to push ${d.pushDeferred===1?"it":"them"} now.`);for(let w of d.notices??[])u.warn(`\u270D\uFE0F ${w}`);let y=d.pull;if(y){let w=y.written+y.overwritten;w>0&&u.success(`Pulled ${w} file${w===1?"":"s"} from the cloud`+(y.overwritten>0?` (${y.overwritten} updated)`:"")),y.deleted>0&&u.info(`Removed ${y.deleted} file${y.deleted===1?"":"s"} deleted in the cloud.`),y.conflicts>0&&u.warn(`${y.conflicts} conflict${y.conflicts===1?"":"s"} \u2014 local changes kept. Re-run with --force to take the cloud version.`);for(let k of y.warnings.slice(0,5))u.info(` \u2022 ${k}`)}{let w=gu(Ye(process.cwd())),k=fu(w.briefs,w.articleCount,m);k&&u.message(G.cyan(k))}if(d.acksFlushed&&d.acksFlushed>0&&u.success(`Flushed ${d.acksFlushed} offline ack${d.acksFlushed===1?"":"s"} from the queue.`),d.acksDropped&&d.acksDropped>0&&u.info(`Dropped ${d.acksDropped} stale ack${d.acksDropped===1?"":"s"} (already settled server-side).`),d.actionsPulled&&d.actionsPulled>0&&(u.success(`Pulled ${d.actionsPulled} pending action${d.actionsPulled===1?"":"s"} \u2192 .seoagent/inbox/`),e.embedded||u.info(' Open Claude Code in this directory and ask it to "process the inbox", or read the files yourself.')),d.actionsRemoved&&d.actionsRemoved>0&&u.info(`Cleaned up ${d.actionsRemoved} stale inbox file${d.actionsRemoved===1?"":"s"}.`),d.failed>0){u.warn(`${d.failed} file${d.failed===1?"":"s"} failed to sync. Will retry next run.`);for(let w of d.errors.slice(0,3))u.info(` \u2022 ${w}`)}}function Zb(e){return e.map(t=>{let n=t.tone==="upsell"?"SEOAgent Cloud":"SEOAgent";return(t.tone==="upsell"?G.magenta:t.tone==="action"?G.cyan:G.dim)(`${n} \u203A ${t.text}`)})}function ek(e){let t=e.synced,n=e.pushDeferred??0,r=e.pull?e.pull.written+e.pull.overwritten:0,o=e.actionsPulled??0,i=e.acksFlushed??0,s=e.pull?.conflicts??0,a=[];return i>0&&a.push(`${i} ack${i===1?"":"s"} flushed`),t>0&&a.push(`${t} pushed`),n>0&&a.push(`${n} deferred`),r>0&&a.push(`${r} pulled`),o>0&&a.push(`${o} action${o===1?"":"s"}`),s>0&&a.push(`${s} conflict${s===1?"":"s"}`),a.length===0&&e.failed===0?null:a.length>0?a.join(" \xB7 "):"Sync complete."}function H(e){let t=C(e);return t||(u.error(`No SEOAgent project here. Run \`${m("init")}\` first.`),process.exitCode=1,null)}async function St(e={}){if(e.print){let t=C(process.cwd())?.domain,n=B(t);if(!n){u.error(`Not logged in. Run \`${m("login")}\` first.`),process.exitCode=1;return}let r=H(process.cwd());if(!r)return;let o=n.api_base||U.BASE,i=`Bearer ${n.user_token}:${n.website_token}`;if(r.domain===Ze){u.warn("`.seoagent/project.md` has `domain: unknown` \u2014 set your site's domain before pulling from the cloud."),process.exitCode=1;return}let s=await et({apiBase:o,authHeader:i,localDomain:r.domain});if(s.verdict==="mismatch"){u.warn(Qs(s.cloudDomain,r.domain)),process.exitCode=1;return}if(s.verdict==="unverified"){u.warn(`Could not verify which cloud site this login is connected to (${s.detail}) \u2014 not pulling.`),process.exitCode=1;return}let a=await Sc({apiBase:o,authHeader:i,path:e.print});if(!a.ok){u.warn(a.error),process.exitCode=1;return}process.stdout.write(a.body),a.body.endsWith(`
|
|
995
995
|
`)||process.stdout.write(`
|
|
996
996
|
`);return}await be({pullOnly:!0,force:e.force,silent:e.silent,embedded:e.embedded})}import{existsSync as tk,readdirSync as nk,unlinkSync as rk}from"fs";import{join as $u}from"path";import{isCancel as ok,select as ik}from"@clack/prompts";var sk=["code","postcard","call","email"];function ak(e){let t={};return e.verify?.trim()&&(t.outcome="owner_verification_required",t.owner_action=e.verify.trim(),e.verifyType&&sk.includes(e.verifyType)&&(t.owner_action_type=e.verifyType)),e.url?.trim()&&(t.listing_url=e.url.trim()),Object.keys(t).length?t:null}async function Ni(e,t={}){if(e.length<=1)return Me(e[0],t);let n=0,r=[];for(let o of e)await Me(o,{...t,silent:!0})?n++:r.push(o);if(!t.silent){let o=t.failed?"declined":"completed";r.length===0?u.success(`${n} action${n===1?"":"s"} marked ${o}${t.failed&&t.reason?` (reason: ${t.reason})`:""}.`):u.warn(`${n} of ${e.length} actions marked ${o}; still pending: ${r.join(", ")}.`)}return r.length===0}async function Me(e,t={}){let n=null;if(e){let p=Number.parseInt(e,10);if(Number.isNaN(p)||p<=0)return u.error(`Invalid action id: "${e}"`),process.exitCode=1,!1;n=p}else if(n=await lk(),n===null)return!1;let r=n,o=C(process.cwd()),i=B(o?.domain);if(!i)return u.error(`Not logged in. Run \`${m("login")}\` first.`),process.exitCode=1,!1;if(!H(process.cwd()))return!1;let s=i.api_base||U.BASE,a=`Bearer ${i.user_token}:${i.website_token}`,l=t.failed?"failed":"completed",c=t.failed?null:ak(t),d;try{d=await j(`${s}/api/cli/actions/${r}/ack`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:a},body:JSON.stringify({status:l,error_message:t.failed&&t.reason?t.reason:null,result:t.failed?{declined:!0,reason:t.reason??null}:{applied:!0,...c??{}}})})}catch{return Nc(process.cwd(),{action_id:r,status:l,reason:t.failed?t.reason??null:null,...c?{details:c}:{}}),Li(r),Wr(process.cwd()),t.silent||u.warn(`Network unavailable \u2014 ack queued locally. Run \`${m("sync")}\` when you're back online; it'll flush automatically.`),!0}if(!d.ok){let p=await d.text().catch(()=>""),g=null;try{g=JSON.parse(p)}catch{g=null}if(d.status===409&&g?.code==="already_acked"){Li(r),Wr(process.cwd());let w=typeof g.error=="string"?g.error.match(/'([^']+)'/)?.[1]??"settled":"settled";return t.silent||u.success(`Action ${r} was already ${w}.`),!0}let f=typeof g?.code=="string"?g.code:null,h=typeof g?.error=="string"?g.error:p,y=f?`, code: ${f}`:"";return u.error(`Server rejected ack (${d.status}${y}): ${h.slice(0,200)}`),process.exitCode=1,!1}return Li(r),Wr(process.cwd()),t.silent||(t.failed?u.success(`Action ${r} marked failed${t.reason?` (reason: ${t.reason})`:""}.`):u.success(`Action ${r} marked completed.`),u.info(` inbox location: ${$u(x,"inbox")}`)),!0}function Li(e){let t=De(process.cwd());if(tk(t)){for(let n of nk(t))if(n.endsWith(`-${e}.md`))try{rk($u(t,n))}catch{}}}async function lk(){let e=process.cwd(),t=Ee(e);if(t.length===0)return u.info("No pending actions in .seoagent/inbox/."),u.message(`Run \`${m("sync")}\` to pull anything autopilot has queued.`),null;if(!process.stdout.isTTY)return u.error(`Cannot show interactive picker in non-TTY mode. Pass an action id explicitly, or run \`${m("inbox")}\` to see what's pending.`),process.exitCode=1,null;let n=await ik({message:`Which action do you want to close out? (${t.length} pending)`,options:t.map(r=>({value:r.id,label:Gt(r)}))});return ok(n)?(u.info("Cancelled \u2014 no action closed out."),null):n}import Yt from"picocolors";function Su(e,t=Date.now(),n=864e5){if(!e)return{status:"never",pulled_at:null,age_ms:null};let r=Date.parse(e);if(Number.isNaN(r))return{status:"never",pulled_at:null,age_ms:null};let o=Math.max(0,t-r);return o>=n?{status:"stale",pulled_at:e,age_ms:o}:{status:"fresh",pulled_at:e,age_ms:o}}function _u(e,t,n){let r=t?` for ${t}`:"";return e.status==="never"?`No completed inbox pull is on record${r} on this machine \u2014 this listing may be missing pending actions. Run \`${n("pull")}\` to refresh it.`:e.status==="stale"?`Inbox last refreshed ${ji(e.age_ms)} ago \u2014 this listing may be behind the dashboard. Run \`${n("pull")}\` to refresh it.`:null}function ji(e){let t=Math.floor(e/1e3);if(t<60)return`${t}s`;let n=Math.floor(t/60);if(n<60)return`${n}m`;let r=Math.floor(n/60);return r<48?`${r}h`:`${Math.floor(r/24)}d`}async function Di(e={}){let t=process.cwd();if(!e.json&&!H(t))return;let n=Ee(t),r=bi(t),o=C(t)?.domain??null,i=Su(Ic(o)),s=_u(i,o,m);if(e.json){process.stdout.write(JSON.stringify({count:n.length,inbox_path:De(t),pulled_at:i.pulled_at,pull_status:i.status,...s?{warning:s}:{},actions:n,declined:r})+`
|
|
997
997
|
`),s&&process.stderr.write(`warning: ${s}
|
|
@@ -1188,8 +1188,8 @@ ${t}
|
|
|
1188
1188
|
`),!(s.subtype==="success"&&!s.is_error))return u.error(" Agent did not complete the citation audit (auth, rate limit, or network)."),ep(),null;i=String(s.result??"")}return i}catch(o){return process.stdout.write(`
|
|
1189
1189
|
`),u.error(` Agent error: ${o.message}`),ep(),null}}function ep(){u.message(Iu.map(e=>W.dim(` ${e}`)).join(`
|
|
1190
1190
|
`))}function $$(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(ro(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(W.dim(` ${W.magenta("\u{1F50E}")} ${n}${o?` ${S$(o,70)}`:""}
|
|
1191
|
-
`))}}function S$(e,t){return e.length<=t?e:e.slice(0,t-1)+"\u2026"}function _$(e){return typeof e!="string"?[]:e.split(",").map(t=>t.trim()).filter(Boolean)}function R$(e){try{if(!k$(e))return null;let t=b$(e).filter(o=>o.endsWith(".json")).sort();if(t.length===0)return null;let n=t[t.length-1],r=JSON.parse(w$(tp(e,n),"utf-8"));return r&&typeof r=="object"&&Array.isArray(r.results)&&typeof r.generated_at=="string"?r:null}catch{return null}}import Qt from"picocolors";async function qn(e={}){if(e.all){await E$();return}let t=process.cwd();if(!H(t))return;
|
|
1192
|
-
`);return}if(l.status===429||c?.skipped?.startsWith("rate-limited")){let d=c?.skipped==="rate-limited:ip"?"this network":"this install";A(a,"warn",`Daily peek quota reached for ${d}.`,E.yellow(`Daily peek quota reached for ${d}.`)),u.message(rp()),process.exitCode=1;return}if(!l.ok||!c){A(a,"error",`Peek failed (${l.status})${c?.error?`: ${c.error}`:""}`,E.red(`Peek failed (${l.status}).`)),process.exitCode=1;return}if(c.skipped==="provider-quota"){A(a,"warn","Real keyword data is temporarily unavailable. Try again later.",E.yellow("Real keyword data is temporarily unavailable. Try again later.")),u.message(E.dim("No action needed on your side \u2014 the maintainer has been notified."));return}if(c.skipped==="no-creds"||c.skipped==="no-table"){A(a,"warn","Real keyword data isn\u2019t enabled on the server yet \u2014 try again later.",E.yellow("Real keyword data is not enabled on the server yet."));return}if(!c.opportunity){A(a,"warn",`No data returned for "${e.keyword}".`,E.yellow(`No data returned for "${e.keyword}".`));return}a?.stop(E.green("Got it.")),u.message(T$(c.opportunity)),c.remaining&&u.message(`${E.dim("Peeks remaining today:")} ${E.bold(String(c.remaining.install))} ${E.dim("\u2014 "+rp())}`)}function T$(e){let n=c=>E.dim(c.padEnd(14)),r=e.volume!=null?E.bold(String(e.volume)):E.dim("n/a"),o=e.difficulty!=null?`${E.bold(String(e.difficulty))} ${E.dim(O$(e.difficulty))}`:E.dim("n/a"),i=op[e.opportunity]??e.opportunity,s=e.opportunity==="easy_win"?E.bold(E.green(i)):e.opportunity==="striking_distance"?E.bold(E.cyan(i)):e.opportunity==="competitor_gap"?E.bold(E.yellow(i)):E.dim(i),a=[{content:`${E.green("\u25CF")} ${E.bold(e.keyword)}`},{sep:!0},{content:`${n("Volume:")}${r} ${E.dim("/mo")}`},{content:`${n("Difficulty:")}${o}`},{content:`${n("Opportunity:")}${s}`}],l=`${E.bold(E.cyan("Keyword peek"))}`;return V(a,{width:55,title:l})}function O$(e){return e<=20?"easy":e<=40?"moderate":e<=60?"hard":"very hard"}function rp(e=B()){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 ks(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 [${op[n.opportunity]??n.opportunity}] ${n.keyword} \u2014 ${r}, ${o}${i}`)}}}function L$(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 N$(e){let t=e.all?"all":"suggested";if(t==="all"&&process.stdin.isTTY){let a=await C$({message:"Delete ALL keywords for this site from the cloud? This cannot be undone."});if(A$(a)||!a){u.info("No changes.");return}}let n=e.apiBase||e.auth.api_base||U.BASE,r=process.stdout.isTTY?xs():null;r?.start(t==="all"?"Purging all keywords\u2026":"Purging suggested keywords\u2026");let o;try{o=await j(`${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){A(r,"error",`Network error: ${a.message}`,E.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"){A(r,"warn","Keyword storage isn\u2019t enabled on the server yet.",E.yellow("Nothing to purge yet."));return}if(!o.ok||i?.status!=="ok"){A(r,"error",`Purge failed (${o.status})${i?.error?`: ${i.error}`:""}`,E.red(`Purge failed (${o.status}).`)),process.exitCode=1;return}let s=i.purged??0;A(r,"success",`Purged ${s} ${t==="all"?"":"suggested "}keyword${s===1?"":"s"}.`,E.green(`Purged ${s} keyword${s===1?"":"s"}.`)),u.message(E.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 lt(e={}){if(e.peek&&e.peek.trim().length>0){await P$({keyword:e.peek.trim(),apiBase:e.apiBase,json:e.json});return}let t=C(process.cwd()),n=B(t?.domain);if(!n){u.error(`Not logged in. Run \`${m("login")}\` first.`),process.exitCode=1;return}if(e.purge){await N$({all:e.all,apiBase:e.apiBase,auth:n});return}let r=e.apiBase||n.api_base||U.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?xs():null;c?.start(a);let d=i==="competitors"||i==="discover"||i==="seed",p;try{p=await j(`${r}${s}`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:o},body:"{}"},d?{timeoutMs:I$,attempts:1}:{})}catch(h){A(c,"error",`Network error: ${h.message}`,E.red("Network error.")),process.exitCode=1;return}let g=null;try{g=await p.json()}catch{g=null}if(p.status===402||g?.code==="upgrade_required"){A(c,"warn",i==="competitors"?"Competitor gap analysis is a paid feature.":"Keyword discovery is a paid feature.",E.yellow("Paid feature \u2014 upgrade to continue.")),u.message(i==="competitors"?`Upgrade to see which keywords competitors rank for that you don't: ${E.cyan(ve("cli-keywords-competitors"))}`:`Upgrade to find new keywords to target: ${E.cyan(ve("cli-keywords-discover"))}`),process.exitCode=1;return}if(!p.ok||g?.status!=="ok"){A(c,"error",`Server rejected request (${p.status})${g?.error?`: ${g.error}`:""}`,E.red(`Server rejected request (${p.status}).`)),process.exitCode=1;return}if(g.skipped==="no-gsc-data"){A(c,"warn","No Search Console data to seed from yet. Run `seoagent gsc connect` to link Google Search Console \u2014 or wait for impressions to accrue on a new site.",E.yellow("No Search Console data yet."));return}if(g.skipped==="no-keywords"||g.skipped==="no-seeds"){A(c,"warn","No keywords yet. Seed from Search Console with `seoagent keywords --seed`, or run the keyword strategy (Phase 2) first, then try again.",E.yellow("No keywords yet."));return}if(g.skipped==="no-competitors"){A(c,"warn","No competitors found yet. Add competitors or run strategy discovery first, then try again.",E.yellow("No competitors yet."));return}if(g.skipped==="no-creds"||g.skipped==="no-columns"||g.skipped==="no-table"){A(c,"warn","Real keyword data isn\u2019t enabled on the server yet \u2014 try again later.",E.yellow("Real keyword data is not enabled on the server yet."));return}if(g.skipped){A(c,"warn",`Skipped: ${g.skipped}`,E.yellow(`Skipped: ${g.skipped}`));return}if(i==="competitors"){let h=g.gaps??0;A(c,"success",`Found ${h} competitor gap${h===1?"":"s"} across ${g.competitors?.length??0} competitor${g.competitors?.length===1?"":"s"} (added as "suggested").`,E.green(`${h} competitor gap${h===1?"":"s"} found.`)),L$(g.topGaps??[]),u.message(`Review them in ${E.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 h=j$(g);A(c,"success",h.headline,E.green(h.short)),ks(g.opportunities??[]),u.message(h.hint(m));return}if(i==="seed"){let h=g.seeded??0;A(c,"success",`Seeded ${h} keyword${h===1?"":"s"} from Search Console (real impressed queries, added as "suggested").`,E.green(`${h} keyword${h===1?"":"s"} seeded from Search Console.`)),ks(g.opportunities??[]),u.message(`These are queries your site already gets impressions for \u2014 striking-distance wins first. Review in ${E.cyan(".seoagent/keywords.md")} and build clusters from them.`);return}let f=g.enriched??0;A(c,"success",`Enriched ${f} keyword${f===1?"":"s"}.`,E.green(`Enriched ${f} keyword${f===1?"":"s"}.`)),ks(g.opportunities??[]),g.paid||u.message(`${E.dim("Free plan:")} enriched up to ${E.bold(String(g.cap??25))} keywords. ${E.dim("Upgrade for full enrichment + discovery + competitor gap:")} ${E.cyan(ve("cli-keywords-cap"))}`)}function j$(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 J from"picocolors";function Vn(e={}){let t=process.cwd(),n=C(t),r=ot(t);if(e.json){process.stdout.write(JSON.stringify(r,null,2)+`
|
|
1191
|
+
`))}}function S$(e,t){return e.length<=t?e:e.slice(0,t-1)+"\u2026"}function _$(e){return typeof e!="string"?[]:e.split(",").map(t=>t.trim()).filter(Boolean)}function R$(e){try{if(!k$(e))return null;let t=b$(e).filter(o=>o.endsWith(".json")).sort();if(t.length===0)return null;let n=t[t.length-1],r=JSON.parse(w$(tp(e,n),"utf-8"));return r&&typeof r=="object"&&Array.isArray(r.results)&&typeof r.generated_at=="string"?r:null}catch{return null}}import Qt from"picocolors";async function qn(e={}){if(e.all){await E$();return}let t=process.cwd();if(!H(t))return;yt(t);let n=Pi(t);np(t);let r=await On(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/${r.file} (${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 E$(){let e=xa();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 ${Qt.bold(P)}\u2026`);let t=0,n=0;for(let r of e){let o=Pi(r);np(r);let i=await On(r),s=i.added>0?Qt.cyan(` +${i.added} page${i.added===1?"":"s"}`):"";if(o.reason==="refreshed"){t++;let a=o.from&&o.from!==o.to?Qt.dim(` (${o.from} \u2192 ${o.to})`):"";u.message(` ${Qt.green("\u2713")} ${r}${a}${s}`)}else n++,u.message(` ${Qt.yellow("!")} ${r} ${Qt.dim("\u2014 "+o.reason)}${s}`)}u.success(`Refreshed ${t}/${e.length} project${e.length===1?"":"s"} to ${P}.`),n>0&&u.info(`${n} could not be refreshed (see above).`)}function np(e){try{ht(e)}catch{}}import{spinner as xs,confirm as C$,isCancel as A$}from"@clack/prompts";import E from"picocolors";var I$=11e4,op={easy_win:"easy win",striking_distance:"striking distance",competitor_gap:"competitor gap",defend:"defend",low_priority:"low priority"};async function P$(e){let t=process.cwd(),n=$e(t);if(!n){u.error(`No .seoagent/project.md found. Run \`${m("init")}\` first.`),process.exitCode=1;return}let r=C(t),o=r?.domain&&r.domain!=="unknown"?r.domain:void 0,i=(e.apiBase||U.BASE).replace(/\/$/,""),a=!!process.stdout.isTTY&&!e.json?xs():null;a?.start(`Looking up ${E.bold(`"${e.keyword}"`)} ${E.dim("via DataForSEO (free peek)\u2026")}`);let l;try{l=await j(`${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){A(a,"error",`Network error: ${d.message}`,E.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})+`
|
|
1192
|
+
`);return}if(l.status===429||c?.skipped?.startsWith("rate-limited")){let d=c?.skipped==="rate-limited:ip"?"this network":"this install";A(a,"warn",`Daily peek quota reached for ${d}.`,E.yellow(`Daily peek quota reached for ${d}.`)),u.message(rp()),process.exitCode=1;return}if(!l.ok||!c){A(a,"error",`Peek failed (${l.status})${c?.error?`: ${c.error}`:""}`,E.red(`Peek failed (${l.status}).`)),process.exitCode=1;return}if(c.skipped==="provider-quota"){A(a,"warn","Real keyword data is temporarily unavailable. Try again later.",E.yellow("Real keyword data is temporarily unavailable. Try again later.")),u.message(E.dim("No action needed on your side \u2014 the maintainer has been notified."));return}if(c.skipped==="no-creds"||c.skipped==="no-table"){A(a,"warn","Real keyword data isn\u2019t enabled on the server yet \u2014 try again later.",E.yellow("Real keyword data is not enabled on the server yet."));return}if(!c.opportunity){A(a,"warn",`No data returned for "${e.keyword}".`,E.yellow(`No data returned for "${e.keyword}".`));return}a?.stop(E.green("Got it.")),u.message(T$(c.opportunity)),c.remaining&&u.message(`${E.dim("Peeks remaining today:")} ${E.bold(String(c.remaining.install))} ${E.dim("\u2014 "+rp())}`)}function T$(e){let n=c=>E.dim(c.padEnd(14)),r=e.volume!=null?E.bold(String(e.volume)):E.dim("n/a"),o=e.difficulty!=null?`${E.bold(String(e.difficulty))} ${E.dim(O$(e.difficulty))}`:E.dim("n/a"),i=op[e.opportunity]??e.opportunity,s=e.opportunity==="easy_win"?E.bold(E.green(i)):e.opportunity==="striking_distance"?E.bold(E.cyan(i)):e.opportunity==="competitor_gap"?E.bold(E.yellow(i)):E.dim(i),a=[{content:`${E.green("\u25CF")} ${E.bold(e.keyword)}`},{sep:!0},{content:`${n("Volume:")}${r} ${E.dim("/mo")}`},{content:`${n("Difficulty:")}${o}`},{content:`${n("Opportunity:")}${s}`}],l=`${E.bold(E.cyan("Keyword peek"))}`;return V(a,{width:55,title:l})}function O$(e){return e<=20?"easy":e<=40?"moderate":e<=60?"hard":"very hard"}function rp(e=B()){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 ks(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 [${op[n.opportunity]??n.opportunity}] ${n.keyword} \u2014 ${r}, ${o}${i}`)}}}function L$(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 N$(e){let t=e.all?"all":"suggested";if(t==="all"&&process.stdin.isTTY){let a=await C$({message:"Delete ALL keywords for this site from the cloud? This cannot be undone."});if(A$(a)||!a){u.info("No changes.");return}}let n=e.apiBase||e.auth.api_base||U.BASE,r=process.stdout.isTTY?xs():null;r?.start(t==="all"?"Purging all keywords\u2026":"Purging suggested keywords\u2026");let o;try{o=await j(`${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){A(r,"error",`Network error: ${a.message}`,E.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"){A(r,"warn","Keyword storage isn\u2019t enabled on the server yet.",E.yellow("Nothing to purge yet."));return}if(!o.ok||i?.status!=="ok"){A(r,"error",`Purge failed (${o.status})${i?.error?`: ${i.error}`:""}`,E.red(`Purge failed (${o.status}).`)),process.exitCode=1;return}let s=i.purged??0;A(r,"success",`Purged ${s} ${t==="all"?"":"suggested "}keyword${s===1?"":"s"}.`,E.green(`Purged ${s} keyword${s===1?"":"s"}.`)),u.message(E.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 ct(e={}){if(e.peek&&e.peek.trim().length>0){await P$({keyword:e.peek.trim(),apiBase:e.apiBase,json:e.json});return}let t=C(process.cwd()),n=B(t?.domain);if(!n){u.error(`Not logged in. Run \`${m("login")}\` first.`),process.exitCode=1;return}if(e.purge){await N$({all:e.all,apiBase:e.apiBase,auth:n});return}let r=e.apiBase||n.api_base||U.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?xs():null;c?.start(a);let d=i==="competitors"||i==="discover"||i==="seed",p;try{p=await j(`${r}${s}`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:o},body:"{}"},d?{timeoutMs:I$,attempts:1}:{})}catch(h){A(c,"error",`Network error: ${h.message}`,E.red("Network error.")),process.exitCode=1;return}let g=null;try{g=await p.json()}catch{g=null}if(p.status===402||g?.code==="upgrade_required"){A(c,"warn",i==="competitors"?"Competitor gap analysis is a paid feature.":"Keyword discovery is a paid feature.",E.yellow("Paid feature \u2014 upgrade to continue.")),u.message(i==="competitors"?`Upgrade to see which keywords competitors rank for that you don't: ${E.cyan(ve("cli-keywords-competitors"))}`:`Upgrade to find new keywords to target: ${E.cyan(ve("cli-keywords-discover"))}`),process.exitCode=1;return}if(!p.ok||g?.status!=="ok"){A(c,"error",`Server rejected request (${p.status})${g?.error?`: ${g.error}`:""}`,E.red(`Server rejected request (${p.status}).`)),process.exitCode=1;return}if(g.skipped==="no-gsc-data"){A(c,"warn","No Search Console data to seed from yet. Run `seoagent gsc connect` to link Google Search Console \u2014 or wait for impressions to accrue on a new site.",E.yellow("No Search Console data yet."));return}if(g.skipped==="no-keywords"||g.skipped==="no-seeds"){A(c,"warn","No keywords yet. Seed from Search Console with `seoagent keywords --seed`, or run the keyword strategy (Phase 2) first, then try again.",E.yellow("No keywords yet."));return}if(g.skipped==="no-competitors"){A(c,"warn","No competitors found yet. Add competitors or run strategy discovery first, then try again.",E.yellow("No competitors yet."));return}if(g.skipped==="no-creds"||g.skipped==="no-columns"||g.skipped==="no-table"){A(c,"warn","Real keyword data isn\u2019t enabled on the server yet \u2014 try again later.",E.yellow("Real keyword data is not enabled on the server yet."));return}if(g.skipped){A(c,"warn",`Skipped: ${g.skipped}`,E.yellow(`Skipped: ${g.skipped}`));return}if(i==="competitors"){let h=g.gaps??0;A(c,"success",`Found ${h} competitor gap${h===1?"":"s"} across ${g.competitors?.length??0} competitor${g.competitors?.length===1?"":"s"} (added as "suggested").`,E.green(`${h} competitor gap${h===1?"":"s"} found.`)),L$(g.topGaps??[]),u.message(`Review them in ${E.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 h=j$(g);A(c,"success",h.headline,E.green(h.short)),ks(g.opportunities??[]),u.message(h.hint(m));return}if(i==="seed"){let h=g.seeded??0;A(c,"success",`Seeded ${h} keyword${h===1?"":"s"} from Search Console (real impressed queries, added as "suggested").`,E.green(`${h} keyword${h===1?"":"s"} seeded from Search Console.`)),ks(g.opportunities??[]),u.message(`These are queries your site already gets impressions for \u2014 striking-distance wins first. Review in ${E.cyan(".seoagent/keywords.md")} and build clusters from them.`);return}let f=g.enriched??0;A(c,"success",`Enriched ${f} keyword${f===1?"":"s"}.`,E.green(`Enriched ${f} keyword${f===1?"":"s"}.`)),ks(g.opportunities??[]),g.paid||u.message(`${E.dim("Free plan:")} enriched up to ${E.bold(String(g.cap??25))} keywords. ${E.dim("Upgrade for full enrichment + discovery + competitor gap:")} ${E.cyan(ve("cli-keywords-cap"))}`)}function j$(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 J from"picocolors";function Vn(e={}){let t=process.cwd(),n=C(t),r=ot(t);if(e.json){process.stdout.write(JSON.stringify(r,null,2)+`
|
|
1193
1193
|
`);return}if(u.message(D$(r)),!n){u.warn(`No SEOAgent project here. Run \`${m("init")}\` first to persist the provider.`);return}X(t,{image_provider:r.provider}),u.info(`Saved to ${J.cyan(".seoagent/project.md")} ${J.dim(`(image_provider: ${r.provider})`)}`)}function D$(e){let n=i=>J.dim(i.padEnd(12)),r=[];if(e.provider==="none"){r.push({content:`${J.dim("\u25CB")} ${J.bold(J.yellow("No image provider detected"))}`}),r.push({sep:!0}),r.push({content:J.dim("Set ONE of these to enable image generation:")});let i=Ta();for(let s of Object.keys(i))r.push({content:`${n(s+":")}${J.cyan(i[s].join(" or "))}`});r.push({sep:!0}),r.push({content:J.dim("Add to your shell, .env.local, or .env, then re-run:")}),r.push({content:` ${J.cyan(m("env-check"))}`})}else{r.push({content:`${J.green("\u2713")} ${J.dim("Image provider:")} ${J.bold(J.green(e.provider))}`}),r.push({sep:!0}),e.matched_key&&r.push({content:`${n("Key:")}${J.bold(e.matched_key)}`}),e.source&&r.push({content:`${n("Source:")}${J.cyan(e.source)}`});let i=e.available_providers.filter(s=>s!==e.provider);i.length>0&&r.push({content:`${n("Available:")}${J.dim(i.join(", "))}`})}let o=`${J.bold(J.cyan("SEOAgent"))} ${J.dim("\xB7")} ${J.dim("env-check")}`;return V(r,{width:64,title:o})}import{existsSync as Ss,mkdirSync as ap,writeFileSync as lp}from"fs";import{dirname as vo,isAbsolute as W$,join as xo,resolve as K$}from"path";import{tmpdir as q$}from"os";import{spinner as V$}from"@clack/prompts";import te from"picocolors";import{Buffer as ip}from"buffer";var M$=6e4,vs=18e4,U$=15e3;async function $s(e){let t=await fetch(e,{signal:AbortSignal.timeout(M$)});if(!t.ok)throw new Error(`Image download failed: ${t.status} ${t.statusText}`);let n=await t.arrayBuffer();return ip.from(n)}async function F$(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(vs)});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:ip.from(o.b64_json,"base64"),contentType:"image/png"};if(o.url)return{bytes:await $s(o.url),contentType:"image/png"};throw new Error("OpenAI image API returned neither b64_json nor url")}async function B$(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(vs)});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 $s(o.url),contentType:o.content_type??"image/png"}}async function G$(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(U$)});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 H$(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(vs)});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 G$(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 $s(o),contentType:"image/png",providerImageId:r.id}}async function sp(e,t){switch(e){case"openai":return F$(t);case"fal":return B$(t);case"replicate":return H$(t);default:throw new Error(`Unknown image provider: ${e}`)}}function Y$(e){return e==="openai"||e==="fal"||e==="replicate"}async function Yn(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=C(t),r=null;if(e.provider){if(!Y$(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=ot(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=Pa(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=W$(e.out)?e.out:K$(t,e.out),a=!e.silent&&!!process.stdout.isTTY?V$():null;a?.start(`Generating image with ${te.bold(r)} ${te.dim(`(${o.envName})`)}\u2026`);try{let l=await sp(r,{prompt:e.prompt,apiKey:o.key,size:e.size});Ss(vo(i))||ap(vo(i),{recursive:!0}),lp(i,l.bytes),e.silent||A(a,"success",`Wrote ${i} (${l.bytes.length} bytes, ${l.contentType}).`,te.green(`Wrote ${i} ${te.dim(`(${l.bytes.length} bytes, ${l.contentType})`)}`))}catch(l){a?.stop(te.red("Image generation failed."));let c=J$(t,{provider:r,envName:o.envName,prompt:e.prompt,size:e.size,outPath:i,error:l});u.message(z$({provider:r,envName:o.envName,prompt:e.prompt,size:e.size,message:l.message})),c&&u.message(`${te.dim("Full error log:")} ${te.cyan(c)}`),process.exitCode=1}}function z$(e){let n=d=>te.dim(d.padEnd(10)),o=e.prompt.length>54?`${e.prompt.slice(0,53)}\u2026`:e.prompt,i=[{content:`${te.red("\u2717")} ${te.bold(te.red("Image generation failed"))}`},{sep:!0},{content:`${n("Provider:")}${te.bold(e.provider)}`},{content:`${n("Key:")}${e.envName}`},{content:`${n("Prompt:")}${te.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:te.red(l)});let c=`${te.bold(te.cyan("SEOAgent"))} ${te.dim("\xB7")} ${te.dim("generate-image")}`;return V(i,{width:70,title:c})}function J$(e,t){let r=`seoagent-generate-image-${new Date().toISOString().replace(/[:.]/g,"-")}.log`,o=xo(e,x),i=xo(o,".errors"),s=Ss(o)?xo(i,r):xo(q$(),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 Ss(vo(s))||ap(vo(s),{recursive:!0}),lp(s,a.join(`
|
|
1194
1194
|
`)),s}catch{return null}}import ae from"picocolors";async function zn(e={}){let t=C(process.cwd()),n=B(t?.domain);if(!n){let l=tt();e.json?process.stdout.write(`${JSON.stringify({logged_in:!1,other_sites:l})}
|
|
1195
1195
|
`):(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}if(e.env){process.stdout.write(`${na(n)}
|
|
@@ -1199,4 +1199,4 @@ ${t}
|
|
|
1199
1199
|
`);return}u.message(X$(s)),a.length>0&&u.message(ae.dim(`Also logged in to: ${a.join(", ")}.`)),s.paid===!1&&u.message(`${ae.dim("Upgrade for autopilot:")} ${ae.cyan(ve("cli-whoami"))}`)}function X$(e){let n=i=>ae.dim(i.padEnd(10)),r=[];if(e.email&&(r.push({content:`${ae.green("\u2713")} ${ae.dim("Logged in as")} ${ae.bold(e.email)}`}),r.push({sep:!0})),e.domain&&r.push({content:`${n("Website:")}${ae.cyan(e.domain)}`}),e.plan){let i=e.paid?ae.bold(ae.green(e.plan)):ae.bold(e.plan);r.push({content:`${n("Plan:")}${i}`})}e.api_base&&r.push({content:`${n("API:")}${ae.dim(e.api_base)}`});let o=`${ae.bold(ae.cyan("SEOAgent"))} ${ae.dim("\xB7")} ${ae.dim("whoami")}`;return V(r,{width:55,title:o})}import{spinner as Q$}from"@clack/prompts";import Jn from"picocolors";function cp(e,t){switch(e){case"missing_header":case"malformed_header":case"invalid_credentials":return"Not logged in for this site. Run `seoagent login` first.";case"oauth_not_configured":return"Google sign-in is not configured on this server. Connect Search Console from the dashboard instead.";default:return typeof e=="string"&&e?`Search Console request failed: ${e}`:`HTTP ${t} (client error)`}}function up(e,t){let n=t&&typeof t=="object"?t:null;return e===404?{status:"error",terminal:!0,message:"This server does not support `seoagent gsc connect` yet. Connect Search Console from the dashboard, or update the CLI."}:e>=500?{status:"error",terminal:!1,message:`HTTP ${e} (server error)`}:e>=400?{status:"error",terminal:!0,message:cp(n?.code,e)}:n?.status==="ok"&&typeof n.auth_url=="string"&&typeof n.since=="string"?{status:"ok",auth_url:n.auth_url,since:n.since}:{status:"error",terminal:!0,message:'Malformed response: expected { status: "ok", auth_url, since }'}}function dp(e,t){let n=t&&typeof t=="object"?t:null;if(e>=500)return{status:"error",terminal:!1,message:`HTTP ${e} (server error)`};if(e===404)return{status:"error",terminal:!0,message:"This server does not support `seoagent gsc status` yet. Update the CLI or check the dashboard."};if(e>=400)return{status:"error",terminal:!0,message:cp(n?.code,e)};if(!n)return{status:"error",terminal:!0,message:"Malformed response: expected an object"};if(n.status==="none"||n.status==="pending")return{status:n.status};if(n.status==="ready"){let r=n.property&&typeof n.property=="object"?n.property:null;return{status:"ready",email:typeof n.email=="string"?n.email:null,property:r&&typeof r.site_url=="string"?{site_url:r.site_url,permission_level:typeof r.permission_level=="string"?r.permission_level:null,verified:r.verified!==!1}:null,properties_count:typeof n.properties_count=="number"?n.properties_count:0}}return{status:"error",terminal:!0,message:`Malformed response: unknown status=${JSON.stringify(n.status)}`}}function _s(e,t){let n=e.email?` as ${e.email}`:"";if(e.property)return`Search Console connected${n}. Property for ${t??"this site"}: ${e.property.site_url} (${e.property.permission_level??"verified"}).`;let r=t??"this site",o=e.properties_count;return`Search Console connected${n}, but that Google account has no verified property for ${r} (${o} propert${o===1?"y":"ies"} found). Add and verify ${r} at search.google.com/search-console, then run \`seoagent gsc connect\` again.`}var Z$=2e3,eS=300*1e3;function tS(e){return new Promise(t=>setTimeout(t,e))}async function pp(e,t,n){try{let r=`${e}/api/cli/gsc/status${n?`?since=${encodeURIComponent(n)}`:""}`,o=await fetch(r,{headers:{Authorization:t,Accept:"application/json"},signal:AbortSignal.timeout(3e4)}),i=null;try{i=await o.json()}catch{}return dp(o.status,i)}catch(r){return{status:"error",terminal:!1,message:r.message||"network error"}}}function Xn(e,t){if(he())return;let n=$e(e);n&&ze({installId:n,event:t,cliVersion:P})}async function Rs(e,t={}){if(e!=="connect"&&e!=="status"){u.error(`Unknown action "${e}". Use \`${m("gsc")} connect\` or \`${m("gsc")} status\`.`),process.exitCode=1;return}let n=process.cwd(),r=C(n),o=r?.domain&&r.domain!=="unknown"?r.domain:null,i=B(o);if(!i){t.json?process.stdout.write(`${JSON.stringify({logged_in:!1})}
|
|
1200
1200
|
`):u.error(`Not logged in${o?` for ${o}`:""}. Run \`${m("login")}\` first \u2014 Search Console connects to the account the CLI is bound to.`),process.exitCode=1;return}let s=(t.apiBase||i.api_base||U.BASE).replace(/\/$/,""),a=`Bearer ${i.user_token}:${i.website_token}`;if(e==="status"){let f=await pp(s,a,null);if(t.json){process.stdout.write(`${JSON.stringify({logged_in:!0,...f})}
|
|
1201
1201
|
`),f.status==="error"&&(process.exitCode=1);return}if(f.status==="ready"){let h=_s(f,o);f.property?u.success(h):u.warn(h);return}if(f.status==="error"){u.error(f.message),process.exitCode=1;return}u.info(`Search Console is not connected. Run \`${m("gsc")} connect\` to sign in with Google \u2014 takes about 30 seconds.`);return}let l;try{let f=await fetch(`${s}/api/cli/gsc/connect/start`,{method:"POST",headers:{Authorization:a,Accept:"application/json"},signal:AbortSignal.timeout(15e3)}),h=null;try{h=await f.json()}catch{}l=up(f.status,h)}catch(f){l={status:"error",terminal:!1,message:f.message||"network error"}}if(l.status==="error"){u.error(l.message),Xn(n,"gsc_connect_failed"),process.exitCode=1;return}u.message("Opening Google to connect Search Console to your SEOAgent account..."),u.info(`If the browser does not open, visit: ${l.auth_url}`),Ir(l.auth_url),u.info("In your browser: choose the Google account that owns the Search Console property, then click Allow.");let d=!!process.stdout.isTTY?Q$():null;d?.start(Jn.dim("Waiting for Google\u2026"));let p=Date.now()+eS,g=!0;for(;Date.now()<p;){g||await tS(Z$),g=!1;let f=await pp(s,a,l.since);if(f.status==="ready"){let h=_s(f,o);f.property?(A(d,"success",h,Jn.green(h)),u.info(`Real queries and indexing verdicts now flow to this site. Next: \`${m("keywords")} --seed\`.`),Xn(n,"gsc_connect_ok")):(A(d,"warn",h,Jn.yellow(h)),Xn(n,"gsc_connect_no_property")),t.json&&process.stdout.write(`${JSON.stringify(f)}
|
|
1202
|
-
`);return}if(f.status==="error"&&f.terminal){A(d,"warn",f.message,Jn.red(f.message)),Xn(n,"gsc_connect_failed"),process.exitCode=1;return}}A(d,"warn",`Timed out waiting for Google. Run \`${m("gsc")} connect\` again to retry.`,Jn.yellow("Timed out waiting for Google.")),d&&u.warn(`Run \`${m("gsc")} connect\` again to retry.`),Xn(n,"gsc_connect_timeout"),process.exitCode=1}import{select as Zn,text as Qn,confirm as mp,isCancel as xe,intro as nS,outro as rS}from"@clack/prompts";import $o from"picocolors";function oS(){return[{command:"status",label:"Show project status",hint:"Audit, strategy, briefs, content, roadmap \u2014 everything at a glance.",run:async()=>bn()},{command:"sync",label:"Sync with the cloud dashboard",hint:"Push local .seoagent/ changes + pull dashboard edits.",run:async()=>await be()},{command:"init",label:"Initialize a SEOAgent project",hint:"Scaffold .seoagent/ + the skill file in this directory.",run:async()=>{await mn()}},{command:"login",label:"Connect this CLI to seoagent.com",hint:"Opens the browser to bind your account.",run:async()=>await fn()},{command:"whoami",label:"Show logged-in account",hint:"Email, bound website, plan, API base.",run:async()=>await zn()},{command:"keywords",label:"Keyword research",hint:"Peek (free, one keyword) \xB7 enrich \xB7 discover \xB7 competitor gaps.",run:async()=>{let e=await Zn({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(!xe(e))if(e==="peek"){let t=await Qn({message:"Keyword to peek:",placeholder:"saas seo",validate:n=>String(n??"").trim()?void 0:"A keyword is required."});if(xe(t))return;await lt({peek:String(t)})}else e==="discover"?await lt({discover:!0}):e==="competitors"?await lt({competitors:!0}):await lt()}},{command:"internal-links",label:"Analyze internal links",hint:"Find orphan pages \u2192 .seoagent/internal-links.md",run:async()=>Un({})},{command:"okf",label:"Open Knowledge Format bundle",hint:"Make your site AI-readable (AEO/GEO) \u2192 .seoagent/okf/",run:async()=>{let e=await Zn({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"}]});xe(e)||Fn(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 Kn()},{command:"generate-image",label:"Generate an image",hint:"Uses the detected (or explicit) provider.",run:async()=>{let e=await Qn({message:"Image prompt:",placeholder:"a minimalist cyan box logo",validate:n=>String(n??"").trim()?void 0:"A prompt is required."});if(xe(e))return;let t=await Qn({message:"Output file path:",placeholder:".seoagent/content/images/hero.png",validate:n=>String(n??"").trim()?void 0:"An output path is required."});xe(t)||await Yn({prompt:String(e),out:String(t)})}},{command:"env-check",label:"Check image-generation provider",hint:"Detect openai / fal / replicate from your env.",run:async()=>Vn({})},{command:"autopilot",label:"Toggle CLI autopilot",hint:"on / off / status \u2014 paid feature for queueing SEO fixes.",run:async()=>{let e=await Zn({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"}]});xe(e)||await jn(String(e))}},{command:"pull",label:"Pull dashboard changes only",hint:"Cloud \u2192 local. Same as the second half of `sync`.",run:async()=>await St()},{command:"ack",label:"Close out a pending inbox action",hint:"Mark a .seoagent/inbox/ action completed or failed.",run:async()=>{let e=await Qn({message:"Action ID to close out:",placeholder:"action-abc123",validate:r=>String(r??"").trim()?void 0:"Action ID is required."});if(xe(e))return;let t=await mp({message:"Mark as failed instead of completed?",initialValue:!1});if(xe(t))return;let n;if(t===!0){let r=await Qn({message:"Reason for failure (optional):",placeholder:"couldn't apply because\u2026"});if(xe(r))return;String(r).trim()&&(n=String(r))}await Me(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 xn()},{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 vn({})},{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 Zn({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"}]});xe(e)||qn({all:e==="all"})}},{command:"logout",label:"Log out",hint:"Clear stored credentials for seoagent.com.",run:async()=>gn()},{command:"uninstall",label:"Uninstall SEOAgent from this project",hint:"Remove .seoagent/, the skill bundle, and the sync hook.",run:async()=>await hn({})}]}var fp="__exit__";async function gp(e=oS()){let t=e.map(o=>({value:o.command,label:o.label,hint:o.hint}));t.push({value:fp,label:"Exit",hint:"Close the menu"});let n=await Zn({message:"What would you like to do?",options:t});if(xe(n)||n===fp)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 Es(){if(!process.stdin.isTTY){u.info("The menu needs an interactive terminal. Use the regular commands directly, e.g. `seoagent --help`.");return}nS(`${$o.bold($o.cyan("SEOAgent"))} ${$o.dim("\xB7 menu")}`);let e=await gp();for(;!e;){let t=await mp({message:"Do another?",initialValue:!0});if(xe(t)||t===!1){e=!0;break}e=await gp()}rS($o.dim("See you next time."))}var T=new iS;T.name("seoagent").description("AI SEO agent for Claude Code").version(P).showSuggestionAfterError(!0).showHelpAfterError(!0);T.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)").option("--code <code>","One-time setup code from seoagent.com/grok \u2014 scaffolds AND binds this project in one command, no browser (also read from SEOAGENT_SETUP_CODE)").action(e=>{mn({yes:e.yes,domain:e.domain,siteType:e.siteType,allowWorktree:e.allowWorktree,code:e.code})});T.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=>{hn({yes:e.yes,global:e.global})});T.command("status").description("Show current SEO project state").action(bn);T.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=>{Ji({json:e.json})});T.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=>{fn({apiBase:e.apiBase,code:e.code})});T.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=>{gn({all:e.all})});T.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`)").option("--strict","Exit 1 when the login or the site binding could not be verified (for unattended runs; default exits 0)").action(e=>{be({strict:e.strict,silent:e.silent,force:e.force,path:e.path,pushOnly:e.pushOnly,pullOnly:e.pullOnly})});T.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=>{St({force:e.force,silent:e.silent,print:e.print})});T.command("ack [action_ids...]").description("Close out pending actions from .seoagent/inbox/ \u2014 one id, several ids with one verdict, or no id for an interactive picker").option("--failed","Mark as failed instead of completed").option("--reason <text>",'Reason for declining (used with --failed). Cite an earlier decline when it covers this one: "covered by #4674: \u2026"').option("--verify <text>","Listing submissions: the directory needs the owner to verify \u2014 what they must do").option("--verify-type <type>","With --verify: code | postcard | call | email").option("--url <url>","Listing submissions: the listing URL the directory showed").action((e,t)=>{Ni(e??[],{failed:t.failed,reason:t.reason,verify:t.verify,verifyType:t.verifyType,url:t.url})});T.command("inbox").description("List pending actions queued by autopilot in .seoagent/inbox/").option("--json","Output as JSON (handy for scripting / `jq`)").action(e=>{Di({json:e.json})});T.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)").option("--budget-usd <n>","Stop once the run has spent this many dollars of tokens (prints RUNNER_BUDGET_REACHED, exits 0)").option("--max-turns <n>","Stop once the run has used this many agent turns in total").option("--unattended","Disposable sandbox only: bypass permission prompts and forbid git commits/pushes (needs --yes)").option("--skip-types <csv>","Leave these action types pending, never opened or acked (comma-separated)").option("--defer-ack","Do not ack applied actions; the caller acks them later (declines are still acked)").action(e=>{Bi({yes:e.yes,model:e.model,budgetUsd:e.budgetUsd!==void 0?Number(e.budgetUsd):void 0,maxTurns:e.maxTurns!==void 0?Number(e.maxTurns):void 0,unattended:e.unattended,skipTypes:Fi(e.skipTypes),deferAck:e.deferAck})});T.command("autopilot <action> [value]").description("Turn CLI autopilot on|off, show status, toggle `auto-approve on|off`, or set `draft-mode cloud|agent` (paid; queues fixes for your agent)").option("--api-base <url>","Override API base URL (for testing)").option("-y, --yes","Confirm `off` up front (required while the free Autopilot trial is running; a TTY is asked instead)").action((e,t,n)=>{jn(e,t,{apiBase:n.apiBase,yes:n.yes})});T.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=>{lt({peek:e.peek,seed:e.seed,discover:e.discover,competitors:e.competitors,purge:e.purge,all:e.all,json:e.json,apiBase:e.apiBase})});T.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=>{Un({json:e.json})});T.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)=>{Vi(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})});T.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)=>{Fn(e,{json:t.json})});T.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=>{Xi({json:e.json})});T.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=>{Qi({url:e.url,json:e.json})});T.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=>{ss({url:e.url,max:e.max,json:e.json})});T.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=>{cs({url:e.url,max:e.max,json:e.json})});T.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)=>{us({files:e??[],json:t.json,quiet:t.quiet})});T.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)=>{ds({files:e??[],json:t.json})});T.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=>{ms({csv:e.csv,direction:e.direction,json:e.json})});T.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=>{Kn({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})});T.command("env-check").description("Detect which image generation provider is available (OPENAI / FAL / REPLICATE)").option("--json","Output detection result as JSON").action(e=>{Vn({json:e.json})});T.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=>{Yn({prompt:e.prompt,out:e.out,provider:e.provider,size:e.size,silent:e.silent})});T.command("gsc <action>").description("Google Search Console: `connect` opens Google sign-in and stores the connection in the cloud; `status` shows the connection and the property for this site").option("--json","Output as JSON (handy for scripting / `jq`)").option("--api-base <url>","Override API base URL (for testing)").action((e,t)=>{Rs(e,{json:t.json,apiBase:t.apiBase})});T.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("--env","Print `SEOAGENT_AUTH=<user>:<site>` for scheduled runs on a fresh machine (store it as the task secret)").option("--api-base <url>","Override API base URL (for testing)").action(e=>{zn({json:e.json,env:e.env,apiBase:e.apiBase})});T.command("upgrade").description("Open the SEOAgent Cloud pricing page in your browser (plan upgrade)").action(xn);T.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=>{vn({dryRun:e.dryRun,projects:e.projects})});T.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=>{qn({all:e.all,crawl:e.crawl})});T.command("menu").description("Interactive menu \u2014 pick a command from a list (no flags required)").action(()=>{Es()});process.argv.length===2&&(T.outputHelp(),process.exit(0));T.parse();
|
|
1202
|
+
`);return}if(f.status==="error"&&f.terminal){A(d,"warn",f.message,Jn.red(f.message)),Xn(n,"gsc_connect_failed"),process.exitCode=1;return}}A(d,"warn",`Timed out waiting for Google. Run \`${m("gsc")} connect\` again to retry.`,Jn.yellow("Timed out waiting for Google.")),d&&u.warn(`Run \`${m("gsc")} connect\` again to retry.`),Xn(n,"gsc_connect_timeout"),process.exitCode=1}import{select as Zn,text as Qn,confirm as mp,isCancel as xe,intro as nS,outro as rS}from"@clack/prompts";import $o from"picocolors";function oS(){return[{command:"status",label:"Show project status",hint:"Audit, strategy, briefs, content, roadmap \u2014 everything at a glance.",run:async()=>bn()},{command:"sync",label:"Sync with the cloud dashboard",hint:"Push local .seoagent/ changes + pull dashboard edits.",run:async()=>await be()},{command:"init",label:"Initialize a SEOAgent project",hint:"Scaffold .seoagent/ + the skill file in this directory.",run:async()=>{await mn()}},{command:"login",label:"Connect this CLI to seoagent.com",hint:"Opens the browser to bind your account.",run:async()=>await fn()},{command:"whoami",label:"Show logged-in account",hint:"Email, bound website, plan, API base.",run:async()=>await zn()},{command:"keywords",label:"Keyword research",hint:"Peek (free, one keyword) \xB7 enrich \xB7 discover \xB7 competitor gaps.",run:async()=>{let e=await Zn({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(!xe(e))if(e==="peek"){let t=await Qn({message:"Keyword to peek:",placeholder:"saas seo",validate:n=>String(n??"").trim()?void 0:"A keyword is required."});if(xe(t))return;await ct({peek:String(t)})}else e==="discover"?await ct({discover:!0}):e==="competitors"?await ct({competitors:!0}):await ct()}},{command:"internal-links",label:"Analyze internal links",hint:"Find orphan pages \u2192 .seoagent/internal-links.md",run:async()=>Un({})},{command:"okf",label:"Open Knowledge Format bundle",hint:"Make your site AI-readable (AEO/GEO) \u2192 .seoagent/okf/",run:async()=>{let e=await Zn({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"}]});xe(e)||Fn(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 Kn()},{command:"generate-image",label:"Generate an image",hint:"Uses the detected (or explicit) provider.",run:async()=>{let e=await Qn({message:"Image prompt:",placeholder:"a minimalist cyan box logo",validate:n=>String(n??"").trim()?void 0:"A prompt is required."});if(xe(e))return;let t=await Qn({message:"Output file path:",placeholder:".seoagent/content/images/hero.png",validate:n=>String(n??"").trim()?void 0:"An output path is required."});xe(t)||await Yn({prompt:String(e),out:String(t)})}},{command:"env-check",label:"Check image-generation provider",hint:"Detect openai / fal / replicate from your env.",run:async()=>Vn({})},{command:"autopilot",label:"Toggle CLI autopilot",hint:"on / off / status \u2014 paid feature for queueing SEO fixes.",run:async()=>{let e=await Zn({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"}]});xe(e)||await jn(String(e))}},{command:"pull",label:"Pull dashboard changes only",hint:"Cloud \u2192 local. Same as the second half of `sync`.",run:async()=>await St()},{command:"ack",label:"Close out a pending inbox action",hint:"Mark a .seoagent/inbox/ action completed or failed.",run:async()=>{let e=await Qn({message:"Action ID to close out:",placeholder:"action-abc123",validate:r=>String(r??"").trim()?void 0:"Action ID is required."});if(xe(e))return;let t=await mp({message:"Mark as failed instead of completed?",initialValue:!1});if(xe(t))return;let n;if(t===!0){let r=await Qn({message:"Reason for failure (optional):",placeholder:"couldn't apply because\u2026"});if(xe(r))return;String(r).trim()&&(n=String(r))}await Me(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 xn()},{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 vn({})},{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 Zn({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"}]});xe(e)||qn({all:e==="all"})}},{command:"logout",label:"Log out",hint:"Clear stored credentials for seoagent.com.",run:async()=>gn()},{command:"uninstall",label:"Uninstall SEOAgent from this project",hint:"Remove .seoagent/, the skill bundle, and the sync hook.",run:async()=>await hn({})}]}var fp="__exit__";async function gp(e=oS()){let t=e.map(o=>({value:o.command,label:o.label,hint:o.hint}));t.push({value:fp,label:"Exit",hint:"Close the menu"});let n=await Zn({message:"What would you like to do?",options:t});if(xe(n)||n===fp)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 Es(){if(!process.stdin.isTTY){u.info("The menu needs an interactive terminal. Use the regular commands directly, e.g. `seoagent --help`.");return}nS(`${$o.bold($o.cyan("SEOAgent"))} ${$o.dim("\xB7 menu")}`);let e=await gp();for(;!e;){let t=await mp({message:"Do another?",initialValue:!0});if(xe(t)||t===!1){e=!0;break}e=await gp()}rS($o.dim("See you next time."))}var T=new iS;T.name("seoagent").description("AI SEO agent for Claude Code").version(P).showSuggestionAfterError(!0).showHelpAfterError(!0);T.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)").option("--code <code>","One-time setup code from seoagent.com/grok \u2014 scaffolds AND binds this project in one command, no browser (also read from SEOAGENT_SETUP_CODE)").action(e=>{mn({yes:e.yes,domain:e.domain,siteType:e.siteType,allowWorktree:e.allowWorktree,code:e.code})});T.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=>{hn({yes:e.yes,global:e.global})});T.command("status").description("Show current SEO project state").action(bn);T.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=>{Ji({json:e.json})});T.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=>{fn({apiBase:e.apiBase,code:e.code})});T.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=>{gn({all:e.all})});T.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`)").option("--strict","Exit 1 when the login or the site binding could not be verified (for unattended runs; default exits 0)").action(e=>{be({strict:e.strict,silent:e.silent,force:e.force,path:e.path,pushOnly:e.pushOnly,pullOnly:e.pullOnly})});T.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=>{St({force:e.force,silent:e.silent,print:e.print})});T.command("ack [action_ids...]").description("Close out pending actions from .seoagent/inbox/ \u2014 one id, several ids with one verdict, or no id for an interactive picker").option("--failed","Mark as failed instead of completed").option("--reason <text>",'Reason for declining (used with --failed). Cite an earlier decline when it covers this one: "covered by #4674: \u2026"').option("--verify <text>","Listing submissions: the directory needs the owner to verify \u2014 what they must do").option("--verify-type <type>","With --verify: code | postcard | call | email").option("--url <url>","Listing submissions: the listing URL the directory showed").action((e,t)=>{Ni(e??[],{failed:t.failed,reason:t.reason,verify:t.verify,verifyType:t.verifyType,url:t.url})});T.command("inbox").description("List pending actions queued by autopilot in .seoagent/inbox/").option("--json","Output as JSON (handy for scripting / `jq`)").action(e=>{Di({json:e.json})});T.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)").option("--budget-usd <n>","Stop once the run has spent this many dollars of tokens (prints RUNNER_BUDGET_REACHED, exits 0)").option("--max-turns <n>","Stop once the run has used this many agent turns in total").option("--unattended","Disposable sandbox only: bypass permission prompts and forbid git commits/pushes (needs --yes)").option("--skip-types <csv>","Leave these action types pending, never opened or acked (comma-separated)").option("--defer-ack","Do not ack applied actions; the caller acks them later (declines are still acked)").action(e=>{Bi({yes:e.yes,model:e.model,budgetUsd:e.budgetUsd!==void 0?Number(e.budgetUsd):void 0,maxTurns:e.maxTurns!==void 0?Number(e.maxTurns):void 0,unattended:e.unattended,skipTypes:Fi(e.skipTypes),deferAck:e.deferAck})});T.command("autopilot <action> [value]").description("Turn CLI autopilot on|off, show status, toggle `auto-approve on|off`, or set `draft-mode cloud|agent` (paid; queues fixes for your agent)").option("--api-base <url>","Override API base URL (for testing)").option("-y, --yes","Confirm `off` up front (required while the free Autopilot trial is running; a TTY is asked instead)").action((e,t,n)=>{jn(e,t,{apiBase:n.apiBase,yes:n.yes})});T.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=>{ct({peek:e.peek,seed:e.seed,discover:e.discover,competitors:e.competitors,purge:e.purge,all:e.all,json:e.json,apiBase:e.apiBase})});T.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=>{Un({json:e.json})});T.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)=>{Vi(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})});T.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)=>{Fn(e,{json:t.json})});T.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=>{Xi({json:e.json})});T.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=>{Qi({url:e.url,json:e.json})});T.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=>{ss({url:e.url,max:e.max,json:e.json})});T.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=>{cs({url:e.url,max:e.max,json:e.json})});T.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)=>{us({files:e??[],json:t.json,quiet:t.quiet})});T.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)=>{ds({files:e??[],json:t.json})});T.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=>{ms({csv:e.csv,direction:e.direction,json:e.json})});T.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=>{Kn({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})});T.command("env-check").description("Detect which image generation provider is available (OPENAI / FAL / REPLICATE)").option("--json","Output detection result as JSON").action(e=>{Vn({json:e.json})});T.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=>{Yn({prompt:e.prompt,out:e.out,provider:e.provider,size:e.size,silent:e.silent})});T.command("gsc <action>").description("Google Search Console: `connect` opens Google sign-in and stores the connection in the cloud; `status` shows the connection and the property for this site").option("--json","Output as JSON (handy for scripting / `jq`)").option("--api-base <url>","Override API base URL (for testing)").action((e,t)=>{Rs(e,{json:t.json,apiBase:t.apiBase})});T.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("--env","Print `SEOAGENT_AUTH=<user>:<site>` for scheduled runs on a fresh machine (store it as the task secret)").option("--api-base <url>","Override API base URL (for testing)").action(e=>{zn({json:e.json,env:e.env,apiBase:e.apiBase})});T.command("upgrade").description("Open the SEOAgent Cloud pricing page in your browser (plan upgrade)").action(xn);T.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=>{vn({dryRun:e.dryRun,projects:e.projects})});T.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=>{qn({all:e.all,crawl:e.crawl})});T.command("menu").description("Interactive menu \u2014 pick a command from a list (no flags required)").action(()=>{Es()});process.argv.length===2&&(T.outputHelp(),process.exit(0));T.parse();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seoagent-official/seoagent",
|
|
3
|
-
"version": "1.113.
|
|
3
|
+
"version": "1.113.4",
|
|
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": {
|