@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.
Files changed (66) hide show
  1. package/README.md +33 -0
  2. package/dist/agent.js +115 -0
  3. package/dist/agent.js.map +1 -0
  4. package/dist/attach.js +144 -0
  5. package/dist/attach.js.map +1 -0
  6. package/dist/auth.js +49 -0
  7. package/dist/auth.js.map +1 -0
  8. package/dist/brand.js +110 -0
  9. package/dist/brand.js.map +1 -0
  10. package/dist/brandgen.js +76 -0
  11. package/dist/brandgen.js.map +1 -0
  12. package/dist/build-info.js +3 -0
  13. package/dist/build-info.js.map +1 -0
  14. package/dist/detect.js +163 -0
  15. package/dist/detect.js.map +1 -0
  16. package/dist/index.js +334 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/ink/App.js +733 -0
  19. package/dist/ink/App.js.map +1 -0
  20. package/dist/ink/components.js +214 -0
  21. package/dist/ink/components.js.map +1 -0
  22. package/dist/ink/run.js +9 -0
  23. package/dist/ink/run.js.map +1 -0
  24. package/dist/intel.js +110 -0
  25. package/dist/intel.js.map +1 -0
  26. package/dist/login.js +99 -0
  27. package/dist/login.js.map +1 -0
  28. package/dist/metering.js +53 -0
  29. package/dist/metering.js.map +1 -0
  30. package/dist/methodology/loader.js +68 -0
  31. package/dist/methodology/loader.js.map +1 -0
  32. package/dist/models.js +24 -0
  33. package/dist/models.js.map +1 -0
  34. package/dist/onboarding/onboard.js +102 -0
  35. package/dist/onboarding/onboard.js.map +1 -0
  36. package/dist/onboarding/schema.js +25 -0
  37. package/dist/onboarding/schema.js.map +1 -0
  38. package/dist/onboarding/setup.js +324 -0
  39. package/dist/onboarding/setup.js.map +1 -0
  40. package/dist/output/callouts.js +81 -0
  41. package/dist/output/callouts.js.map +1 -0
  42. package/dist/output/docx.js +155 -0
  43. package/dist/output/docx.js.map +1 -0
  44. package/dist/output/pdf.js +195 -0
  45. package/dist/output/pdf.js.map +1 -0
  46. package/dist/pipeline.js +140 -0
  47. package/dist/pipeline.js.map +1 -0
  48. package/dist/plan/targeting.js +165 -0
  49. package/dist/plan/targeting.js.map +1 -0
  50. package/dist/shell.js +286 -0
  51. package/dist/shell.js.map +1 -0
  52. package/dist/site/resources.js +88 -0
  53. package/dist/site/resources.js.map +1 -0
  54. package/dist/site/sitemap.js +92 -0
  55. package/dist/site/sitemap.js.map +1 -0
  56. package/dist/store.js +110 -0
  57. package/dist/store.js.map +1 -0
  58. package/dist/topics.js +50 -0
  59. package/dist/topics.js.map +1 -0
  60. package/dist/ui/spinner.js +111 -0
  61. package/dist/ui/spinner.js.map +1 -0
  62. package/dist/ui/statusbar.js +90 -0
  63. package/dist/ui/statusbar.js.map +1 -0
  64. package/dist/update.js +45 -0
  65. package/dist/update.js.map +1 -0
  66. package/package.json +47 -0
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Learn a client's website from its sitemap.xml so 8ContentPro can insert real
3
+ * internal links automatically. Pure fetch + parse, no model calls. Re-run any
4
+ * time (onboarding, `tune sitemap`, or a scheduled refresh) to pick up new
5
+ * pages/posts the client publishes.
6
+ */
7
+ const UA = "8ContentPro/0.1 (+https://weactive8.com)";
8
+ async function fetchText(url) {
9
+ const res = await fetch(url, { headers: { "user-agent": UA }, redirect: "follow" });
10
+ if (!res.ok)
11
+ throw new Error(`${res.status} ${url}`);
12
+ return res.text();
13
+ }
14
+ /** Extract the text of every <loc> tag. */
15
+ function locs(xml) {
16
+ return [...xml.matchAll(/<loc>\s*([^<\s]+)\s*<\/loc>/gi)].map((m) => m[1].trim());
17
+ }
18
+ /** Pull <url><loc/><lastmod/></url> pairs from a urlset. */
19
+ function urlEntries(xml) {
20
+ const out = [];
21
+ for (const m of xml.matchAll(/<url>([\s\S]*?)<\/url>/gi)) {
22
+ const block = m[1];
23
+ const loc = block.match(/<loc>\s*([^<\s]+)\s*<\/loc>/i)?.[1]?.trim();
24
+ if (!loc)
25
+ continue;
26
+ const lastmod = block.match(/<lastmod>\s*([^<\s]+)\s*<\/lastmod>/i)?.[1]?.trim();
27
+ out.push({ url: loc, ...(lastmod ? { lastmod } : {}) });
28
+ }
29
+ return out;
30
+ }
31
+ /** Candidate sitemap URLs to try for a bare domain. Adds https:// if missing. */
32
+ function candidates(siteUrl) {
33
+ let base = siteUrl.trim().replace(/\/+$/, "");
34
+ if (!/^https?:\/\//i.test(base))
35
+ base = "https://" + base; // "www.x.com" -> "https://www.x.com"
36
+ const origin = base.match(/^https?:\/\/[^/]+/)?.[0] ?? base;
37
+ return base.endsWith(".xml") ? [base] : [`${origin}/sitemap.xml`, `${origin}/sitemap_index.xml`, `${origin}/sitemap-index.xml`];
38
+ }
39
+ /**
40
+ * Resolve and crawl a site's sitemap (following one level of sitemap-index
41
+ * nesting), returning up to `maxPages` pages. Throws only if nothing resolves.
42
+ */
43
+ export async function learnSitemap(siteUrl, maxPages = 500) {
44
+ let rootXml = "";
45
+ let sitemapUrl = "";
46
+ for (const url of candidates(siteUrl)) {
47
+ try {
48
+ rootXml = await fetchText(url);
49
+ sitemapUrl = url;
50
+ break;
51
+ }
52
+ catch {
53
+ /* try next candidate */
54
+ }
55
+ }
56
+ if (!rootXml)
57
+ throw new Error(`No sitemap found for ${siteUrl} (tried ${candidates(siteUrl).join(", ")})`);
58
+ // Sitemap index → fetch child sitemaps (bounded); else it's a urlset already.
59
+ const isIndex = /<sitemapindex[\s>]/i.test(rootXml);
60
+ const pages = [];
61
+ const seen = new Set();
62
+ const add = (ps) => {
63
+ for (const p of ps) {
64
+ if (pages.length >= maxPages)
65
+ break;
66
+ if (seen.has(p.url))
67
+ continue;
68
+ seen.add(p.url);
69
+ pages.push(p);
70
+ }
71
+ };
72
+ if (isIndex) {
73
+ const children = locs(rootXml).slice(0, 25);
74
+ for (const child of children) {
75
+ if (pages.length >= maxPages)
76
+ break;
77
+ try {
78
+ add(urlEntries(await fetchText(child)));
79
+ }
80
+ catch {
81
+ /* skip unreachable child sitemap */
82
+ }
83
+ }
84
+ }
85
+ else {
86
+ add(urlEntries(rootXml));
87
+ if (pages.length === 0)
88
+ add(locs(rootXml).map((url) => ({ url }))); // fallback: bare <loc>s
89
+ }
90
+ return { sitemapUrl, pages };
91
+ }
92
+ //# sourceMappingURL=sitemap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sitemap.js","sourceRoot":"","sources":["../../src/site/sitemap.ts"],"names":[],"mappings":"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"}
package/dist/store.js ADDED
@@ -0,0 +1,110 @@
1
+ import { mkdirSync, readFileSync, writeFileSync, existsSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { loadAuthToken } from "./brand.js";
5
+ /**
6
+ * Profile persistence.
7
+ *
8
+ * SOURCE OF TRUTH = WeActive8 backend on AX41 (profiles table in 8cp-postgres),
9
+ * keyed by the client's license. On every save the backend ALSO writes an
10
+ * organized, human-navigable copy to a dedicated Storage Box tree:
11
+ * storagebox:WeActive8/8contentpro/<client-slug>/<license>/{profile.json,
12
+ * personas/, services/, projects/<slug>/, meta.json} (see that tree's README)
13
+ * That write is server-side only (Storage Box creds are AX41-only per CLAUDE.md).
14
+ * No Hetzner Object Storage bucket (cost hard-stop); the restic infra backup of
15
+ * 8cp-postgres additionally covers DR.
16
+ *
17
+ * On every run the CLI FETCHES the client's profile from the backend by license,
18
+ * so their saved personas/services/voice come down and drive the content. A
19
+ * local cache under ~/.8contentpro/ makes it work offline and avoids a round trip
20
+ * when nothing changed.
21
+ *
22
+ * TODO(M4): point CONTENTPRO_API_URL at the real gateway once the backend +
23
+ * profile API are stood up. Until then, save/fetch degrade to the local cache.
24
+ */
25
+ const API_BASE = process.env.CONTENTPRO_API_URL?.trim() || "https://8contentpro.com/api";
26
+ function configDir() {
27
+ const dir = join(homedir(), ".8contentpro");
28
+ mkdirSync(dir, { recursive: true });
29
+ return dir;
30
+ }
31
+ const cachePath = () => join(configDir(), "profile.json");
32
+ function license() {
33
+ return process.env.CONTENTPRO_LICENSE?.trim() || loadAuthToken();
34
+ }
35
+ /** Save to the backend (source of truth) and mirror to the local cache. */
36
+ export async function saveProfile(profile) {
37
+ writeFileSync(cachePath(), JSON.stringify(profile, null, 2), "utf8");
38
+ const key = license();
39
+ if (!key)
40
+ return { synced: false }; // dev/internal: local only
41
+ try {
42
+ const res = await fetch(`${API_BASE}/profile`, {
43
+ method: "PUT",
44
+ headers: { "content-type": "application/json", authorization: `Bearer ${key}` },
45
+ body: JSON.stringify(profile),
46
+ });
47
+ return { synced: res.ok };
48
+ }
49
+ catch {
50
+ return { synced: false }; // backend not up yet — cached locally, will sync later
51
+ }
52
+ }
53
+ /** Gateway base (strip the /v1 the model proxy lives under). */
54
+ function gatewayBase() {
55
+ return (process.env.CONTENTPRO_PROXY_URL?.trim() || "https://8cp.wa8.us/v1").replace(/\/v1\/?$/, "");
56
+ }
57
+ /** Report a completed generation to the gateway so credits + cost are metered.
58
+ * No-op in local/dev mode (no license). Best-effort — never blocks the user. */
59
+ export async function reportUsage(type, costUsd) {
60
+ const key = license();
61
+ if (!key)
62
+ return;
63
+ try {
64
+ await fetch(`${gatewayBase()}/usage`, {
65
+ method: "POST",
66
+ headers: { "content-type": "application/json", authorization: `Bearer ${key}` },
67
+ body: JSON.stringify({ type, costUsd }),
68
+ });
69
+ }
70
+ catch {
71
+ /* best-effort */
72
+ }
73
+ }
74
+ /** The account's plan + credits remaining, or null (local/dev or unreachable). */
75
+ export async function getAccount() {
76
+ const key = license();
77
+ if (!key)
78
+ return null;
79
+ try {
80
+ const res = await fetch(`${gatewayBase()}/account`, { headers: { authorization: `Bearer ${key}` } });
81
+ return res.ok ? await res.json() : null;
82
+ }
83
+ catch {
84
+ return null;
85
+ }
86
+ }
87
+ /** Fetch the client's profile: backend first (authoritative), else local cache. */
88
+ export async function loadProfile() {
89
+ const key = license();
90
+ if (key) {
91
+ try {
92
+ const res = await fetch(`${API_BASE}/profile`, {
93
+ headers: { authorization: `Bearer ${key}` },
94
+ });
95
+ if (res.ok) {
96
+ const profile = (await res.json());
97
+ writeFileSync(cachePath(), JSON.stringify(profile, null, 2), "utf8");
98
+ return profile;
99
+ }
100
+ }
101
+ catch {
102
+ // fall through to cache
103
+ }
104
+ }
105
+ if (existsSync(cachePath())) {
106
+ return JSON.parse(readFileSync(cachePath(), "utf8"));
107
+ }
108
+ return undefined;
109
+ }
110
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
package/dist/topics.js ADDED
@@ -0,0 +1,50 @@
1
+ import { homedir } from "node:os";
2
+ import { join } from "node:path";
3
+ import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
4
+ function file() {
5
+ const dir = join(homedir(), ".8contentpro");
6
+ mkdirSync(dir, { recursive: true });
7
+ return join(dir, "topics.json");
8
+ }
9
+ function loadAll() {
10
+ try {
11
+ return JSON.parse(readFileSync(file(), "utf8"));
12
+ }
13
+ catch {
14
+ return [];
15
+ }
16
+ }
17
+ function saveAll(all) {
18
+ writeFileSync(file(), JSON.stringify(all, null, 2), "utf8");
19
+ }
20
+ /** All saved topics for a brand, newest first. */
21
+ export function loadTopics(brand) {
22
+ return loadAll()
23
+ .filter((t) => t.brand === brand)
24
+ .sort((a, b) => b.createdAt - a.createdAt);
25
+ }
26
+ /** Merge new ideas in (dedup by title), keeping existing ones. Returns the brand's set. */
27
+ export function addTopics(brand, topics) {
28
+ const all = loadAll();
29
+ const existing = new Set(all.filter((t) => t.brand === brand).map((t) => t.title.toLowerCase().trim()));
30
+ const now = Date.now();
31
+ for (const t of topics) {
32
+ const key = t.title.toLowerCase().trim();
33
+ if (!key || existing.has(key))
34
+ continue;
35
+ existing.add(key);
36
+ all.push({ ...t, category: t.category || "General", brand, used: false, createdAt: now });
37
+ }
38
+ saveAll(all);
39
+ return loadTopics(brand);
40
+ }
41
+ /** Mark a topic used (struck through next time). */
42
+ export function markUsed(brand, title) {
43
+ const all = loadAll();
44
+ const t = all.find((x) => x.brand === brand && x.title === title);
45
+ if (t && !t.used) {
46
+ t.used = true;
47
+ saveAll(all);
48
+ }
49
+ }
50
+ //# sourceMappingURL=topics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"topics.js","sourceRoot":"","sources":["../src/topics.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,111 @@
1
+ import { stdout } from "node:process";
2
+ /**
3
+ * A Claude-style working indicator: a twinkling glyph, a whimsical gerund, and a
4
+ * live meter — elapsed time · output tokens · what it's actually doing — with a
5
+ * rotating tip printed above. Animated in place on one line so it never spams the
6
+ * scrollback. Coexists with the pinned bottom bar (writes inside the scroll region;
7
+ * the bar owns the reserved bottom row).
8
+ *
9
+ * startSpinner("Writing your article…") // prints a tip, starts the meter
10
+ * updateSpinner("Researching: trust accounting") // swaps the "doing" substatus
11
+ * setSpinnerTokens(31200) // live ↓ token count from the SDK
12
+ * logAbove("multi-line result block") // print above, keep spinning
13
+ * stopSpinner() // clear the line, restore the cursor
14
+ */
15
+ const FRAMES = ["✶", "✷", "✸", "✹", "✺", "✹", "✸", "✷"];
16
+ const WHIMSY = [
17
+ "Herding", "Doodling", "Puzzling", "Noodling", "Percolating", "Simmering", "Conjuring",
18
+ "Untangling", "Marinating", "Ruminating", "Whirring", "Brewing", "Pondering", "Tinkering",
19
+ "Wrangling", "Composing", "Calibrating", "Distilling", "Spelunking", "Cooking",
20
+ ];
21
+ const TIPS = [
22
+ "Type 'ideas' at the topic prompt for 4 research-backed angles.",
23
+ "Every draft is tested across independent AI-detectors before it lands.",
24
+ "Guides export as a polished PDF; articles as a Word .docx.",
25
+ "Retune one thing (personas, pages, voice) without redoing the whole setup.",
26
+ "Attach a brand doc or a screenshot any time — just type 'attach'.",
27
+ "Internal links are pulled from your learned sitemap automatically.",
28
+ "Pick a funnel stage, or let it choose the best fit for the topic.",
29
+ ];
30
+ const CSI = "\x1b[";
31
+ let timer;
32
+ let label = "";
33
+ let t0 = 0;
34
+ let frame = 0;
35
+ let on = false;
36
+ let whimsyIdx = 0;
37
+ let whimsyAt = 0;
38
+ let tokens = 0;
39
+ function fmtElapsed(ms) {
40
+ const s = Math.floor(ms / 1000);
41
+ return s < 60 ? `${s}s` : `${Math.floor(s / 60)}m ${s % 60}s`;
42
+ }
43
+ function fmtTokens(n) {
44
+ return n < 1000 ? `${n}` : `${(n / 1000).toFixed(1)}k`;
45
+ }
46
+ function pick(arr) {
47
+ return Math.floor(Math.random() * arr.length);
48
+ }
49
+ function render() {
50
+ if (!on)
51
+ return;
52
+ frame = (frame + 1) % FRAMES.length;
53
+ const now = Date.now();
54
+ if (now - whimsyAt > 4500) {
55
+ whimsyIdx = (whimsyIdx + 1) % WHIMSY.length;
56
+ whimsyAt = now;
57
+ }
58
+ const meter = [fmtElapsed(now - t0)];
59
+ if (tokens > 0)
60
+ meter.push(`↓ ${fmtTokens(tokens)} tokens`);
61
+ if (label)
62
+ meter.push(label);
63
+ stdout.write(`\r${CSI}35m${FRAMES[frame]}${CSI}0m ${WHIMSY[whimsyIdx]}… ${CSI}90m(${meter.join(" · ")})${CSI}0m${CSI}K`);
64
+ }
65
+ export function startSpinner(initialLabel = "") {
66
+ label = initialLabel;
67
+ tokens = 0;
68
+ if (!stdout.isTTY || on)
69
+ return;
70
+ on = true;
71
+ t0 = Date.now();
72
+ frame = 0;
73
+ whimsyAt = Date.now();
74
+ whimsyIdx = pick(WHIMSY);
75
+ stdout.write(`${CSI}90m⎿ Tip: ${TIPS[pick(TIPS)]}${CSI}0m\n`); // one permanent tip line above
76
+ stdout.write(`${CSI}?25l`); // hide cursor while spinning
77
+ render();
78
+ timer = setInterval(render, 120);
79
+ timer.unref?.();
80
+ }
81
+ export function updateSpinner(message) {
82
+ const clean = message.replace(/\s+/g, " ").trim().slice(0, 72); // one short line, never dump prose
83
+ if (!on)
84
+ return startSpinner(clean);
85
+ if (clean)
86
+ label = clean;
87
+ }
88
+ /** Live output-token count from the SDK stream. */
89
+ export function setSpinnerTokens(n) {
90
+ tokens = n;
91
+ }
92
+ /** Print a permanent line (or multi-line block) above the spinner, then keep spinning. */
93
+ export function logAbove(line) {
94
+ if (!on) {
95
+ stdout.write(line + "\n");
96
+ return;
97
+ }
98
+ stdout.write(`\r${CSI}K${line}\n`);
99
+ render();
100
+ }
101
+ export function stopSpinner(final) {
102
+ if (timer)
103
+ clearInterval(timer);
104
+ timer = undefined;
105
+ if (on)
106
+ stdout.write(`\r${CSI}K${CSI}?25h`); // clear the line, show the cursor
107
+ on = false;
108
+ if (final)
109
+ stdout.write(final + "\n");
110
+ }
111
+ //# sourceMappingURL=spinner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spinner.js","sourceRoot":"","sources":["../../src/ui/spinner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,90 @@
1
+ import { stdout } from "node:process";
2
+ /**
3
+ * A pinned status bar on the very bottom row of the terminal.
4
+ *
5
+ * It reserves the bottom line with a DECSTBM scroll region (rows 1..rows-1),
6
+ * so ALL normal output — inquirer prompts, logs, generation progress — scrolls
7
+ * in the region above while the bar stays put. This is the same mechanism a
8
+ * terminal bottom-bar uses; it keeps the working inquirer flows completely
9
+ * untouched (no full Ink/React rewrite), which is why it can't regress them.
10
+ *
11
+ * The bar is context-aware: a LEFT hint (what keys/commands work on the current
12
+ * screen) and a RIGHT badge (brand · plan/credits) that persists across screens.
13
+ */
14
+ const CSI = "\x1b[";
15
+ const SAVE = "\x1b7"; // DECSC — save cursor
16
+ const RESTORE = "\x1b8"; // DECRC — restore cursor
17
+ const REVERSE = `${CSI}7m`;
18
+ const RESET = `${CSI}0m`;
19
+ let active = false;
20
+ let leftHint = "";
21
+ let rightBadge = "";
22
+ let installed = false;
23
+ const rows = () => stdout.rows || 24;
24
+ const cols = () => stdout.columns || 80;
25
+ function paint() {
26
+ if (!active || !stdout.isTTY)
27
+ return;
28
+ const width = cols();
29
+ const left = ` ${leftHint}`;
30
+ const right = `${rightBadge} `;
31
+ let gap = width - left.length - right.length;
32
+ let leftShown = left;
33
+ if (gap < 1) {
34
+ // Not enough room — truncate the hint, keep the badge.
35
+ const room = Math.max(0, width - right.length - 2);
36
+ leftShown = ` ${leftHint.slice(0, room)}`;
37
+ gap = Math.max(0, width - leftShown.length - right.length);
38
+ }
39
+ const line = (leftShown + " ".repeat(gap) + right).slice(0, width);
40
+ stdout.write(`${SAVE}${CSI}${rows()};1H${CSI}2K${REVERSE}${line}${RESET}${RESTORE}`);
41
+ }
42
+ function onResize() {
43
+ if (!active)
44
+ return;
45
+ stdout.write(`${CSI}1;${rows() - 2}r`); // re-clamp; leave a blank row above the bar for breathing room
46
+ paint();
47
+ }
48
+ /** Turn on the bar. Call this BEFORE printing the splash so everything flows in-region. */
49
+ export function enableStatusBar(hint, badge) {
50
+ leftHint = hint;
51
+ rightBadge = badge;
52
+ if (!stdout.isTTY)
53
+ return; // piped / non-interactive — no bar, no region games
54
+ active = true;
55
+ // Reserve the bottom two rows (a blank breathing line + the bar). Homes cursor to (1,1).
56
+ stdout.write(`${CSI}1;${rows() - 2}r`);
57
+ paint();
58
+ if (!installed) {
59
+ stdout.on("resize", onResize);
60
+ process.on("exit", disableStatusBar);
61
+ // Backstop: Ctrl+C during an AI step (not an inquirer raw-mode prompt) fires a real
62
+ // SIGINT that would kill us before `finally` resets the region. Restore, then exit.
63
+ // (During inquirer prompts raw mode suppresses SIGINT, so inquirer handles it and
64
+ // our normal finally-path cleans up — this only fires between prompts, in cooked mode.)
65
+ for (const sig of ["SIGINT", "SIGTERM", "SIGHUP"]) {
66
+ process.on(sig, () => {
67
+ disableStatusBar();
68
+ process.exit(sig === "SIGINT" ? 130 : 0);
69
+ });
70
+ }
71
+ installed = true;
72
+ }
73
+ }
74
+ /** Update the left hint (what works on this screen). Badge stays unless given. */
75
+ export function setStatus(hint, badge) {
76
+ leftHint = hint;
77
+ if (badge !== undefined)
78
+ rightBadge = badge;
79
+ paint();
80
+ }
81
+ /** Restore the full-screen scroll region and clear the bar (call on exit). */
82
+ export function disableStatusBar() {
83
+ if (!active || !stdout.isTTY)
84
+ return;
85
+ active = false;
86
+ stdout.write(`${CSI}1;${rows()}r`); // reset scroll region to the whole screen
87
+ stdout.write(`${CSI}${rows()};1H${CSI}2K`); // clear the old bar line
88
+ stdout.write(`${CSI}?25h`); // make sure the cursor is visible
89
+ }
90
+ //# sourceMappingURL=statusbar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"statusbar.js","sourceRoot":"","sources":["../../src/ui/statusbar.ts"],"names":[],"mappings":"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"}
package/dist/update.js ADDED
@@ -0,0 +1,45 @@
1
+ import { BUILD } from "./build-info.js";
2
+ /**
3
+ * Update mechanism for the CLI. On startup we ask the WeActive8 server for the latest
4
+ * published version; if it's newer than the running build, we tell the user how to
5
+ * update. This is the standard npm-CLI "update notifier" pattern, sourced from OUR
6
+ * server (not the public npm registry) so it also works for the managed distribution.
7
+ *
8
+ * How customers actually get it: they installed via `npm i -g @weactive8/8contentpro`,
9
+ * and `npm i -g @weactive8/8contentpro@latest` pulls the newest published version.
10
+ */
11
+ const VERSION_URL = (process.env.CONTENTPRO_INTEL_URL?.trim() || "https://8cp-upload.wa8.us") + "/version";
12
+ /** Compare semver-ish "a.b.c" strings. Returns true if `latest` > `current`. */
13
+ function isNewer(latest, current) {
14
+ const a = latest.split(".").map((n) => parseInt(n, 10) || 0);
15
+ const b = current.split(".").map((n) => parseInt(n, 10) || 0);
16
+ for (let i = 0; i < 3; i++) {
17
+ if ((a[i] ?? 0) > (b[i] ?? 0))
18
+ return true;
19
+ if ((a[i] ?? 0) < (b[i] ?? 0))
20
+ return false;
21
+ }
22
+ return false;
23
+ }
24
+ /** Check for a newer published version. Fails soft (never blocks startup). */
25
+ export async function checkForUpdate() {
26
+ const current = BUILD.version;
27
+ try {
28
+ const res = await fetch(VERSION_URL, { signal: AbortSignal.timeout(2500) });
29
+ if (!res.ok)
30
+ return { current, updateAvailable: false };
31
+ const { latest } = (await res.json());
32
+ return { current, latest, updateAvailable: Boolean(latest && isNewer(latest, current)) };
33
+ }
34
+ catch {
35
+ return { current, updateAvailable: false };
36
+ }
37
+ }
38
+ /** The line shown in the CLI: version + commit, and an update hint if one's available. */
39
+ export function versionLine(info) {
40
+ const base = `8ContentPro v${BUILD.version}${BUILD.commit && BUILD.commit !== "dev" ? ` (${BUILD.commit})` : ""}`;
41
+ if (info?.updateAvailable)
42
+ return `${base} — update available: v${info.latest}. Run: npm i -g @weactive8/8contentpro@latest`;
43
+ return `${base} — up to date`;
44
+ }
45
+ //# sourceMappingURL=update.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update.js","sourceRoot":"","sources":["../src/update.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAExC;;;;;;;;GAQG;AAEH,MAAM,WAAW,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,oBAAoB,EAAE,IAAI,EAAE,IAAI,2BAA2B,CAAC,GAAG,UAAU,CAAC;AAE3G,gFAAgF;AAChF,SAAS,OAAO,CAAC,MAAc,EAAE,OAAe;IAC9C,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IAC7D,MAAM,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IAC9D,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC3B,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC;QAC3C,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YAAE,OAAO,KAAK,CAAC;IAC9C,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAQD,8EAA8E;AAC9E,MAAM,CAAC,KAAK,UAAU,cAAc;IAClC,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;IAC9B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,WAAW,EAAE,EAAE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC5E,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,KAAK,EAAE,CAAC;QACxD,MAAM,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAwB,CAAC;QAC7D,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EAAE,CAAC;IAC3F,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,KAAK,EAAE,CAAC;IAC7C,CAAC;AACH,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,WAAW,CAAC,IAAiB;IAC3C,MAAM,IAAI,GAAG,gBAAgB,KAAK,CAAC,OAAO,GAAG,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;IAClH,IAAI,IAAI,EAAE,eAAe;QAAE,OAAO,GAAG,IAAI,yBAAyB,IAAI,CAAC,MAAM,+CAA+C,CAAC;IAC7H,OAAO,GAAG,IAAI,eAAe,CAAC;AAChC,CAAC"}
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@weactive8/8contentpro",
3
+ "version": "0.1.0",
4
+ "description": "ICP-driven content marketing as a CLI. Runs the WeActive8 Blog Protocol on your own Claude (BYOC).",
5
+ "type": "module",
6
+ "bin": {
7
+ "8contentpro": "dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "scripts": {
13
+ "dev": "tsx src/index.ts",
14
+ "prebuild": "node scripts/stamp.mjs",
15
+ "build": "node scripts/stamp.mjs && tsc -p tsconfig.json",
16
+ "start": "node dist/index.js",
17
+ "typecheck": "tsc --noEmit"
18
+ },
19
+ "engines": {
20
+ "node": ">=18.0.0"
21
+ },
22
+ "dependencies": {
23
+ "@anthropic-ai/claude-agent-sdk": "^0.3.201",
24
+ "@anthropic-ai/sdk": "^0.110.0",
25
+ "@inquirer/prompts": "^8.5.2",
26
+ "@modelcontextprotocol/sdk": "^1.29.0",
27
+ "commander": "^12.1.0",
28
+ "docx": "^9.0.0",
29
+ "ink": "^5.2.1",
30
+ "ink-select-input": "^6.2.0",
31
+ "marked": "^12.0.0",
32
+ "pdfkit": "^0.15.0",
33
+ "react": "^18.3.1",
34
+ "zod": "^4.0.0"
35
+ },
36
+ "devDependencies": {
37
+ "@types/node": "^20.14.0",
38
+ "@types/pdfkit": "^0.13.4",
39
+ "@types/react": "^18.3.31",
40
+ "tsx": "^4.16.0",
41
+ "typescript": "^5.5.0"
42
+ },
43
+ "license": "UNLICENSED",
44
+ "publishConfig": {
45
+ "access": "public"
46
+ }
47
+ }