@weactive8/8contentpro 0.1.0
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/README.md +33 -0
- package/dist/agent.js +115 -0
- package/dist/agent.js.map +1 -0
- package/dist/attach.js +144 -0
- package/dist/attach.js.map +1 -0
- package/dist/auth.js +49 -0
- package/dist/auth.js.map +1 -0
- package/dist/brand.js +110 -0
- package/dist/brand.js.map +1 -0
- package/dist/brandgen.js +76 -0
- package/dist/brandgen.js.map +1 -0
- package/dist/build-info.js +3 -0
- package/dist/build-info.js.map +1 -0
- package/dist/detect.js +163 -0
- package/dist/detect.js.map +1 -0
- package/dist/index.js +334 -0
- package/dist/index.js.map +1 -0
- package/dist/ink/App.js +733 -0
- package/dist/ink/App.js.map +1 -0
- package/dist/ink/components.js +214 -0
- package/dist/ink/components.js.map +1 -0
- package/dist/ink/run.js +9 -0
- package/dist/ink/run.js.map +1 -0
- package/dist/intel.js +110 -0
- package/dist/intel.js.map +1 -0
- package/dist/login.js +99 -0
- package/dist/login.js.map +1 -0
- package/dist/metering.js +53 -0
- package/dist/metering.js.map +1 -0
- package/dist/methodology/loader.js +68 -0
- package/dist/methodology/loader.js.map +1 -0
- package/dist/models.js +24 -0
- package/dist/models.js.map +1 -0
- package/dist/onboarding/onboard.js +102 -0
- package/dist/onboarding/onboard.js.map +1 -0
- package/dist/onboarding/schema.js +25 -0
- package/dist/onboarding/schema.js.map +1 -0
- package/dist/onboarding/setup.js +324 -0
- package/dist/onboarding/setup.js.map +1 -0
- package/dist/output/callouts.js +81 -0
- package/dist/output/callouts.js.map +1 -0
- package/dist/output/docx.js +155 -0
- package/dist/output/docx.js.map +1 -0
- package/dist/output/pdf.js +195 -0
- package/dist/output/pdf.js.map +1 -0
- package/dist/pipeline.js +140 -0
- package/dist/pipeline.js.map +1 -0
- package/dist/plan/targeting.js +165 -0
- package/dist/plan/targeting.js.map +1 -0
- package/dist/shell.js +286 -0
- package/dist/shell.js.map +1 -0
- package/dist/site/resources.js +88 -0
- package/dist/site/resources.js.map +1 -0
- package/dist/site/sitemap.js +92 -0
- package/dist/site/sitemap.js.map +1 -0
- package/dist/store.js +110 -0
- package/dist/store.js.map +1 -0
- package/dist/topics.js +50 -0
- package/dist/topics.js.map +1 -0
- package/dist/ui/spinner.js +111 -0
- package/dist/ui/spinner.js.map +1 -0
- package/dist/ui/statusbar.js +90 -0
- package/dist/ui/statusbar.js.map +1 -0
- package/dist/update.js +45 -0
- package/dist/update.js.map +1 -0
- package/package.json +47 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
2
|
+
import { createInterface } from "node:readline/promises";
|
|
3
|
+
import { stdin, stdout } from "node:process";
|
|
4
|
+
import { resolveModelAccess } from "../auth.js";
|
|
5
|
+
import { MODELS } from "../models.js";
|
|
6
|
+
import { trendSignals, keywordSignals } from "../intel.js";
|
|
7
|
+
/** Instruction for each content type, injected into the ideation prompt. */
|
|
8
|
+
const TYPE_BRIEF = {
|
|
9
|
+
mix: "Generate a deliberate MIX: one thought-leadership/contrarian POV, one educational how-to, one timely/news-reaction, and one comparison or data piece. The four must be genuinely different types from each other, not four variations of the same angle.",
|
|
10
|
+
"thought-leadership": "ALL four must be thought leadership: forward-looking industry takes, a contrarian point of view, 'why the common belief about X is wrong', predictions, or a strong original opinion. NOT news-reaction, NOT how-to, NOT deadline-driven compliance pieces.",
|
|
11
|
+
"how-to": "ALL four must be educational how-to / evergreen: practical, actionable guides that teach a repeatable process and stay relevant over time. Not tied to a news event.",
|
|
12
|
+
timely: "ALL four must be timely / news-reaction: anchored to something that genuinely just changed in the industry or a rising trend. Freshness is the point.",
|
|
13
|
+
comparison: "ALL four must be comparison / buyer-decision pieces: X vs Y, alternatives to Z, or a framework for choosing between options. These are consideration-stage.",
|
|
14
|
+
data: "ALL four must be data / research-driven: analysis of real numbers, benchmarks, or observable patterns, with a clear data-backed thesis.",
|
|
15
|
+
};
|
|
16
|
+
const FENCE = /```json\s*([\s\S]*?)```/;
|
|
17
|
+
/** Dedupe a list of {query} or string, case-insensitively, preserving order. */
|
|
18
|
+
function dedupe(items) {
|
|
19
|
+
const seen = new Set();
|
|
20
|
+
const out = [];
|
|
21
|
+
for (const it of items) {
|
|
22
|
+
const key = (typeof it === "string" ? it : it.query).toLowerCase().trim();
|
|
23
|
+
if (key && !seen.has(key)) {
|
|
24
|
+
seen.add(key);
|
|
25
|
+
out.push(it);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return out;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The ENFORCED ideation pipeline (WeActive8 Blog Protocol, ideation stage):
|
|
32
|
+
*
|
|
33
|
+
* 1. DISCOVER — fresh WebSearch (novelty) + pytrends RISING queries (direction).
|
|
34
|
+
* Trends are the LEADING indicator: where demand is forming, before it's a
|
|
35
|
+
* saturated head term. This is what keeps ideas un-repetitive.
|
|
36
|
+
* 2. GATE — the model must reject any trend that doesn't intersect the ICP.
|
|
37
|
+
* 3. ENRICH — pytrends suggest + Keyword Planner variations for UNIQUE long-tails
|
|
38
|
+
* (never anchor to the same head keyword every time).
|
|
39
|
+
* 4. DEDUP — cross-check the learned sitemap so we never re-pitch published work.
|
|
40
|
+
* 5. SHAPE — 4 distinct angles, each tied to a persona + funnel stage.
|
|
41
|
+
*
|
|
42
|
+
* Do NOT regress this to keyword/volume-led or GSC-led ideation — that saturates.
|
|
43
|
+
* See methodology/BLOG-PROTOCOL.md § Ideation and ARCHITECTURE.md.
|
|
44
|
+
*/
|
|
45
|
+
export async function ideate(profile, model, onProgress, onTokens, intent) {
|
|
46
|
+
resolveModelAccess();
|
|
47
|
+
const brand = profile ? `${profile.brand} — ${profile.summary}` : "the brand";
|
|
48
|
+
const personas = profile?.personas.map((p) => `${p.name} (${p.role})`).join("; ") ?? "";
|
|
49
|
+
// Seeds for our own intelligence: the brand's services + a summary term.
|
|
50
|
+
const seeds = [...(profile?.services?.map((s) => s.name) ?? []), profile?.summary]
|
|
51
|
+
.filter((s) => Boolean(s && s.trim()))
|
|
52
|
+
.slice(0, 3);
|
|
53
|
+
onProgress?.("Pulling live trends");
|
|
54
|
+
const trends = (await Promise.all(seeds.map((s) => trendSignals(s)))).filter((t) => Boolean(t));
|
|
55
|
+
onProgress?.("Pulling keyword variations");
|
|
56
|
+
const kws = (await Promise.all(seeds.map((s) => keywordSignals(s)))).flat();
|
|
57
|
+
const rising = dedupe(trends.flatMap((t) => t.rising)).slice(0, 12);
|
|
58
|
+
const suggestions = dedupe([...trends.flatMap((t) => t.suggestions)]).slice(0, 12);
|
|
59
|
+
const variations = dedupe(kws.map((k) => ({ query: k.keyword, value: k.volume }))).slice(0, 12);
|
|
60
|
+
const covered = (profile?.sitePages ?? [])
|
|
61
|
+
.map((pg) => pg.title || pg.url.replace(/^https?:\/\/[^/]+/, "").replace(/\/$/, ""))
|
|
62
|
+
.filter(Boolean)
|
|
63
|
+
.slice(0, 40);
|
|
64
|
+
const grounding = [
|
|
65
|
+
rising.length ? `RISING Google Trends queries in this space (LEAD here — where demand is forming): ${rising.map((r) => `"${r.query}"`).join(", ")}.` : "",
|
|
66
|
+
suggestions.length ? `Autocomplete / long-tail seeds: ${suggestions.map((s) => `"${s}"`).join(", ")}.` : "",
|
|
67
|
+
variations.length
|
|
68
|
+
? `Real keyword variations with monthly volume (use for UNIQUE long-tails, never the saturated head term): ${variations.map((v) => `"${v.query}" (${v.value}/mo)`).join(", ")}.`
|
|
69
|
+
: "",
|
|
70
|
+
covered.length ? `ALREADY PUBLISHED — do NOT propose anything that overlaps these existing pages: ${covered.join("; ")}.` : "",
|
|
71
|
+
]
|
|
72
|
+
.filter(Boolean)
|
|
73
|
+
.join("\n");
|
|
74
|
+
const sys = [
|
|
75
|
+
`You are a content strategist for ${brand}.`,
|
|
76
|
+
personas ? `Buyer personas: ${personas}.` : "",
|
|
77
|
+
intent?.type ? `REQUESTED CONTENT TYPE (this is the operator's explicit ask — honor it strictly): ${TYPE_BRIEF[intent.type]}` : "",
|
|
78
|
+
intent?.persona ? `PERSONA FOCUS: target every idea at "${intent.persona}" specifically.` : "",
|
|
79
|
+
"ENFORCED METHOD — discovery first, in this order:",
|
|
80
|
+
"1. Use fresh WebSearch to find what is genuinely NEW and being discussed right now (Reddit, LinkedIn, industry forums, news). Novelty is the goal.",
|
|
81
|
+
"2. ANCHOR each idea to the RISING trend signals below (where the world is heading), NOT to saturated head terms or what already ranks.",
|
|
82
|
+
"3. RELEVANCE GATE: ignore any trend or query that does not genuinely intersect this brand's buyers and services.",
|
|
83
|
+
"4. Make each idea UNIQUE: choose a long-tail / variation angle from the signals; do not reuse the same head keyword.",
|
|
84
|
+
"5. Each idea must be DISTINCT from the already-published pages listed below (no overlap, no near-duplicates).",
|
|
85
|
+
grounding ? `SIGNALS FROM OUR OWN INTELLIGENCE (ground your ideas in these, they are real):\n${grounding}` : "",
|
|
86
|
+
"Propose exactly 4 DISTINCT ideas, each top- or middle-of-funnel, tied to a specific persona, and able to beat what already ranks.",
|
|
87
|
+
'Also tag each idea with a "category": a SHORT 1-3 word topic bucket the operator would file it under (e.g. "AI", "Compliance", "Time Tracking", "Billing", "Client Growth"). Reuse the same bucket name across ideas that share a theme so they group cleanly.',
|
|
88
|
+
'Output ONLY: ```json [{"title":"","angle":"","whyNow":"","funnelStage":"TOF|MOF|BOF","targetPersona":"","category":""}] ``` with 4 items.',
|
|
89
|
+
]
|
|
90
|
+
.filter(Boolean)
|
|
91
|
+
.join("\n");
|
|
92
|
+
let out = "";
|
|
93
|
+
let outTokens = 0;
|
|
94
|
+
for await (const m of query({
|
|
95
|
+
prompt: "Give me 4 ideas.",
|
|
96
|
+
options: { systemPrompt: sys, model: model ?? MODELS.ideate, allowedTools: ["WebSearch", "WebFetch"], permissionMode: "acceptEdits", maxTurns: 12 },
|
|
97
|
+
})) {
|
|
98
|
+
if (m.type === "assistant") {
|
|
99
|
+
if (onProgress)
|
|
100
|
+
for (const b of m.message.content)
|
|
101
|
+
if (b.type === "tool_use" || b.type === "text")
|
|
102
|
+
onProgress(b.type === "tool_use" ? "Researching trends" : "Shaping ideas");
|
|
103
|
+
const u = m.message.usage;
|
|
104
|
+
if (u?.output_tokens)
|
|
105
|
+
onTokens?.((outTokens += u.output_tokens));
|
|
106
|
+
}
|
|
107
|
+
if (m.type === "result" && m.subtype === "success")
|
|
108
|
+
out = m.result;
|
|
109
|
+
}
|
|
110
|
+
const mt = out.match(FENCE);
|
|
111
|
+
return mt ? JSON.parse(mt[1]) : [];
|
|
112
|
+
}
|
|
113
|
+
/** Interactive: idea (own or AI-proposed) → funnel stage → service to feature. */
|
|
114
|
+
export async function chooseTarget(profile, model) {
|
|
115
|
+
const rl = createInterface({ input: stdin, output: stdout });
|
|
116
|
+
try {
|
|
117
|
+
stdout.write("\nDo you have a topic in mind, or want 4 research-backed ideas? Type a topic, or 'ideas'.\n");
|
|
118
|
+
const first = (await rl.question("> ")).trim();
|
|
119
|
+
let idea = first;
|
|
120
|
+
if (first.toLowerCase() === "ideas" || first === "") {
|
|
121
|
+
stdout.write("\nResearching what's hot right now...\n");
|
|
122
|
+
const ideas = await ideate(profile, model, (l) => stdout.write(" " + l + "\n"));
|
|
123
|
+
ideas.forEach((it, i) => stdout.write(`\n[${i + 1}] ${it.title}\n ${it.angle}\n Why now: ${it.whyNow} (${it.funnelStage ?? "TOF"}, ${it.targetPersona ?? ""})\n`));
|
|
124
|
+
const pick = (await rl.question("\nPick 1-" + ideas.length + " (or type your own topic): ")).trim();
|
|
125
|
+
const n = Number(pick);
|
|
126
|
+
idea = n >= 1 && n <= ideas.length ? `${ideas[n - 1].title} — ${ideas[n - 1].angle}` : pick;
|
|
127
|
+
}
|
|
128
|
+
stdout.write("\nFunnel stage? TOF (awareness) / MOF (consideration) / BOF (decision) [TOF]\n");
|
|
129
|
+
const s = (await rl.question("> ")).trim().toUpperCase();
|
|
130
|
+
const stage = s === "MOF" || s === "BOF" ? s : "TOF";
|
|
131
|
+
let feature;
|
|
132
|
+
if (profile?.services?.length) {
|
|
133
|
+
stdout.write("\nFeature a specific service/product? Pick a number, or press enter for none:\n");
|
|
134
|
+
profile.services.forEach((sv, i) => stdout.write(` [${i + 1}] ${sv.name}\n`));
|
|
135
|
+
const f = (await rl.question("> ")).trim();
|
|
136
|
+
const fn = Number(f);
|
|
137
|
+
feature = fn >= 1 && fn <= profile.services.length ? profile.services[fn - 1].name : f || undefined;
|
|
138
|
+
}
|
|
139
|
+
return { idea, stage, feature };
|
|
140
|
+
}
|
|
141
|
+
finally {
|
|
142
|
+
rl.close();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/** Fold the target into a generation brief. Unspecified choices are inferred by
|
|
146
|
+
* the model from the topic + the brand's saved ICP — so terse input is enough. */
|
|
147
|
+
export function targetedBrief(profile, t) {
|
|
148
|
+
const stageWord = t.stage === "MOF"
|
|
149
|
+
? "a middle-of-funnel (consideration) blog post"
|
|
150
|
+
: t.stage === "BOF"
|
|
151
|
+
? "a bottom-of-funnel (decision) blog post"
|
|
152
|
+
: t.stage === "TOF"
|
|
153
|
+
? "a top-of-funnel (awareness) blog post"
|
|
154
|
+
: "a blog post — you choose the funnel stage (TOF/MOF/BOF) that best fits this topic and the brand's buyers";
|
|
155
|
+
return [
|
|
156
|
+
`Write ${stageWord} on: ${t.idea}.`,
|
|
157
|
+
t.feature
|
|
158
|
+
? `Naturally feature the "${t.feature}" offering${profile?.brand ? ` from ${profile.brand}` : ""} where it genuinely fits — no forced pitch.`
|
|
159
|
+
: "Feature a relevant service only if it fits naturally; otherwise keep it educational.",
|
|
160
|
+
"Pick the most fitting persona and angle from the saved ICP. Full Blog Protocol and operating procedure. Markdown, no preamble.",
|
|
161
|
+
]
|
|
162
|
+
.filter(Boolean)
|
|
163
|
+
.join(" ");
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=targeting.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"targeting.js","sourceRoot":"","sources":["../../src/plan/targeting.ts"],"names":[],"mappings":"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"}
|
package/dist/shell.js
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import { stdout } from "node:process";
|
|
2
|
+
import { mkdirSync } from "node:fs";
|
|
3
|
+
import { select, input } from "@inquirer/prompts";
|
|
4
|
+
import { splash, step } from "./brand.js";
|
|
5
|
+
import { loadProfile, saveProfile, getAccount } from "./store.js";
|
|
6
|
+
import { runSetup, tuneSitemap, tunePersonas, tuneServices, tuneVoice } from "./onboarding/setup.js";
|
|
7
|
+
import { generateArticle, generateGuide } from "./pipeline.js";
|
|
8
|
+
import { ideate, targetedBrief } from "./plan/targeting.js";
|
|
9
|
+
import { attachFile } from "./attach.js";
|
|
10
|
+
import { keywordContext, strikingDistanceFor, gscPropertyFor, gscRegister, gscVerify, GSC_SERVICE_ACCOUNT } from "./intel.js";
|
|
11
|
+
import { isProfileComplete } from "./onboarding/schema.js";
|
|
12
|
+
import { enableStatusBar, setStatus, disableStatusBar } from "./ui/statusbar.js";
|
|
13
|
+
import { startSpinner, updateSpinner, stopSpinner, logAbove, setSpinnerTokens } from "./ui/spinner.js";
|
|
14
|
+
/**
|
|
15
|
+
* The guided shell. Navigation is arrow-key menus (inquirer) — free, no tokens.
|
|
16
|
+
* A pinned bottom bar (ui/statusbar) always shows what works on the current screen
|
|
17
|
+
* plus the brand/plan badge, so the available commands never scroll out of sight.
|
|
18
|
+
*/
|
|
19
|
+
const out = (s) => stdout.write(s + "\n");
|
|
20
|
+
const bar = (pct) => "▓".repeat(Math.round(pct / 10)) + "░".repeat(10 - Math.round(pct / 10)) + ` ${pct}%`;
|
|
21
|
+
// Per-screen key/command hints shown at the left of the bottom bar.
|
|
22
|
+
const HINT_MENU = "↑↓ move · Enter select · Ctrl+C quit";
|
|
23
|
+
const HINT_SETUP = "type your answer + Enter · commands you can type: attach · back · restart · quit";
|
|
24
|
+
const HINT_INPUT = "type + Enter · Ctrl+C to cancel";
|
|
25
|
+
const HINT_WORK = "working… · this step uses AI";
|
|
26
|
+
/** The right-side badge: brand · plan/credits (managed) or local (your Claude). */
|
|
27
|
+
async function badge(profile) {
|
|
28
|
+
const acct = await getAccount();
|
|
29
|
+
const plan = acct ? `${acct.tier} · ${acct.remaining}/${acct.monthlyCredits} left` : "local (your Claude)";
|
|
30
|
+
return `${profile?.brand ? profile.brand + " · " : ""}${plan}`;
|
|
31
|
+
}
|
|
32
|
+
/** Offer to attach a reference file; returns a brief fragment (or ""). */
|
|
33
|
+
async function maybeAttach() {
|
|
34
|
+
const yes = await select({
|
|
35
|
+
message: "Attach a reference file (brand doc, image, PDF) for context?",
|
|
36
|
+
choices: [
|
|
37
|
+
{ name: "No, continue", value: false },
|
|
38
|
+
{ name: "Yes, upload one", value: true },
|
|
39
|
+
],
|
|
40
|
+
});
|
|
41
|
+
if (!yes)
|
|
42
|
+
return "";
|
|
43
|
+
const atts = await attachFile((l) => out(step(l)));
|
|
44
|
+
if (!atts.length)
|
|
45
|
+
return "";
|
|
46
|
+
for (const a of atts)
|
|
47
|
+
out(step(` ${a.filename} → ${a.fileUrl}`));
|
|
48
|
+
return ` Reference material to read and use for context (fetch these URLs): ${atts.map((a) => a.fileUrl).join(" , ")} .`;
|
|
49
|
+
}
|
|
50
|
+
export async function shell() {
|
|
51
|
+
// Turn the bar on BEFORE the splash so everything flows above it from the top.
|
|
52
|
+
enableStatusBar(HINT_MENU, "8ContentPro");
|
|
53
|
+
stdout.write(splash());
|
|
54
|
+
let profile = await loadProfile();
|
|
55
|
+
const workdir = "./drafts";
|
|
56
|
+
mkdirSync(workdir, { recursive: true });
|
|
57
|
+
const b = await badge(profile);
|
|
58
|
+
try {
|
|
59
|
+
if (!profile || !isProfileComplete(profile)) {
|
|
60
|
+
out("\nWelcome. Let's get you set up. A few quick questions first.");
|
|
61
|
+
out(step("This setup is free — no AI credit until the very last step (one cheap pass)."));
|
|
62
|
+
out(step("You can type 'attach' any time to upload a file/image, 'back' to fix the last answer, 'restart', or 'quit'."));
|
|
63
|
+
setStatus(HINT_SETUP, b);
|
|
64
|
+
profile = await runSetup((l) => updateSpinner(l));
|
|
65
|
+
stopSpinner();
|
|
66
|
+
await saveProfile(profile);
|
|
67
|
+
out(step(`Built ${profile.personas.length} personas: ${profile.personas.map((p) => p.name).join(" · ")}`));
|
|
68
|
+
if (profile.sitePages?.length)
|
|
69
|
+
out(step(`Learned ${profile.sitePages.length} pages for internal linking.`));
|
|
70
|
+
out("\n" + step("You're configured. " + bar(100)));
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
out("\n" + step(`Welcome back, ${profile.brand}. Setup ${bar(100)}`));
|
|
74
|
+
}
|
|
75
|
+
setStatus(HINT_MENU, await badge(profile));
|
|
76
|
+
for (;;) {
|
|
77
|
+
setStatus(HINT_MENU, await badge(profile));
|
|
78
|
+
const choice = await select({
|
|
79
|
+
message: "What would you like to do today?",
|
|
80
|
+
choices: [
|
|
81
|
+
{ name: "Write a blog article", value: "article" },
|
|
82
|
+
{ name: "Create a gated asset (ebook / guide)", value: "asset" },
|
|
83
|
+
{ name: "Retune my setup", value: "tune" },
|
|
84
|
+
{ name: "Exit", value: "exit" },
|
|
85
|
+
],
|
|
86
|
+
});
|
|
87
|
+
if (choice === "exit") {
|
|
88
|
+
out("\nSee you next time.");
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
if (choice === "article")
|
|
92
|
+
await articleFlow(profile, workdir);
|
|
93
|
+
else if (choice === "asset")
|
|
94
|
+
await assetFlow(profile, workdir);
|
|
95
|
+
else if (choice === "tune") {
|
|
96
|
+
const what = await select({
|
|
97
|
+
message: "Retune what? (just this one thing — the rest stays)",
|
|
98
|
+
choices: [
|
|
99
|
+
{ name: "Website pages (re-crawl your sitemap)", value: "sitemap" },
|
|
100
|
+
{ name: "Personas", value: "personas" },
|
|
101
|
+
{ name: "Services / products", value: "services" },
|
|
102
|
+
{ name: "Brand voice", value: "voice" },
|
|
103
|
+
{ name: "Connect Google Search Console (unlock quick-win ideas)", value: "gsc" },
|
|
104
|
+
{ name: "Everything (full setup)", value: "all" },
|
|
105
|
+
{ name: "Back", value: "back" },
|
|
106
|
+
],
|
|
107
|
+
});
|
|
108
|
+
if (what === "back")
|
|
109
|
+
continue;
|
|
110
|
+
if (what === "gsc") {
|
|
111
|
+
await connectSearchConsole(profile);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
try {
|
|
115
|
+
setStatus(what === "all" ? HINT_SETUP : HINT_WORK, await badge(profile));
|
|
116
|
+
if (what === "sitemap")
|
|
117
|
+
profile = await tuneSitemap(profile, (l) => updateSpinner(l));
|
|
118
|
+
else if (what === "personas")
|
|
119
|
+
profile = await tunePersonas(profile, (l) => updateSpinner(l));
|
|
120
|
+
else if (what === "services")
|
|
121
|
+
profile = await tuneServices(profile, (l) => updateSpinner(l));
|
|
122
|
+
else if (what === "voice")
|
|
123
|
+
profile = await tuneVoice(profile);
|
|
124
|
+
else
|
|
125
|
+
profile = await runSetup((l) => updateSpinner(l));
|
|
126
|
+
stopSpinner();
|
|
127
|
+
await saveProfile(profile);
|
|
128
|
+
out("\n" + step("Saved."));
|
|
129
|
+
}
|
|
130
|
+
catch (e) {
|
|
131
|
+
stopSpinner();
|
|
132
|
+
out(step(`Couldn't update: ${e.message}`));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
catch (e) {
|
|
138
|
+
// inquirer throws ExitPromptError on Ctrl+C — treat as a clean exit.
|
|
139
|
+
if (e.name !== "ExitPromptError")
|
|
140
|
+
out("\n" + step(`Didn't complete: ${e.message}`));
|
|
141
|
+
else
|
|
142
|
+
out("\nBye.");
|
|
143
|
+
}
|
|
144
|
+
finally {
|
|
145
|
+
stopSpinner();
|
|
146
|
+
disableStatusBar();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
async function articleFlow(profile, workdir) {
|
|
150
|
+
setStatus(HINT_INPUT, await badge(profile));
|
|
151
|
+
const t = (await input({ message: "What's the topic? (a few words — or type 'ideas' for 4 researched ideas)" })).trim();
|
|
152
|
+
let idea = t;
|
|
153
|
+
if (t.toLowerCase() === "ideas" || t === "") {
|
|
154
|
+
// Steer the KIND first — otherwise the 4 ideas come back monotone (all one angle).
|
|
155
|
+
const ideaType = await select({
|
|
156
|
+
message: "What kind of ideas? (this steers the research)",
|
|
157
|
+
choices: [
|
|
158
|
+
{ name: "A mix (one of each type)", value: "mix" },
|
|
159
|
+
{ name: "Thought leadership / contrarian POV", value: "thought-leadership" },
|
|
160
|
+
{ name: "Educational how-to / evergreen", value: "how-to" },
|
|
161
|
+
{ name: "Timely / news reaction (what just changed)", value: "timely" },
|
|
162
|
+
{ name: "Comparison / buyer-decision", value: "comparison" },
|
|
163
|
+
{ name: "Data / research-driven", value: "data" },
|
|
164
|
+
],
|
|
165
|
+
});
|
|
166
|
+
let personaFocus;
|
|
167
|
+
if (profile.personas?.length) {
|
|
168
|
+
personaFocus = await select({
|
|
169
|
+
message: "Aim at one persona, or spread across all?",
|
|
170
|
+
choices: [{ name: "Spread across all", value: undefined }, ...profile.personas.map((p) => ({ name: `${p.name} (${p.role})`, value: p.name }))],
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
setStatus(HINT_WORK, await badge(profile));
|
|
174
|
+
startSpinner("Researching live trends + your keyword data…");
|
|
175
|
+
const ideas = await ideate(profile, undefined, (l) => updateSpinner(l), (n) => setSpinnerTokens(n), { type: ideaType, persona: personaFocus });
|
|
176
|
+
// Quick-win lane (separate from discovery): queries the client already ranks 5-20 for.
|
|
177
|
+
const property = gscPropertyFor(profile.site);
|
|
178
|
+
const quickWins = property ? await strikingDistanceFor(property) : [];
|
|
179
|
+
stopSpinner();
|
|
180
|
+
setStatus(HINT_MENU, await badge(profile));
|
|
181
|
+
if (ideas.length === 0 && quickWins.length === 0) {
|
|
182
|
+
idea = (await input({ message: "Couldn't fetch ideas — type a topic" })).trim();
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
const qwChoices = quickWins.slice(0, 4).map((q) => ({
|
|
186
|
+
name: `[Quick win] "${q.query}" — you rank #${Math.round(q.position)}, ${q.impressions} impressions${q.volume ? `, ${q.volume}/mo` : ""}`,
|
|
187
|
+
value: `${q.query} — a quick-win piece: we already rank ~#${Math.round(q.position)} for this with real impressions; write the definitive piece to push it onto page 1`,
|
|
188
|
+
}));
|
|
189
|
+
const pick = await select({
|
|
190
|
+
message: quickWins.length ? "Pick a trend-led idea, or a quick win you already rank for:" : "Pick an idea:",
|
|
191
|
+
choices: [...ideas.map((it) => ({ name: `${it.title} — ${it.angle}`, value: `${it.title} — ${it.angle}` })), ...qwChoices, { name: "Type my own instead", value: "__own" }],
|
|
192
|
+
});
|
|
193
|
+
idea = pick === "__own" ? (await input({ message: "Your topic" })).trim() : pick;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
const stage = await select({
|
|
197
|
+
message: "Funnel stage?",
|
|
198
|
+
choices: [
|
|
199
|
+
{ name: "Let me pick the best fit (recommended)", value: "auto" },
|
|
200
|
+
{ name: "Top of funnel (awareness)", value: "TOF" },
|
|
201
|
+
{ name: "Middle of funnel (consideration)", value: "MOF" },
|
|
202
|
+
{ name: "Bottom of funnel (decision)", value: "BOF" },
|
|
203
|
+
],
|
|
204
|
+
});
|
|
205
|
+
let feature;
|
|
206
|
+
if (profile.services?.length) {
|
|
207
|
+
feature = await select({
|
|
208
|
+
message: "Feature a specific service?",
|
|
209
|
+
choices: [{ name: "Let me decide", value: undefined }, ...profile.services.map((s) => ({ name: s.name, value: s.name }))],
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
const ref = await maybeAttach();
|
|
213
|
+
const kw = await keywordContext(idea);
|
|
214
|
+
setStatus(HINT_WORK, await badge(profile));
|
|
215
|
+
startSpinner("Writing your article — research, human voice, AI-detection tested…");
|
|
216
|
+
const { docxPath } = await generateArticle(targetedBrief(profile, { idea, stage: stage === "auto" ? undefined : stage, feature }) + ref + kw, {
|
|
217
|
+
workdir,
|
|
218
|
+
profile,
|
|
219
|
+
onProgress: (l) => updateSpinner(l),
|
|
220
|
+
onResult: (b) => logAbove(b),
|
|
221
|
+
onTokens: (n) => setSpinnerTokens(n),
|
|
222
|
+
});
|
|
223
|
+
stopSpinner();
|
|
224
|
+
out("\n" + step(`Done. Open in Word: ${docxPath}`));
|
|
225
|
+
}
|
|
226
|
+
async function assetFlow(profile, workdir) {
|
|
227
|
+
setStatus(HINT_INPUT, await badge(profile));
|
|
228
|
+
const topic = (await input({ message: "What should the resource guide / ebook be about?" })).trim();
|
|
229
|
+
if (!topic) {
|
|
230
|
+
out(step("No topic — back to the menu."));
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
const brand = profile.brand ? ` for ${profile.brand}` : "";
|
|
234
|
+
const brief = [
|
|
235
|
+
`Create a downloadable RESOURCE GUIDE / EBOOK (a gated lead magnet, not a blog post)${brand} on: ${topic}.`,
|
|
236
|
+
"Longer and more structured than a post (~1500-2500 words): a clear H1 title, an intro that frames the stakes,",
|
|
237
|
+
"H2 sections that teach a repeatable framework, bullet checklists, concrete steps, and takeaways. Same operating",
|
|
238
|
+
"procedure: real research, human voice, no em-dashes. Markdown only, no preamble.",
|
|
239
|
+
].join(" ");
|
|
240
|
+
const ref = await maybeAttach();
|
|
241
|
+
setStatus(HINT_WORK, await badge(profile));
|
|
242
|
+
startSpinner("Building your gated asset — research, human voice…");
|
|
243
|
+
const { pdfPath } = await generateGuide(brief + ref, {
|
|
244
|
+
workdir,
|
|
245
|
+
profile,
|
|
246
|
+
onProgress: (l) => updateSpinner(l),
|
|
247
|
+
onResult: (b) => logAbove(b),
|
|
248
|
+
onTokens: (n) => setSpinnerTokens(n),
|
|
249
|
+
});
|
|
250
|
+
stopSpinner();
|
|
251
|
+
out("\n" + step(`Done. Downloadable PDF: ${pdfPath}`));
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Connect the client's Google Search Console so ideation can surface quick-wins
|
|
255
|
+
* (queries they already rank 5-20 for). They add our service account as a Restricted
|
|
256
|
+
* user; we verify the SA can see the property, then it's ingested per client.
|
|
257
|
+
*/
|
|
258
|
+
async function connectSearchConsole(profile) {
|
|
259
|
+
const guess = gscPropertyFor(profile.site);
|
|
260
|
+
out("\n" + step("Connect Google Search Console — unlocks 'quick win' ideas from queries you already rank for."));
|
|
261
|
+
out(step("1. Open Search Console → Settings → Users and permissions → Add user"));
|
|
262
|
+
out(step(`2. Add this as a Restricted user: ${GSC_SERVICE_ACCOUNT}`));
|
|
263
|
+
out(step(" (We only ever READ your data, and it stays yours — never shared.)"));
|
|
264
|
+
const property = (await input({ message: "Your Search Console property", default: guess })).trim();
|
|
265
|
+
if (!property) {
|
|
266
|
+
out(step("No property — skipped."));
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
await gscRegister(property, profile.brand);
|
|
270
|
+
await input({ message: "Press Enter once you've added the user above (give Google a minute)" });
|
|
271
|
+
setStatus(HINT_WORK, await badge(profile));
|
|
272
|
+
startSpinner("Verifying access to your Search Console…");
|
|
273
|
+
const v = await gscVerify(property);
|
|
274
|
+
stopSpinner();
|
|
275
|
+
if (v?.verified) {
|
|
276
|
+
out(step(`Connected — I can see ${property}. Quick-win ideas will use your real rankings after the next data pull.`));
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
out(step("Not visible yet. Google can take a few minutes after you add the user."));
|
|
280
|
+
if (v?.visibleProperties?.length)
|
|
281
|
+
out(step(`I currently have access to: ${v.visibleProperties.join(", ")} — if your property is one of these, re-run and enter it exactly.`));
|
|
282
|
+
else
|
|
283
|
+
out(step("Once you've added the user, re-run this to verify."));
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
//# sourceMappingURL=shell.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell.js","sourceRoot":"","sources":["../src/shell.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
const UA = "Mozilla/5.0 (compatible; 8ContentPro/0.1; +https://weactive8.com)";
|
|
2
|
+
const CATEGORY_WORDS = "guides|ebooks|infographics|webinars|whitepapers|checklists|templates|case-studies|reports";
|
|
3
|
+
function titleFromSlug(url) {
|
|
4
|
+
const slug = url.replace(/[#?].*$/, "").replace(/\/$/, "").split("/").pop() || "";
|
|
5
|
+
return slug
|
|
6
|
+
.replace(/[-_]+/g, " ")
|
|
7
|
+
.replace(/\b\w/g, (c) => c.toUpperCase())
|
|
8
|
+
.trim();
|
|
9
|
+
}
|
|
10
|
+
/** Asset type from the URL path, so the writer uses the right CTA verb (download vs watch). */
|
|
11
|
+
function typeFromUrl(url) {
|
|
12
|
+
if (/webinar/i.test(url))
|
|
13
|
+
return "webinar";
|
|
14
|
+
if (/infographic/i.test(url))
|
|
15
|
+
return "infographic";
|
|
16
|
+
if (/ebook/i.test(url))
|
|
17
|
+
return "ebook";
|
|
18
|
+
if (/checklist/i.test(url))
|
|
19
|
+
return "checklist";
|
|
20
|
+
if (/template/i.test(url))
|
|
21
|
+
return "template";
|
|
22
|
+
if (/whitepaper|report/i.test(url))
|
|
23
|
+
return "report";
|
|
24
|
+
return "guide";
|
|
25
|
+
}
|
|
26
|
+
async function fetchHtml(url) {
|
|
27
|
+
try {
|
|
28
|
+
const r = await fetch(url, { headers: { "user-agent": UA }, redirect: "follow" });
|
|
29
|
+
return r.ok ? await r.text() : "";
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return "";
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/** A link is an individual gated asset (a download/landing page), not a category index. */
|
|
36
|
+
function isAssetLink(href, text) {
|
|
37
|
+
if (new RegExp(`/resources/(?:${CATEGORY_WORDS})/?$`, "i").test(href))
|
|
38
|
+
return false; // category index
|
|
39
|
+
return /get the (?:guide|ebook|report)|download|watch the/i.test(text) || /^https?:\/\/go\./i.test(href) || new RegExp(`/(?:${CATEGORY_WORDS})/[^/]+`, "i").test(href);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Crawl a client's resource library and return every INDIVIDUAL gated asset (guide,
|
|
43
|
+
* ebook, infographic, webinar, checklist…) with its download/landing URL and type.
|
|
44
|
+
* Works from the root `/resources/` (discovers + crawls category sub-pages) or from a
|
|
45
|
+
* single category page. Skips category index pages. Fails soft to [].
|
|
46
|
+
*/
|
|
47
|
+
export async function crawlResourceLibrary(startUrl) {
|
|
48
|
+
const base = startUrl.startsWith("http") ? startUrl : `https://${startUrl}`;
|
|
49
|
+
let origin;
|
|
50
|
+
try {
|
|
51
|
+
origin = new URL(base).origin;
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
const assets = new Map();
|
|
57
|
+
const abs = (h) => (h.startsWith("/") ? origin + h : h);
|
|
58
|
+
const harvest = (html) => {
|
|
59
|
+
for (const m of html.matchAll(/<a[^>]+href="([^"]+)"[^>]*>([\s\S]*?)<\/a>/gi)) {
|
|
60
|
+
const href = abs(m[1]);
|
|
61
|
+
const text = m[2].replace(/<[^>]+>/g, "").replace(/\s+/g, " ").trim();
|
|
62
|
+
if (!href.includes(new URL(origin).host) && !/^https?:\/\/go\./i.test(href))
|
|
63
|
+
continue;
|
|
64
|
+
if (!isAssetLink(href, text))
|
|
65
|
+
continue;
|
|
66
|
+
const key = href.replace(/[#?].*$/, "").replace(/\/$/, "");
|
|
67
|
+
if (assets.has(key))
|
|
68
|
+
continue;
|
|
69
|
+
const name = titleFromSlug(key);
|
|
70
|
+
if (name.length < 5)
|
|
71
|
+
continue;
|
|
72
|
+
assets.set(key, { name, url: key, topic: typeFromUrl(key) });
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
const rootHtml = await fetchHtml(base);
|
|
76
|
+
harvest(rootHtml);
|
|
77
|
+
// Discover category sub-pages on the root and crawl them (one level, bounded).
|
|
78
|
+
const cats = new Set();
|
|
79
|
+
for (const m of rootHtml.matchAll(/<a[^>]+href="([^"]+)"/gi)) {
|
|
80
|
+
const h = abs(m[1]).replace(/[#?].*$/, "");
|
|
81
|
+
if (new RegExp(`/resources/(?:${CATEGORY_WORDS})/?$`, "i").test(h) && h.replace(/\/$/, "") !== base.replace(/\/$/, ""))
|
|
82
|
+
cats.add(h);
|
|
83
|
+
}
|
|
84
|
+
for (const cat of [...cats].slice(0, 8))
|
|
85
|
+
harvest(await fetchHtml(cat));
|
|
86
|
+
return [...assets.values()].slice(0, 40);
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=resources.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resources.js","sourceRoot":"","sources":["../../src/site/resources.ts"],"names":[],"mappings":"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"}
|