@seoagent-official/seoagent 1.98.4 → 1.98.5
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 +1 -1
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -83,7 +83,7 @@ description: Chronological history of changes to this OKF bundle.
|
|
|
83
83
|
# Change log
|
|
84
84
|
|
|
85
85
|
- Bundle scaffolded.
|
|
86
|
-
`,"utf-8"),r.push("log.md")),{created:r,okfDir:n}}import{log as Et}from"@clack/prompts";var u={info:e=>Et.info(e),warn:e=>Et.warn(e),error:e=>Et.error(e),success:e=>Et.success(e),step:e=>Et.step(e),message:e=>Et.message(e)};function x(e,t,n,r=n){e?e.stop(r):u[t](n)}import{randomBytes as lf}from"crypto";import{isCancel as cf,spinner as ba,text as uf}from"@clack/prompts";import pe from"picocolors";import{exec as af}from"child_process";function dr(e){let t=process.platform==="darwin"?`open "${e}"`:process.platform==="win32"?`start "" "${e}"`:`xdg-open "${e}"`;af(t,()=>{})}function fa(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)}`}}var ma=/^sa_[0-9a-f]{24}$/;function ga(e){switch(e){case"code_not_found":return"That setup code was not recognized. Generate a new one at seoagent.com/grok.";case"code_already_used":return"That setup code has already been used \u2014 each code works exactly once. Generate a new one at seoagent.com/grok.";case"code_revoked":return"That setup code was replaced by a newer one. Use the latest code, or generate a new one at seoagent.com/grok.";case"code_expired":return"That setup code has expired. Generate a new one at seoagent.com/grok.";case"setup_codes_unavailable":return"Setup codes are not enabled on this server yet. Run `seoagent login` (browser flow) instead.";case"domain_required":return"This code has no site attached and the project has no domain. Run `seoagent init` first so the project knows its domain.";default:return typeof e=="string"&&e?`Login failed: ${e}`:"Login failed."}}function ha(e,t){let n=t&&typeof t=="object"?t:null;if(e>=500)return e===503&&n?.error==="setup_codes_unavailable"?{status:"error",terminal:!0,message:ga(n.error)}:{status:"error",terminal:!1,message:`HTTP ${e} (server error)`};if(e>=400)return{status:"error",terminal:!0,message:ga(n?.error)};if(!n)return{status:"error",terminal:!0,message:`Malformed response: expected an object, got ${typeof t}`};if(n.status==="ready"){if(typeof n.user_token=="string"&&typeof n.website_token=="string"){let r=typeof n.email=="string"&&n.email?n.email:void 0,o=typeof n.domain=="string"&&n.domain?n.domain:void 0;return{status:"ready",user_token:n.user_token,website_token:n.website_token,...r?{email:r}:{},...o?{domain:o}:{}}}return{status:"error",terminal:!0,message:"Malformed response: status=ready without user_token+website_token"}}return{status:"error",terminal:!0,message:`Malformed response: unknown status=${JSON.stringify(n.status)}`}}var df=1500,pf=300*1e3,ff=3,gf=2e3;function pr(e,t){if(Be())return;let n=Oe(e);n&&Ze({installId:n,event:t,cliVersion:C})}function mf(){return lf(16).toString("hex")}function hf(e,t,n){let r=new URLSearchParams({session:t});return n&&n!=="unknown"&&r.set("domain",n),`${e}/cli/auth?${r.toString()}`}function ya(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 yf(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 wf(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 fa(r.status,o)}catch(n){return{status:"error",terminal:!1,message:n.message||"network error"}}}function ka(e){return new Promise(t=>setTimeout(t,e))}async function va(e,t,n,r){let o=O(),i=await yf(o,e.website_token,c=>We({apiBase:c.api_base,authHeader:`Bearer ${c.user_token}:${c.website_token}`,localDomain:""}));i&&o&&oo(o,i),oo({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 ${pe.bold(e.email)} (${pe.cyan(e.domain)}).`:e.email?`Logged in as ${pe.bold(e.email)}.`:"Logged in.";x(r,"success",s,pe.green(a)),r&&u.info("Future SEO work in this repo will sync to your dashboard.");let l=Ke().filter(c=>c!==He(e.domain||n));l.length>0&&u.info(`Also logged in to: ${l.join(", ")}. Each project auto-selects its own site \u2014 no need to log out first.`)}function bf(e,t,n){return{code:e,...t&&t!=="unknown"?{domain:t}:{},...n?{install_id:n.installId,cli_version:n.cliVersion,platform:process.platform}:{}}}function Co(e){if(Be())return null;let t=Oe(e);return t?{installId:t,cliVersion:C}:null}async function kf(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(bf(t,n,r)),signal:AbortSignal.timeout(1e4)}),i=null;try{i=await o.json()}catch{}return ha(o.status,i)}catch(o){return{status:"error",terminal:!1,message:o.message||"network error"}}}function vf(e){return e!=="ready"}async function Ro(e,t,n,r){if(!ma.test(e))return u.warn("That does not look like a setup code (expected `sa_` + 24 hex characters). Generate one at seoagent.com/grok."),"invalid";let i=!!process.stdout.isTTY?ba():null;i?.start(pe.dim("Redeeming setup code\u2026"));let s="network error";for(let a=0;a<ff;a++){a>0&&await ka(gf);let l=await kf(t,e,n,r);if(l.status==="ready")return await va(l,t,n,i),"ready";if(l.terminal)return x(i,"warn",l.message,pe.red(l.message)),"rejected";s=l.message}return x(i,"warn",`Could not reach the server (${s}). Check your connection and run \`${m("login")} --code <code>\` again.`,pe.yellow("Could not reach the server.")),i&&u.warn(`Run \`${m("login")} --code <code>\` again to retry.`),"unreachable"}function Eo(e){return e&&e!=="unknown"?ys(e):!!O()}function wa(){return`Next: run \`${m("sync")}\` in this repo \u2014 it pulls your strategy, briefs and inbox into .seoagent/. Nothing syncs until it runs.`}async function Zt(e={}){let t=process.cwd(),n=_(t),r=(e.apiBase||I.BASE||Qe).replace(/\/$/,""),o=n?.domain??null;if(o==="unknown"&&process.stdin.isTTY){let g=await uf({message:".seoagent/project.md has `domain: unknown`. What is your site's URL?",placeholder:"example.com",validate(y){if(!String(y??"").trim())return"A site URL is required.";if(!ya(String(y)))return"Please enter a valid domain like `example.com`."}});if(cf(g)){u.warn("Login cancelled.");return}let h=ya(String(g));if(h){o=h;try{J(t,{domain:h}),u.success(`Saved domain: ${h} \u2192 .seoagent/project.md`)}catch{}}}if(Eo(o)){let g=o&&o!=="unknown"?` for ${o}`:"";u.info(`Already logged in${g}. To switch accounts${g}, run \`${m("logout")}\` first.`);return}let s=(e.code??process.env.SEOAGENT_SETUP_CODE??"").trim();if(s){let g=await Ro(s,r,o,Co(t));vf(g)?process.exitCode=1:u.info(wa());return}let a=mf(),l=hf(r,a,o);u.message("Opening seoagent.com to connect this CLI to your account..."),u.info(`If the browser does not open, visit: ${l}`),dr(l),u.info('In your browser: sign in (if needed) and click "Connect this CLI" to finish.');let d=!!process.stdout.isTTY?ba():null;d?.start(pe.dim("Waiting for browser confirmation\u2026"));let p=Date.now()+pf,f=!0;for(;Date.now()<p;){f||await ka(df),f=!1;let g=await wf(r,a);if(g.status==="ready"){await va(g,r,o,d),pr(t,"login_browser_ok"),u.info(wa());return}if(g.status==="expired"){pr(t,"login_browser_expired"),x(d,"warn",`Session expired. Run \`${m("login")}\` again.`,pe.red("Session expired.")),d&&u.warn(`Run \`${m("login")}\` again.`);return}if(g.status==="error"){if(g.terminal){x(d,"warn",`Login failed: ${g.message}. Run \`${m("login")}\` again.`,pe.red(`Login failed: ${g.message}.`)),d&&u.warn(`Run \`${m("login")}\` again.`),pr(t,"login_browser_failed");return}continue}}x(d,"warn",`Login timed out. Run \`${m("login")}\` again to retry.`,pe.yellow("Login timed out.")),d&&u.warn(`Run \`${m("login")}\` again to retry.`),pr(t,"login_browser_timeout")}function en(e={}){let t=e.all?null:_(process.cwd()),n=!e.all&&t?.domain&&t.domain!=="unknown"?t.domain:null;if(!ws(e.all?null:n)){u.info(n?`You were not logged in for ${n}.`:"You were not logged in.");return}u.success(n?`Logged out of ${n}.`:"Logged out.");let o=Ke();o.length>0&&u.info(`Still logged in to: ${o.join(", ")}. Run \`${m("logout")} --all\` to log out of everything.`)}function fr(e,t=process.env){return(e.code??t.SEOAGENT_SETUP_CODE??"").trim()}function _f(e,t=m("init")){return e?`${t} --code ${e}`:t}async function Ra(e,t,n){if(!n)return"none";let r=t!==dt;if(Eo(r?t:null))return u.info(`Already logged in${r?` for ${t}`:""} \u2014 setup code not needed, skipping it.`),"already";let o=(I.BASE||Qe).replace(/\/$/,"");return Ro(n,o,r?t:null,Co(e))}var _a=[{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"}],dt="unknown",Cf=new Set(["saas","service","product","content","marketplace","tool","app","nonprofit","community","unknown"]);function Rf(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 gr(e){if(!e?.trim())return null;let t=e.trim().toLowerCase();return Cf.has(t)?t:null}function Ea(e,t){t.agentsMdPath&&u.info(`Added a SEOAgent section to ${At(e,t.agentsMdPath)} so agents that read it (Codex, others) find the skill.`),t.cursorRulePath&&u.info(`Wrote ${At(e,t.cursorRulePath)} so Cursor agents find the skill.`)}async function tn(e={}){let t=process.cwd();if(cs(t)){let l=_(t),c=$t(t);ot(t),J(t,{skill_version:C}),it(t);let d=At(t,c.skillFile),p=c.claudeSymlink?` (symlinked at ${At(t,c.claudeSymlink)})`:"",f=l?.skill_version&&l.skill_version!==C?`${l.skill_version} \u2192 ${C}`:C;if(u.success(`Refreshed the SEOAgent skill (${f}).`),u.info("Your `.seoagent/` knowledge was left untouched."),u.info(`Skill at ${d}${p} \u2014 agents read this file at session start.`),Ea(t,c),u.info(`Run \`${m("status")}\` to see your project, or \`${m("uninstall")}\` to start fresh.`),!Be()){let h=Oe(t);h&&await Ze({installId:h,event:"reinit",cliVersion:C})&&u.info(ro)}let g=await Ra(t,l?.domain??dt,fr(e));(g==="rejected"||g==="unreachable"||g==="invalid")&&u.warn(Ef());return}let n=!!e.yes||!process.stdin.isTTY,r=aa(t);if(r.isWorktree&&!e.allowWorktree){let l=_f(fr(e)),c=r.mainRoot?`cd ${r.mainRoot} && ${l}`:`run ${l} from the main repository root`;n&&(u.warn("This directory is a temporary git worktree \u2014 a `.seoagent/` created here is deleted with the worktree, and images/un-synced edits cannot be recovered from the cloud."),u.info(`Init at the main checkout instead: ${c}`),u.info("To init here anyway (not recommended), pass --allow-worktree."),process.exit(1)),$a("SEOAgent \u2014 AI SEO Agent");let d=await Sa({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});(ct(d)||!d)&&(ut(`Good call. ${c}`),process.exit(0))}else n||$a("SEOAgent \u2014 AI SEO Agent");let o=Ys(t,l=>u.step(l)),i=()=>{if(o.evidence.length!==0){u.info("Inferred from your project:");for(let l of o.evidence)u.info(` \u2022 ${Rf(l)}`)}},s=e.domain?.trim()||process.env.SEOAGENT_DOMAIN?.trim()||o.domain||null,a=gr(e.siteType)||gr(process.env.SEOAGENT_SITE_TYPE)||o.siteType||null;if(n){e.siteType?.trim()&&gr(e.siteType)===null&&(u.warn("Invalid --site-type. Use: saas, service, product, content, marketplace, tool, nonprofit, unknown, app, community."),process.exit(1)),process.env.SEOAGENT_SITE_TYPE?.trim()&&gr(process.env.SEOAGENT_SITE_TYPE)===null&&(u.warn("Invalid SEOAGENT_SITE_TYPE environment value."),process.exit(1)),i(),s||(u.info("Couldn't detect your site URL from this repo \u2014 scaffolding anyway; you'll be prompted to supply it."),s=dt),a||(a="unknown"),await Ca(t,s,a,fr(e));return}if(i(),!s){let l=await xa({message:"Website domain",placeholder:"example.com",validate:c=>c.trim()?void 0:"Domain is required"});ct(l)&&(ut("Cancelled"),process.exit(0)),s=String(l)}if(!a){let l=await Ao({message:"What kind of site is this?",options:[..._a]});ct(l)&&(ut("Cancelled"),process.exit(0)),a=l}for(;;){let l=await Sa({message:`Create .seoagent for ${s} (${a})?`});if(ct(l)&&(ut("Cancelled"),process.exit(0)),l)break;let c=await Ao({message:"What should we change?",options:[{value:"domain",label:"Domain"},{value:"site_type",label:"Site type"},{value:"abort",label:"Cancel setup"}]});if((ct(c)||c==="abort")&&(ut("Cancelled"),process.exit(0)),c==="domain"){let d=await xa({message:"Website domain",placeholder:"example.com",validate:p=>p.trim()?void 0:"Domain is required"});ct(d)&&(ut("Cancelled"),process.exit(0)),s=String(d)}else{let d=await Ao({message:"What kind of site is this?",options:[..._a]});ct(d)&&(ut("Cancelled"),process.exit(0)),a=d}}await Ca(t,s,a,fr(e))}async function Ca(e,t,n,r){let o=Sf();o.start("Setting up your SEO project");let i=Gs(e),s=qe(e),a=Jn(e),l=no(),c={domain:t,site_type:n,language:Vi(t),initialized_at:new Date().toISOString(),seoagent_version:C,skill_version:C,install_id:l,...i.cms!=="none"?{cms:i.cms}:{},...i.blog_path?{blog_path:i.blog_path}:{},...s.provider!=="none"?{image_provider:s.provider}:{},...a.dir?{public_dir:a.dir}:{}},d=Be(),p=d?Promise.resolve(!1):Ze({installId:l,event:"init",cliVersion:C}),f=d||t===dt?Promise.resolve(null):bs({domain:t,installId:l}),g=O(t),h=g&&t!==dt?We({apiBase:g.api_base,authHeader:`Bearer ${g.user_token}:${g.website_token}`,localDomain:t}).catch(()=>null):Promise.resolve(null);ds(e),us(e,c),it(e),Ms(e,n),Rs(e);let y=ur(e,t!==dt?t:null),w=$t(e),b=At(e,w.skillFile),R=$f(b),B=`${R}/references/audit-checks.md`,T=w.claudeSymlink?At(e,w.claudeSymlink):void 0;ot(e);let D=t!==dt,te=await Ra(e,t,r),W=te==="ready"||te==="already",K=te==="rejected"||te==="unreachable"||te==="invalid";K&&(process.exitCode=1);let U={pages:[],dynamicCount:0},ne={wrote:!1,added:0,total:0,merged:!1,file:"pages.md"};try{U=ve(e,D?t:null),ne=ir(e,U.pages)}catch{}if(o.stop(D?`Created .seoagent/ project for ${c.domain}`:"Created .seoagent/ project (site URL not detected yet \u2014 see step 2)"),i.cms!=="none"){let F=i.evidence.map(we=>`${we.detail} (${we.source})`).slice(0,2).join(", ");u.info(`CMS detected: ${i.cms} \u2014 ${F}`)}if(i.blog_path&&u.info(`Blog route: ${i.blog_path}`),a.dir&&u.info(`Static files: ${a.dir}/ (${a.evidence}) \u2014 your llms.txt and OKF bundle publish there.`),y.created.length>0&&u.info("Scaffolded .seoagent/okf/ \u2014 the AI-readable knowledge bundle answer engines cite. The audit will offer to fill it."),s.provider!=="none"&&u.info(`Image generation: ${s.provider} (detected ${s.matched_key}) \u2014 articles can get AI hero images.`),U.pages.length>0){let F=ne.merged?ne.added>0?`Re-scanned your codebase: +${ne.added} new page${ne.added===1?"":"s"} merged into .seoagent/pages.md (${ne.total} total)`:`Re-scanned your codebase: .seoagent/pages.md already current (${ne.total} pages)`:`Found ${U.pages.length} page${U.pages.length===1?"":"s"} in your codebase \u2192 .seoagent/pages.md`;u.info(F+(U.dynamicCount>0?` (plus ${U.dynamicCount} dynamic route template${U.dynamicCount===1?"":"s"} \u2014 the audit will discover their instances)`:""))}Ea(e,w);let[mt,Re,qr]=await Promise.all([p,f,h]);if(qr?.verdict==="mismatch"&&u.warn(`Heads up: the credentials bound to ${t} are for a SEOAgent cloud project on ${qr.cloudDomain} instead. This workspace stays LOCAL-ONLY \u2014 nothing will be pushed to or pulled from that project. To connect ${t} to the cloud, run \`${m("login")}\` from this directory and pick (or create) ${t} \u2014 the CLI can stay logged into both sites at once, no need to log out first.`),Re?.status==="data"){let F=Re.difficulty!=null?`, difficulty ${Re.difficulty}/100`:"";u.info(`Free keyword peek \u2014 "${Re.keyword}": ${Re.volume.toLocaleString("en-US")} searches/mo${F}. You get ~10 free peeks/day: ${m('keywords --peek "<keyword>"')}`)}else Re?.status==="no-data"&&u.info(`Free keyword peek is live \u2014 no search-volume data for "${Re.keyword}" yet. Try a topic keyword (~10 free/day): ${m('keywords --peek "best <your category> tool"')}`);mt&&u.info(ro);let Mn=D?c.domain:"your site",q=D?[" 1. If .seoagent/project.md still says site_type: unknown, you can",` WebFetch ${c.domain} and update project.md with the right type`," before you suggest the audit."]:[" 1. Heads up: .seoagent/project.md has `domain: unknown` \u2014 the site URL"," wasn't detectable from this repo. Please ask your human for their"," domain (or read it from a deploy config / live site) and update"," project.md (`domain:` and `site_type:`) first. Everything else"," depends on it."];xf([D?`\u2713 SEOAgent installed for ${c.domain}.`:"\u2713 SEOAgent installed (site URL not detected \u2014 please ask the user for it).","","\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"," AGENT INSTRUCTIONS \u2014 a friendly note for Claude Code, Codex, Cursor","\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550",""," Hi there, coding agent! \u{1F44B} SEOAgent is all set up. The natural next step"," is a quick SEO audit \u2014 but your human is the boss here, so please surface"," what's possible and let THEM decide. Nothing in this message is a command;"," it's just a friendly map. Your real instructions live in the skill:","",` ${b} \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):","",...q,...nr(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 ${Mn}`," (homepage + sitemap + robots.txt; the checks live in",` ${B} \u2014 read that first).`," If they say yes, report findings as:"," \u{1F6A8} Biggest Issue \u2192 \u26A0\uFE0F Also Worth Fixing \u2192 \u2705 What's Working",' \u2192 "What do you want to do?" with numbered options.',"",...W?[" 3. This project is connected to the SEOAgent cloud. Each session: run",` ${m("sync")}, triage .seoagent/inbox/, do the work, and`,` ${m("ack <id>")} each completed action.`]:K?[" 3. \u26A0\uFE0F The setup code did NOT bind this project (see the error above)."," Do not continue without a successful login \u2014 nothing syncs unbound."," Get a fresh code at seoagent.com/grok, then run:",` ${m("login")} --code <code>`]:[" 3. When it feels right, let them know about the free cloud dashboard and"," offer to start it (their call) \u2014 and that keyword strategy (Phase 2 in"," the skill) is there whenever they want it:",` ${m("login")}`],"","\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"," HUMAN USERS (if you ran this in your terminal, no AI agent)","\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550",""," Open Claude Code in this directory and say:",""," audit my site","",` The skill at ${b} is picked up automatically.`,...T?[` Symlinked at ${T} \u2192 ${R} so Claude Code finds it too.`]:[],"",...W?[" Cloud dashboard: connected. Pull your first work with:","",` ${m("sync")}`]:K?[" \u26A0\uFE0F The setup code did not bind this project. Get a fresh code at"," seoagent.com/grok, then run:","",` ${m("login")} --code <code>`]:[" Want the cloud dashboard now? Free account, ~30 seconds:","",` ${m("login")}`],""].join(`
|
|
86
|
+
`,"utf-8"),r.push("log.md")),{created:r,okfDir:n}}import{log as Et}from"@clack/prompts";var u={info:e=>Et.info(e),warn:e=>Et.warn(e),error:e=>Et.error(e),success:e=>Et.success(e),step:e=>Et.step(e),message:e=>Et.message(e)};function x(e,t,n,r=n){e?e.stop(r):u[t](n)}import{randomBytes as lf}from"crypto";import{isCancel as cf,spinner as ba,text as uf}from"@clack/prompts";import pe from"picocolors";import{exec as af}from"child_process";function dr(e){let t=process.platform==="darwin"?`open "${e}"`:process.platform==="win32"?`start "" "${e}"`:`xdg-open "${e}"`;af(t,()=>{})}function fa(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)}`}}var ma=/^sa_[0-9a-f]{24}$/;function ga(e){switch(e){case"code_not_found":return"That setup code was not recognized. Generate a new one at seoagent.com/grok.";case"code_already_used":return"That setup code has already been used \u2014 each code works exactly once. Generate a new one at seoagent.com/grok.";case"code_revoked":return"That setup code was replaced by a newer one. Use the latest code, or generate a new one at seoagent.com/grok.";case"code_expired":return"That setup code has expired. Generate a new one at seoagent.com/grok.";case"setup_codes_unavailable":return"Setup codes are not enabled on this server yet. Run `seoagent login` (browser flow) instead.";case"domain_required":return"This code has no site attached and the project has no domain. Run `seoagent init` first so the project knows its domain.";default:return typeof e=="string"&&e?`Login failed: ${e}`:"Login failed."}}function ha(e,t){let n=t&&typeof t=="object"?t:null;if(e>=500)return e===503&&n?.error==="setup_codes_unavailable"?{status:"error",terminal:!0,message:ga(n.error)}:{status:"error",terminal:!1,message:`HTTP ${e} (server error)`};if(e>=400)return{status:"error",terminal:!0,message:ga(n?.error)};if(!n)return{status:"error",terminal:!0,message:`Malformed response: expected an object, got ${typeof t}`};if(n.status==="ready"){if(typeof n.user_token=="string"&&typeof n.website_token=="string"){let r=typeof n.email=="string"&&n.email?n.email:void 0,o=typeof n.domain=="string"&&n.domain?n.domain:void 0;return{status:"ready",user_token:n.user_token,website_token:n.website_token,...r?{email:r}:{},...o?{domain:o}:{}}}return{status:"error",terminal:!0,message:"Malformed response: status=ready without user_token+website_token"}}return{status:"error",terminal:!0,message:`Malformed response: unknown status=${JSON.stringify(n.status)}`}}var df=1500,pf=300*1e3,ff=3,gf=2e3;function pr(e,t){if(Be())return;let n=Oe(e);n&&Ze({installId:n,event:t,cliVersion:C})}function mf(){return lf(16).toString("hex")}function hf(e,t,n){let r=new URLSearchParams({session:t});return n&&n!=="unknown"&&r.set("domain",n),`${e}/cli/auth?${r.toString()}`}function ya(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 yf(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 wf(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 fa(r.status,o)}catch(n){return{status:"error",terminal:!1,message:n.message||"network error"}}}function ka(e){return new Promise(t=>setTimeout(t,e))}async function va(e,t,n,r){let o=O(),i=await yf(o,e.website_token,c=>We({apiBase:c.api_base,authHeader:`Bearer ${c.user_token}:${c.website_token}`,localDomain:""}));i&&o&&oo(o,i),oo({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 ${pe.bold(e.email)} (${pe.cyan(e.domain)}).`:e.email?`Logged in as ${pe.bold(e.email)}.`:"Logged in.";x(r,"success",s,pe.green(a)),r&&u.info("Future SEO work in this repo will sync to your dashboard.");let l=Ke().filter(c=>c!==He(e.domain||n));l.length>0&&u.info(`Also logged in to: ${l.join(", ")}. Each project auto-selects its own site \u2014 no need to log out first.`)}function bf(e,t,n){return{code:e,...t&&t!=="unknown"?{domain:t}:{},...n?{install_id:n.installId,cli_version:n.cliVersion,platform:process.platform}:{}}}function Co(e){if(Be())return null;let t=Oe(e);return t?{installId:t,cliVersion:C}:null}async function kf(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(bf(t,n,r)),signal:AbortSignal.timeout(1e4)}),i=null;try{i=await o.json()}catch{}return ha(o.status,i)}catch(o){return{status:"error",terminal:!1,message:o.message||"network error"}}}function vf(e){return e!=="ready"}async function Ro(e,t,n,r){if(!ma.test(e))return u.warn("That does not look like a setup code (expected `sa_` + 24 hex characters). Generate one at seoagent.com/grok."),"invalid";let i=!!process.stdout.isTTY?ba():null;i?.start(pe.dim("Redeeming setup code\u2026"));let s="network error";for(let a=0;a<ff;a++){a>0&&await ka(gf);let l=await kf(t,e,n,r);if(l.status==="ready")return await va(l,t,n,i),"ready";if(l.terminal)return x(i,"warn",l.message,pe.red(l.message)),"rejected";s=l.message}return x(i,"warn",`Could not reach the server (${s}). Check your connection and run \`${m("login")} --code <code>\` again.`,pe.yellow("Could not reach the server.")),i&&u.warn(`Run \`${m("login")} --code <code>\` again to retry.`),"unreachable"}function Eo(e){return e&&e!=="unknown"?ys(e):!!O()}function wa(){return`Next: run \`${m("sync")}\` in this repo \u2014 it pulls your strategy, briefs and inbox into .seoagent/. Nothing syncs until it runs.`}async function Zt(e={}){let t=process.cwd(),n=_(t),r=(e.apiBase||I.BASE||Qe).replace(/\/$/,""),o=n?.domain??null;if(o==="unknown"&&process.stdin.isTTY){let h=await uf({message:".seoagent/project.md has `domain: unknown`. What is your site's URL?",placeholder:"example.com",validate(w){if(!String(w??"").trim())return"A site URL is required.";if(!ya(String(w)))return"Please enter a valid domain like `example.com`."}});if(cf(h)){u.warn("Login cancelled.");return}let y=ya(String(h));if(y){o=y;try{J(t,{domain:y}),u.success(`Saved domain: ${y} \u2192 .seoagent/project.md`)}catch{}}}let i=(e.code??process.env.SEOAGENT_SETUP_CODE??"").trim(),s=o&&o!=="unknown";if(i&&!s?!1:Eo(o)){let h=s?` for ${o}`:"";u.info(`Already logged in${h}. To switch accounts${h}, run \`${m("logout")}\` first.`);return}if(i){let h=await Ro(i,r,o,Co(t));vf(h)?process.exitCode=1:u.info(wa());return}let l=mf(),c=hf(r,l,o);u.message("Opening seoagent.com to connect this CLI to your account..."),u.info(`If the browser does not open, visit: ${c}`),dr(c),u.info('In your browser: sign in (if needed) and click "Connect this CLI" to finish.');let p=!!process.stdout.isTTY?ba():null;p?.start(pe.dim("Waiting for browser confirmation\u2026"));let f=Date.now()+pf,g=!0;for(;Date.now()<f;){g||await ka(df),g=!1;let h=await wf(r,l);if(h.status==="ready"){await va(h,r,o,p),pr(t,"login_browser_ok"),u.info(wa());return}if(h.status==="expired"){pr(t,"login_browser_expired"),x(p,"warn",`Session expired. Run \`${m("login")}\` again.`,pe.red("Session expired.")),p&&u.warn(`Run \`${m("login")}\` again.`);return}if(h.status==="error"){if(h.terminal){x(p,"warn",`Login failed: ${h.message}. Run \`${m("login")}\` again.`,pe.red(`Login failed: ${h.message}.`)),p&&u.warn(`Run \`${m("login")}\` again.`),pr(t,"login_browser_failed");return}continue}}x(p,"warn",`Login timed out. Run \`${m("login")}\` again to retry.`,pe.yellow("Login timed out.")),p&&u.warn(`Run \`${m("login")}\` again to retry.`),pr(t,"login_browser_timeout")}function en(e={}){let t=e.all?null:_(process.cwd()),n=!e.all&&t?.domain&&t.domain!=="unknown"?t.domain:null;if(!ws(e.all?null:n)){u.info(n?`You were not logged in for ${n}.`:"You were not logged in.");return}u.success(n?`Logged out of ${n}.`:"Logged out.");let o=Ke();o.length>0&&u.info(`Still logged in to: ${o.join(", ")}. Run \`${m("logout")} --all\` to log out of everything.`)}function fr(e,t=process.env){return(e.code??t.SEOAGENT_SETUP_CODE??"").trim()}function _f(e,t=m("init")){return e?`${t} --code ${e}`:t}async function Ra(e,t,n){if(!n)return"none";let r=t!==dt;if(r&&Eo(t))return u.info(`Already logged in for ${t} \u2014 setup code not needed, skipping it.`),"already";let o=(I.BASE||Qe).replace(/\/$/,"");return Ro(n,o,r?t:null,Co(e))}var _a=[{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"}],dt="unknown",Cf=new Set(["saas","service","product","content","marketplace","tool","app","nonprofit","community","unknown"]);function Rf(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 gr(e){if(!e?.trim())return null;let t=e.trim().toLowerCase();return Cf.has(t)?t:null}function Ea(e,t){t.agentsMdPath&&u.info(`Added a SEOAgent section to ${At(e,t.agentsMdPath)} so agents that read it (Codex, others) find the skill.`),t.cursorRulePath&&u.info(`Wrote ${At(e,t.cursorRulePath)} so Cursor agents find the skill.`)}async function tn(e={}){let t=process.cwd();if(cs(t)){let l=_(t),c=$t(t);ot(t),J(t,{skill_version:C}),it(t);let d=At(t,c.skillFile),p=c.claudeSymlink?` (symlinked at ${At(t,c.claudeSymlink)})`:"",f=l?.skill_version&&l.skill_version!==C?`${l.skill_version} \u2192 ${C}`:C;if(u.success(`Refreshed the SEOAgent skill (${f}).`),u.info("Your `.seoagent/` knowledge was left untouched."),u.info(`Skill at ${d}${p} \u2014 agents read this file at session start.`),Ea(t,c),u.info(`Run \`${m("status")}\` to see your project, or \`${m("uninstall")}\` to start fresh.`),!Be()){let h=Oe(t);h&&await Ze({installId:h,event:"reinit",cliVersion:C})&&u.info(ro)}let g=await Ra(t,l?.domain??dt,fr(e));(g==="rejected"||g==="unreachable"||g==="invalid")&&u.warn(Ef());return}let n=!!e.yes||!process.stdin.isTTY,r=aa(t);if(r.isWorktree&&!e.allowWorktree){let l=_f(fr(e)),c=r.mainRoot?`cd ${r.mainRoot} && ${l}`:`run ${l} from the main repository root`;n&&(u.warn("This directory is a temporary git worktree \u2014 a `.seoagent/` created here is deleted with the worktree, and images/un-synced edits cannot be recovered from the cloud."),u.info(`Init at the main checkout instead: ${c}`),u.info("To init here anyway (not recommended), pass --allow-worktree."),process.exit(1)),$a("SEOAgent \u2014 AI SEO Agent");let d=await Sa({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});(ct(d)||!d)&&(ut(`Good call. ${c}`),process.exit(0))}else n||$a("SEOAgent \u2014 AI SEO Agent");let o=Ys(t,l=>u.step(l)),i=()=>{if(o.evidence.length!==0){u.info("Inferred from your project:");for(let l of o.evidence)u.info(` \u2022 ${Rf(l)}`)}},s=e.domain?.trim()||process.env.SEOAGENT_DOMAIN?.trim()||o.domain||null,a=gr(e.siteType)||gr(process.env.SEOAGENT_SITE_TYPE)||o.siteType||null;if(n){e.siteType?.trim()&&gr(e.siteType)===null&&(u.warn("Invalid --site-type. Use: saas, service, product, content, marketplace, tool, nonprofit, unknown, app, community."),process.exit(1)),process.env.SEOAGENT_SITE_TYPE?.trim()&&gr(process.env.SEOAGENT_SITE_TYPE)===null&&(u.warn("Invalid SEOAGENT_SITE_TYPE environment value."),process.exit(1)),i(),s||(u.info("Couldn't detect your site URL from this repo \u2014 scaffolding anyway; you'll be prompted to supply it."),s=dt),a||(a="unknown"),await Ca(t,s,a,fr(e));return}if(i(),!s){let l=await xa({message:"Website domain",placeholder:"example.com",validate:c=>c.trim()?void 0:"Domain is required"});ct(l)&&(ut("Cancelled"),process.exit(0)),s=String(l)}if(!a){let l=await Ao({message:"What kind of site is this?",options:[..._a]});ct(l)&&(ut("Cancelled"),process.exit(0)),a=l}for(;;){let l=await Sa({message:`Create .seoagent for ${s} (${a})?`});if(ct(l)&&(ut("Cancelled"),process.exit(0)),l)break;let c=await Ao({message:"What should we change?",options:[{value:"domain",label:"Domain"},{value:"site_type",label:"Site type"},{value:"abort",label:"Cancel setup"}]});if((ct(c)||c==="abort")&&(ut("Cancelled"),process.exit(0)),c==="domain"){let d=await xa({message:"Website domain",placeholder:"example.com",validate:p=>p.trim()?void 0:"Domain is required"});ct(d)&&(ut("Cancelled"),process.exit(0)),s=String(d)}else{let d=await Ao({message:"What kind of site is this?",options:[..._a]});ct(d)&&(ut("Cancelled"),process.exit(0)),a=d}}await Ca(t,s,a,fr(e))}async function Ca(e,t,n,r){let o=Sf();o.start("Setting up your SEO project");let i=Gs(e),s=qe(e),a=Jn(e),l=no(),c={domain:t,site_type:n,language:Vi(t),initialized_at:new Date().toISOString(),seoagent_version:C,skill_version:C,install_id:l,...i.cms!=="none"?{cms:i.cms}:{},...i.blog_path?{blog_path:i.blog_path}:{},...s.provider!=="none"?{image_provider:s.provider}:{},...a.dir?{public_dir:a.dir}:{}},d=Be(),p=d?Promise.resolve(!1):Ze({installId:l,event:"init",cliVersion:C}),f=d||t===dt?Promise.resolve(null):bs({domain:t,installId:l}),g=O(t),h=g&&t!==dt?We({apiBase:g.api_base,authHeader:`Bearer ${g.user_token}:${g.website_token}`,localDomain:t}).catch(()=>null):Promise.resolve(null);ds(e),us(e,c),it(e),Ms(e,n),Rs(e);let y=ur(e,t!==dt?t:null),w=$t(e),b=At(e,w.skillFile),R=$f(b),B=`${R}/references/audit-checks.md`,T=w.claudeSymlink?At(e,w.claudeSymlink):void 0;ot(e);let D=t!==dt,te=await Ra(e,t,r),W=te==="ready"||te==="already",K=te==="rejected"||te==="unreachable"||te==="invalid";K&&(process.exitCode=1);let U={pages:[],dynamicCount:0},ne={wrote:!1,added:0,total:0,merged:!1,file:"pages.md"};try{U=ve(e,D?t:null),ne=ir(e,U.pages)}catch{}if(o.stop(D?`Created .seoagent/ project for ${c.domain}`:"Created .seoagent/ project (site URL not detected yet \u2014 see step 2)"),i.cms!=="none"){let F=i.evidence.map(we=>`${we.detail} (${we.source})`).slice(0,2).join(", ");u.info(`CMS detected: ${i.cms} \u2014 ${F}`)}if(i.blog_path&&u.info(`Blog route: ${i.blog_path}`),a.dir&&u.info(`Static files: ${a.dir}/ (${a.evidence}) \u2014 your llms.txt and OKF bundle publish there.`),y.created.length>0&&u.info("Scaffolded .seoagent/okf/ \u2014 the AI-readable knowledge bundle answer engines cite. The audit will offer to fill it."),s.provider!=="none"&&u.info(`Image generation: ${s.provider} (detected ${s.matched_key}) \u2014 articles can get AI hero images.`),U.pages.length>0){let F=ne.merged?ne.added>0?`Re-scanned your codebase: +${ne.added} new page${ne.added===1?"":"s"} merged into .seoagent/pages.md (${ne.total} total)`:`Re-scanned your codebase: .seoagent/pages.md already current (${ne.total} pages)`:`Found ${U.pages.length} page${U.pages.length===1?"":"s"} in your codebase \u2192 .seoagent/pages.md`;u.info(F+(U.dynamicCount>0?` (plus ${U.dynamicCount} dynamic route template${U.dynamicCount===1?"":"s"} \u2014 the audit will discover their instances)`:""))}Ea(e,w);let[mt,Re,qr]=await Promise.all([p,f,h]);if(qr?.verdict==="mismatch"&&u.warn(`Heads up: the credentials bound to ${t} are for a SEOAgent cloud project on ${qr.cloudDomain} instead. This workspace stays LOCAL-ONLY \u2014 nothing will be pushed to or pulled from that project. To connect ${t} to the cloud, run \`${m("login")}\` from this directory and pick (or create) ${t} \u2014 the CLI can stay logged into both sites at once, no need to log out first.`),Re?.status==="data"){let F=Re.difficulty!=null?`, difficulty ${Re.difficulty}/100`:"";u.info(`Free keyword peek \u2014 "${Re.keyword}": ${Re.volume.toLocaleString("en-US")} searches/mo${F}. You get ~10 free peeks/day: ${m('keywords --peek "<keyword>"')}`)}else Re?.status==="no-data"&&u.info(`Free keyword peek is live \u2014 no search-volume data for "${Re.keyword}" yet. Try a topic keyword (~10 free/day): ${m('keywords --peek "best <your category> tool"')}`);mt&&u.info(ro);let Mn=D?c.domain:"your site",q=D?[" 1. If .seoagent/project.md still says site_type: unknown, you can",` WebFetch ${c.domain} and update project.md with the right type`," before you suggest the audit."]:[" 1. Heads up: .seoagent/project.md has `domain: unknown` \u2014 the site URL"," wasn't detectable from this repo. Please ask your human for their"," domain (or read it from a deploy config / live site) and update"," project.md (`domain:` and `site_type:`) first. Everything else"," depends on it."];xf([D?`\u2713 SEOAgent installed for ${c.domain}.`:"\u2713 SEOAgent installed (site URL not detected \u2014 please ask the user for it).","","\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"," AGENT INSTRUCTIONS \u2014 a friendly note for Claude Code, Codex, Cursor","\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550",""," Hi there, coding agent! \u{1F44B} SEOAgent is all set up. The natural next step"," is a quick SEO audit \u2014 but your human is the boss here, so please surface"," what's possible and let THEM decide. Nothing in this message is a command;"," it's just a friendly map. Your real instructions live in the skill:","",` ${b} \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):","",...q,...nr(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 ${Mn}`," (homepage + sitemap + robots.txt; the checks live in",` ${B} \u2014 read that first).`," If they say yes, report findings as:"," \u{1F6A8} Biggest Issue \u2192 \u26A0\uFE0F Also Worth Fixing \u2192 \u2705 What's Working",' \u2192 "What do you want to do?" with numbered options.',"",...W?[" 3. This project is connected to the SEOAgent cloud. Each session: run",` ${m("sync")}, triage .seoagent/inbox/, do the work, and`,` ${m("ack <id>")} each completed action.`]:K?[" 3. \u26A0\uFE0F The setup code did NOT bind this project (see the error above)."," Do not continue without a successful login \u2014 nothing syncs unbound."," Get a fresh code at seoagent.com/grok, then run:",` ${m("login")} --code <code>`]:[" 3. When it feels right, let them know about the free cloud dashboard and"," offer to start it (their call) \u2014 and that keyword strategy (Phase 2 in"," the skill) is there whenever they want it:",` ${m("login")}`],"","\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"," HUMAN USERS (if you ran this in your terminal, no AI agent)","\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550",""," Open Claude Code in this directory and say:",""," audit my site","",` The skill at ${b} is picked up automatically.`,...T?[` Symlinked at ${T} \u2192 ${R} so Claude Code finds it too.`]:[],"",...W?[" Cloud dashboard: connected. Pull your first work with:","",` ${m("sync")}`]:K?[" \u26A0\uFE0F The setup code did not bind this project. Get a fresh code at"," seoagent.com/grok, then run:","",` ${m("login")} --code <code>`]:[" Want the cloud dashboard now? Free account, ~30 seconds:","",` ${m("login")}`],""].join(`
|
|
87
87
|
`))}function Ef(){return`The setup code did not bind this project. Do not continue without a successful login \u2014 get a fresh code at seoagent.com/grok, then run \`${m("login")} --code <code>\`.`}import{existsSync as Ia}from"fs";import{intro as Pf,outro as If,confirm as Tf,isCancel as Of,cancel as Lf}from"@clack/prompts";import{existsSync as mr,rmSync as Af}from"fs";import{join as hr}from"path";function Aa(e,t={}){let n=[],r=hr(e,k);mr(r)&&n.push({label:".seoagent/ \u2014 project state + all generated work",path:r,kind:"dir"});let o=hr(e,Pe.CLAUDE);mr(o)&&n.push({label:".claude/skills/seoagent/ \u2014 installed skill bundle",path:o,kind:"dir"});let i=hr(e,Pe.AGENTS);return mr(i)&&n.push({label:".agents/skills/seoagent/ \u2014 installed skill bundle",path:i,kind:"dir"}),As(e)&&n.push({label:".claude/settings.json \u2014 sync hook entry (other settings preserved)",path:hr(e,".claude","settings.json"),kind:"hook"}),t.global&&mr(z)&&n.push({label:"~/.config/seoagent/ \u2014 login credentials + sync state (all projects)",path:z,kind:"global"}),n}function Pa(e,t){for(let n of t)n.kind==="hook"?Ps(e):Af(n.path,{recursive:!0,force:!0})}async function nn(e={}){let t=process.cwd(),n=Aa(t,{global:e.global});if(n.length===0){u.info("No SEOAgent install found in this directory \u2014 nothing to remove."),!e.global&&Ia(z)&&u.info("Your seoagent.com login is still stored at ~/.config/seoagent/. Re-run with --global to wipe it too.");return}let r=!!e.yes||!process.stdin.isTTY,o=n.map(s=>` \u2022 ${s.label}`).join(`
|
|
88
88
|
`);if(!r){Pf("SEOAgent \u2014 uninstall"),u.message(`This will remove:
|
|
89
89
|
${o}`);let s=await Tf({message:"Remove these and start fresh?"});if(Of(s)||!s){Lf("Cancelled \u2014 nothing was removed.");return}}Pa(t,n);let i=n.map(s=>` \u2713 ${s.label}`).join(`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seoagent-official/seoagent",
|
|
3
|
-
"version": "1.98.
|
|
3
|
+
"version": "1.98.5",
|
|
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": {
|