@seoagent-official/seoagent 1.95.0 → 1.95.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 +1 -1
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -90,7 +90,7 @@ ${o}`);let s=await Wp({message:"Remove these and start fresh?"});if(Kp(s)||!s){q
|
|
|
90
90
|
`);r?u.success(`Removed:
|
|
91
91
|
${i}`):Hp(`Removed:
|
|
92
92
|
${i}`),!e.global&&ia(K)&&u.info("Your seoagent.com login (~/.config/seoagent/) was kept \u2014 it is shared across projects. Run `uninstall --global` to wipe it too."),u.info(`Re-install any time with \`${m("init")}\`.`)}import E from"picocolors";import{existsSync as Xt,readdirSync as ho,statSync as zp,readFileSync as sa}from"fs";import{join as _t}from"path";function Yp(e){let t=(e.match(/^\s*-\s+\[\s\]/gm)??[]).length,n=(e.match(/^\s*-\s+\[x\]/gim)??[]).length;return{open:t,done:n}}function Vp(e){let t=_t(e,"audit","latest.md");if(!Xt(t))return null;let n=mt(t);if(!n)return null;let r=sa(t,"utf-8"),o=Yp(r);return{exists:!0,date:typeof n.data.audited_at=="string"?n.data.audited_at:void 0,issueCount:o.open}}function Jp(e){try{let n=sa(e,"utf-8").split(/\r?\n/),r=!1,o=0;for(let i of n){let s=i.trim();if(s.startsWith("|")&&s.endsWith("|")){if(/^\|\s*-+\s*(\|\s*-+\s*)+\|$/.test(s)){r=!0;continue}r&&o++}else if(r&&s==="")break}return o}catch{return 0}}function Xp(e){let t=_t(e,"strategy","clusters");if(!Xt(t))return null;let n=ho(t).filter(o=>o.endsWith(".md"));if(n.length===0)return null;let r=0;for(let o of n)r+=Jp(_t(t,o));return{exists:!0,clusterCount:n.length,articleCount:r}}function Qp(e){let t=_t(e,"briefs");if(!Xt(t))return null;let n=ho(t).filter(r=>r.endsWith(".md"));return n.length>0?{exists:!0,count:n.length}:null}function Zp(e){let t=_t(e,"content");if(!Xt(t))return null;let n=ho(t).filter(r=>r.endsWith(".md"));return n.length===0?null:{exists:!0,count:n.length}}function ef(e){let t=_t(e,"roadmap.md");return Xt(t)?{exists:!0,updatedAt:zp(t).mtime.toISOString()}:null}function aa(e){let t=_(e);if(!t)return null;let n=Pe(e);return{domain:t.domain,audit:Vp(n),strategy:Xp(n),briefs:Qp(n),content:Zp(n),roadmap:ef(n)}}import He from"picocolors";var la=/\x1b\[[0-9;]*m/g;function Qt(e){return e.replace(la,"").length}var tf=51,nf=24,rf=80;function of(e,t,n=tf){let r=0;for(let c of e)"sep"in c||(r=Math.max(r,Qt(c.content)));let o=t?Qt(t)+6:0,i=Math.max(n,r+4,o),s=process.stdout?.columns,a=(typeof s=="number"&&s>0?s:rf)-2,l=Math.max(nf,a);return Math.min(i,l)}function sf(e,t){if(Qt(e)<=t)return e;if(t<=0)return"";let n=t-1,r="",o=0,i=!1,s=d=>{for(let f of d){if(o+f.length>n)return;r+=f,o+=f.length}},a=new RegExp(la.source,"g"),l=0,c;for(;(c=a.exec(e))!==null;)s(e.slice(l,c.index)),r+=c[0],i=!0,l=c.index+c[0].length;return s(e.slice(l)),`${r}\u2026${i?"\x1B[0m":""}`}function M(e,t={}){let n=of(e,t.title,t.width),r=n-4,o="\u2500".repeat(n-2),i=af(n,t.title),s=He.dim(`\u251C${o}\u2524`),a=He.dim(`\u2514${o}\u2518`),l=[i];for(let c of e){if("sep"in c){l.push(s);continue}l.push(lf(c.content,r))}return l.push(a),l.join(`
|
|
93
|
-
`)}function af(e,t){if(!t)return He.dim(`\u250C${"\u2500".repeat(e-2)}\u2510`);let n=Qt(t),r=e-5-n;return r<1?He.dim(`\u250C${"\u2500".repeat(e-2)}\u2510`):`${He.dim("\u250C\u2500 ")}${t}${He.dim(` ${"\u2500".repeat(r)}\u2510`)}`}function lf(e,t){let n=Qt(e),r=n<=t?e+" ".repeat(t-n):sf(e,t);return`${He.dim("\u2502")} ${r} ${He.dim("\u2502")}`}function yo(e){let t=Date.now()-new Date(e).getTime(),n=Math.floor(t/6e4);if(n<60)return`${n} minutes ago`;let r=Math.floor(n/60);return r<24?`${r} hours ago`:`${Math.floor(r/24)} days ago`}function Zt(){let e=aa(process.cwd());if(!e){u.warn("No SEOAgent project found in this directory."),u.info(`Run \`${m("init")}\` to get started.`);return}u.message(cf(e))}function cf(e){if(!e)return"";let t=11,n=s=>E.dim(s.padEnd(t)),r=[];D(e.domain)?(r.push({content:`${E.green("\u2713")} ${E.dim("Logged in")}`}),r.push({content:E.dim(` Run \`${m("whoami")}\` for email / plan.`)})):(r.push({content:`${E.dim("\u25CB Not logged in")}`}),r.push({content:E.dim(` Run \`${m("login")}\` to connect.`)})),r.push({sep:!0}),r.push({content:e.audit?.exists?`${n("Audit:")}${E.green(`last run ${yo(e.audit.date??"")}`)} ${E.dim(`(${e.audit.issueCount??0} issues)`)}`:`${n("Audit:")}${E.dim("not yet run")}`}),r.push({content:e.strategy?.exists?`${n("Strategy:")}${E.bold(E.cyan(String(e.strategy.clusterCount??0)))} ${E.dim("clusters")} \xB7 ${E.bold(E.cyan(String(e.strategy.articleCount??0)))} ${E.dim("ideas")}`:`${n("Strategy:")}${E.dim("not yet created")}`}),r.push({content:e.briefs?.exists?`${n("Briefs:")}${E.bold(E.cyan(String(e.briefs.count??0)))} ${E.dim("ready")}`:`${n("Briefs:")}${E.dim("none created")}`}),r.push({content:e.content?.exists?`${n("Content:")}${E.green("\u2713")} ${E.bold(E.cyan(String(e.content.count??0)))} ${E.dim("articles written")}`:`${n("Content:")}${E.dim("no articles yet")}`}),r.push({content:e.roadmap?.exists?`${n("Roadmap:")}${E.green("updated")} ${E.dim(yo(e.roadmap.updatedAt??""))}`:`${n("Roadmap:")}${E.dim("not yet created")}`});let i=`${E.bold(E.cyan("SEOAgent"))} ${E.dim("\xB7")} ${E.bold(e.domain)}`;return M(r,{width:78,title:i})}import{exec as uf,spawn as ca}from"child_process";import{confirm as df,isCancel as pf}from"@clack/prompts";import X from"picocolors";function We(e,t){let n=i=>i.replace(/[-+].*$/,"").split(".").map(s=>Number.parseInt(s,10)||0).slice(0,3),r=n(e),o=n(t);for(let i=0;i<3;i++){let s=(r[i]??0)-(o[i]??0);if(s!==0)return s}return 0}var ff=`https://registry.npmjs.org/${Re}/latest`,en=["free","starter","pro","scale","enterprise"],ir={free:"Free",starter:"Starter",pro:"Pro",scale:"Scale",enterprise:"Enterprise"};function gf(e){let t=e.trim().toLowerCase().split(/\s+/)[0];return en.includes(t)?t:null}function mf(e){let t=en.indexOf(e);return t===-1||t===en.length-1?null:en[t+1]}async function hf(e,t=fetch){if(!e)return null;try{let n=(e.api_base||N.BASE).replace(/\/$/,""),r=await t(`${n}/api/cli/whoami`,{headers:{Authorization:`Bearer ${e.user_token}:${e.website_token}`,Accept:"application/json"}});if(!r.ok)return null;let o=await r.json();return typeof o.plan!="string"||!o.plan?null:{plan:o.plan,email:typeof o.email=="string"&&o.email?o.email:void 0,domain:typeof o.domain=="string"&&o.domain?o.domain:void 0}}catch{return null}}function yf(e){let t=[],r=s=>X.dim(s.padEnd(10)),o=!1;e.email&&(t.push({content:`${r("Email:")}${X.bold(e.email)}`}),o=!0),e.domain&&(t.push({content:`${r("Website:")}${X.cyan(e.domain)}`}),o=!0),o&&t.push({sep:!0});for(let s of en){let a=ir[s];s===e.current?t.push({content:`${X.bold(X.green("\u25CF"))} ${X.bold(X.green(a.padEnd(11)))} ${X.dim("\u2190 you are here")}`}):s===e.next?t.push({content:`${X.yellow("\u25C6")} ${X.bold(X.yellow(a.padEnd(11)))} ${X.yellow("next tier")}`}):t.push({content:X.dim(` ${a}`)})}let i=`${X.bold(X.cyan("SEOAgent Cloud"))} ${X.dim("\xB7")} ${X.dim("your plan")}`;return M(t,{width:55,title:i})}async function tn(){let e=_(process.cwd()),t=D(e?.domain),n=!!process.stdin.isTTY;if(t&&n){let r=await hf(t);if(r){let o=gf(r.plan),i=o?mf(o):null;if(o&&u.message(yf({email:r.email,domain:r.domain,current:o,next:i})),o&&!i){u.success(`You're on ${ir[o]} (highest tier).`);return}if(o&&i){let s=await df({message:`Upgrade from ${ir[o]} to ${ir[i]}?`});if(pf(s)||!s){u.info("No changes.");return}}}}wf()}function wf(){let e=bf();u.info("Opening SEOAgent Cloud pricing..."),u.message(`URL: ${e}`);let t=process.platform==="darwin"?`open "${e}"`:process.platform==="win32"?`start "${e}"`:`xdg-open "${e}"`;uf(t,n=>{n&&u.warn("Could not open browser. Visit the URL above to upgrade.")})}function bf(){let e=_(process.cwd());return he("cli-upgrade",e?.domain)}async function nn(e={}){let t=A,n=await vf();if(!n){u.warn("Couldn't reach the npm registry to check for a newer version."),u.message(`You're on ${t}. To force-update: npm install -g ${Re}@latest`);return}if(We(t,n)>=0){u.success(`You're on the latest CLI (${t}).`);return}if(!Ur()){u.info(`A newer CLI is available: ${t} \u2192 ${n}.`),u.message("You're running via npx, which caches the package \u2014 an unpinned spec won't auto-upgrade."),u.message(`Run the latest now by pinning the tag: npx -y ${Re}@latest <cmd>`),u.message(`Still on ${t} after that? Clear the npx cache: rm -rf ~/.npm/_npx`),u.message(`Or install globally once so \`seoagent <cmd>\` + \`update-cli\` work in place: npm install -g ${Re}@latest`);return}if(e.dryRun){u.info(`(dry-run) would run: npm install -g ${Re}@latest`);return}if(u.info(`Updating CLI: ${t} \u2192 ${n}\u2026`),!await $f()){u.error(`Update failed. Try manually: npm install -g ${Re}@latest (you may need sudo on Linux/macOS).`);return}if(u.success(`Updated to ${n}.`),e.projects===!1){u.info("Skipped project refresh (--no-projects). Each project updates itself on its next sync.");return}u.info("Refreshing your other projects to the new version\u2026"),await kf()||u.info("Could not run the project sweep automatically \u2014 run `seoagent refresh --all` to update all projects now (otherwise each updates itself the next time you open it in Claude Code).")}function kf(){return new Promise(e=>{try{let t=ca("seoagent",["refresh","--all"],{stdio:"inherit"});t.on("error",()=>e(!1)),t.on("exit",n=>e(n===0))}catch{e(!1)}})}async function vf(e=fetch){try{let t=await e(ff,{headers:{Accept:"application/json"}});if(!t.ok)return null;let n=await t.json();return typeof n.version=="string"?n.version:null}catch{return null}}function $f(){return new Promise(e=>{let t=ca("npm",["install","-g",`${Re}@latest`],{stdio:"inherit"});t.on("error",()=>e(!1)),t.on("exit",n=>e(n===0))})}import{exec as xf}from"child_process";import{randomBytes as Sf}from"crypto";import{isCancel as _f,spinner as ma,text as Cf}from"@clack/prompts";import ce from"picocolors";function ua(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 pa=/^sa_[0-9a-f]{24}$/;function da(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 (codes last 24 hours). 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 fa(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:da(n.error)}:{status:"error",terminal:!1,message:`HTTP ${e} (server error)`};if(e>=400)return{status:"error",terminal:!0,message:da(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 Rf=1500,Ef=300*1e3,Af=3,Pf=2e3;function If(e){let t=process.platform==="darwin"?`open "${e}"`:process.platform==="win32"?`start "" "${e}"`:`xdg-open "${e}"`;xf(t,()=>{})}function Tf(){return Sf(16).toString("hex")}function Of(e,t,n){let r=new URLSearchParams({session:t});return n&&n!=="unknown"&&r.set("domain",n),`${e}/cli/auth?${r.toString()}`}function ga(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 Lf(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 Nf(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 ua(r.status,o)}catch(n){return{status:"error",terminal:!1,message:n.message||"network error"}}}function ha(e){return new Promise(t=>setTimeout(t,e))}async function ya(e,t,n,r){let o=D(),i=await Lf(o,e.website_token,c=>Me({apiBase:c.api_base,authHeader:`Bearer ${c.user_token}:${c.website_token}`,localDomain:""}));i&&o&&Vr(o,i),Vr({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 ${ce.bold(e.email)} (${ce.cyan(e.domain)}).`:e.email?`Logged in as ${ce.bold(e.email)}.`:"Logged in.";x(r,"success",s,ce.green(a)),r&&u.info("Future SEO work in this repo will sync to your dashboard.");let l=Ue().filter(c=>c!==Fe(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.`)}async function jf(e,t,n){try{let r=await fetch(`${e}/api/cli/auth/redeem`,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify({code:t,...n&&n!=="unknown"?{domain:n}:{}}),signal:AbortSignal.timeout(1e4)}),o=null;try{o=await r.json()}catch{}return fa(r.status,o)}catch(r){return{status:"error",terminal:!1,message:r.message||"network error"}}}async function Df(e,t,n){if(!pa.test(e)){u.warn("That does not look like a setup code (expected `sa_` + 24 hex characters). Generate one at seoagent.com/grok.");return}let o=!!process.stdout.isTTY?ma():null;o?.start(ce.dim("Redeeming setup code\u2026"));let i="network error";for(let s=0;s<Af;s++){s>0&&await ha(Pf);let a=await jf(t,e,n);if(a.status==="ready"){await ya(a,t,n,o);return}if(a.terminal){x(o,"warn",a.message,ce.red(a.message));return}i=a.message}x(o,"warn",`Could not reach the server (${i}). Check your connection and run \`${m("login")} --code <code>\` again.`,ce.yellow("Could not reach the server.")),o&&u.warn(`Run \`${m("login")} --code <code>\` again to retry.`)}async function rn(e={}){let t=process.cwd(),n=_(t),r=(e.apiBase||N.BASE||Mt).replace(/\/$/,""),o=n?.domain??null;if(o==="unknown"&&process.stdin.isTTY){let g=await Cf({message:".seoagent/project.md has `domain: unknown`. What is your site's URL?",placeholder:"example.com",validate(h){if(!String(h??"").trim())return"A site URL is required.";if(!ga(String(h)))return"Please enter a valid domain like `example.com`."}});if(_f(g)){u.warn("Login cancelled.");return}let y=ga(String(g));if(y){o=y;try{J(t,{domain:y}),u.success(`Saved domain: ${y} \u2192 .seoagent/project.md`)}catch{}}}if(o&&o!=="unknown"?ns(o):!!D()){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){await Df(s,r,o);return}let a=Tf(),l=Of(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}`),If(l),u.info('In your browser: sign in (if needed) and click "Connect this CLI" to finish.');let d=!!process.stdout.isTTY?ma():null;d?.start(ce.dim("Waiting for browser confirmation\u2026"));let f=Date.now()+Ef,p=!0;for(;Date.now()<f;){p||await ha(Rf),p=!1;let g=await Nf(r,a);if(g.status==="ready"){await ya(g,r,o,d);return}if(g.status==="expired"){x(d,"warn",`Session expired. Run \`${m("login")}\` again.`,ce.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.`,ce.red(`Login failed: ${g.message}.`)),d&&u.warn(`Run \`${m("login")}\` again.`);return}continue}}x(d,"warn",`Login timed out. Run \`${m("login")}\` again to retry.`,ce.yellow("Login timed out.")),d&&u.warn(`Run \`${m("login")}\` again to retry.`)}function on(e={}){let t=e.all?null:_(process.cwd()),n=!e.all&&t?.domain&&t.domain!=="unknown"?t.domain:null;if(!rs(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=Ue();o.length>0&&u.info(`Still logged in to: ${o.join(", ")}. Run \`${m("logout")} --all\` to log out of everything.`)}import{spinner as Ah}from"@clack/prompts";import H from"picocolors";import{existsSync as Do,mkdirSync as Tm,readFileSync as Rl,readdirSync as Om,writeFileSync as Lm}from"fs";import{join as Fo,relative as Nm,sep as jm}from"path";import{existsSync as Ff,mkdirSync as Mf,readdirSync as ba,readFileSync as bo,writeFileSync as wa}from"fs";import{dirname as Uf,join as Ct,relative as Bf}from"path";var ue=e=>typeof e=="string"&&e?e:void 0;function ko(e,t){let n=Ct(e,t),r;try{r=ba(n)}catch{return[]}let o=[];for(let i of r){if(!i.endsWith(".md")&&!i.endsWith(".mdx")||i.startsWith("_")||i.startsWith("."))continue;let s=Ct(n,i),a;try{a=le(bo(s,"utf-8")).data}catch{continue}let l=ue(a.slug)??i.replace(/\.mdx?$/,"");o.push({slug:l,title:ue(a.title)??l,draft:a.draft===!0,page_type:ue(a.page_type)??ue(a.type),path:Bf(e,s)})}return o}function sn(e){let t;try{t=ba(e)}catch{return[]}let n=[];for(let r of t){if(!r.endsWith(".md"))continue;let o=Ct(e,r),i;try{i=le(bo(o,"utf-8")).data}catch{continue}n.push({slug:ue(i.slug)??r.replace(/\.md$/,""),source:ue(i.source)??"external",repo_path:ue(i.repo_path)??null,status:ue(i.status)??null,file:o})}return n}function vo(e,t,n){let r=new Set(t.map(l=>l.slug)),o=e.filter(l=>!l.draft&&!r.has(l.slug)),i=new Map(e.filter(l=>!l.draft).map(l=>[l.slug,l])),s=[];for(let l of t){if((l.status??"")==="published")continue;let c=i.get(l.slug);c&&s.push({pointer:l,article:c})}let a=t.filter(l=>l.source==="repo"&&l.repo_path!==null&&!n(l.repo_path));return{toTrack:o,toPublish:s,stale:a,alreadyTracked:t.length-a.length}}function $o(e){return t=>Ff(Ct(e,t))}function sr(e){return e.trim().replace(/^.*\/([^/]+)$/,"$1").replace(/\.[a-z0-9]+$/i,"").toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")}function ka(e){return e.split("-").filter(Boolean).map(t=>t.charAt(0).toUpperCase()+t.slice(1)).join(" ")}function Rt(e,t){if(t.url&&t.url.trim())return t.url.trim().replace(/\/+$/,"");if(!t.domain||t.domain==="unknown")return null;let n=t.domain.replace(/^https?:\/\//,"").replace(/\/$/,""),r=(t.blogPath||"").replace(/\/+$/,"");return`https://${n}${r}/${e}`}var wo="This is a pointer so the cloud dashboard can track it without duplicating";function va(e){if(!e.includes(wo))return null;let t;try{t=le(e)}catch{return null}return!ue(t.data.slug)||!t.body.includes(wo)?null:{status:ue(t.data.status)??null,repo_path:ue(t.data.repo_path)??null}}function an(e){let t=["---",`slug: ${e.slug}`,`title: ${JSON.stringify(e.title)}`];e.page_type&&t.push(`page_type: ${e.page_type}`),e.canonical&&t.push(`canonical: ${e.canonical}`),t.push(`status: ${e.status}`),t.push(`source: ${e.source}`),e.repo_path&&t.push(`repo_path: ${e.repo_path}`),e.brief&&t.push(`brief: ${JSON.stringify(e.brief)}`),e.supersedes&&t.push(`supersedes: ${e.supersedes}`),e.primary_keyword&&t.push(`primary_keyword: ${JSON.stringify(e.primary_keyword)}`),t.push(`tracked_at: ${e.tracked_at}`),t.push("---");let n=e.repo_path?`rendered from \`${e.repo_path}\` (the source of truth for its body)`:"rendered from its source (repo or CMS)",r=e.canonical?`
|
|
93
|
+
`)}function af(e,t){if(!t)return He.dim(`\u250C${"\u2500".repeat(e-2)}\u2510`);let n=Qt(t),r=e-5-n;return r<1?He.dim(`\u250C${"\u2500".repeat(e-2)}\u2510`):`${He.dim("\u250C\u2500 ")}${t}${He.dim(` ${"\u2500".repeat(r)}\u2510`)}`}function lf(e,t){let n=Qt(e),r=n<=t?e+" ".repeat(t-n):sf(e,t);return`${He.dim("\u2502")} ${r} ${He.dim("\u2502")}`}function yo(e){let t=Date.now()-new Date(e).getTime(),n=Math.floor(t/6e4);if(n<60)return`${n} minutes ago`;let r=Math.floor(n/60);return r<24?`${r} hours ago`:`${Math.floor(r/24)} days ago`}function Zt(){let e=aa(process.cwd());if(!e){u.warn("No SEOAgent project found in this directory."),u.info(`Run \`${m("init")}\` to get started.`);return}u.message(cf(e))}function cf(e){if(!e)return"";let t=11,n=s=>E.dim(s.padEnd(t)),r=[];D(e.domain)?(r.push({content:`${E.green("\u2713")} ${E.dim("Logged in")}`}),r.push({content:E.dim(` Run \`${m("whoami")}\` for email / plan.`)})):(r.push({content:`${E.dim("\u25CB Not logged in")}`}),r.push({content:E.dim(` Run \`${m("login")}\` to connect.`)})),r.push({sep:!0}),r.push({content:e.audit?.exists?`${n("Audit:")}${E.green(`last run ${yo(e.audit.date??"")}`)} ${E.dim(`(${e.audit.issueCount??0} issues)`)}`:`${n("Audit:")}${E.dim("not yet run")}`}),r.push({content:e.strategy?.exists?`${n("Strategy:")}${E.bold(E.cyan(String(e.strategy.clusterCount??0)))} ${E.dim("clusters")} \xB7 ${E.bold(E.cyan(String(e.strategy.articleCount??0)))} ${E.dim("ideas")}`:`${n("Strategy:")}${E.dim("not yet created")}`}),r.push({content:e.briefs?.exists?`${n("Briefs:")}${E.bold(E.cyan(String(e.briefs.count??0)))} ${E.dim("ready")}`:`${n("Briefs:")}${E.dim("none created")}`}),r.push({content:e.content?.exists?`${n("Content:")}${E.green("\u2713")} ${E.bold(E.cyan(String(e.content.count??0)))} ${E.dim("articles written")}`:`${n("Content:")}${E.dim("no articles yet")}`}),r.push({content:e.roadmap?.exists?`${n("Roadmap:")}${E.green("updated")} ${E.dim(yo(e.roadmap.updatedAt??""))}`:`${n("Roadmap:")}${E.dim("not yet created")}`});let i=`${E.bold(E.cyan("SEOAgent"))} ${E.dim("\xB7")} ${E.bold(e.domain)}`;return M(r,{width:78,title:i})}import{exec as uf,spawn as ca}from"child_process";import{confirm as df,isCancel as pf}from"@clack/prompts";import X from"picocolors";function We(e,t){let n=i=>i.replace(/[-+].*$/,"").split(".").map(s=>Number.parseInt(s,10)||0).slice(0,3),r=n(e),o=n(t);for(let i=0;i<3;i++){let s=(r[i]??0)-(o[i]??0);if(s!==0)return s}return 0}var ff=`https://registry.npmjs.org/${Re}/latest`,en=["free","starter","pro","scale","enterprise"],ir={free:"Free",starter:"Starter",pro:"Pro",scale:"Scale",enterprise:"Enterprise"};function gf(e){let t=e.trim().toLowerCase().split(/\s+/)[0];return en.includes(t)?t:null}function mf(e){let t=en.indexOf(e);return t===-1||t===en.length-1?null:en[t+1]}async function hf(e,t=fetch){if(!e)return null;try{let n=(e.api_base||N.BASE).replace(/\/$/,""),r=await t(`${n}/api/cli/whoami`,{headers:{Authorization:`Bearer ${e.user_token}:${e.website_token}`,Accept:"application/json"}});if(!r.ok)return null;let o=await r.json();return typeof o.plan!="string"||!o.plan?null:{plan:o.plan,email:typeof o.email=="string"&&o.email?o.email:void 0,domain:typeof o.domain=="string"&&o.domain?o.domain:void 0}}catch{return null}}function yf(e){let t=[],r=s=>X.dim(s.padEnd(10)),o=!1;e.email&&(t.push({content:`${r("Email:")}${X.bold(e.email)}`}),o=!0),e.domain&&(t.push({content:`${r("Website:")}${X.cyan(e.domain)}`}),o=!0),o&&t.push({sep:!0});for(let s of en){let a=ir[s];s===e.current?t.push({content:`${X.bold(X.green("\u25CF"))} ${X.bold(X.green(a.padEnd(11)))} ${X.dim("\u2190 you are here")}`}):s===e.next?t.push({content:`${X.yellow("\u25C6")} ${X.bold(X.yellow(a.padEnd(11)))} ${X.yellow("next tier")}`}):t.push({content:X.dim(` ${a}`)})}let i=`${X.bold(X.cyan("SEOAgent Cloud"))} ${X.dim("\xB7")} ${X.dim("your plan")}`;return M(t,{width:55,title:i})}async function tn(){let e=_(process.cwd()),t=D(e?.domain),n=!!process.stdin.isTTY;if(t&&n){let r=await hf(t);if(r){let o=gf(r.plan),i=o?mf(o):null;if(o&&u.message(yf({email:r.email,domain:r.domain,current:o,next:i})),o&&!i){u.success(`You're on ${ir[o]} (highest tier).`);return}if(o&&i){let s=await df({message:`Upgrade from ${ir[o]} to ${ir[i]}?`});if(pf(s)||!s){u.info("No changes.");return}}}}wf()}function wf(){let e=bf();u.info("Opening SEOAgent Cloud pricing..."),u.message(`URL: ${e}`);let t=process.platform==="darwin"?`open "${e}"`:process.platform==="win32"?`start "${e}"`:`xdg-open "${e}"`;uf(t,n=>{n&&u.warn("Could not open browser. Visit the URL above to upgrade.")})}function bf(){let e=_(process.cwd());return he("cli-upgrade",e?.domain)}async function nn(e={}){let t=A,n=await vf();if(!n){u.warn("Couldn't reach the npm registry to check for a newer version."),u.message(`You're on ${t}. To force-update: npm install -g ${Re}@latest`);return}if(We(t,n)>=0){u.success(`You're on the latest CLI (${t}).`);return}if(!Ur()){u.info(`A newer CLI is available: ${t} \u2192 ${n}.`),u.message("You're running via npx, which caches the package \u2014 an unpinned spec won't auto-upgrade."),u.message(`Run the latest now by pinning the tag: npx -y ${Re}@latest <cmd>`),u.message(`Still on ${t} after that? Clear the npx cache: rm -rf ~/.npm/_npx`),u.message(`Or install globally once so \`seoagent <cmd>\` + \`update-cli\` work in place: npm install -g ${Re}@latest`);return}if(e.dryRun){u.info(`(dry-run) would run: npm install -g ${Re}@latest`);return}if(u.info(`Updating CLI: ${t} \u2192 ${n}\u2026`),!await $f()){u.error(`Update failed. Try manually: npm install -g ${Re}@latest (you may need sudo on Linux/macOS).`);return}if(u.success(`Updated to ${n}.`),e.projects===!1){u.info("Skipped project refresh (--no-projects). Each project updates itself on its next sync.");return}u.info("Refreshing your other projects to the new version\u2026"),await kf()||u.info("Could not run the project sweep automatically \u2014 run `seoagent refresh --all` to update all projects now (otherwise each updates itself the next time you open it in Claude Code).")}function kf(){return new Promise(e=>{try{let t=ca("seoagent",["refresh","--all"],{stdio:"inherit"});t.on("error",()=>e(!1)),t.on("exit",n=>e(n===0))}catch{e(!1)}})}async function vf(e=fetch){try{let t=await e(ff,{headers:{Accept:"application/json"}});if(!t.ok)return null;let n=await t.json();return typeof n.version=="string"?n.version:null}catch{return null}}function $f(){return new Promise(e=>{let t=ca("npm",["install","-g",`${Re}@latest`],{stdio:"inherit"});t.on("error",()=>e(!1)),t.on("exit",n=>e(n===0))})}import{exec as xf}from"child_process";import{randomBytes as Sf}from"crypto";import{isCancel as _f,spinner as ma,text as Cf}from"@clack/prompts";import ce from"picocolors";function ua(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 pa=/^sa_[0-9a-f]{24}$/;function da(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 fa(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:da(n.error)}:{status:"error",terminal:!1,message:`HTTP ${e} (server error)`};if(e>=400)return{status:"error",terminal:!0,message:da(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 Rf=1500,Ef=300*1e3,Af=3,Pf=2e3;function If(e){let t=process.platform==="darwin"?`open "${e}"`:process.platform==="win32"?`start "" "${e}"`:`xdg-open "${e}"`;xf(t,()=>{})}function Tf(){return Sf(16).toString("hex")}function Of(e,t,n){let r=new URLSearchParams({session:t});return n&&n!=="unknown"&&r.set("domain",n),`${e}/cli/auth?${r.toString()}`}function ga(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 Lf(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 Nf(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 ua(r.status,o)}catch(n){return{status:"error",terminal:!1,message:n.message||"network error"}}}function ha(e){return new Promise(t=>setTimeout(t,e))}async function ya(e,t,n,r){let o=D(),i=await Lf(o,e.website_token,c=>Me({apiBase:c.api_base,authHeader:`Bearer ${c.user_token}:${c.website_token}`,localDomain:""}));i&&o&&Vr(o,i),Vr({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 ${ce.bold(e.email)} (${ce.cyan(e.domain)}).`:e.email?`Logged in as ${ce.bold(e.email)}.`:"Logged in.";x(r,"success",s,ce.green(a)),r&&u.info("Future SEO work in this repo will sync to your dashboard.");let l=Ue().filter(c=>c!==Fe(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.`)}async function jf(e,t,n){try{let r=await fetch(`${e}/api/cli/auth/redeem`,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify({code:t,...n&&n!=="unknown"?{domain:n}:{}}),signal:AbortSignal.timeout(1e4)}),o=null;try{o=await r.json()}catch{}return fa(r.status,o)}catch(r){return{status:"error",terminal:!1,message:r.message||"network error"}}}async function Df(e,t,n){if(!pa.test(e)){u.warn("That does not look like a setup code (expected `sa_` + 24 hex characters). Generate one at seoagent.com/grok.");return}let o=!!process.stdout.isTTY?ma():null;o?.start(ce.dim("Redeeming setup code\u2026"));let i="network error";for(let s=0;s<Af;s++){s>0&&await ha(Pf);let a=await jf(t,e,n);if(a.status==="ready"){await ya(a,t,n,o);return}if(a.terminal){x(o,"warn",a.message,ce.red(a.message));return}i=a.message}x(o,"warn",`Could not reach the server (${i}). Check your connection and run \`${m("login")} --code <code>\` again.`,ce.yellow("Could not reach the server.")),o&&u.warn(`Run \`${m("login")} --code <code>\` again to retry.`)}async function rn(e={}){let t=process.cwd(),n=_(t),r=(e.apiBase||N.BASE||Mt).replace(/\/$/,""),o=n?.domain??null;if(o==="unknown"&&process.stdin.isTTY){let g=await Cf({message:".seoagent/project.md has `domain: unknown`. What is your site's URL?",placeholder:"example.com",validate(h){if(!String(h??"").trim())return"A site URL is required.";if(!ga(String(h)))return"Please enter a valid domain like `example.com`."}});if(_f(g)){u.warn("Login cancelled.");return}let y=ga(String(g));if(y){o=y;try{J(t,{domain:y}),u.success(`Saved domain: ${y} \u2192 .seoagent/project.md`)}catch{}}}if(o&&o!=="unknown"?ns(o):!!D()){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){await Df(s,r,o);return}let a=Tf(),l=Of(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}`),If(l),u.info('In your browser: sign in (if needed) and click "Connect this CLI" to finish.');let d=!!process.stdout.isTTY?ma():null;d?.start(ce.dim("Waiting for browser confirmation\u2026"));let f=Date.now()+Ef,p=!0;for(;Date.now()<f;){p||await ha(Rf),p=!1;let g=await Nf(r,a);if(g.status==="ready"){await ya(g,r,o,d);return}if(g.status==="expired"){x(d,"warn",`Session expired. Run \`${m("login")}\` again.`,ce.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.`,ce.red(`Login failed: ${g.message}.`)),d&&u.warn(`Run \`${m("login")}\` again.`);return}continue}}x(d,"warn",`Login timed out. Run \`${m("login")}\` again to retry.`,ce.yellow("Login timed out.")),d&&u.warn(`Run \`${m("login")}\` again to retry.`)}function on(e={}){let t=e.all?null:_(process.cwd()),n=!e.all&&t?.domain&&t.domain!=="unknown"?t.domain:null;if(!rs(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=Ue();o.length>0&&u.info(`Still logged in to: ${o.join(", ")}. Run \`${m("logout")} --all\` to log out of everything.`)}import{spinner as Ah}from"@clack/prompts";import H from"picocolors";import{existsSync as Do,mkdirSync as Tm,readFileSync as Rl,readdirSync as Om,writeFileSync as Lm}from"fs";import{join as Fo,relative as Nm,sep as jm}from"path";import{existsSync as Ff,mkdirSync as Mf,readdirSync as ba,readFileSync as bo,writeFileSync as wa}from"fs";import{dirname as Uf,join as Ct,relative as Bf}from"path";var ue=e=>typeof e=="string"&&e?e:void 0;function ko(e,t){let n=Ct(e,t),r;try{r=ba(n)}catch{return[]}let o=[];for(let i of r){if(!i.endsWith(".md")&&!i.endsWith(".mdx")||i.startsWith("_")||i.startsWith("."))continue;let s=Ct(n,i),a;try{a=le(bo(s,"utf-8")).data}catch{continue}let l=ue(a.slug)??i.replace(/\.mdx?$/,"");o.push({slug:l,title:ue(a.title)??l,draft:a.draft===!0,page_type:ue(a.page_type)??ue(a.type),path:Bf(e,s)})}return o}function sn(e){let t;try{t=ba(e)}catch{return[]}let n=[];for(let r of t){if(!r.endsWith(".md"))continue;let o=Ct(e,r),i;try{i=le(bo(o,"utf-8")).data}catch{continue}n.push({slug:ue(i.slug)??r.replace(/\.md$/,""),source:ue(i.source)??"external",repo_path:ue(i.repo_path)??null,status:ue(i.status)??null,file:o})}return n}function vo(e,t,n){let r=new Set(t.map(l=>l.slug)),o=e.filter(l=>!l.draft&&!r.has(l.slug)),i=new Map(e.filter(l=>!l.draft).map(l=>[l.slug,l])),s=[];for(let l of t){if((l.status??"")==="published")continue;let c=i.get(l.slug);c&&s.push({pointer:l,article:c})}let a=t.filter(l=>l.source==="repo"&&l.repo_path!==null&&!n(l.repo_path));return{toTrack:o,toPublish:s,stale:a,alreadyTracked:t.length-a.length}}function $o(e){return t=>Ff(Ct(e,t))}function sr(e){return e.trim().replace(/^.*\/([^/]+)$/,"$1").replace(/\.[a-z0-9]+$/i,"").toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")}function ka(e){return e.split("-").filter(Boolean).map(t=>t.charAt(0).toUpperCase()+t.slice(1)).join(" ")}function Rt(e,t){if(t.url&&t.url.trim())return t.url.trim().replace(/\/+$/,"");if(!t.domain||t.domain==="unknown")return null;let n=t.domain.replace(/^https?:\/\//,"").replace(/\/$/,""),r=(t.blogPath||"").replace(/\/+$/,"");return`https://${n}${r}/${e}`}var wo="This is a pointer so the cloud dashboard can track it without duplicating";function va(e){if(!e.includes(wo))return null;let t;try{t=le(e)}catch{return null}return!ue(t.data.slug)||!t.body.includes(wo)?null:{status:ue(t.data.status)??null,repo_path:ue(t.data.repo_path)??null}}function an(e){let t=["---",`slug: ${e.slug}`,`title: ${JSON.stringify(e.title)}`];e.page_type&&t.push(`page_type: ${e.page_type}`),e.canonical&&t.push(`canonical: ${e.canonical}`),t.push(`status: ${e.status}`),t.push(`source: ${e.source}`),e.repo_path&&t.push(`repo_path: ${e.repo_path}`),e.brief&&t.push(`brief: ${JSON.stringify(e.brief)}`),e.supersedes&&t.push(`supersedes: ${e.supersedes}`),e.primary_keyword&&t.push(`primary_keyword: ${JSON.stringify(e.primary_keyword)}`),t.push(`tracked_at: ${e.tracked_at}`),t.push("---");let n=e.repo_path?`rendered from \`${e.repo_path}\` (the source of truth for its body)`:"rendered from its source (repo or CMS)",r=e.canonical?`
|
|
94
94
|
|
|
95
95
|
Live: ${e.canonical}`:"",o=[`# ${e.title}`,"",`Tracked record \u2014 this article is **${e.status}** and ${n}.`,wo,"the body. Edit the source file to change the article."+r,""].join(`
|
|
96
96
|
`);return`${t.join(`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seoagent-official/seoagent",
|
|
3
|
-
"version": "1.95.
|
|
3
|
+
"version": "1.95.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": {
|