@seoagent-official/seoagent 1.128.2 → 1.128.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/index.js +1 -1
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -1423,7 +1423,7 @@ ${t}
1423
1423
  `))}function _E(e){if(!e||typeof e!="object")return;let t=e.type;if(t==="text"){let n=e.text;typeof n=="string"&&n&&process.stdout.write(Mo(n));return}if(t==="tool_use"){let n=String(e.name??"tool"),r=e.input,o=n==="WebSearch"?String(r?.query??""):n==="WebFetch"?String(r?.url??""):"";process.stdout.write(Y.dim(` ${Y.magenta("\u{1F50E}")} ${n}${o?` ${vE(o,70)}`:""}
1424
1424
  `))}}function vE(e,t){return e.length<=t?e:e.slice(0,t-1)+"\u2026"}function $E(e){return typeof e!="string"?[]:e.split(",").map(t=>t.trim()).filter(Boolean)}function SE(e){try{if(!bE(e))return null;let t=wE(e).filter(o=>o.endsWith(".json")).sort();if(t.length===0)return null;let n=t[t.length-1],r=JSON.parse(yE(ng(e,n),"utf-8"));return r&&typeof r=="object"&&Array.isArray(r.results)&&typeof r.generated_at=="string"?r:null}catch{return null}}import hn from"picocolors";async function gr(e={}){if(e.all){await RE();return}let t=process.cwd();if(!V(t))return;$t(t);let n=Ms(t);rg(t);let r=await zn(t,{enrichSitemap:!0,crawl:e.crawl});if(n.reason==="refreshed"?(d.success(n.from&&n.from!==n.to?`Refreshed the SEOAgent skill ${n.from} \u2192 ${n.to}.`:`Refreshed the SEOAgent skill (now ${n.to}).`),d.info("Your `.seoagent/` knowledge was left untouched.")):(d.error(`Could not refresh the skill (${n.reason}).`),process.exitCode=1),r.added>0?d.success(`Re-scanned the codebase: +${r.added} new page${r.added===1?"":"s"} merged into .seoagent/${r.file} (${r.total} total).`):r.total>0&&d.info(`Page inventory already current (${r.total} pages).`),r.crawl){let{crawled:o,empty:i,errors:s}=r.crawl;d.success(`Crawled ${o} page${o===1?"":"s"} \u2192 filled Status / Rendered / Word count`+(i>0?`, ${i} render empty`:"")+(s>0?`, ${s} unreachable`:"")+".")}}async function RE(){let e=jl();if(e.length===0){d.info("No SEOAgent projects are registered on this machine yet. They register themselves on `init` / `sync`.");return}d.message(`Refreshing ${e.length} project${e.length===1?"":"s"} to SEOAgent ${hn.bold(L)}\u2026`);let t=0,n=0;for(let r of e){let o=Ms(r);rg(r);let i=await zn(r),s=i.added>0?hn.cyan(` +${i.added} page${i.added===1?"":"s"}`):"";if(o.reason==="refreshed"){t++;let a=o.from&&o.from!==o.to?hn.dim(` (${o.from} \u2192 ${o.to})`):"";d.message(` ${hn.green("\u2713")} ${r}${a}${s}`)}else n++,d.message(` ${hn.yellow("!")} ${r} ${hn.dim("\u2014 "+o.reason)}${s}`)}d.success(`Refreshed ${t}/${e.length} project${e.length===1?"":"s"} to ${L}.`),n>0&&d.info(`${n} could not be refreshed (see above).`)}function rg(e){try{vt(e)}catch{}}import{spinner as ja,confirm as EE,isCancel as CE}from"@clack/prompts";import A from"picocolors";var AE=11e4,ig={easy_win:"easy win",striking_distance:"striking distance",competitor_gap:"competitor gap",defend:"defend",low_priority:"low priority"};async function TE(e){let t=process.cwd(),n=Ae(t);if(!n){d.error(`No .seoagent/project.md found. Run \`${h("init")}\` first.`),process.exitCode=1;return}let r=T(t),o=r?.domain&&r.domain!=="unknown"?r.domain:void 0,i=(e.apiBase||G.BASE).replace(/\/$/,""),a=!!process.stdout.isTTY&&!e.json?ja():null;a?.start(`Looking up ${A.bold(`"${e.keyword}"`)} ${A.dim("via DataForSEO (free peek)\u2026")}`);let l;try{l=await M(`${i}/api/cli/keywords/peek`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({install_id:n,keyword:e.keyword,...o?{domain:o}:{}})})}catch(u){P(a,"error",`Network error: ${u.message}`,A.red("Network error.")),process.exitCode=1;return}let c=null;try{c=await l.json()}catch{c=null}if(e.json){process.stdout.write(JSON.stringify({status:c?.status??"error",http_status:l.status,body:c??null})+`
1425
1425
  `);return}if(l.status===429||c?.skipped?.startsWith("rate-limited")){let u=c?.skipped==="rate-limited:ip"?"this network":"this install";P(a,"warn",`Daily peek quota reached for ${u}.`,A.yellow(`Daily peek quota reached for ${u}.`)),d.message(og()),process.exitCode=1;return}if(!l.ok||!c){P(a,"error",`Peek failed (${l.status})${c?.error?`: ${c.error}`:""}`,A.red(`Peek failed (${l.status}).`)),process.exitCode=1;return}if(c.skipped==="provider-quota"){P(a,"warn","Real keyword data is temporarily unavailable. Try again later.",A.yellow("Real keyword data is temporarily unavailable. Try again later.")),d.message(A.dim("No action needed on your side \u2014 the maintainer has been notified."));return}if(c.skipped==="no-creds"||c.skipped==="no-table"){P(a,"warn","Real keyword data isn\u2019t enabled on the server yet \u2014 try again later.",A.yellow("Real keyword data is not enabled on the server yet."));return}if(!c.opportunity){P(a,"warn",`No data returned for "${e.keyword}".`,A.yellow(`No data returned for "${e.keyword}".`));return}a?.stop(A.green("Got it.")),d.message(PE(c.opportunity)),c.remaining&&d.message(`${A.dim("Peeks remaining today:")} ${A.bold(String(c.remaining.install))} ${A.dim("\u2014 "+og())}`)}function PE(e){let n=c=>A.dim(c.padEnd(14)),r=e.volume!=null?A.bold(String(e.volume)):A.dim("n/a"),o=e.difficulty!=null?`${A.bold(String(e.difficulty))} ${A.dim(IE(e.difficulty))}`:A.dim("n/a"),i=ig[e.opportunity]??e.opportunity,s=e.opportunity==="easy_win"?A.bold(A.green(i)):e.opportunity==="striking_distance"?A.bold(A.cyan(i)):e.opportunity==="competitor_gap"?A.bold(A.yellow(i)):A.dim(i),a=[{content:`${A.green("\u25CF")} ${A.bold(e.keyword)}`},{sep:!0},{content:`${n("Volume:")}${r} ${A.dim("/mo")}`},{content:`${n("Difficulty:")}${o}`},{content:`${n("Opportunity:")}${s}`}],l=`${A.bold(A.cyan("Keyword peek"))}`;return z(a,{width:55,title:l})}function IE(e){return e<=20?"easy":e<=40?"moderate":e<=60?"hard":"very hard"}function og(e=K()){return e?`You're logged in \u2014 run \`${h("keywords")}\` for full enrichment (no quota). Pro unlocks the full inventory beyond the ~25 cap plus \`${h("keywords --discover")}\` (new targets) + \`${h("keywords --competitors")}\` (competitor gaps): ${h("upgrade")}.`:`Log in for the full top-25 enrichment, no quota: ${h("login")}`}function Da(e){let t=e.filter(n=>n.opportunity!=="low_priority");if(t.length!==0){d.message("Top opportunities:");for(let n of t){let r=n.volume!=null?`vol ${n.volume}`:"vol n/a",o=n.difficulty!=null?`KD ${n.difficulty}`:"KD n/a",i=n.our_position!=null?`, you're #${n.our_position}`:", not ranking";d.message(` \u2022 [${ig[n.opportunity]??n.opportunity}] ${n.keyword} \u2014 ${r}, ${o}${i}`)}}}function OE(e){if(e.length!==0){d.message("Top competitor gaps:");for(let t of e){let n=t.volume!=null?`vol ${t.volume}`:"vol n/a",r=t.difficulty!=null?`KD ${t.difficulty}`:"KD n/a",o=t.their_position!=null?`#${t.their_position}`:"top 10";d.message(` \u2022 ${t.keyword} \u2014 ${t.competitor_domain} ranks ${o}, ${n}, ${r}`)}}}async function LE(e){let t=e.all?"all":"suggested";if(t==="all"&&process.stdin.isTTY){let a=await EE({message:"Delete ALL keywords for this site from the cloud? This cannot be undone."});if(CE(a)||!a){d.info("No changes.");return}}let n=e.apiBase||e.auth.api_base||G.BASE,r=process.stdout.isTTY?ja():null;r?.start(t==="all"?"Purging all keywords\u2026":"Purging suggested keywords\u2026");let o;try{o=await M(`${n}/api/cli/keywords/purge`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e.auth.user_token}:${e.auth.website_token}`},body:JSON.stringify({scope:t})})}catch(a){P(r,"error",`Network error: ${a.message}`,A.red("Network error.")),process.exitCode=1;return}let i=null;try{i=await o.json()}catch{i=null}if(i?.skipped==="no-table"||i?.skipped==="no-columns"){P(r,"warn","Keyword storage isn\u2019t enabled on the server yet.",A.yellow("Nothing to purge yet."));return}if(!o.ok||i?.status!=="ok"){P(r,"error",`Purge failed (${o.status})${i?.error?`: ${i.error}`:""}`,A.red(`Purge failed (${o.status}).`)),process.exitCode=1;return}let s=i.purged??0;P(r,"success",`Purged ${s} ${t==="all"?"":"suggested "}keyword${s===1?"":"s"}.`,A.green(`Purged ${s} keyword${s===1?"":"s"}.`)),d.message(A.dim(t==="all"?"Cleared the cloud inventory. Re-run `seoagent keywords --seed` / strategy to rebuild it.":"Cleared discovery noise. Your clustered keywords were kept."))}async function ht(e={}){if(e.peek&&e.peek.trim().length>0){await TE({keyword:e.peek.trim(),apiBase:e.apiBase,json:e.json});return}let t=T(process.cwd()),n=K(t?.domain);if(!n){d.error(`Not logged in. Run \`${h("login")}\` first.`),process.exitCode=1;return}if(e.purge){await LE({all:e.all,apiBase:e.apiBase,auth:n});return}let r=e.apiBase||n.api_base||G.BASE,o=`Bearer ${n.user_token}:${n.website_token}`,i=e.competitors?"competitors":e.discover?"discover":e.seed?"seed":"enrich",s=i==="competitors"?"/api/cli/keywords/competitors":i==="discover"?"/api/cli/keywords/discover":i==="seed"?"/api/cli/keywords/seed":"/api/cli/keywords/enrich",a=i==="competitors"?"Finding keywords your competitors rank for\u2026":i==="discover"?"Discovering new keywords to target\u2026":i==="seed"?"Seeding keywords from your Search Console data\u2026":"Enriching keywords with real volume + difficulty\u2026",c=!!process.stdout.isTTY?ja():null;c?.start(a);let u=i==="competitors"||i==="discover"||i==="seed",p;try{p=await M(`${r}${s}`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:o},body:"{}"},u?{timeoutMs:AE,attempts:1}:{})}catch(m){P(c,"error",`Network error: ${m.message}`,A.red("Network error.")),process.exitCode=1;return}let g=null;try{g=await p.json()}catch{g=null}if(p.status===402||g?.code==="upgrade_required"){P(c,"warn",i==="competitors"?"Competitor gap analysis is a paid feature.":"Keyword discovery is a paid feature.",A.yellow("Paid feature \u2014 upgrade to continue.")),d.message(i==="competitors"?`Upgrade to see which keywords competitors rank for that you don't: ${A.cyan(Ce("cli-keywords-competitors"))}`:`Upgrade to find new keywords to target: ${A.cyan(Ce("cli-keywords-discover"))}`),process.exitCode=1;return}if(!p.ok||g?.status!=="ok"){P(c,"error",`Server rejected request (${p.status})${g?.error?`: ${g.error}`:""}`,A.red(`Server rejected request (${p.status}).`)),process.exitCode=1;return}if(g.skipped==="no-gsc-data"){P(c,"warn","No Search Console data to seed from yet. Run `seoagent gsc connect` to link Google Search Console \u2014 or wait for impressions to accrue on a new site.",A.yellow("No Search Console data yet."));return}if(g.skipped==="no-keywords"||g.skipped==="no-seeds"){P(c,"warn","No keywords yet. Seed from Search Console with `seoagent keywords --seed`, or run the keyword strategy (Phase 2) first, then try again.",A.yellow("No keywords yet."));return}if(g.skipped==="no-competitors"){P(c,"warn","No competitors found yet. Add competitors or run strategy discovery first, then try again.",A.yellow("No competitors yet."));return}if(g.skipped==="no-creds"||g.skipped==="no-columns"||g.skipped==="no-table"){P(c,"warn","Real keyword data isn\u2019t enabled on the server yet \u2014 try again later.",A.yellow("Real keyword data is not enabled on the server yet."));return}if(g.skipped){P(c,"warn",`Skipped: ${g.skipped}`,A.yellow(`Skipped: ${g.skipped}`));return}if(i==="competitors"){let m=g.gaps??0;P(c,"success",`Found ${m} competitor gap${m===1?"":"s"} across ${g.competitors?.length??0} competitor${g.competitors?.length===1?"":"s"} (added as "suggested").`,A.green(`${m} competitor gap${m===1?"":"s"} found.`)),OE(g.topGaps??[]),d.message(`Review them in ${A.cyan(".seoagent/keywords.md")} (a read-only projection \u2014 don't hand-edit it). Keep the on-topic ones for your clusters; clear off-topic noise with \`${h("keywords --purge")}\`. The analyzed competitors are saved to your dashboard's Competitors section.`);return}if(i==="discover"){let m=NE(g);P(c,"success",m.headline,A.green(m.short)),Da(g.opportunities??[]),d.message(m.hint(h));return}if(i==="seed"){let m=g.seeded??0;P(c,"success",`Seeded ${m} keyword${m===1?"":"s"} from Search Console (real impressed queries, added as "suggested").`,A.green(`${m} keyword${m===1?"":"s"} seeded from Search Console.`)),Da(g.opportunities??[]),d.message(`These are queries your site already gets impressions for \u2014 striking-distance wins first. Review in ${A.cyan(".seoagent/keywords.md")} and build clusters from them.`);return}let f=g.enriched??0;P(c,"success",`Enriched ${f} keyword${f===1?"":"s"}.`,A.green(`Enriched ${f} keyword${f===1?"":"s"}.`)),Da(g.opportunities??[]),g.paid||d.message(`${A.dim("Free plan:")} enriched up to ${A.bold(String(g.cap??25))} keywords. ${A.dim("Upgrade for full enrichment + discovery + competitor gap:")} ${A.cyan(Ce("cli-keywords-cap"))}`)}function NE(e){let t=e.discovered??0,n=e.ignored??0,r=(s,a)=>`${s} ${a}${s===1?"":"s"}`,o="Review them in .seoagent/keywords.md (a read-only projection \u2014 don't hand-edit it).";if(e.judgePending)return{headline:`Discovered ${r(t,"new keyword")} to target (added as "suggested", not yet relevance-judged).`,short:`${r(t,"new keyword")} discovered (unjudged).`,hint:s=>`${o} The relevance judge could not run for this site (it needs business context in .seoagent/context.md), so relevance-check before adopting; clear off-topic noise with \`${s("keywords --purge")}\`.`};if(t===0&&n>0)return{headline:`Found ${r(n,"candidate")}; the relevance judge ruled every one off-topic for this site, so nothing was added to target.`,short:`0 new keywords kept (${n} judged off-topic).`,hint:()=>"Nothing to review. Off-topic verdicts are stored, so the same junk is not proposed again."};let i=n>0?` Hid ${r(n,"off-topic candidate")} (judge verdict: ignore).`:"";return{headline:`Discovered ${r(t,"new keyword")} to target (judged relevant to your business, added as "suggested").${i}`,short:`${r(t,"new keyword")} discovered${n>0?` (${n} hidden as off-topic)`:""}.`,hint:s=>`${o} Keep the ones that fit your clusters; \`${s("keywords --purge")}\` clears anything the judge missed.`}}import te from"picocolors";function hr(e={}){let t=process.cwd(),n=T(t),r=lt(t);if(e.json){process.stdout.write(JSON.stringify(r,null,2)+`
1426
- `);return}if(d.message(DE(r)),!n){d.warn(`No SEOAgent project here. Run \`${h("init")}\` first to persist the provider.`);return}Q(t,{image_provider:r.provider}),d.info(`Saved to ${te.cyan(".seoagent/project.md")} ${te.dim(`(image_provider: ${r.provider})`)}`)}function DE(e){let n=i=>te.dim(i.padEnd(12)),r=[];if(e.provider==="none"){r.push({content:`${te.dim("\u25CB")} ${te.bold(te.yellow("No image provider detected"))}`}),r.push({sep:!0}),r.push({content:te.dim("Set ONE of these to enable image generation:")});let i=Vl();for(let s of Object.keys(i))r.push({content:`${n(s+":")}${te.cyan(i[s].join(" or "))}`});r.push({sep:!0}),r.push({content:te.dim("Add to your shell, .env.local, or .env, then re-run:")}),r.push({content:` ${te.cyan(h("env-check"))}`})}else{r.push({content:`${te.green("\u2713")} ${te.dim("Image provider:")} ${te.bold(te.green(e.provider))}`}),r.push({sep:!0}),e.matched_key&&r.push({content:`${n("Key:")}${te.bold(e.matched_key)}`}),e.source&&r.push({content:`${n("Source:")}${te.cyan(e.source)}`});let i=e.available_providers.filter(s=>s!==e.provider);i.length>0&&r.push({content:`${n("Available:")}${te.dim(i.join(", "))}`})}let o=`${te.bold(te.cyan("SEOAgent"))} ${te.dim("\xB7")} ${te.dim("env-check")}`;return z(r,{width:64,title:o})}import{existsSync as Ma,mkdirSync as lg,writeFileSync as cg}from"fs";import{dirname as oi,isAbsolute as WE,join as ri,resolve as KE}from"path";import{tmpdir as qE}from"os";import{spinner as VE}from"@clack/prompts";import se from"picocolors";import{Buffer as sg}from"buffer";var jE=6e4,Fa=18e4,FE=15e3;async function Ua(e){let t=await fetch(e,{signal:AbortSignal.timeout(jE)});if(!t.ok)throw new Error(`Image download failed: ${t.status} ${t.statusText}`);let n=await t.arrayBuffer();return sg.from(n)}function UE(e=process.env){let t=(e.OPENAI_BASE_URL||"").trim().replace(/\/+$/,"");return`${/^https?:\/\//.test(t)?t:"https://api.openai.com/v1"}/images/generations`}async function ME(e){let t=e.size??"1024x1024",n=await fetch(UE(),{method:"POST",headers:{Authorization:`Bearer ${e.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify({model:"gpt-image-1",prompt:e.prompt,size:t,n:1}),signal:AbortSignal.timeout(Fa)});if(!n.ok){let i=await n.text().catch(()=>"");throw new Error(`OpenAI image API ${n.status}: ${i.slice(0,300)}`)}let o=(await n.json()).data?.[0];if(!o)throw new Error("OpenAI image API returned no data");if(o.b64_json){let i=sg.from(o.b64_json,"base64");return{bytes:i,contentType:i[0]===255&&i[1]===216?"image/jpeg":"image/png"}}if(o.url)return{bytes:await Ua(o.url),contentType:"image/png"};throw new Error("OpenAI image API returned neither b64_json nor url")}async function BE(e){let t=process.env.FAL_MODEL||"fal-ai/flux/schnell",n=await fetch(`https://fal.run/${t}`,{method:"POST",headers:{Authorization:`Key ${e.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify({prompt:e.prompt,image_size:"landscape_16_9",num_images:1}),signal:AbortSignal.timeout(Fa)});if(!n.ok){let s=await n.text().catch(()=>"");throw new Error(`fal.ai API ${n.status}: ${s.slice(0,300)}`)}let o=(await n.json()).images?.[0];if(!o?.url)throw new Error("fal.ai API returned no image url");return{bytes:await Ua(o.url),contentType:o.content_type??"image/png"}}async function HE(e,t,n=9e4){let r=Date.now(),o=1e3;for(;Date.now()-r<n;){let i=await fetch(`https://api.replicate.com/v1/predictions/${e}`,{headers:{Authorization:`Token ${t}`},signal:AbortSignal.timeout(FE)});if(!i.ok)throw new Error(`Replicate poll failed: ${i.status}`);let s=await i.json();if(s.status==="succeeded"||s.status==="failed"||s.status==="canceled")return s;await new Promise(a=>setTimeout(a,o)),o=Math.min(o*1.5,5e3)}throw new Error("Replicate prediction timed out")}async function GE(e){let t=process.env.REPLICATE_MODEL||"black-forest-labs/flux-schnell",n=await fetch(`https://api.replicate.com/v1/models/${t}/predictions`,{method:"POST",headers:{Authorization:`Token ${e.apiKey}`,"Content-Type":"application/json",Prefer:"wait"},body:JSON.stringify({input:{prompt:e.prompt,aspect_ratio:"16:9"}}),signal:AbortSignal.timeout(Fa)});if(!n.ok){let s=await n.text().catch(()=>"");throw new Error(`Replicate API ${n.status}: ${s.slice(0,300)}`)}let r=await n.json();if(r.status!=="succeeded"&&r.status!=="failed"&&(r=await HE(r.id,e.apiKey)),r.status!=="succeeded")throw new Error(`Replicate prediction ${r.status}: ${r.error??""}`);let o=Array.isArray(r.output)?r.output[0]:r.output;if(!o)throw new Error("Replicate prediction returned no output");return{bytes:await Ua(o),contentType:"image/png",providerImageId:r.id}}async function ag(e,t){switch(e){case"openai":return ME(t);case"fal":return BE(t);case"replicate":return GE(t);default:throw new Error(`Unknown image provider: ${e}`)}}function YE(e){return e==="openai"||e==="fal"||e==="replicate"}async function mr(e={}){let t=process.cwd();if(!e.prompt){d.warn(`Missing --prompt. Example: ${h('generate-image --prompt "..." --out content/images/hero.png')}`),process.exitCode=1;return}if(!e.out){d.warn("Missing --out. Example: --out .seoagent/content/images/hero.png"),process.exitCode=1;return}let n=T(t),r=null;if(e.provider){if(!YE(e.provider)){d.warn(`Invalid --provider "${e.provider}". Use one of: openai, fal, replicate.`),process.exitCode=1;return}r=e.provider}else n?.image_provider&&n.image_provider!=="none"?r=n.image_provider:r=lt(t).provider;if(!r||r==="none"){d.warn(`No image generation provider available. Set OPENAI_API_KEY, FAL_KEY, or REPLICATE_API_TOKEN, then run \`${h("env-check")}\`.`),process.exitCode=1;return}let o=ql(t,r);if(!o){d.warn(`Provider "${r}" selected but no API key found. Set the env var, then re-run.`),process.exitCode=1;return}let i=WE(e.out)?e.out:KE(t,e.out),a=!e.silent&&!!process.stdout.isTTY?VE():null;a?.start(`Generating image with ${se.bold(r)} ${se.dim(`(${o.envName})`)}\u2026`);try{let l=await ag(r,{prompt:e.prompt,apiKey:o.key,size:e.size});Ma(oi(i))||lg(oi(i),{recursive:!0}),cg(i,l.bytes),e.silent||P(a,"success",`Wrote ${i} (${l.bytes.length} bytes, ${l.contentType}).`,se.green(`Wrote ${i} ${se.dim(`(${l.bytes.length} bytes, ${l.contentType})`)}`))}catch(l){a?.stop(se.red("Image generation failed."));let c=JE(t,{provider:r,envName:o.envName,prompt:e.prompt,size:e.size,outPath:i,error:l});d.message(zE({provider:r,envName:o.envName,prompt:e.prompt,size:e.size,message:l.message})),c&&d.message(`${se.dim("Full error log:")} ${se.cyan(c)}`),process.exitCode=1}}function zE(e){let n=u=>se.dim(u.padEnd(10)),o=e.prompt.length>54?`${e.prompt.slice(0,53)}\u2026`:e.prompt,i=[{content:`${se.red("\u2717")} ${se.bold(se.red("Image generation failed"))}`},{sep:!0},{content:`${n("Provider:")}${se.bold(e.provider)}`},{content:`${n("Key:")}${e.envName}`},{content:`${n("Prompt:")}${se.dim(`"${o}"`)}`}];e.size&&i.push({content:`${n("Size:")}${e.size}`}),i.push({sep:!0});let s=e.message.replace(/\s+/g," ").trim(),a=64,l=s.length>a?`${s.slice(0,a-1)}\u2026`:s;i.push({content:se.red(l)});let c=`${se.bold(se.cyan("SEOAgent"))} ${se.dim("\xB7")} ${se.dim("generate-image")}`;return z(i,{width:70,title:c})}function JE(e,t){let r=`seoagent-generate-image-${new Date().toISOString().replace(/[:.]/g,"-")}.log`,o=ri(e,_),i=ri(o,".errors"),s=Ma(o)?ri(i,r):ri(qE(),r),a=["# SEOAgent generate-image error",`# ${new Date().toISOString()}`,"","## Request",`provider: ${t.provider}`,`env_var: ${t.envName}`,`prompt: ${JSON.stringify(t.prompt)}`,`size: ${t.size??"(default)"}`,`out_path: ${t.outPath}`,`cwd: ${e}`,"","## Error",`message: ${t.error.message}`,"","## Stack",t.error.stack??"(no stack)",""];t.error.cause&&a.push("## Cause",String(t.error.cause),"");try{return Ma(oi(s))||lg(oi(s),{recursive:!0}),cg(s,a.join(`
1426
+ `);return}if(d.message(DE(r)),!n){d.warn(`No SEOAgent project here. Run \`${h("init")}\` first to persist the provider.`);return}Q(t,{image_provider:r.provider}),d.info(`Saved to ${te.cyan(".seoagent/project.md")} ${te.dim(`(image_provider: ${r.provider})`)}`)}function DE(e){let n=i=>te.dim(i.padEnd(12)),r=[];if(e.provider==="none"){r.push({content:`${te.dim("\u25CB")} ${te.bold(te.yellow("No image provider detected"))}`}),r.push({sep:!0}),r.push({content:te.dim("Set ONE of these to enable image generation:")});let i=Vl();for(let s of Object.keys(i))r.push({content:`${n(s+":")}${te.cyan(i[s].join(" or "))}`});r.push({sep:!0}),r.push({content:te.dim("Add to your shell, .env.local, or .env, then re-run:")}),r.push({content:` ${te.cyan(h("env-check"))}`})}else{r.push({content:`${te.green("\u2713")} ${te.dim("Image provider:")} ${te.bold(te.green(e.provider))}`}),r.push({sep:!0}),e.matched_key&&r.push({content:`${n("Key:")}${te.bold(e.matched_key)}`}),e.source&&r.push({content:`${n("Source:")}${te.cyan(e.source)}`});let i=e.available_providers.filter(s=>s!==e.provider);i.length>0&&r.push({content:`${n("Available:")}${te.dim(i.join(", "))}`})}let o=`${te.bold(te.cyan("SEOAgent"))} ${te.dim("\xB7")} ${te.dim("env-check")}`;return z(r,{width:64,title:o})}import{existsSync as Ma,mkdirSync as lg,writeFileSync as cg}from"fs";import{dirname as oi,isAbsolute as WE,join as ri,resolve as KE}from"path";import{tmpdir as qE}from"os";import{spinner as VE}from"@clack/prompts";import se from"picocolors";import{Buffer as sg}from"buffer";var jE=6e4,Fa=18e4,FE=15e3;async function Ua(e){let t=await fetch(e,{signal:AbortSignal.timeout(jE)});if(!t.ok)throw new Error(`Image download failed: ${t.status} ${t.statusText}`);let n=await t.arrayBuffer();return sg.from(n)}function UE(e=process.env){let t=(e.OPENAI_BASE_URL||"").trim().replace(/\/+$/,"");return`${/^https?:\/\//.test(t)?t:"https://api.openai.com/v1"}/images/generations`}async function ME(e){let t=e.size??"1024x1024",n=await fetch(UE(),{method:"POST",headers:{Authorization:`Bearer ${e.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify({model:"gpt-image-2",prompt:e.prompt,size:t,n:1}),signal:AbortSignal.timeout(Fa)});if(!n.ok){let i=await n.text().catch(()=>"");throw new Error(`OpenAI image API ${n.status}: ${i.slice(0,300)}`)}let o=(await n.json()).data?.[0];if(!o)throw new Error("OpenAI image API returned no data");if(o.b64_json){let i=sg.from(o.b64_json,"base64");return{bytes:i,contentType:i[0]===255&&i[1]===216?"image/jpeg":"image/png"}}if(o.url)return{bytes:await Ua(o.url),contentType:"image/png"};throw new Error("OpenAI image API returned neither b64_json nor url")}async function BE(e){let t=process.env.FAL_MODEL||"fal-ai/flux/schnell",n=await fetch(`https://fal.run/${t}`,{method:"POST",headers:{Authorization:`Key ${e.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify({prompt:e.prompt,image_size:"landscape_16_9",num_images:1}),signal:AbortSignal.timeout(Fa)});if(!n.ok){let s=await n.text().catch(()=>"");throw new Error(`fal.ai API ${n.status}: ${s.slice(0,300)}`)}let o=(await n.json()).images?.[0];if(!o?.url)throw new Error("fal.ai API returned no image url");return{bytes:await Ua(o.url),contentType:o.content_type??"image/png"}}async function HE(e,t,n=9e4){let r=Date.now(),o=1e3;for(;Date.now()-r<n;){let i=await fetch(`https://api.replicate.com/v1/predictions/${e}`,{headers:{Authorization:`Token ${t}`},signal:AbortSignal.timeout(FE)});if(!i.ok)throw new Error(`Replicate poll failed: ${i.status}`);let s=await i.json();if(s.status==="succeeded"||s.status==="failed"||s.status==="canceled")return s;await new Promise(a=>setTimeout(a,o)),o=Math.min(o*1.5,5e3)}throw new Error("Replicate prediction timed out")}async function GE(e){let t=process.env.REPLICATE_MODEL||"black-forest-labs/flux-schnell",n=await fetch(`https://api.replicate.com/v1/models/${t}/predictions`,{method:"POST",headers:{Authorization:`Token ${e.apiKey}`,"Content-Type":"application/json",Prefer:"wait"},body:JSON.stringify({input:{prompt:e.prompt,aspect_ratio:"16:9"}}),signal:AbortSignal.timeout(Fa)});if(!n.ok){let s=await n.text().catch(()=>"");throw new Error(`Replicate API ${n.status}: ${s.slice(0,300)}`)}let r=await n.json();if(r.status!=="succeeded"&&r.status!=="failed"&&(r=await HE(r.id,e.apiKey)),r.status!=="succeeded")throw new Error(`Replicate prediction ${r.status}: ${r.error??""}`);let o=Array.isArray(r.output)?r.output[0]:r.output;if(!o)throw new Error("Replicate prediction returned no output");return{bytes:await Ua(o),contentType:"image/png",providerImageId:r.id}}async function ag(e,t){switch(e){case"openai":return ME(t);case"fal":return BE(t);case"replicate":return GE(t);default:throw new Error(`Unknown image provider: ${e}`)}}function YE(e){return e==="openai"||e==="fal"||e==="replicate"}async function mr(e={}){let t=process.cwd();if(!e.prompt){d.warn(`Missing --prompt. Example: ${h('generate-image --prompt "..." --out content/images/hero.png')}`),process.exitCode=1;return}if(!e.out){d.warn("Missing --out. Example: --out .seoagent/content/images/hero.png"),process.exitCode=1;return}let n=T(t),r=null;if(e.provider){if(!YE(e.provider)){d.warn(`Invalid --provider "${e.provider}". Use one of: openai, fal, replicate.`),process.exitCode=1;return}r=e.provider}else n?.image_provider&&n.image_provider!=="none"?r=n.image_provider:r=lt(t).provider;if(!r||r==="none"){d.warn(`No image generation provider available. Set OPENAI_API_KEY, FAL_KEY, or REPLICATE_API_TOKEN, then run \`${h("env-check")}\`.`),process.exitCode=1;return}let o=ql(t,r);if(!o){d.warn(`Provider "${r}" selected but no API key found. Set the env var, then re-run.`),process.exitCode=1;return}let i=WE(e.out)?e.out:KE(t,e.out),a=!e.silent&&!!process.stdout.isTTY?VE():null;a?.start(`Generating image with ${se.bold(r)} ${se.dim(`(${o.envName})`)}\u2026`);try{let l=await ag(r,{prompt:e.prompt,apiKey:o.key,size:e.size});Ma(oi(i))||lg(oi(i),{recursive:!0}),cg(i,l.bytes),e.silent||P(a,"success",`Wrote ${i} (${l.bytes.length} bytes, ${l.contentType}).`,se.green(`Wrote ${i} ${se.dim(`(${l.bytes.length} bytes, ${l.contentType})`)}`))}catch(l){a?.stop(se.red("Image generation failed."));let c=JE(t,{provider:r,envName:o.envName,prompt:e.prompt,size:e.size,outPath:i,error:l});d.message(zE({provider:r,envName:o.envName,prompt:e.prompt,size:e.size,message:l.message})),c&&d.message(`${se.dim("Full error log:")} ${se.cyan(c)}`),process.exitCode=1}}function zE(e){let n=u=>se.dim(u.padEnd(10)),o=e.prompt.length>54?`${e.prompt.slice(0,53)}\u2026`:e.prompt,i=[{content:`${se.red("\u2717")} ${se.bold(se.red("Image generation failed"))}`},{sep:!0},{content:`${n("Provider:")}${se.bold(e.provider)}`},{content:`${n("Key:")}${e.envName}`},{content:`${n("Prompt:")}${se.dim(`"${o}"`)}`}];e.size&&i.push({content:`${n("Size:")}${e.size}`}),i.push({sep:!0});let s=e.message.replace(/\s+/g," ").trim(),a=64,l=s.length>a?`${s.slice(0,a-1)}\u2026`:s;i.push({content:se.red(l)});let c=`${se.bold(se.cyan("SEOAgent"))} ${se.dim("\xB7")} ${se.dim("generate-image")}`;return z(i,{width:70,title:c})}function JE(e,t){let r=`seoagent-generate-image-${new Date().toISOString().replace(/[:.]/g,"-")}.log`,o=ri(e,_),i=ri(o,".errors"),s=Ma(o)?ri(i,r):ri(qE(),r),a=["# SEOAgent generate-image error",`# ${new Date().toISOString()}`,"","## Request",`provider: ${t.provider}`,`env_var: ${t.envName}`,`prompt: ${JSON.stringify(t.prompt)}`,`size: ${t.size??"(default)"}`,`out_path: ${t.outPath}`,`cwd: ${e}`,"","## Error",`message: ${t.error.message}`,"","## Stack",t.error.stack??"(no stack)",""];t.error.cause&&a.push("## Cause",String(t.error.cause),"");try{return Ma(oi(s))||lg(oi(s),{recursive:!0}),cg(s,a.join(`
1427
1427
  `)),s}catch{return null}}import he from"picocolors";async function yr(e={}){let t=T(process.cwd()),n=K(t?.domain);if(!n){let l=st();e.json?process.stdout.write(`${JSON.stringify({logged_in:!1,other_sites:l})}
1428
1428
  `):(d.error(`Not logged in. Run \`${h("login")}\` first.`),l.length>0&&d.info(`(Logged in to: ${l.join(", ")} \u2014 not this project's site.)`)),process.exitCode=1;return}if(e.env){process.stdout.write(`${wl(n)}
1429
1429
  `);return}let r=(e.apiBase||n.api_base||G.BASE).replace(/\/$/,""),o=`Bearer ${n.user_token}:${n.website_token}`,i;try{i=await fetch(`${r}/api/cli/whoami`,{method:"GET",headers:{Authorization:o,Accept:"application/json"},signal:AbortSignal.timeout(1e4)})}catch(l){d.error(`Network error: ${l.message}`),process.exitCode=1;return}if(i.status===404){if(e.json){process.stdout.write(`${JSON.stringify({logged_in:!0,server_supports_whoami:!1,api_base:r})}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seoagent-official/seoagent",
3
- "version": "1.128.2",
3
+ "version": "1.128.3",
4
4
  "description": "The persistent AI SEO agent for Claude Code. Audits, keyword strategy, briefs, articles, real product screenshots from your repo, and the autopilot loop (cloud detects → CLI executes → ack closes) — other SEO tools write the prompt, SEOAgent runs it.",
5
5
  "type": "module",
6
6
  "bin": {