@seoagent-official/seoagent 1.136.0 → 1.136.1

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 CHANGED
@@ -94,7 +94,7 @@ description: Chronological history of changes to this OKF bundle.
94
94
  - Bundle scaffolded.
95
95
  `,"utf-8"),r.push("log.md")),{created:r,okfDir:n}}import{log as tn}from"@clack/prompts";var Bn=null;function nn(e,t){Bn?Bn.push(t):e(t)}var d={info:e=>nn(tn.info,e),warn:e=>nn(tn.warn,e),error:e=>nn(tn.error,e),success:e=>nn(tn.success,e),step:e=>nn(tn.step,e),message:e=>nn(tn.message,e)};function Zc(){let e=[];Bn=e;let t=!1;return()=>t?"":(t=!0,Bn===e&&(Bn=null),e.join(`
96
96
 
97
- `))}function T(e,t,n,r=n){e?e.stop(r):d[t](n)}var eu=[{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 tu(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 eu)if(n.some(o=>t.endsWith(o)&&t.length>o.length))return r;return null}var vP=eu.flatMap(({suffixes:e,platform:t})=>e.map(n=>({suffix:n,id:t.id,api:t.api})));function nu(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{execFileSync as Xy}from"child_process";var Qy=2048,Zy=200,ew=10,tw=/^[A-Za-z0-9._-]+$/,nw=/^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/,rw=new Set(["https:","http:","ssh:","git:","git+ssh:","ssh+git:"]),ow=/^(?:[^@\s/]+@)?([^:/\s@]+):(?!\/\/)(.+)$/;function ru(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>ew)return null;for(let i of n)if(!tw.test(i)||i==="."||i==="..")return null;let r=n[n.length-1],o=n.slice(0,-1).join("/");return`${o}/${r}`.length>Zy?null:{owner:o,repo:r}}function ou(e){let t=e.trim().toLowerCase().replace(/\.$/,"");return t.length<2||t.length>253?null:nw.test(t)?t:null}function iw(e){if(typeof e!="string")return null;let t=e.trim();if(!t||t.length>Qy||/\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(!rw.has(r.protocol.toLowerCase()))return null;let o=ou(r.hostname);if(!o)return null;let i=ru(sw(r.pathname));return i?{host:o,...i}:null}let n=t.match(ow);if(n){if(n[1].length<2)return null;let r=ou(n[1]);if(!r)return null;let o=ru(n[2]);return o?{host:r,...o}:null}return null}function sw(e){try{return decodeURIComponent(e)}catch{return e}}function aw(e){return e?{host:e.host,full_name:`${e.owner}/${e.repo}`}:null}var lw=1500,cw=e=>Xy("git",["-C",e,"remote","get-url","origin"],{encoding:"utf8",timeout:lw,stdio:["ignore","pipe","ignore"],windowsHide:!0,env:{...process.env,GIT_TERMINAL_PROMPT:"0"}});function ft(e,t=cw){try{return aw(iw(t(e)))}catch{return null}}function iu(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"github_app_not_configured":case"github_unavailable":return"GitHub is not set up on this server yet. Keep working through your coding agent.";default:return typeof e=="string"&&e?`GitHub request failed: ${e}`:`HTTP ${t} (client error)`}}function ls(e){return e&&typeof e=="object"?e:null}function su(e){let t=ls(e.setup_pr);return{status:"connected",repo:typeof e.repo=="string"?e.repo:null,account_login:typeof e.account_login=="string"?e.account_login:null,setup_pr:t?{number:typeof t.number=="number"?t.number:null,url:typeof t.url=="string"?t.url:null,state:typeof t.state=="string"?t.state:null,onboarded_at:typeof t.onboarded_at=="string"?t.onboarded_at:null}:null}}function au(e,t){return e===404?{status:"error",terminal:!0,message:"This server does not support `seoagent github` yet. Connect GitHub from the dashboard (Site settings), or update the CLI."}:e===503?{status:"error",terminal:!0,message:iu(t?.code,e)}:e>=500?{status:"error",terminal:!1,message:`HTTP ${e} (server error)`}:e>=400?{status:"error",terminal:!0,message:iu(t?.code,e)}:null}function lu(e,t){let n=ls(t),r=au(e,n);return r||(n?.status==="connected"?su(n):n?.status==="not_connected"?{status:"not_connected"}:{status:"error",terminal:!0,message:'Malformed response: expected { status: "connected" | "not_connected" }'})}function cu(e,t){let n=ls(t),r=au(e,n);return r||(n?.status==="connected"?su(n):n?.status==="link"&&typeof n.url=="string"&&/^https?:\/\//.test(n.url)?{status:"link",url:n.url,expires_at:typeof n.expires_at=="string"?n.expires_at:null,repo:typeof n.repo=="string"?n.repo:null}:{status:"error",terminal:!0,message:'Malformed response: expected { status: "link", url }'})}function cs(e){return e?e.host==="github.com"?null:e.host:null}function go(e,t){let n=e.repo?`${e.repo}${e.account_login&&!e.repo.startsWith(`${e.account_login}/`)?` (${e.account_login})`:""}`:"your repo",r=`GitHub connected${t?` for ${t}`:""}: ${n}.`,o=e.setup_pr;if(!o||!o.state)return`${r} SEOAgent is opening its first pull request ("Set up SEOAgent") now; it takes about 5 minutes.`;let i=o.number?`#${o.number}`:"The setup pull request",s=o.url?` ${o.url}`:"";return o.state==="merged"||o.onboarded_at?`${r} Setup pull request ${i} is merged: SEOAgent opens a pull request each weekday.`:o.state==="open"?`${r} Setup pull request ${i} is open \u2014 review and merge it:${s}`:o.state==="not_needed"?`${r} The repo already had SEOAgent's files: weekday pull requests are on.`:o.state==="closed"?`${r} Setup pull request ${i} was closed without merging. Reopen it on GitHub, or ask SEOAgent in the dashboard to open a new one.`:o.state==="failed"?`${r} SEOAgent could not open the setup pull request; it retries, and the dashboard shows why.`:`${r} Setup pull request: ${o.state}.`}function uu(e){let t=e.repo?` 2. Choose "Only select repositories", pick ${e.repo}, then click Install & Authorize.`:` 2. Pick the repository that builds ${e.domain??"your site"}, then click Install & Authorize.`;return[`Connect GitHub${e.domain?` for ${e.domain}`:""} \u2014 open this link (it works for 30 minutes):`,` ${e.url}`,"On GitHub:",` 1. Choose the account or organization that owns ${e.repo??"the repo"}.`,t,'SEOAgent then opens its first pull request ("Set up SEOAgent") in about 5 minutes. You review',"and merge every change; after that it opens one pull request each weekday, even while your","editor is closed.","Coding agent: give the user this link and these two steps. Then run `seoagent github --wait`","(it finishes as soon as they are done), or run `seoagent github` again when they say it is done."]}function du(e){return cs(e)?null:e?`Next: connect GitHub so SEOAgent opens pull requests on ${e.full_name} by itself \u2014 run \`seoagent github\`.`:"Next: if this site's repo is on GitHub, connect it so SEOAgent opens pull requests by itself \u2014 run `seoagent github`."}import{randomBytes as ww}from"crypto";import{isCancel as bw,spinner as yu,text as kw}from"@clack/prompts";import ve from"picocolors";import{exec as uw}from"child_process";function rn(e){let t=process.platform==="darwin"?`open "${e}"`:process.platform==="win32"?`start "" "${e}"`:`xdg-open "${e}"`;uw(t,()=>{})}function pu(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 dw,mkdirSync as pw,readFileSync as fw,writeFileSync as gw}from"fs";import{join as hw}from"path";var mw="install-id",yw=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;function fu(e=re){let t=hw(e,mw);try{if(dw(t)){let r=fw(t,"utf-8").trim();if(yw.test(r))return r}let n=$t();return pw(e,{recursive:!0}),gw(t,n+`
97
+ `))}function T(e,t,n,r=n){e?e.stop(r):d[t](n)}var eu=[{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 tu(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 eu)if(n.some(o=>t.endsWith(o)&&t.length>o.length))return r;return null}var vP=eu.flatMap(({suffixes:e,platform:t})=>e.map(n=>({suffix:n,id:t.id,api:t.api})));function nu(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{execFileSync as Xy}from"child_process";var Qy=2048,Zy=200,ew=10,tw=/^[A-Za-z0-9._-]+$/,nw=/^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/,rw=new Set(["https:","http:","ssh:","git:","git+ssh:","ssh+git:"]),ow=/^(?:[^@\s/]+@)?([^:/\s@]+):(?!\/\/)(.+)$/;function ru(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>ew)return null;for(let i of n)if(!tw.test(i)||i==="."||i==="..")return null;let r=n[n.length-1],o=n.slice(0,-1).join("/");return`${o}/${r}`.length>Zy?null:{owner:o,repo:r}}function ou(e){let t=e.trim().toLowerCase().replace(/\.$/,"");return t.length<2||t.length>253?null:nw.test(t)?t:null}function iw(e){if(typeof e!="string")return null;let t=e.trim();if(!t||t.length>Qy||/\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(!rw.has(r.protocol.toLowerCase()))return null;let o=ou(r.hostname);if(!o)return null;let i=ru(sw(r.pathname));return i?{host:o,...i}:null}let n=t.match(ow);if(n){if(n[1].length<2)return null;let r=ou(n[1]);if(!r)return null;let o=ru(n[2]);return o?{host:r,...o}:null}return null}function sw(e){try{return decodeURIComponent(e)}catch{return e}}function aw(e){return e?{host:e.host,full_name:`${e.owner}/${e.repo}`}:null}var lw=1500,cw=e=>Xy("git",["-C",e,"remote","get-url","origin"],{encoding:"utf8",timeout:lw,stdio:["ignore","pipe","ignore"],windowsHide:!0,env:{...process.env,GIT_TERMINAL_PROMPT:"0"}});function ft(e,t=cw){try{return aw(iw(t(e)))}catch{return null}}function iu(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"github_app_not_configured":case"github_unavailable":return"GitHub is not set up on this server yet. Keep working through your coding agent.";default:return typeof e=="string"&&e?`GitHub request failed: ${e}`:`HTTP ${t} (client error)`}}function ls(e){return e&&typeof e=="object"?e:null}function su(e){let t=ls(e.setup_pr);return{status:"connected",repo:typeof e.repo=="string"?e.repo:null,account_login:typeof e.account_login=="string"?e.account_login:null,setup_pr:t?{number:typeof t.number=="number"?t.number:null,url:typeof t.url=="string"?t.url:null,state:typeof t.state=="string"?t.state:null,onboarded_at:typeof t.onboarded_at=="string"?t.onboarded_at:null}:null}}function au(e,t){return e===404?{status:"error",terminal:!0,message:"This server does not support `seoagent github` yet. Connect GitHub from the dashboard (Site settings), or update the CLI."}:e===503?{status:"error",terminal:!0,message:iu(t?.code,e)}:e>=500?{status:"error",terminal:!1,message:`HTTP ${e} (server error)`}:e>=400?{status:"error",terminal:!0,message:iu(t?.code,e)}:null}function lu(e,t){let n=ls(t),r=au(e,n);return r||(n?.status==="connected"?su(n):n?.status==="not_connected"?{status:"not_connected"}:{status:"error",terminal:!0,message:'Malformed response: expected { status: "connected" | "not_connected" }'})}function cu(e,t){let n=ls(t),r=au(e,n);return r||(n?.status==="connected"?su(n):n?.status==="link"&&typeof n.url=="string"&&/^https?:\/\//.test(n.url)?{status:"link",url:n.url,expires_at:typeof n.expires_at=="string"?n.expires_at:null,repo:typeof n.repo=="string"?n.repo:null}:{status:"error",terminal:!0,message:'Malformed response: expected { status: "link", url }'})}function cs(e){return e?e.host==="github.com"?null:e.host:null}function go(e,t){let n=e.repo?`${e.repo}${e.account_login&&!e.repo.startsWith(`${e.account_login}/`)?` (${e.account_login})`:""}`:"your repo",r=`GitHub connected${t?` for ${t}`:""}: ${n}.`,o=e.setup_pr;if(!o||!o.state)return`${r} SEOAgent is opening its first pull request ("Set up SEOAgent") now; it takes about 5 minutes.`;let i=o.number?`#${o.number}`:"The setup pull request",s=o.url?` ${o.url}`:"";return o.state==="merged"||o.onboarded_at?`${r} Setup pull request ${i} is merged: SEOAgent opens a pull request each weekday.`:o.state==="open"?`${r} Setup pull request ${i} is open \u2014 review and merge it:${s}`:o.state==="not_needed"?`${r} The repo already had SEOAgent's files: weekday pull requests are on.`:o.state==="closed"?`${r} Setup pull request ${i} was closed without merging. Reopen it on GitHub, or ask SEOAgent in the dashboard to open a new one.`:o.state==="failed"?`${r} SEOAgent could not open the setup pull request; it retries, and the dashboard shows why.`:`${r} Setup pull request: ${o.state}.`}function uu(e){let t=e.command??"seoagent github",n=e.domain??"your site",r=e.repo?` 2. Choose "Only select repositories", pick the repository that builds ${n} (${e.repo}, if that is the one), then click Install & Authorize.`:` 2. Pick the repository that builds ${n}, then click Install & Authorize.`;return[`Connect GitHub${e.domain?` for ${e.domain}`:""} \u2014 open this link (it works for 30 minutes):`,` ${e.url}`,"On GitHub:"," 1. Choose the account or organization that owns that repository.",r,'SEOAgent then opens its first pull request ("Set up SEOAgent") in about 5 minutes. You review',"and merge every change; after that it opens one pull request each weekday, even while your","editor is closed.","Coding agent: put this link and these two steps in your reply to the user; do not wait for",`them. When they say it is done, run \`${t} status\`: it prints "GitHub connected" and the`,"setup pull request."]}function du(e,t="seoagent github"){return cs(e)?null:e?`Next: connect GitHub so SEOAgent opens pull requests on ${e.full_name} by itself \u2014 run \`${t}\`.`:`Next: if this site's repo is on GitHub, connect it so SEOAgent opens pull requests by itself \u2014 run \`${t}\`.`}import{randomBytes as ww}from"crypto";import{isCancel as bw,spinner as yu,text as kw}from"@clack/prompts";import ve from"picocolors";import{exec as uw}from"child_process";function rn(e){let t=process.platform==="darwin"?`open "${e}"`:process.platform==="win32"?`start "" "${e}"`:`xdg-open "${e}"`;uw(t,()=>{})}function pu(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 dw,mkdirSync as pw,readFileSync as fw,writeFileSync as gw}from"fs";import{join as hw}from"path";var mw="install-id",yw=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;function fu(e=re){let t=hw(e,mw);try{if(dw(t)){let r=fw(t,"utf-8").trim();if(yw.test(r))return r}let n=$t();return pw(e,{recursive:!0}),gw(t,n+`
98
98
  `,"utf-8"),n}catch{return null}}var _w=1500,xw=900*1e3,$w=2700*1e3;function vw(e){return e?$w:xw}function Sw(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 gu="Login timed out. If the browser page shows a `seoagent login --code \u2026` line, run that here; otherwise run `seoagent login` again.",Rw=3,Ew=2e3;function ho(e,t){if($e())return;let n=Pe(e);n&&nt({installId:n,event:t,cliVersion:F})}function Aw(){return ww(16).toString("hex")}function Cw(e,t,n){let r=new URLSearchParams({session:t});return n&&n!=="unknown"&&r.set("domain",n),`${e}/cli/auth?${r.toString()}`}function hu(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 Tw(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 Pw(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 pu(r.status,o)}catch(n){return{status:"error",terminal:!1,message:n.message||"network error"}}}function wu(e){return new Promise(t=>setTimeout(t,e))}async function bu(e,t,n,r){let o=V(),i=await Tw(o,e.website_token,c=>lt({apiBase:c.api_base,authHeader:`Bearer ${c.user_token}:${c.website_token}`,localDomain:""}));i&&o&&Ni(o,i),Ni({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 ${ve.bold(e.email)} (${ve.cyan(e.domain)}).`:e.email?`Logged in as ${ve.bold(e.email)}.`:"Logged in.";T(r,"success",s,ve.green(a)),r&&d.info("Future SEO work in this repo will sync to your dashboard.");let l=ct().filter(c=>c!==Me(e.domain||n));l.length>0&&d.info(`Also logged in to: ${l.join(", ")}. Each project auto-selects its own site \u2014 no need to log out first.`)}function Iw(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 us(e,t){if($e())return null;let n=Pe(e);if(n){let o=ft(e);return{installId:n,cliVersion:F,command:t,installScope:"project",...o?{repo:o}:{}}}let r=fu();return r?{installId:r,cliVersion:F,command:t,installScope:"machine"}:null}async function Ow(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(Iw(t,n,r)),signal:AbortSignal.timeout(1e4)}),i=null;try{i=await o.json()}catch{}return El(o.status,i)}catch(o){return{status:"error",terminal:!1,message:o.message||"network error"}}}var Nw=new Set(["code_already_used","code_revoked","code_expired"]);function Lw(e,t){return`That setup code was for ${e}, but this directory is the ${t} project \u2014 it stays ${t}, and running \`${h("sync")}\` here syncs ${t}. Credentials for ${e} are saved. One site per directory: run \`${h("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 Dw="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 ds(e){return e!=="ready"&&e!=="already"}function jw(e,t,n){if(!e||!n||t&&t!=="unknown")return!1;try{if(!ne(e,{domain:n}))return!1}catch{return!1}return d.info(`Saved domain: ${n} \u2192 .seoagent/project.md (from the setup code).`),!0}async function ps(e,t,n,r,o,i={}){if(!Gr.test(e))return d.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?yu():null;c?.start(ve.dim("Redeeming setup code\u2026"));let u="network error";for(let p=0;p<Rw;p++){p>0&&await wu(Ew);let g=await Ow(t,e,a,r);if(g.status==="ready")return await bu(g,t,n,c),s&&g.domain&&Me(g.domain)!==Me(n)?(d.warn(Lw(g.domain,n)),"other-site"):(jw(o,n,g.domain),d.info(Dw),i.onBound?.({email:g.email,domain:g.domain}),"ready");if(g.terminal)return s&&g.code&&Nw.has(g.code)?(T(c,"info",`Already logged in for ${n} \u2014 that setup code is spent (each works once); nothing to do.`,ve.dim(`Already logged in for ${n} \u2014 setup code spent, nothing to do.`)),"already"):(T(c,"warn",g.message,ve.red(g.message)),"rejected");u=g.message}return T(c,"warn",`Could not reach the server (${u}). Check your connection and run \`${h("login")} --code <code>\` again.`,ve.yellow("Could not reach the server.")),c&&d.warn(`Run \`${h("login")} --code <code>\` again to retry.`),"unreachable"}function mo(e){return e&&e!=="unknown"?Vr(e):!!V()}function mu(){return`Next: run \`${h("sync")}\` in this repo \u2014 it pulls your strategy, briefs and inbox into .seoagent/. Nothing syncs until it runs.`}async function Gn(e={}){let t=process.cwd(),n=C(t),r=(e.apiBase||H.BASE||et).replace(/\/$/,""),o=n?.domain??null;if(o==="unknown"&&process.stdin.isTTY){let m=await kw({message:".seoagent/project.md has `domain: unknown`. What is your site's URL?",placeholder:"example.com",validate(b){if(!String(b??"").trim())return"A site URL is required.";if(!hu(String(b)))return"Please enter a valid domain like `example.com`."}});if(bw(m)){d.warn("Login cancelled.");return}let w=hu(String(m));if(w){o=w;try{ne(t,{domain:w}),d.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&&mo(o)){let m=s?` for ${o}`:"";d.info(`Already logged in${m}. To switch accounts${m}, run \`${h("logout")}\` first.`);return}if(i){let m=await ps(i,r,o,us(t,"login"),t,{siteAlreadyBound:!!s&&mo(o)});ds(m)?process.exitCode=1:m==="ready"&&d.info(mu());return}let a=Aw(),l=Cw(r,a,o);d.message("Opening seoagent.com to connect this CLI to your account..."),d.info(`If the browser does not open, visit: ${l}`),rn(l),d.info('In your browser: sign in (if needed) and click "Connect this CLI" to finish.');let c=!!process.stdout.isTTY,u=c?yu():null,p=vw(c),g=Sw(p);u?u.start(ve.dim(g)):d.info(g);let f=Date.now()+p,y=!0;for(;Date.now()<f;){y||await wu(_w),y=!1;let m=await Pw(r,a);if(m.status==="ready"){await bu(m,r,o,u),ho(t,"login_browser_ok"),d.info(mu());return}if(m.status==="expired"){ho(t,"login_browser_expired"),T(u,"warn",`Session expired. Run \`${h("login")}\` again.`,ve.red("Session expired.")),u&&d.warn(`Run \`${h("login")}\` again.`);return}if(m.status==="error"){if(m.terminal){T(u,"warn",`Login failed: ${m.message}. Run \`${h("login")}\` again.`,ve.red(`Login failed: ${m.message}.`)),u&&d.warn(`Run \`${h("login")}\` again.`),ho(t,"login_browser_failed");return}continue}}T(u,"warn",gu,ve.yellow("Login timed out.")),u&&d.warn(gu),ho(t,"login_browser_timeout")}function Hn(e={}){let t=e.all?null:C(process.cwd()),n=!e.all&&t?.domain&&t.domain!=="unknown"?t.domain:null;if(!Ll(e.all?null:n)){d.info(n?`You were not logged in for ${n}.`:"You were not logged in.");return}d.success(n?`Logged out of ${n}.`:"Logged out.");let o=ct();o.length>0&&d.info(`Still logged in to: ${o.join(", ")}. Run \`${h("logout")} --all\` to log out of everything.`)}import{spinner as d$}from"@clack/prompts";import Y from"picocolors";import{existsSync as Zs,readFileSync as ex,readdirSync as tx}from"fs";import{join as ea,relative as nx,sep as rx}from"path";var Fw=["claude-code","cursor","codex","grok-bot","hosted-runner","unknown"],Uw="SEOAGENT_AGENT";function yo(e=process.env){let t=(e[Uw]??"").trim().toLowerCase();return Fw.includes(t)?t:e.CLAUDECODE||e.CLAUDE_CODE||e.CLAUDE_CODE_ENTRYPOINT?"claude-code":e.CURSOR_TRACE_ID||e.CURSOR_AGENT||e.CURSOR_SESSION_ID?"cursor":e.CODEX_SANDBOX||e.CODEX_THREAD_ID||e.OPENAI_CODEX?"codex":e.GROK_SANDBOX||e.XAI_SANDBOX?"grok-bot":"unknown"}var Mw="SEOAGENT_RUN_CLAIM",Bw="X-SEOAgent-Run-Claim",Gw=/^run-[A-Za-z0-9_-]{8,80}$/;function ku(e=process.env){let t=_u(e);return t?{[Bw]:t}:{}}function _u(e=process.env){let t=(e[Mw]??"").trim();return Gw.test(t)?t:null}function wo(e,t=process.env){return!e.failed&&_u(t)!==null}function Wn(e){return`Left action ${e} open on purpose: this hosted run's pull request closes it when the owner merges it. Nothing else to do.`}import{existsSync as yb,mkdirSync as wb,readdirSync as Nu,readFileSync as $o,writeFileSync as ws}from"fs";import{dirname as bb,join as sn,relative as kb}from"path";import{existsSync as qn,readFileSync as _o,readdirSync as Zw,statSync as eb}from"fs";import{extname as Ru,join as gt}from"path";import{existsSync as Hw,readdirSync as Ww,readFileSync as Kw}from"fs";import{join as on}from"path";var qw=["next.config.js","next.config.mjs","next.config.cjs","next.config.ts","next.config.mts"],Vw=["","public","static","dist"];function fs(e){if(typeof e!="string")return null;let t=e.trim();return!t.startsWith("/")||t.startsWith("//")||/[:*()[\]{}?+!\\]/.test(t)?null:M(t)}function Yw(e){let t=e.replace(/^\s*\/\/.*$/gm,"").replace(/\/\*[\s\S]*?\*\//g,""),n=[];for(let r of t.matchAll(/\{[^{}]*\}/g)){let o=r[0];if(!/\b(?:permanent|statusCode)\s*:/.test(o)||!/\bdestination\s*:/.test(o)||/\b(?:has|missing)\s*:/.test(o))continue;let i=/\bsource\s*:\s*(['"`])([^'"`\n]*)\1/.exec(o);if(!i)continue;let s=fs(i[2]);s&&n.push(s)}return n}function zw(e){let t;try{t=JSON.parse(e)}catch{return[]}if(!Array.isArray(t.redirects))return[];let n=[];for(let r of t.redirects){if(!r||typeof r.destination!="string"||Array.isArray(r.has)&&r.has.length>0||Array.isArray(r.missing)&&r.missing.length>0)continue;let o=fs(r.source);o&&n.push(o)}return n}function Jw(e){let t=[];for(let n of e.split(/\r?\n/)){let r=n.replace(/#.*$/,"").trim();if(!r)continue;let o=r.split(/\s+/);if(o.length<2)continue;let i=o.slice(2).find(a=>/^\d{3}!?$/.test(a));if(i){let a=Number.parseInt(i,10);if(a<300||a>399)continue}let s=fs(o[0]);s&&t.push(s)}return t}function bo(e){try{return Kw(e,"utf-8")}catch{return null}}function Xw(e){let t=new Set,n;try{n=ro(e)}catch{n=[e]}for(let r of n){for(let i of qw){let s=bo(on(r,i));if(s)for(let a of Yw(s))t.add(a)}let o=bo(on(r,"vercel.json"));if(o)for(let i of zw(o))t.add(i);for(let i of Vw){let s=bo(on(r,i,"_redirects"));if(s)for(let a of Jw(s))t.add(a)}}return t}function Qw(e){let t=new Set,n=on(e,k,"inbox"),r;try{r=Ww(n)}catch{return t}for(let o of r){if(!/^cli_prune_pending-\d+\.md$/.test(o))continue;let i=bo(on(n,o));if(!i)continue;let s;try{s=oe(i).data}catch{continue}if(s.owner_approved!==!0&&s.owner_approved!=="true")continue;let a=typeof s.page_url=="string"?s.page_url.trim():"";if(!a)continue;let l=typeof s.raw_path=="string"?s.raw_path.trim():"";l&&Hw(on(e,l))||t.add(M(a))}return t}function ko(e){let t=new Set;try{for(let n of Xw(e))t.add(n)}catch{}try{for(let n of Qw(e))t.add(n)}catch{}return t.delete("/"),t}var Vn="llms.md",tb=300,xu=100,nb=160;function xo(e){return Nt(e).replace(/\s+/g," ").replace(/[[\]]/g,"").trim()}function gs(e,t=tb){let n=xo(e);if(n.length<=t)return n;let r=-1,o=/[.!?]["'”’)\]]?(?=\s)/g;for(let l=o.exec(n);l&&l.index+l[0].length<=t;l=o.exec(n))r=l.index+l[0].length;if(r>=t/2)return n.slice(0,r);let i=n.slice(0,t-1),s=i.replace(/\s+\S*$/,"");return(s.length>=t/2?s:i).replace(/[\s,;:.\u2013\u2014-]+$/,"")+"\u2026"}function ms(e){let t=M(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 rb=new Set(["readme","changelog","license","licence","contributing","contributors","code_of_conduct","code-of-conduct","security","authors","notice","agents","claude","cursorrules","codeowners","copying","maintainers"]);function ob(e){let t=Number.parseInt(String(e).trim(),10);return Number.isFinite(t)?t>=400&&t<=599:!1}function ys(e){return rb.has(e.replace(/\.(md|mdx|txt|html?)$/i,"").toLowerCase())}function Eu(e){let t=M(e).split("/").filter(Boolean).pop();return t?ys(t):!1}function $u(e){let t=gs(e.label,nb)||ms(e.url),n=e.description?gs(e.description):"";return`- [${t}](${e.url})${n?`: ${n}`:""}`}function Au(e){let t=[`# ${gs(e.siteName,100)}`,""],n=e.summary?xo(e.summary):"";if(n&&t.push(`> ${n}`,""),e.pages.length>0){t.push("## Pages","");for(let r of e.pages)t.push($u(r));t.push("")}if(e.articles.length>0){t.push("## Articles","");for(let r of e.articles)t.push($u(r));t.push("")}return t.join(`
99
99
  `).replace(/\n+$/,`
100
100
  `)}function ib(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=M(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 sb(e){let t=gt(e,k,"audit","evidence.md");if(!qn(t))return new Map;try{return ib(_o(t,"utf-8"))}catch{return new Map}}function Cu(e){let t=gt(e,k,"content");if(!qn(t))return[];let n;try{n=Zw(t)}catch{return[]}let r=[];for(let o of n.sort())if(o.toLowerCase().endsWith(".md"))try{let i=oe(_o(gt(t,o),"utf-8")).data;r.push({name:o,data:i})}catch{continue}return r}function hs(e){return typeof e=="object"&&e!==null}function Tu(e){let t=[e,e.seo,e.meta,e.metadata].filter(hs);for(let n of t){if(n.noindex===!0||n.noindex==="true")return!0;let r=n.robots;if(r===!1||typeof r=="string"&&/\bnoindex\b/i.test(r)||hs(r)&&(r.index===!1||r.noindex===!0))return!0}return!1}var Pu=new Set([".md",".mdx",".markdown",".mdoc"]),ab=new Set([".html",".htm"]),lb=new Set([".tsx",".jsx",".ts",".js",".mjs"]);function cb(e,t){let n=Ru(e).toLowerCase();if(Pu.has(n)){if(!/^?---\r?\n/.test(t))return!1;try{return Tu(oe(t.replace(/^/,"")).data)}catch{return!1}}if(ab.has(n)){let r=t.search(/<\/head>/i);return Lt(r===-1?t:t.slice(0,r))}return lb.has(n)?/\brobots\s*:\s*(['"`])[^'"`\n]*\bnoindex\b[^'"`\n]*\1/.test(t)||/\brobots\s*:\s*\{[^}]*\bindex\s*:\s*false\b/.test(t):!1}function ub(e){let t=new Set;for(let{data:n}of Cu(e)){let r=typeof n.canonical=="string"?n.canonical.trim():"";if(!r)continue;if(Tu(n)){t.add(M(r));continue}let o=typeof n.repo_path=="string"?n.repo_path.trim():"";if(!o)continue;let i=gt(e,o);try{if(!eb(i).isFile())continue;cb(i,_o(i,"utf-8"))&&t.add(M(r))}catch{continue}}return t}function db(e){if(typeof e!="string")return;let t=e.trim();if(!(!t||/^[>|][+-]?$/.test(t)))return t}function pb(e,t){if(!Pu.has(Ru(t).toLowerCase()))return{};let n;try{let a=_o(gt(e,t),"utf-8").replace(/^/,"");if(!/^---\r?\n/.test(a))return{};n=oe(a).data}catch{return{}}let r=[n,n.seo,n.meta,n.metadata].filter(hs),o=a=>{for(let l of r)for(let c of a){let u=db(l[c]);if(u)return u}},i=o(["title","meta_title"]),s=o(["description","meta_description"]);return{...i?{title:i}:{},...s?{description:s}:{}}}function fb(e,t,n,r){let o=new Map;for(let{name:i,data:s}of Cu(e)){let a=typeof s.canonical=="string"?s.canonical.trim():"",l=typeof s.status=="string"?s.status.toLowerCase():"";if(!a||l==="draft"||Eu(a)||n?.has(M(a)))continue;let c=typeof s.source=="string"?s.source.trim().toLowerCase():"",u=typeof s.repo_path=="string"?s.repo_path.trim():"";if(c==="repo"&&u&&!qn(gt(e,u))&&!r?.has(M(a)))continue;let p=typeof s.title=="string"&&s.title.trim()?s.title.trim():ms(a),g=t.get(M(a)),f=(!g?.title||!g?.description)&&u?pb(e,u):{},y=g?.description||f.description,m={url:a,label:g?.title||f.title||p,...y?{description:y}:{}},w=typeof s.slug=="string"?s.slug.trim():i.slice(0,-3),b=M(a).split("/").filter(Boolean).pop()??"",$=w===b,O=o.get(a);(!O||$&&!O.slugMatchesUrl)&&o.set(a,{link:m,slugMatchesUrl:$})}return Array.from(o.values(),i=>i.link)}function vu(e){try{return new URL(e).host.toLowerCase().replace(/^www\./,"")}catch{return""}}function Kn(e){return/^http:\/\//i.test(e)}function gb(e,t,n,r){let o=[Un,io].map(a=>gt(e,k,a)).filter(a=>qn(a));if(o.length===0)return[];let i=t.toLowerCase().replace(/^https?:\/\//,"").replace(/\/.*$/,""),s=new Map;for(let a of o.flatMap(l=>[...ao(l).keys()])){if(!/^https?:\/\//i.test(a))continue;let l=M(a);if(Ge(l)||Eu(a)||r?.has(l))continue;let u=`${vu(a)}${l}`,p=s.get(u);if(!p){s.set(u,a);continue}if(Kn(p)!==Kn(a)){Kn(p)&&s.set(u,a);continue}let g="";try{g=new URL(a).hostname.toLowerCase()}catch{g=""}g===i&&s.set(u,a)}return n?Array.from(s.values()):Array.from(s.values(),a=>Kn(a)&&vu(a)===i.replace(/^www\./,"")?a.replace(/^http:\/\//i,"https://"):a)}function hb(e){let t=new Set,n=new Set;for(let r of[Un,io]){let o=gt(e,k,r);if(qn(o))for(let i of ao(o).values()){let s=M(i.url);t.add(s),ob(i.status)&&n.add(s)}}return{listed:t,dead:n}}function mb(e){let t=new Set;for(let[n,r]of e)r.noindex&&t.add(n);return t}function Su(e){return[...e].sort((t,n)=>{let r=M(t.url),o=M(n.url);return r==="/"?o==="/"?0:-1:o==="/"?1:r.localeCompare(o)})}function Iu(e,t,n){let r=t.domain.replace(/^https?:\/\//,"").replace(/\/+$/,""),o=(()=>{try{return Ie(e)}catch{return null}})(),i=sb(e),s=hb(e),a=new Set([...s.dead,...mb(i),...ub(e),...n??ko(e)]),l=fb(e,i,a,s.listed),c=new Set(l.map(f=>M(f.url))),u=[];for(let f of gb(e,r,Kn(t.domain),a)){let y=M(f);if(c.has(y))continue;let m=i.get(y);u.push({url:f,label:m?.title||ms(f),...m?.description?{description:m.description}:{}})}if(u.length===0&&l.length===0)return null;let p=o?.business?.name?.trim()||i.get("/")?.title||r,g=o?.business?.description?.trim()||i.get("/")?.description||null;return{siteName:p,summary:g,pages:Su(u).slice(0,xu),articles:Su(l).slice(0,xu)}}var Se=e=>typeof e=="string"&&e?e:void 0;function bs(e,t){let n=sn(e,t),r;try{r=Nu(n)}catch{return[]}let o=[];for(let i of r){if(!i.endsWith(".md")&&!i.endsWith(".mdx")||i.startsWith("_")||i.startsWith(".")||ys(i))continue;let s=sn(n,i),a;try{a=oe($o(s,"utf-8")).data}catch{continue}let l=Se(a.slug)??i.replace(/\.mdx?$/,"");o.push({slug:l,title:Se(a.title)??l,draft:a.draft===!0,page_type:Se(a.page_type)??Se(a.type),path:kb(e,s)})}return o}function zn(e){let t;try{t=Nu(e)}catch{return[]}let n=[];for(let r of t){if(!r.endsWith(".md"))continue;let o=sn(e,r),i;try{i=oe($o(o,"utf-8")).data}catch{continue}n.push({slug:Se(i.slug)??r.replace(/\.md$/,""),source:Se(i.source)??"external",repo_path:Se(i.repo_path)??null,status:Se(i.status)??null,canonical:Se(i.canonical)??null,file:o})}return n}function ks(e,t,n,r={}){let o=new Set(t.map(u=>u.slug)),i=e.filter(u=>!u.draft&&!o.has(u.slug)),s=new Map(e.filter(u=>!u.draft).map(u=>[u.slug,u])),a=[];for(let u of t){if((u.status??"")==="published")continue;let p=s.get(u.slug);p&&a.push({pointer:u,article:p})}let l=[],c=[];for(let u of t){if(u.source!=="repo"||u.repo_path===null||n(u.repo_path))continue;let p=s.get(u.slug)?.path??_b(u.canonical??null,r,n);p?(u.status??"")==="published"&&l.push({pointer:u,repoPath:p}):c.push(u)}return{toTrack:i,toPublish:a,toRelink:l,stale:c,alreadyTracked:t.length-c.length}}function _b(e,t,n){if(!e||!t.contentDir)return null;let r;try{r=new URL(e).pathname.replace(/\/+$/,"")}catch{return null}let o=(t.blogPath??"").replace(/\/+$/,"");if(o){if(r!==o&&!r.startsWith(`${o}/`))return null;r=r.slice(o.length)}if(r=r.replace(/^\/+/,""),!r)return null;let i=t.contentDir.replace(/\/+$/,"");for(let s of[".mdx",".md"]){let a=`${i}/${r}${s}`;if(n(a))return a}return null}function _s(e,t){let n=e.match(/^---\r?\n([\s\S]*?)\r?\n---(\r?\n[\s\S]*)?$/);if(!n)return null;let r=n[1].split(/\r?\n/),o=r.find(l=>/^repo_path:\s/.test(l)),i=o?o.replace(/^repo_path:\s*/,"").trim():null,s=de(r,"repo_path",t),a=n[2]??`
@@ -1185,7 +1185,7 @@ skill knows what to do.
1185
1185
  \u2026 and ${t-5} more`:"";return`inbox: ${t} pending SEOAgent action${t===1?"":"s"} waiting in .seoagent/inbox/:
1186
1186
  ${n}${r}
1187
1187
  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 m$(e){try{let t=C(e);return t?.domain?nr(t.domain).last_run_at??null:null}catch{return null}}async function ke(e={}){let t=e.silent?e.hookInput!==void 0?e.hookInput:await gp():null;if(e.silent&&pp({input:t,projectDir:process.cwd(),lastRunAt:m$(process.cwd())}))return;let n=e.announceAll??(!e.silent||dp(t)),r=e.invokedBy??hp({silent:!!e.silent,hook:t,agent:yo(),stdinIsTTY:!!process.stdin.isTTY}),o=e.silent&&ra(t)&&t?.event==="PostToolUse"?Zc():null,i=()=>{if(!o)return;let c=fp(o());c&&process.stdout.write(c+`
1188
- `)},s=null,a=Xd(()=>{jo(process.cwd());let c=g$(s,3e4,h);(!e.silent||!c.inboxRefreshed)&&d.warn(c.text),i(),process.exit(e.strict&&!c.inboxRefreshed?1:0)}),l=null;try{await y$({...e,announceAll:n,invokedBy:r},{deadlineAt:a.deadlineAt,onPhase:c=>{s=c},onResult:c=>{l=c}})}finally{a.cancel(),i()}return l}async function y$(e,t={}){let n=Qd(process.cwd());if(n.refreshed){try{At(process.cwd())}catch{}e.silent||d.info(Y.dim(`\u21BB Updated SEOAgent skill ${n.from} \u2192 ${n.to}.`))}let r=await ep(process.cwd());r.updated&&!e.silent&&d.info(Y.dim("\u21BB Updated the SEOAgent skill to the latest published version.")),r.reason==="cli-too-old"&&!e.silent&&d.message(Y.yellow(`A newer SEOAgent skill is available but needs CLI ${r.minCli} or later (you have ${F}). Run \`${h("update-cli")}\` to get it.`)),Ct(process.cwd());let o=await tp(F);o&&d.message(Y.yellow(o));let i=!e.silent&&!!process.stdout.isTTY,s=e.pullOnly?"Pulling":e.pushOnly?"Pushing":"Syncing",a=i?d$():null;a?.start(`${s} ${Y.dim(".seoagent/")} with your dashboard\u2026`);let{force:l,forcePaths:c}=Sd(e.force),u=await Mo(process.cwd(),{pathFilter:e.path,silent:!!e.silent,invokedBy:e.invokedBy,force:l,forcePaths:c,pushOnly:e.pushOnly,pullOnly:e.pullOnly,deadlineAt:t.deadlineAt,onPhase:t.onPhase});t.onResult?.(u);let p=!!e.strict&&oa(u.reason);p&&(process.exitCode=1,d.warn(`sync --strict: nothing was synced (${u.reason}) - exiting 1.`));let g=u.verifyRecs;if(g){g.sourceRender&&d.message(Y.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)d.message(Y.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 Yd(u.aiFiles??null,h))!w.always&&e.silent||d.message(w.tone==="warn"?Y.yellow(w.text):Y.dim(w.text));let f=u.pagesRescan;if(f&&!e.silent&&d.message(Y.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=Le(process.cwd())}catch{w=[]}w.length>0&&d.message(Y.yellow(mp(w)))}else u.actionsNew&&u.actionsNew.length>0&&d.message(Y.yellow(mp(u.actionsNew)));u.actionsUnknownNew&&u.actionsUnknownNew>0&&d.message(Y.yellow(`inbox: ${u.actionsUnknownNew} pending action${u.actionsUnknownNew===1?"":"s"} need a newer CLI to display \u2014 run \`${h("update-cli")}\` and sync again.`));for(let w of w$(u.directives??[]))d.message(w);if(e.silent){!u.ok&&!p&&(process.exitCode=0);return}if(u.reason==="no-auth"){let w=h$(Kr(),h);T(a,"warn",w,Y.yellow(w)),process.exitCode=1,(await lp({cwd:process.cwd()})).sent&&d.info(Y.dim(Sl));return}if(u.reason==="no-project"){T(a,"info",`No SEOAgent project here. Run \`${h("init")}\` first.`,Y.dim("No SEOAgent project \u2014 sync skipped.")),a&&d.info(`Run \`${h("init")}\` first.`);return}if(u.reason==="domain-unknown"){T(a,"info","`.seoagent/project.md` has `domain: unknown` \u2014 cloud sync skipped until you set your site's domain.",Y.dim("domain: unknown \u2014 cloud sync skipped.")),a&&d.info("Set `domain:` in .seoagent/project.md, then re-run sync.");return}if(u.reason==="project-mismatch"){let w=`Cloud project is ${u.cloudDomain} but this workspace is ${u.localDomain} \u2014 not syncing. Run \`${h("login")}\` from this directory to connect ${u.localDomain} \u2014 the CLI can stay logged into both sites at once, no need to log out first.`;T(a,"warn",w,Y.yellow(w));return}if(u.reason==="unverified"){T(a,"info","Could not verify which cloud site this login is connected to \u2014 sync skipped (nothing pushed or pulled).",Y.dim("Cloud binding unverified \u2014 sync skipped.")),a&&u.errors[0]&&d.info(` \u2022 ${u.errors[0]}`);return}if(u.reason==="no-changes"){T(a,"info","Already in sync.",Y.green("Already in sync."));return}if(u.reason==="busy"){T(a,"info","Another sync is in progress \u2014 skipping this run.",Y.dim("Another sync is in progress \u2014 skipping this run."));return}let y=b$(u);T(a,"success",y??"Sync complete.",Y.green(y??"Sync complete.")),u.synced>0&&d.success(`Synced ${u.synced} file${u.synced===1?"":"s"} to your dashboard.`),u.pushDeferred&&u.pushDeferred>0&&d.info(`${u.pushDeferred} changed file${u.pushDeferred===1?"":"s"} deferred to the next sync (this run stopped pushing to leave time to pull your inbox). Run \`${h("sync")}\` again to push ${u.pushDeferred===1?"it":"them"} now.`);for(let w of u.notices??[])d.warn(`\u270D\uFE0F ${w}`);let m=u.pull;if(m){let w=m.written+m.overwritten;w>0&&d.success(`Pulled ${w} file${w===1?"":"s"} from the cloud`+(m.overwritten>0?` (${m.overwritten} updated)`:"")),m.deleted>0&&d.info(`Removed ${m.deleted} file${m.deleted===1?"":"s"} deleted in the cloud.`),m.conflicts>0&&d.warn(`${m.conflicts} conflict${m.conflicts===1?"":"s"} \u2014 local changes kept. Re-run with --force to take the cloud version.`);for(let b of m.warnings.slice(0,5))d.info(` \u2022 ${b}`)}{let w=ap(tt(process.cwd())),b=sp(w.briefs,w.articleCount,h);b&&d.message(Y.cyan(b))}if(u.acksFlushed&&u.acksFlushed>0&&d.success(`Flushed ${u.acksFlushed} offline ack${u.acksFlushed===1?"":"s"} from the queue.`),u.acksDropped&&u.acksDropped>0&&d.info(`Dropped ${u.acksDropped} stale ack${u.acksDropped===1?"":"s"} (already settled server-side).`),u.actionsPulled&&u.actionsPulled>0&&(d.success(`Pulled ${u.actionsPulled} pending action${u.actionsPulled===1?"":"s"} \u2192 .seoagent/inbox/`),e.embedded||d.info(' Open Claude Code in this directory and ask it to "process the inbox", or read the files yourself.')),u.actionsRemoved&&u.actionsRemoved>0&&d.info(`Cleaned up ${u.actionsRemoved} stale inbox file${u.actionsRemoved===1?"":"s"}.`),u.failed>0){d.warn(`${u.failed} file${u.failed===1?"":"s"} failed to sync. Will retry next run.`);for(let w of u.errors.slice(0,3))d.info(` \u2022 ${w}`)}}function w$(e){return e.map(t=>{let n=t.tone==="upsell"?"SEOAgent Cloud":"SEOAgent";return(t.tone==="upsell"?Y.magenta:t.tone==="action"?Y.cyan:Y.dim)(`${n} \u203A ${t.text}`)})}function b$(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 Bo(e,t=process.env){return(e.code??t.SEOAGENT_SETUP_CODE??"").trim()}function $$(e,t=h("init")){return e?`${t} --code ${e}`:t}async function $p(e,t,n){if(!n)return{outcome:"none",who:null};let r=t!==De,o=(H.BASE||et).replace(/\/$/,""),i=null;return{outcome:await ps(n,o,r?t:null,us(e,"init"),e,{siteAlreadyBound:r&&mo(t),onBound:a=>{i=a}}),who:i}}var v$="\u2713 First sync done \u2014 your briefs (and any finished article) are in .seoagent/; step 4 of the paste block already ran.",kp="The first sync did not complete (see above). The project IS bound; run `seoagent sync` here to pull your briefs.";async function vp(e,t,n={}){if(t.sync===!1||e!=="ready"&&e!=="already")return"skipped";d.step("Running the first sync (pulls your briefs into .seoagent/, pushes the scaffold)\u2026");try{let r=await(n.sync??ke)({embedded:!0,hookInput:null});return r&&!r.ok?(d.warn(kp),oa(r.reason)&&(process.exitCode=1),"failed"):(d.success(v$),"ran")}catch(r){return d.warn(`${kp} (${r.message})`),"failed"}}function Sp(e,t,n){let r=t?.domain||(n!==De?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 _p=[{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"}],De="unknown",S$=new Set(["saas","service","product","content","marketplace","tool","app","nonprofit","community","unknown"]);function R$(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 Go(e){if(!e?.trim())return null;let t=e.trim().toLowerCase();return S$.has(t)?t:null}function Rp(e,t){t.agentsMdPath&&d.info(`Added a SEOAgent section to ${mn(e,t.agentsMdPath)} so agents that read it (Codex, others) find the skill.`),t.cursorRulePath&&d.info(`Wrote ${mn(e,t.cursorRulePath)} so Cursor agents find the skill.`)}async function ir(e={}){let t=process.cwd();if(xl(t)){let l=C(t),c=zt(t);At(t),ne(t,{skill_version:F}),Ct(t);let u=mn(t,c.skillFile),p=c.claudeSymlink?` (symlinked at ${mn(t,c.claudeSymlink)})`:"",g=l?.skill_version&&l.skill_version!==F?`${l.skill_version} \u2192 ${F}`:F;d.success(`Refreshed the SEOAgent skill (${g}).`),d.info("Your `.seoagent/` knowledge was left untouched."),d.info(`Skill at ${u}${p} \u2014 agents read this file at session start.`),Rp(t,c),d.info(`Run \`${h("status")}\` to see your project, or \`${h("uninstall")}\` to start fresh.`);let f=Bo(e);if(!$e()){let b=Pe(t);b&&await nt({installId:b,event:Ti("reinit"),cliVersion:F,codeHint:Hr(f),meta:Pi(f)})&&d.info(Ai)}let{outcome:y,who:m}=await $p(t,l?.domain??De,f);(y==="rejected"||y==="unreachable"||y==="invalid")&&d.warn(E$()),y!=="none"&&ds(y)&&(process.exitCode=1),await vp(y,e);let w=Sp(y,m,l?.domain??De);w&&d.success(w);return}let n=!!e.yes||!process.stdin.isTTY,r=Wc(t);if(r.isWorktree&&!e.allowWorktree){let l=$$(Bo(e)),c=r.mainRoot?`cd ${r.mainRoot} && ${l}`:`run ${l} from the main repository root`;n&&(d.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."),d.info(`Init at the main checkout instead: ${c}`),d.info("To init here anyway (not recommended), pass --allow-worktree."),process.exit(1)),yp("SEOAgent \u2014 AI SEO Agent");let u=await bp({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});(Ft(u)||!u)&&(Ut(`Good call. ${c}`),process.exit(0))}else n||yp("SEOAgent \u2014 AI SEO Agent");let o=pc(t,l=>d.step(l)),i=()=>{if(o.evidence.length!==0){d.info("Inferred from your project:");for(let l of o.evidence)d.info(` \u2022 ${R$(l)}`)}},s=e.domain?.trim()||process.env.SEOAGENT_DOMAIN?.trim()||o.domain||null,a=Go(e.siteType)||Go(process.env.SEOAGENT_SITE_TYPE)||o.siteType||null;if(n){e.siteType?.trim()&&Go(e.siteType)===null&&(d.warn("Invalid --site-type. Use: saas, service, product, content, marketplace, tool, nonprofit, unknown, app, community."),process.exit(1)),process.env.SEOAGENT_SITE_TYPE?.trim()&&Go(process.env.SEOAGENT_SITE_TYPE)===null&&(d.warn("Invalid SEOAGENT_SITE_TYPE environment value."),process.exit(1)),i(),s||(d.info("Couldn't detect your site URL from this repo \u2014 scaffolding anyway; you'll be prompted to supply it."),s=De),a||(a="unknown"),await xp(t,s,a,Bo(e),{sync:e.sync});return}if(i(),!s){let l=await wp({message:"Website domain",placeholder:"example.com",validate:c=>c.trim()?void 0:"Domain is required"});Ft(l)&&(Ut("Cancelled"),process.exit(0)),s=String(l)}if(!a){let l=await ia({message:"What kind of site is this?",options:[..._p]});Ft(l)&&(Ut("Cancelled"),process.exit(0)),a=l}for(;;){let l=await bp({message:`Create .seoagent for ${s} (${a})?`});if(Ft(l)&&(Ut("Cancelled"),process.exit(0)),l)break;let c=await ia({message:"What should we change?",options:[{value:"domain",label:"Domain"},{value:"site_type",label:"Site type"},{value:"abort",label:"Cancel setup"}]});if((Ft(c)||c==="abort")&&(Ut("Cancelled"),process.exit(0)),c==="domain"){let u=await wp({message:"Website domain",placeholder:"example.com",validate:p=>p.trim()?void 0:"Domain is required"});Ft(u)&&(Ut("Cancelled"),process.exit(0)),s=String(u)}else{let u=await ia({message:"What kind of site is this?",options:[..._p]});Ft(u)&&(Ut("Cancelled"),process.exit(0)),a=u}}await xp(t,s,a,Bo(e),{sync:e.sync})}async function xp(e,t,n,r,o={}){let i=x$();i.start("Setting up your SEO project");let s=oc(e),a=dt(e),l=jr(e),c=$t(),u={domain:t,site_type:n,language:sl(t),initialized_at:new Date().toISOString(),seoagent_version:F,skill_version:F,install_id:c,...s.cms!=="none"?{cms:s.cms}:{},...s.blog_path?{blog_path:s.blog_path}:{},...a.provider!=="none"?{image_provider:a.provider}:{},...l.dir?{public_dir:l.dir}:{}},p=$e(),g=p?Promise.resolve(!1):nt({installId:c,event:Ti("init"),cliVersion:F,codeHint:Hr(r),meta:Pi(r)}),f=p||t===De||Ci()?Promise.resolve(null):Dl({domain:t,installId:c}),y=V(t),m=y&&t!==De?lt({apiBase:y.api_base,authHeader:`Bearer ${y.user_token}:${y.website_token}`,localDomain:t}).catch(()=>null):Promise.resolve(null);vl(e),$l(e,u),Ct(e),tc(e,n),Wl(e);let w=fo(e,t!==De?t:null),b=zt(e),$=mn(e,b.skillFile),O=k$($),I=`${O}/references/audit-checks.md`,x=b.claudeSymlink?mn(e,b.claudeSymlink):void 0;At(e);let{outcome:S,who:P}=await $p(e,t,r),_=S==="ready"||S==="already";if(S==="ready"&&t===De){let D=C(e)?.domain;D&&D!==De&&(t=D,u.domain=D)}let j=t!==De,R=S==="rejected"||S==="unreachable"||S==="invalid",N=S==="other-site";(R||N)&&(process.exitCode=1);let L={pages:[],dynamicCount:0},Z={wrote:!1,added:0,total:0,merged:!1,file:"pages.md"};try{L=He(e,j?t:null),Z=so(e,L.pages)}catch{}if(i.stop(j?`Created .seoagent/ project for ${u.domain}`:"Created .seoagent/ project (site URL not detected yet \u2014 see step 2)"),s.cms!=="none"){let D=s.evidence.map(q=>`${q.detail} (${q.source})`).slice(0,2).join(", ");d.info(`CMS detected: ${s.cms} \u2014 ${D}`)}if(s.blog_path&&d.info(`Blog route: ${s.blog_path}`),l.dir&&d.info(`Static files: ${l.dir}/ (${l.evidence}) \u2014 your llms.txt and OKF bundle publish there.`),w.created.length>0&&d.info("Scaffolded .seoagent/okf/ \u2014 the AI-readable knowledge bundle answer engines cite. The audit will offer to fill it."),a.provider!=="none"&&d.info(`Image generation: ${a.provider} (detected ${a.matched_key}) \u2014 articles can get AI hero images.`),L.pages.length>0){let D=Z.merged?Z.added>0?`Re-scanned your codebase: +${Z.added} new page${Z.added===1?"":"s"} merged into .seoagent/pages.md (${Z.total} total)`:`Re-scanned your codebase: .seoagent/pages.md already current (${Z.total} pages)`:`Found ${L.pages.length} page${L.pages.length===1?"":"s"} in your codebase \u2192 .seoagent/pages.md`;d.info(D+(L.dynamicCount>0?` (plus ${L.dynamicCount} dynamic route template${L.dynamicCount===1?"":"s"} \u2014 the audit will discover their instances)`:""))}Rp(e,b);let[te,ce,_e]=await Promise.all([g,f,m]);if(_e?.verdict==="mismatch"&&d.warn(`Heads up: the credentials bound to ${t} are for a SEOAgent cloud project on ${_e.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 \`${h("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.`),ce?.status==="data"){let D=ce.difficulty!=null?`, difficulty ${ce.difficulty}/100`:"";d.info(`Free keyword peek \u2014 "${ce.keyword}": ${ce.volume.toLocaleString("en-US")} searches/mo${D}. You get ~10 free peeks/day: ${h('keywords --peek "<keyword>"')}`)}else ce?.status==="no-data"&&d.info(`Free keyword peek is live \u2014 no search-volume data for "${ce.keyword}" yet. Try a topic keyword (~10 free/day): ${h('keywords --peek "best <your category> tool"')}`);te&&d.info(Ai),await vp(S,o);let Je=j?u.domain:"your site",X=j?tu(u.domain):null,B=X?null:du(ft(e)),K=j?[" 1. If .seoagent/project.md still says site_type: unknown, you can",` WebFetch ${u.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."];_$([j?`\u2713 SEOAgent installed for ${u.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:","",` ${$} \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):","",...K,...X?["",...nu(u.domain,X)]:[],...Jt(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 ${Je}`," (homepage + sitemap + robots.txt; the checks live in",` ${I} \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.',"",..._?[" 3. This project is connected to the SEOAgent cloud. Each session: run",` ${h("sync")}, triage .seoagent/inbox/, do the work, and`,` ${h("ack <id>")} each completed action.`,...B?["",` 4. ${B}`," It prints a link and two steps for your human; walk them through it."]:[]]:N?[" 3. \u26A0\uFE0F The setup code was for a DIFFERENT site than this project (see",` above). This directory stays ${u.domain}; do not run`,` ${h("sync")} here for the other site. One site per directory:`,` run ${h("init")} --domain <that site> in an empty directory`," of its own \u2014 it is already logged in there."]:R?[" 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:",` ${h("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:",` ${h("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 ${$} is picked up automatically.`,...X?[` Your site is on ${X.label}, so there is no code to edit here \u2014 this folder`,X.api?` is the workspace. The agent will ask you once for ${X.enableHint}.`:` is the workspace. The agent writes each article for you to paste into ${X.label}.`]:[],...x?[` Symlinked at ${x} \u2192 ${O} so Claude Code finds it too.`]:[],"",..._?[" Cloud dashboard: connected. Pull your first work with:","",` ${h("sync")}`]:N?[` \u26A0\uFE0F That setup code was for another site; this project stays ${u.domain}.`," Give the other site a directory of its own:","",` ${h("init")} --domain <that site>`]:R?[" \u26A0\uFE0F The setup code did not bind this project. Get a fresh code at"," seoagent.com/grok, then run:","",` ${h("login")} --code <code>`]:[" Want the cloud dashboard now? Free account, ~30 seconds:","",` ${h("login")}`],"",..._?[Sp(S,P,u.domain)]:[]].join(`
1188
+ `)},s=null,a=Xd(()=>{jo(process.cwd());let c=g$(s,3e4,h);(!e.silent||!c.inboxRefreshed)&&d.warn(c.text),i(),process.exit(e.strict&&!c.inboxRefreshed?1:0)}),l=null;try{await y$({...e,announceAll:n,invokedBy:r},{deadlineAt:a.deadlineAt,onPhase:c=>{s=c},onResult:c=>{l=c}})}finally{a.cancel(),i()}return l}async function y$(e,t={}){let n=Qd(process.cwd());if(n.refreshed){try{At(process.cwd())}catch{}e.silent||d.info(Y.dim(`\u21BB Updated SEOAgent skill ${n.from} \u2192 ${n.to}.`))}let r=await ep(process.cwd());r.updated&&!e.silent&&d.info(Y.dim("\u21BB Updated the SEOAgent skill to the latest published version.")),r.reason==="cli-too-old"&&!e.silent&&d.message(Y.yellow(`A newer SEOAgent skill is available but needs CLI ${r.minCli} or later (you have ${F}). Run \`${h("update-cli")}\` to get it.`)),Ct(process.cwd());let o=await tp(F);o&&d.message(Y.yellow(o));let i=!e.silent&&!!process.stdout.isTTY,s=e.pullOnly?"Pulling":e.pushOnly?"Pushing":"Syncing",a=i?d$():null;a?.start(`${s} ${Y.dim(".seoagent/")} with your dashboard\u2026`);let{force:l,forcePaths:c}=Sd(e.force),u=await Mo(process.cwd(),{pathFilter:e.path,silent:!!e.silent,invokedBy:e.invokedBy,force:l,forcePaths:c,pushOnly:e.pushOnly,pullOnly:e.pullOnly,deadlineAt:t.deadlineAt,onPhase:t.onPhase});t.onResult?.(u);let p=!!e.strict&&oa(u.reason);p&&(process.exitCode=1,d.warn(`sync --strict: nothing was synced (${u.reason}) - exiting 1.`));let g=u.verifyRecs;if(g){g.sourceRender&&d.message(Y.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)d.message(Y.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 Yd(u.aiFiles??null,h))!w.always&&e.silent||d.message(w.tone==="warn"?Y.yellow(w.text):Y.dim(w.text));let f=u.pagesRescan;if(f&&!e.silent&&d.message(Y.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=Le(process.cwd())}catch{w=[]}w.length>0&&d.message(Y.yellow(mp(w)))}else u.actionsNew&&u.actionsNew.length>0&&d.message(Y.yellow(mp(u.actionsNew)));u.actionsUnknownNew&&u.actionsUnknownNew>0&&d.message(Y.yellow(`inbox: ${u.actionsUnknownNew} pending action${u.actionsUnknownNew===1?"":"s"} need a newer CLI to display \u2014 run \`${h("update-cli")}\` and sync again.`));for(let w of w$(u.directives??[]))d.message(w);if(e.silent){!u.ok&&!p&&(process.exitCode=0);return}if(u.reason==="no-auth"){let w=h$(Kr(),h);T(a,"warn",w,Y.yellow(w)),process.exitCode=1,(await lp({cwd:process.cwd()})).sent&&d.info(Y.dim(Sl));return}if(u.reason==="no-project"){T(a,"info",`No SEOAgent project here. Run \`${h("init")}\` first.`,Y.dim("No SEOAgent project \u2014 sync skipped.")),a&&d.info(`Run \`${h("init")}\` first.`);return}if(u.reason==="domain-unknown"){T(a,"info","`.seoagent/project.md` has `domain: unknown` \u2014 cloud sync skipped until you set your site's domain.",Y.dim("domain: unknown \u2014 cloud sync skipped.")),a&&d.info("Set `domain:` in .seoagent/project.md, then re-run sync.");return}if(u.reason==="project-mismatch"){let w=`Cloud project is ${u.cloudDomain} but this workspace is ${u.localDomain} \u2014 not syncing. Run \`${h("login")}\` from this directory to connect ${u.localDomain} \u2014 the CLI can stay logged into both sites at once, no need to log out first.`;T(a,"warn",w,Y.yellow(w));return}if(u.reason==="unverified"){T(a,"info","Could not verify which cloud site this login is connected to \u2014 sync skipped (nothing pushed or pulled).",Y.dim("Cloud binding unverified \u2014 sync skipped.")),a&&u.errors[0]&&d.info(` \u2022 ${u.errors[0]}`);return}if(u.reason==="no-changes"){T(a,"info","Already in sync.",Y.green("Already in sync."));return}if(u.reason==="busy"){T(a,"info","Another sync is in progress \u2014 skipping this run.",Y.dim("Another sync is in progress \u2014 skipping this run."));return}let y=b$(u);T(a,"success",y??"Sync complete.",Y.green(y??"Sync complete.")),u.synced>0&&d.success(`Synced ${u.synced} file${u.synced===1?"":"s"} to your dashboard.`),u.pushDeferred&&u.pushDeferred>0&&d.info(`${u.pushDeferred} changed file${u.pushDeferred===1?"":"s"} deferred to the next sync (this run stopped pushing to leave time to pull your inbox). Run \`${h("sync")}\` again to push ${u.pushDeferred===1?"it":"them"} now.`);for(let w of u.notices??[])d.warn(`\u270D\uFE0F ${w}`);let m=u.pull;if(m){let w=m.written+m.overwritten;w>0&&d.success(`Pulled ${w} file${w===1?"":"s"} from the cloud`+(m.overwritten>0?` (${m.overwritten} updated)`:"")),m.deleted>0&&d.info(`Removed ${m.deleted} file${m.deleted===1?"":"s"} deleted in the cloud.`),m.conflicts>0&&d.warn(`${m.conflicts} conflict${m.conflicts===1?"":"s"} \u2014 local changes kept. Re-run with --force to take the cloud version.`);for(let b of m.warnings.slice(0,5))d.info(` \u2022 ${b}`)}{let w=ap(tt(process.cwd())),b=sp(w.briefs,w.articleCount,h);b&&d.message(Y.cyan(b))}if(u.acksFlushed&&u.acksFlushed>0&&d.success(`Flushed ${u.acksFlushed} offline ack${u.acksFlushed===1?"":"s"} from the queue.`),u.acksDropped&&u.acksDropped>0&&d.info(`Dropped ${u.acksDropped} stale ack${u.acksDropped===1?"":"s"} (already settled server-side).`),u.actionsPulled&&u.actionsPulled>0&&(d.success(`Pulled ${u.actionsPulled} pending action${u.actionsPulled===1?"":"s"} \u2192 .seoagent/inbox/`),e.embedded||d.info(' Open Claude Code in this directory and ask it to "process the inbox", or read the files yourself.')),u.actionsRemoved&&u.actionsRemoved>0&&d.info(`Cleaned up ${u.actionsRemoved} stale inbox file${u.actionsRemoved===1?"":"s"}.`),u.failed>0){d.warn(`${u.failed} file${u.failed===1?"":"s"} failed to sync. Will retry next run.`);for(let w of u.errors.slice(0,3))d.info(` \u2022 ${w}`)}}function w$(e){return e.map(t=>{let n=t.tone==="upsell"?"SEOAgent Cloud":"SEOAgent";return(t.tone==="upsell"?Y.magenta:t.tone==="action"?Y.cyan:Y.dim)(`${n} \u203A ${t.text}`)})}function b$(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 Bo(e,t=process.env){return(e.code??t.SEOAGENT_SETUP_CODE??"").trim()}function $$(e,t=h("init")){return e?`${t} --code ${e}`:t}async function $p(e,t,n){if(!n)return{outcome:"none",who:null};let r=t!==De,o=(H.BASE||et).replace(/\/$/,""),i=null;return{outcome:await ps(n,o,r?t:null,us(e,"init"),e,{siteAlreadyBound:r&&mo(t),onBound:a=>{i=a}}),who:i}}var v$="\u2713 First sync done \u2014 your briefs (and any finished article) are in .seoagent/; step 4 of the paste block already ran.",kp="The first sync did not complete (see above). The project IS bound; run `seoagent sync` here to pull your briefs.";async function vp(e,t,n={}){if(t.sync===!1||e!=="ready"&&e!=="already")return"skipped";d.step("Running the first sync (pulls your briefs into .seoagent/, pushes the scaffold)\u2026");try{let r=await(n.sync??ke)({embedded:!0,hookInput:null});return r&&!r.ok?(d.warn(kp),oa(r.reason)&&(process.exitCode=1),"failed"):(d.success(v$),"ran")}catch(r){return d.warn(`${kp} (${r.message})`),"failed"}}function Sp(e,t,n){let r=t?.domain||(n!==De?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 _p=[{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"}],De="unknown",S$=new Set(["saas","service","product","content","marketplace","tool","app","nonprofit","community","unknown"]);function R$(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 Go(e){if(!e?.trim())return null;let t=e.trim().toLowerCase();return S$.has(t)?t:null}function Rp(e,t){t.agentsMdPath&&d.info(`Added a SEOAgent section to ${mn(e,t.agentsMdPath)} so agents that read it (Codex, others) find the skill.`),t.cursorRulePath&&d.info(`Wrote ${mn(e,t.cursorRulePath)} so Cursor agents find the skill.`)}async function ir(e={}){let t=process.cwd();if(xl(t)){let l=C(t),c=zt(t);At(t),ne(t,{skill_version:F}),Ct(t);let u=mn(t,c.skillFile),p=c.claudeSymlink?` (symlinked at ${mn(t,c.claudeSymlink)})`:"",g=l?.skill_version&&l.skill_version!==F?`${l.skill_version} \u2192 ${F}`:F;d.success(`Refreshed the SEOAgent skill (${g}).`),d.info("Your `.seoagent/` knowledge was left untouched."),d.info(`Skill at ${u}${p} \u2014 agents read this file at session start.`),Rp(t,c),d.info(`Run \`${h("status")}\` to see your project, or \`${h("uninstall")}\` to start fresh.`);let f=Bo(e);if(!$e()){let b=Pe(t);b&&await nt({installId:b,event:Ti("reinit"),cliVersion:F,codeHint:Hr(f),meta:Pi(f)})&&d.info(Ai)}let{outcome:y,who:m}=await $p(t,l?.domain??De,f);(y==="rejected"||y==="unreachable"||y==="invalid")&&d.warn(E$()),y!=="none"&&ds(y)&&(process.exitCode=1),await vp(y,e);let w=Sp(y,m,l?.domain??De);w&&d.success(w);return}let n=!!e.yes||!process.stdin.isTTY,r=Wc(t);if(r.isWorktree&&!e.allowWorktree){let l=$$(Bo(e)),c=r.mainRoot?`cd ${r.mainRoot} && ${l}`:`run ${l} from the main repository root`;n&&(d.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."),d.info(`Init at the main checkout instead: ${c}`),d.info("To init here anyway (not recommended), pass --allow-worktree."),process.exit(1)),yp("SEOAgent \u2014 AI SEO Agent");let u=await bp({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});(Ft(u)||!u)&&(Ut(`Good call. ${c}`),process.exit(0))}else n||yp("SEOAgent \u2014 AI SEO Agent");let o=pc(t,l=>d.step(l)),i=()=>{if(o.evidence.length!==0){d.info("Inferred from your project:");for(let l of o.evidence)d.info(` \u2022 ${R$(l)}`)}},s=e.domain?.trim()||process.env.SEOAGENT_DOMAIN?.trim()||o.domain||null,a=Go(e.siteType)||Go(process.env.SEOAGENT_SITE_TYPE)||o.siteType||null;if(n){e.siteType?.trim()&&Go(e.siteType)===null&&(d.warn("Invalid --site-type. Use: saas, service, product, content, marketplace, tool, nonprofit, unknown, app, community."),process.exit(1)),process.env.SEOAGENT_SITE_TYPE?.trim()&&Go(process.env.SEOAGENT_SITE_TYPE)===null&&(d.warn("Invalid SEOAGENT_SITE_TYPE environment value."),process.exit(1)),i(),s||(d.info("Couldn't detect your site URL from this repo \u2014 scaffolding anyway; you'll be prompted to supply it."),s=De),a||(a="unknown"),await xp(t,s,a,Bo(e),{sync:e.sync});return}if(i(),!s){let l=await wp({message:"Website domain",placeholder:"example.com",validate:c=>c.trim()?void 0:"Domain is required"});Ft(l)&&(Ut("Cancelled"),process.exit(0)),s=String(l)}if(!a){let l=await ia({message:"What kind of site is this?",options:[..._p]});Ft(l)&&(Ut("Cancelled"),process.exit(0)),a=l}for(;;){let l=await bp({message:`Create .seoagent for ${s} (${a})?`});if(Ft(l)&&(Ut("Cancelled"),process.exit(0)),l)break;let c=await ia({message:"What should we change?",options:[{value:"domain",label:"Domain"},{value:"site_type",label:"Site type"},{value:"abort",label:"Cancel setup"}]});if((Ft(c)||c==="abort")&&(Ut("Cancelled"),process.exit(0)),c==="domain"){let u=await wp({message:"Website domain",placeholder:"example.com",validate:p=>p.trim()?void 0:"Domain is required"});Ft(u)&&(Ut("Cancelled"),process.exit(0)),s=String(u)}else{let u=await ia({message:"What kind of site is this?",options:[..._p]});Ft(u)&&(Ut("Cancelled"),process.exit(0)),a=u}}await xp(t,s,a,Bo(e),{sync:e.sync})}async function xp(e,t,n,r,o={}){let i=x$();i.start("Setting up your SEO project");let s=oc(e),a=dt(e),l=jr(e),c=$t(),u={domain:t,site_type:n,language:sl(t),initialized_at:new Date().toISOString(),seoagent_version:F,skill_version:F,install_id:c,...s.cms!=="none"?{cms:s.cms}:{},...s.blog_path?{blog_path:s.blog_path}:{},...a.provider!=="none"?{image_provider:a.provider}:{},...l.dir?{public_dir:l.dir}:{}},p=$e(),g=p?Promise.resolve(!1):nt({installId:c,event:Ti("init"),cliVersion:F,codeHint:Hr(r),meta:Pi(r)}),f=p||t===De||Ci()?Promise.resolve(null):Dl({domain:t,installId:c}),y=V(t),m=y&&t!==De?lt({apiBase:y.api_base,authHeader:`Bearer ${y.user_token}:${y.website_token}`,localDomain:t}).catch(()=>null):Promise.resolve(null);vl(e),$l(e,u),Ct(e),tc(e,n),Wl(e);let w=fo(e,t!==De?t:null),b=zt(e),$=mn(e,b.skillFile),O=k$($),I=`${O}/references/audit-checks.md`,x=b.claudeSymlink?mn(e,b.claudeSymlink):void 0;At(e);let{outcome:S,who:P}=await $p(e,t,r),_=S==="ready"||S==="already";if(S==="ready"&&t===De){let D=C(e)?.domain;D&&D!==De&&(t=D,u.domain=D)}let j=t!==De,R=S==="rejected"||S==="unreachable"||S==="invalid",N=S==="other-site";(R||N)&&(process.exitCode=1);let L={pages:[],dynamicCount:0},Z={wrote:!1,added:0,total:0,merged:!1,file:"pages.md"};try{L=He(e,j?t:null),Z=so(e,L.pages)}catch{}if(i.stop(j?`Created .seoagent/ project for ${u.domain}`:"Created .seoagent/ project (site URL not detected yet \u2014 see step 2)"),s.cms!=="none"){let D=s.evidence.map(q=>`${q.detail} (${q.source})`).slice(0,2).join(", ");d.info(`CMS detected: ${s.cms} \u2014 ${D}`)}if(s.blog_path&&d.info(`Blog route: ${s.blog_path}`),l.dir&&d.info(`Static files: ${l.dir}/ (${l.evidence}) \u2014 your llms.txt and OKF bundle publish there.`),w.created.length>0&&d.info("Scaffolded .seoagent/okf/ \u2014 the AI-readable knowledge bundle answer engines cite. The audit will offer to fill it."),a.provider!=="none"&&d.info(`Image generation: ${a.provider} (detected ${a.matched_key}) \u2014 articles can get AI hero images.`),L.pages.length>0){let D=Z.merged?Z.added>0?`Re-scanned your codebase: +${Z.added} new page${Z.added===1?"":"s"} merged into .seoagent/pages.md (${Z.total} total)`:`Re-scanned your codebase: .seoagent/pages.md already current (${Z.total} pages)`:`Found ${L.pages.length} page${L.pages.length===1?"":"s"} in your codebase \u2192 .seoagent/pages.md`;d.info(D+(L.dynamicCount>0?` (plus ${L.dynamicCount} dynamic route template${L.dynamicCount===1?"":"s"} \u2014 the audit will discover their instances)`:""))}Rp(e,b);let[te,ce,_e]=await Promise.all([g,f,m]);if(_e?.verdict==="mismatch"&&d.warn(`Heads up: the credentials bound to ${t} are for a SEOAgent cloud project on ${_e.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 \`${h("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.`),ce?.status==="data"){let D=ce.difficulty!=null?`, difficulty ${ce.difficulty}/100`:"";d.info(`Free keyword peek \u2014 "${ce.keyword}": ${ce.volume.toLocaleString("en-US")} searches/mo${D}. You get ~10 free peeks/day: ${h('keywords --peek "<keyword>"')}`)}else ce?.status==="no-data"&&d.info(`Free keyword peek is live \u2014 no search-volume data for "${ce.keyword}" yet. Try a topic keyword (~10 free/day): ${h('keywords --peek "best <your category> tool"')}`);te&&d.info(Ai),await vp(S,o);let Je=j?u.domain:"your site",X=j?tu(u.domain):null,B=X?null:du(ft(e),h("github")),K=j?[" 1. If .seoagent/project.md still says site_type: unknown, you can",` WebFetch ${u.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."];_$([j?`\u2713 SEOAgent installed for ${u.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:","",` ${$} \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):","",...K,...X?["",...nu(u.domain,X)]:[],...Jt(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 ${Je}`," (homepage + sitemap + robots.txt; the checks live in",` ${I} \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.',"",..._?[" 3. This project is connected to the SEOAgent cloud. Each session: run",` ${h("sync")}, triage .seoagent/inbox/, do the work, and`,` ${h("ack <id>")} each completed action.`,...B?["",` 4. ${B}`," It prints a link and two steps for your human; walk them through it."]:[]]:N?[" 3. \u26A0\uFE0F The setup code was for a DIFFERENT site than this project (see",` above). This directory stays ${u.domain}; do not run`,` ${h("sync")} here for the other site. One site per directory:`,` run ${h("init")} --domain <that site> in an empty directory`," of its own \u2014 it is already logged in there."]:R?[" 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:",` ${h("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:",` ${h("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 ${$} is picked up automatically.`,...X?[` Your site is on ${X.label}, so there is no code to edit here \u2014 this folder`,X.api?` is the workspace. The agent will ask you once for ${X.enableHint}.`:` is the workspace. The agent writes each article for you to paste into ${X.label}.`]:[],...x?[` Symlinked at ${x} \u2192 ${O} so Claude Code finds it too.`]:[],"",..._?[" Cloud dashboard: connected. Pull your first work with:","",` ${h("sync")}`]:N?[` \u26A0\uFE0F That setup code was for another site; this project stays ${u.domain}.`," Give the other site a directory of its own:","",` ${h("init")} --domain <that site>`]:R?[" \u26A0\uFE0F The setup code did not bind this project. Get a fresh code at"," seoagent.com/grok, then run:","",` ${h("login")} --code <code>`]:[" Want the cloud dashboard now? Free account, ~30 seconds:","",` ${h("login")}`],"",..._?[Sp(S,P,u.domain)]:[]].join(`
1189
1189
  `))}function E$(){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 \`${h("login")} --code <code>\`.`}import{existsSync as Cp}from"fs";import{intro as C$,outro as T$,confirm as P$,isCancel as I$,cancel as O$}from"@clack/prompts";import{existsSync as Ho,rmSync as A$}from"fs";import{join as Wo}from"path";function Ep(e,t={}){let n=[],r=Wo(e,k);Ho(r)&&n.push({label:".seoagent/ \u2014 project state + all generated work",path:r,kind:"dir"});let o=Wo(e,Ze.CLAUDE);Ho(o)&&n.push({label:".claude/skills/seoagent/ \u2014 installed skill bundle",path:o,kind:"dir"});let i=Wo(e,Ze.AGENTS);return Ho(i)&&n.push({label:".agents/skills/seoagent/ \u2014 installed skill bundle",path:i,kind:"dir"}),ql(e)&&n.push({label:".claude/settings.json \u2014 sync hook entry (other settings preserved)",path:Wo(e,".claude","settings.json"),kind:"hook"}),t.global&&Ho(re)&&n.push({label:"~/.config/seoagent/ \u2014 login credentials + sync state (all projects)",path:re,kind:"global"}),n}function Ap(e,t){for(let n of t)n.kind==="hook"?Vl(e):A$(n.path,{recursive:!0,force:!0})}async function sr(e={}){let t=process.cwd(),n=Ep(t,{global:e.global});if(n.length===0){d.info("No SEOAgent install found in this directory \u2014 nothing to remove."),!e.global&&Cp(re)&&d.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(`
1190
1190
  `);if(!r){C$("SEOAgent \u2014 uninstall"),d.message(`This will remove:
1191
1191
  ${o}`);let s=await P$({message:"Remove these and start fresh?"});if(I$(s)||!s){O$("Cancelled \u2014 nothing was removed.");return}}Ap(t,n);let i=n.map(s=>` \u2713 ${s.label}`).join(`
@@ -1458,6 +1458,6 @@ ${t}
1458
1458
  `):d.info(w);return}let p=await fC(a,l,c);if(p.status==="error"){t.json?process.stdout.write(`${JSON.stringify(p)}
1459
1459
  `):d.error(p.message),process.exitCode=1;return}if(p.status==="connected"){t.json?process.stdout.write(`${JSON.stringify(p)}
1460
1460
  `):d.success(go(p,i));return}if(t.json)process.stdout.write(`${JSON.stringify(p)}
1461
- `);else for(let w of uu({url:p.url,repo:p.repo,domain:i}))d.message(w);if(t.open!==!1&&rn(p.url),!t.wait)return;let f=!!process.stdout.isTTY&&!t.json?cC():null;f?.start(mi.dim("Waiting for the GitHub install\u2026"));let y=Date.now()+gC(t.timeout)*1e3;for(;Date.now()<y;){await pC(uC);let w=await Wg(a,l);if(w.status==="connected"){let b=go(w,i);T(f,"success",b,mi.green(b)),t.json&&process.stdout.write(`${JSON.stringify(w)}
1461
+ `);else for(let w of uu({url:p.url,repo:p.repo,domain:i,command:h("github")}))d.message(w);if(t.open!==!1&&rn(p.url),!t.wait)return;let f=!!process.stdout.isTTY&&!t.json?cC():null;f?.start(mi.dim("Waiting for the GitHub install\u2026"));let y=Date.now()+gC(t.timeout)*1e3;for(;Date.now()<y;){await pC(uC);let w=await Wg(a,l);if(w.status==="connected"){let b=go(w,i);T(f,"success",b,mi.green(b)),t.json&&process.stdout.write(`${JSON.stringify(w)}
1462
1462
  `);return}if(w.status==="error"&&w.terminal){T(f,"warn",w.message,mi.red(w.message)),process.exitCode=1;return}}let m=`Not connected yet. When the user has clicked Install on GitHub, run \`${h("github")}\` again.`;T(f,"warn",m,mi.yellow(m)),t.json&&process.stdout.write(`${JSON.stringify({status:"not_connected",timed_out:!0})}
1463
1463
  `)}import{select as Cr,text as Ar,confirm as Vg,isCancel as Ce,intro as hC,outro as mC}from"@clack/prompts";import yi from"picocolors";function yC(){return[{command:"status",label:"Show project status",hint:"Audit, strategy, briefs, content, roadmap \u2014 everything at a glance.",run:async()=>cr()},{command:"sync",label:"Sync with the cloud dashboard",hint:"Push local .seoagent/ changes + pull dashboard edits.",run:async()=>await ke()},{command:"init",label:"Initialize a SEOAgent project",hint:"Scaffold .seoagent/ + the skill file in this directory.",run:async()=>{await ir()}},{command:"login",label:"Connect this CLI to seoagent.com",hint:"Opens the browser to bind your account.",run:async()=>await Gn()},{command:"whoami",label:"Show logged-in account",hint:"Email, bound website, plan, API base.",run:async()=>await Sr()},{command:"keywords",label:"Keyword research",hint:"Peek (free, one keyword) \xB7 enrich \xB7 discover \xB7 competitor gaps.",run:async()=>{let e=await Cr({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(!Ce(e))if(e==="peek"){let t=await Ar({message:"Keyword to peek:",placeholder:"saas seo",validate:n=>String(n??"").trim()?void 0:"A keyword is required."});if(Ce(t))return;await kt({peek:String(t)})}else e==="discover"?await kt({discover:!0}):e==="competitors"?await kt({competitors:!0}):await kt()}},{command:"internal-links",label:"Analyze internal links",hint:"Find orphan pages \u2192 .seoagent/internal-links.md",run:async()=>mr({})},{command:"okf",label:"Open Knowledge Format bundle",hint:"Make your site AI-readable (AEO/GEO) \u2192 .seoagent/okf/",run:async()=>{let e=await Cr({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"}]});Ce(e)||yr(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 _r()},{command:"generate-image",label:"Generate an image",hint:"Uses the detected (or explicit) provider.",run:async()=>{let e=await Ar({message:"Image prompt:",placeholder:"a minimalist cyan box logo",validate:n=>String(n??"").trim()?void 0:"A prompt is required."});if(Ce(e))return;let t=await Ar({message:"Output file path:",placeholder:".seoagent/content/images/hero.png",validate:n=>String(n??"").trim()?void 0:"An output path is required."});Ce(t)||await vr({prompt:String(e),out:String(t)})}},{command:"env-check",label:"Check image-generation provider",hint:"Detect openai / fal / replicate from your env.",run:async()=>$r({})},{command:"autopilot",label:"Toggle CLI autopilot",hint:"on / off / status \u2014 paid feature for queueing SEO fixes.",run:async()=>{let e=await Cr({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"}]});Ce(e)||await hr(String(e))}},{command:"pull",label:"Pull dashboard changes only",hint:"Cloud \u2192 local. Same as the second half of `sync`.",run:async()=>await Mt()},{command:"ack",label:"Close out a pending inbox action",hint:"Mark a .seoagent/inbox/ action completed or failed.",run:async()=>{let e=await Ar({message:"Action ID to close out:",placeholder:"action-abc123",validate:r=>String(r??"").trim()?void 0:"Action ID is required."});if(Ce(e))return;let t=await Vg({message:"Mark as failed instead of completed?",initialValue:!1});if(Ce(t))return;let n;if(t===!0){let r=await Ar({message:"Reason for failure (optional):",placeholder:"couldn't apply because\u2026"});if(Ce(r))return;String(r).trim()&&(n=String(r))}await Ke(String(e),{failed:t===!0,reason:n})}},{command:"upgrade",label:"Upgrade your SEOAgent plan",hint:"Open the pricing page; contextual prompt if logged in.",run:async()=>await dr()},{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 pr({})},{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 Cr({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"}]});Ce(e)||xr({all:e==="all"})}},{command:"logout",label:"Log out",hint:"Clear stored credentials for seoagent.com.",run:async()=>Hn()},{command:"uninstall",label:"Uninstall SEOAgent from this project",hint:"Remove .seoagent/, the skill bundle, and the sync hook.",run:async()=>await sr({})}]}var Kg="__exit__";async function qg(e=yC()){let t=e.map(o=>({value:o.command,label:o.label,hint:o.hint}));t.push({value:Kg,label:"Exit",hint:"Close the menu"});let n=await Cr({message:"What would you like to do?",options:t});if(Ce(n)||n===Kg)return!0;let r=e.find(o=>o.command===n);if(!r)return d.warn(`Unknown menu pick "${String(n)}" \u2014 returning to menu.`),!1;try{await r.run()}catch(o){d.error(`\`${r.command}\` failed: ${o.message??String(o)}`)}return!1}async function ol(){if(!process.stdin.isTTY){d.info("The menu needs an interactive terminal. Use the regular commands directly, e.g. `seoagent --help`.");return}hC(`${yi.bold(yi.cyan("SEOAgent"))} ${yi.dim("\xB7 menu")}`);let e=await qg();for(;!e;){let t=await Vg({message:"Do another?",initialValue:!0});if(Ce(t)||t===!1){e=!0;break}e=await qg()}mC(yi.dim("See you next time."))}var U=new wC;U.name("seoagent").description("AI SEO agent for Claude Code").version(F).showSuggestionAfterError(!0).showHelpAfterError(!0);U.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, binds this project AND runs the first sync in one command, no browser (also read from SEOAGENT_SETUP_CODE)").option("--no-sync","Skip the first sync a successful --code bind runs (the paste block relies on the sync; this is for debugging)").action(e=>{ir({yes:e.yes,domain:e.domain,siteType:e.siteType,allowWorktree:e.allowWorktree,code:e.code,sync:e.sync})});U.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=>{sr({yes:e.yes,global:e.global})});U.command("status").description("Show current SEO project state").action(cr);U.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=>{$a({json:e.json})});U.command("facts").description("Fact check against commits: lists the facts in .seoagent/context.md whose source file changed since the last check, so pages that state them can be corrected. --mark records HEAD as checked.").option("--mark","Record HEAD as the commit at which every fact was confirmed against its source").option("--json","Output the report as JSON (for scripting / the agent)").action(e=>{va({json:e.json,mark:e.mark})});U.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=>{Gn({apiBase:e.apiBase,code:e.code})});U.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=>{Hn({all:e.all})});U.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=>{ke({strict:e.strict,silent:e.silent,force:e.force,path:e.path,pushOnly:e.pushOnly,pullOnly:e.pullOnly})});U.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=>{Mt({force:e.force,silent:e.silent,print:e.print})});U.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("--awaiting-deploy","Applied in the repo (or packaged for the owner) but this workflow does not deploy \u2014 the owner ships it. Clears the inbox; the cloud re-checks live later").option("--reason <text>",'Reason for declining (used with --failed), what the owner must ship (with --awaiting-deploy), or a note of what you did on a plain completed ack. 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").option("--plan <entry>",'Competitor review: a page this site will build, "<target query> | <their url> | <what ours adds>" (repeat per page). Goes to the cloud planner',(e,t)=>[...t,e],[]).option("--skip <entry>",'Competitor review: a page not worth answering, "<their url> | <reason>" (repeat per page)',(e,t)=>[...t,e],[]).action((e,t)=>{ca(e??[],{failed:t.failed,awaitingDeploy:t.awaitingDeploy,reason:t.reason,verify:t.verify,verifyType:t.verifyType,url:t.url,plan:t.plan,skip:t.skip})});U.command("inbox").description("List pending actions queued by autopilot in .seoagent/inbox/").option("--json","Output as JSON (handy for scripting / `jq`)").action(e=>{da({json:e.json})});U.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("-y, --yes","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)").option("--task-file <path>","Hosted runner only: run ONE unattended agent turn on the task in this file instead of the inbox (needs --yes --unattended)").action(e=>{ya({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:ma(e.skipTypes),deferAck:e.deferAck,taskFile:e.taskFile})});U.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)=>{hr(e,t,{apiBase:n.apiBase,yes:n.yes})});U.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=>{kt({peek:e.peek,seed:e.seed,discover:e.discover,competitors:e.competitors,purge:e.purge,all:e.all,json:e.json,apiBase:e.apiBase})});U.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=>{mr({json:e.json})});U.command("content [action] [path]").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("--author-key <key>","How this repo names the article's author (the post's author value or authors-file entry), so the next draft already knows it").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,n)=>{ka(e,{path:t,slug:n.slug,url:n.url,file:n.file,title:n.title,type:n.type,status:n.status,primaryKeyword:n.primaryKeyword,brief:n.brief,supersedes:n.supersedes,action:n.action,authorKey:n.authorKey,dir:n.dir,prune:n.prune})});U.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)=>{yr(e,{json:t.json})});U.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=>{Sa({json:e.json})});U.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=>{Ra({url:e.url,json:e.json})});U.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").option("--preview","The --url is a preview deployment (Vercel/Netlify PR preview, staging): ignore the host's preview noindex header and canonicals that point at production, write the reports to .seoagent/audit/preview/, and never save the URL as live_url").option("--header <header>",'Extra request header for the crawl origin only, e.g. "x-vercel-protection-bypass: <secret>" (repeatable)',(e,t=[])=>[...t,e]).action(e=>{Da({url:e.url,max:e.max,json:e.json,preview:e.preview,headers:e.header})});U.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=>{Ua({url:e.url,max:e.max,json:e.json})});U.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 \u2014 or, when you name files, only those files. 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)=>{Ma({files:e??[],json:t.json,quiet:t.quiet})});U.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)=>{Ba({files:e??[],json:t.json})});U.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=>{Ka({csv:e.csv,direction:e.direction,json:e.json})});U.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=>{_r({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})});U.command("env-check").description("Detect which image generation provider is available (OPENAI / FAL / REPLICATE)").option("--json","Output detection result as JSON").action(e=>{$r({json:e.json})});U.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=>{vr({prompt:e.prompt,out:e.out,provider:e.provider,size:e.size,silent:e.silent})});U.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)=>{nl(e,{json:t.json,apiBase:t.apiBase})});U.command("github [action]").description("Connect the SEOAgent GitHub App for this site (weekday pull requests while your editor is closed): prints the install link and the two steps; `status` shows the connection and the setup pull request").option("--wait","Wait until the install lands (default 2 minutes)").option("--timeout <seconds>","How long --wait waits").option("--no-open","Print the link without opening a browser").option("--json","Output as JSON (handy for scripting / `jq`)").option("--api-base <url>","Override API base URL (for testing)").action((e,t)=>{rl(e,{wait:t.wait,timeout:t.timeout,open:t.open,json:t.json,apiBase:t.apiBase})});U.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=>{Sr({json:e.json,env:e.env,apiBase:e.apiBase})});U.command("upgrade").description("Open the SEOAgent Cloud pricing page in your browser (plan upgrade)").action(dr);U.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=>{pr({dryRun:e.dryRun,projects:e.projects})});U.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=>{xr({all:e.all,crawl:e.crawl})});U.command("menu").description("Interactive menu \u2014 pick a command from a list (no flags required)").action(()=>{ol()});process.argv.length===2&&(U.outputHelp(),process.exit(0));U.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seoagent-official/seoagent",
3
- "version": "1.136.0",
3
+ "version": "1.136.1",
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": {
@@ -11,7 +11,7 @@ The local skill does the full content loop for free — audit, strategy, briefs,
11
11
  | The user asks "which of my pages are indexed?" / "why isn't page X on Google?" | **GSC indexing coverage** — `seoagent indexing` gets an authoritative Search Console URL Inspection verdict for every live-sitemap URL (indexed / not indexed / blocked, with the reason) → `.seoagent/audit/indexing.md`. Also runs automatically as audit Phase 1 step 9. Free tier — needs only `seoagent login` + the GSC connection. Never answer indexing questions from `site:` searches or guesswork. |
12
12
  | The user runs SEOAgent across more than one site | **Cross-site dashboard** — every site's findings/strategy/content in one place |
13
13
  | After keyword research, or when the user asks about real volume / difficulty | **Real keyword volume + difficulty + opportunity classification** (DataForSEO Labs) for the strategy's top keywords — `seoagent keywords` after login. Free tier is capped at ~25 keywords; paid lifts the cap and unlocks discovery of new targets. |
14
- | Logged in, the repo is on GitHub, and `seoagent github status` says not connected (`seoagent init` names it as step 4 after a bind) | **Pull requests from SEOAgent while the editor is closed** — the SEOAgent GitHub App: a "Set up SEOAgent" pull request in about 5 minutes, then one each weekday (part of the 14-day trial; paid after it), all reviewed and merged by the user. `seoagent github` prints a link minted for this account and two steps (on GitHub: pick this repo, click Install & Authorize). Give the user both, run `seoagent github --wait`, and report the setup pull request it prints. Never click through GitHub's screens yourself. Remote on another host (GitLab, Bitbucket) → it says so; drop it. |
14
+ | Logged in, the repo is on GitHub, and `seoagent github status` says not connected (`seoagent init` names it as step 4 after a bind) | **Pull requests from SEOAgent while the editor is closed** — the SEOAgent GitHub App: a "Set up SEOAgent" pull request in about 5 minutes, then one each weekday (part of the 14-day trial; paid after it), all reviewed and merged by the user. `seoagent github` prints a link minted for this account and two steps (on GitHub: pick this repo, click Install & Authorize). Give the user both in your reply and do not wait; when they say it is done, run `seoagent github status` and report the setup pull request it prints (`seoagent github` again only for a fresh link). Never click through GitHub's screens yourself. Remote on another host (GitLab, Bitbucket) → it says so; drop it. |
15
15
  | The user is tired of maintaining the sitemap by hand, or asks "do I have to keep checking this myself?" | **Automatic sitemap management — connect Cloud + GSC.** Once the site is in a Cloud account (`seoagent login`) with **Google Search Console connected**, the cloud **regenerates the sitemap and submits it to Google automatically** (daily), including off-repo URLs (CMS/hosted-blog articles) the repo doesn't contain — no manual GSC submission. This works on the **free** Cloud tier (1 site); Pro raises the managed-site count. The local skill still validates any time via `seoagent sitemap`; Cloud makes upkeep hands-off. |
16
16
 
17
17
  **Do NOT promise these — they are not delivered today (promising them erodes trust on first contact):**