@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
package/README.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# 8ContentPro
|
|
2
|
+
|
|
3
|
+
**ICP-driven content marketing, as a CLI.** Install it, tell it about your business once, and it researches, plans, writes, and (for WeActive8) publishes on-brand blog posts and gated assets — running the WeActive8 Blog Protocol methodology on your own machine, on your own Claude.
|
|
4
|
+
|
|
5
|
+
> **Product owner:** WeActive8 · **Canonical spec:** `protocols/WeActive8/8ContentPro/CONTENTPRO.md` (AX41)
|
|
6
|
+
|
|
7
|
+
## What makes it different
|
|
8
|
+
|
|
9
|
+
- **Bring Your Own Claude (BYOC).** It runs on *your* Claude — Pro, Max, or your own API key. You pay Anthropic for tokens directly; we charge only for the product. Near-zero model cost.
|
|
10
|
+
- **The methodology is the product.** `methodology/BLOG-PROTOCOL.md` is the exact, unabridged process that produces WeActive8's own content. Nothing is dumbed down for clients.
|
|
11
|
+
- **Learns your ICP once.** A one-time onboarding captures your personas, services, and voice. Every piece after that is written for *your* buyers.
|
|
12
|
+
- **Local drafts, your review.** Client content is written to a `.docx` on your machine. Read it in Word, come back to the CLI, ask for changes, it revises the file.
|
|
13
|
+
|
|
14
|
+
## How it works (two planes)
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
YOUR MACHINE WEACTIVE8 BACKEND
|
|
18
|
+
──────────── ─────────────────
|
|
19
|
+
8contentpro CLI ──TLS──► Intelligence API
|
|
20
|
+
runs on YOUR Claude ◄──model── keyword volumes, live SERP, trends,
|
|
21
|
+
writes .docx locally backlinks, GSC striking-distance
|
|
22
|
+
License + metering
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Every WeActive8 secret stays server-side. The CLI holds only your license and your Claude auth.
|
|
26
|
+
|
|
27
|
+
## Status
|
|
28
|
+
|
|
29
|
+
Pre-alpha. Milestone 0 (repo + methodology core). See `ARCHITECTURE.md` and the roadmap in the canonical spec.
|
|
30
|
+
|
|
31
|
+
## License
|
|
32
|
+
|
|
33
|
+
Proprietary — © WeActive8. Not for redistribution.
|
package/dist/agent.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
2
|
+
import { buildSystemPrompt } from "./methodology/loader.js";
|
|
3
|
+
import { resolveModelAccess } from "./auth.js";
|
|
4
|
+
import { MODELS } from "./models.js";
|
|
5
|
+
export const DEFAULT_MODEL = MODELS.article;
|
|
6
|
+
/** Turn an SDK content block into a short "what it's doing now" status line. */
|
|
7
|
+
function statusFor(block) {
|
|
8
|
+
if (block.type === "tool_use") {
|
|
9
|
+
const q = block.input ?? {};
|
|
10
|
+
if (block.name === "WebSearch" && q.query)
|
|
11
|
+
return `Researching: ${q.query}`;
|
|
12
|
+
if (block.name === "WebFetch" && q.url)
|
|
13
|
+
return `Reading ${new URL(q.url).host}`;
|
|
14
|
+
return "Researching the web";
|
|
15
|
+
}
|
|
16
|
+
if (block.type === "text" && block.text?.trim())
|
|
17
|
+
return "Writing";
|
|
18
|
+
return "Thinking";
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Core run: the Blog Protocol (as system prompt) executed on the operator's own
|
|
22
|
+
* Claude via the Agent SDK. Headless — no interactive permission prompts.
|
|
23
|
+
*
|
|
24
|
+
* M0 uses the SDK's built-in research + file tools. M2 registers the WeActive8
|
|
25
|
+
* Intelligence API (keyword volumes / SERP / trends / backlinks / GSC) as MCP
|
|
26
|
+
* tools layered on top.
|
|
27
|
+
*/
|
|
28
|
+
export async function run(opts) {
|
|
29
|
+
// Resolve model access (Managed license -> proxy, or internal dev key) and set
|
|
30
|
+
// the env the Agent SDK reads. Throws AccessError with guidance if neither.
|
|
31
|
+
resolveModelAccess();
|
|
32
|
+
const systemPrompt = buildSystemPrompt(opts.profile);
|
|
33
|
+
let output = "";
|
|
34
|
+
let costUsd;
|
|
35
|
+
let turns;
|
|
36
|
+
let outTokens = 0;
|
|
37
|
+
for await (const message of query({
|
|
38
|
+
prompt: opts.brief,
|
|
39
|
+
options: {
|
|
40
|
+
// The Blog Protocol replaces the default coding-agent prompt entirely:
|
|
41
|
+
// this is a content agent, not a code agent.
|
|
42
|
+
systemPrompt,
|
|
43
|
+
model: opts.model ?? DEFAULT_MODEL,
|
|
44
|
+
cwd: opts.workdir,
|
|
45
|
+
// Web-only: research + fetch uploaded reference URLs. NO Read/Write/Glob/Grep
|
|
46
|
+
// — 8ContentPro never touches the user's disk (privacy stance). Uploaded files
|
|
47
|
+
// come in as https URLs the model fetches, not local paths.
|
|
48
|
+
allowedTools: ["WebSearch", "WebFetch"],
|
|
49
|
+
permissionMode: "acceptEdits",
|
|
50
|
+
// Backstop against runaway agentic loops (Sonnet was hitting ~25 turns of
|
|
51
|
+
// one-search-at-a-time). Generous enough not to truncate a real research pass.
|
|
52
|
+
maxTurns: 18,
|
|
53
|
+
...(opts.abortController ? { abortController: opts.abortController } : {}),
|
|
54
|
+
},
|
|
55
|
+
})) {
|
|
56
|
+
if (message.type === "assistant") {
|
|
57
|
+
// Surface WHAT it's doing (a short status), not the draft prose itself.
|
|
58
|
+
if (opts.onProgress)
|
|
59
|
+
for (const block of message.message.content)
|
|
60
|
+
opts.onProgress(statusFor(block));
|
|
61
|
+
const u = message.message.usage;
|
|
62
|
+
if (u?.output_tokens)
|
|
63
|
+
opts.onTokens?.((outTokens += u.output_tokens));
|
|
64
|
+
}
|
|
65
|
+
if (message.type === "result" && message.subtype === "success") {
|
|
66
|
+
output = message.result;
|
|
67
|
+
costUsd = message.total_cost_usd;
|
|
68
|
+
turns = message.num_turns;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return { output, costUsd, turns };
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Apply the operator's requested changes to an existing draft and return the
|
|
75
|
+
* full revised Markdown. Same methodology + voice; a targeted edit, not a rewrite,
|
|
76
|
+
* unless they ask for a fresh angle.
|
|
77
|
+
*/
|
|
78
|
+
export async function revise(opts) {
|
|
79
|
+
resolveModelAccess();
|
|
80
|
+
const systemPrompt = buildSystemPrompt(opts.profile);
|
|
81
|
+
const prompt = [
|
|
82
|
+
"Here is the current draft (Markdown):",
|
|
83
|
+
"",
|
|
84
|
+
"---",
|
|
85
|
+
opts.current,
|
|
86
|
+
"---",
|
|
87
|
+
"",
|
|
88
|
+
`The operator wants these changes: ${opts.instruction}`,
|
|
89
|
+
"",
|
|
90
|
+
"Apply the changes with full Blog Protocol rigor and the same voice. Modify the",
|
|
91
|
+
"existing piece rather than starting over unless a fresh angle is requested.",
|
|
92
|
+
"Return ONLY the complete revised piece as clean Markdown — no commentary.",
|
|
93
|
+
].join("\n");
|
|
94
|
+
let output = "";
|
|
95
|
+
for await (const message of query({
|
|
96
|
+
prompt,
|
|
97
|
+
options: {
|
|
98
|
+
systemPrompt,
|
|
99
|
+
model: opts.model ?? DEFAULT_MODEL,
|
|
100
|
+
allowedTools: ["WebSearch", "WebFetch"], // web-only; never touches the disk
|
|
101
|
+
permissionMode: "acceptEdits",
|
|
102
|
+
},
|
|
103
|
+
})) {
|
|
104
|
+
if (message.type === "assistant" && opts.onProgress) {
|
|
105
|
+
for (const block of message.message.content) {
|
|
106
|
+
if (block.type === "text")
|
|
107
|
+
opts.onProgress(block.text);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (message.type === "result" && message.subtype === "success")
|
|
111
|
+
output = message.result;
|
|
112
|
+
}
|
|
113
|
+
return { output };
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=agent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.js","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"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"}
|
package/dist/attach.js
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { exec } from "node:child_process";
|
|
2
|
+
import { startSpinner, updateSpinner, stopSpinner } from "./ui/spinner.js";
|
|
3
|
+
/**
|
|
4
|
+
* Attach a file for context — the privacy-preserving way. The CLI never reads the
|
|
5
|
+
* user's disk. Instead it opens a browser to the WeActive8 upload page, the user
|
|
6
|
+
* chooses a file, and it comes back as an https URL that Claude fetches during
|
|
7
|
+
* generation. See server/upload.mjs (https://8cp-upload.wa8.us).
|
|
8
|
+
*/
|
|
9
|
+
const UPLOAD_BASE = process.env.CONTENTPRO_UPLOAD_URL?.trim() || "https://8cp-upload.wa8.us";
|
|
10
|
+
export function openBrowser(url) {
|
|
11
|
+
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? 'start ""' : "xdg-open";
|
|
12
|
+
exec(`${cmd} "${url}"`, () => { }); // best-effort; the URL is also printed for manual open
|
|
13
|
+
}
|
|
14
|
+
/** Create an upload session (for a front-end that drives its own polling UI, e.g. Ink). */
|
|
15
|
+
export async function createUploadSession() {
|
|
16
|
+
try {
|
|
17
|
+
const res = await fetch(`${UPLOAD_BASE}/new`);
|
|
18
|
+
if (!res.ok)
|
|
19
|
+
return null;
|
|
20
|
+
return (await res.json());
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/** Poll an upload session once. Returns done + the files accumulated so far. */
|
|
27
|
+
export async function pollUploadStatus(statusUrl) {
|
|
28
|
+
try {
|
|
29
|
+
const s = await fetch(statusUrl);
|
|
30
|
+
if (!s.ok)
|
|
31
|
+
return { done: false, files: [] };
|
|
32
|
+
const j = (await s.json());
|
|
33
|
+
return { done: Boolean(j.done), files: j.files ?? [] };
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return { done: false, files: [] };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Open the upload page, wait (polling) for the user to add one OR MORE files and
|
|
41
|
+
* click Done, and return their public URLs. Returns [] on timeout / unreachable.
|
|
42
|
+
* If the user uploads files but the window times out before Done, we still return
|
|
43
|
+
* whatever was uploaded rather than lose it.
|
|
44
|
+
*/
|
|
45
|
+
export async function attachFile(onLog, timeoutMs = 300_000) {
|
|
46
|
+
let session;
|
|
47
|
+
try {
|
|
48
|
+
const res = await fetch(`${UPLOAD_BASE}/new`);
|
|
49
|
+
if (!res.ok)
|
|
50
|
+
throw new Error(String(res.status));
|
|
51
|
+
session = (await res.json());
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
onLog("Couldn't reach the upload service — continuing without an attachment.");
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
onLog(`Opening your browser: ${session.uploadUrl}`);
|
|
58
|
+
onLog("Paste screenshots (⌘V / Ctrl+V), drag files in, or choose them — add as many as you like, then click Done. (We never access your machine.)");
|
|
59
|
+
openBrowser(session.uploadUrl);
|
|
60
|
+
// Suppress the terminal while we wait: no inquirer prompt is active, so without this
|
|
61
|
+
// the user's keystrokes (arrow keys → ^[[A etc.) echo as raw garbage. Raw mode kills
|
|
62
|
+
// the echo; we drain input and still honor Ctrl+C.
|
|
63
|
+
const tin = process.stdin;
|
|
64
|
+
const wasRaw = Boolean(tin.isRaw);
|
|
65
|
+
let cancelled = false;
|
|
66
|
+
const drain = (d) => {
|
|
67
|
+
if (d.includes(3)) {
|
|
68
|
+
// Ctrl+C — hard exit (raw mode suppressed the default SIGINT).
|
|
69
|
+
stopSpinner();
|
|
70
|
+
restore();
|
|
71
|
+
process.exit(130);
|
|
72
|
+
}
|
|
73
|
+
else if (d.length === 1 && d[0] === 27) {
|
|
74
|
+
// A lone Escape (not an arrow-key ESC-[ sequence) cancels the wait.
|
|
75
|
+
cancelled = true;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
const restore = () => {
|
|
79
|
+
tin.off("data", drain);
|
|
80
|
+
if (tin.isTTY) {
|
|
81
|
+
try {
|
|
82
|
+
tin.setRawMode(wasRaw);
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
/* ignore */
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
if (tin.isTTY) {
|
|
90
|
+
try {
|
|
91
|
+
tin.setRawMode(true);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
/* ignore */
|
|
95
|
+
}
|
|
96
|
+
tin.resume();
|
|
97
|
+
tin.on("data", drain);
|
|
98
|
+
}
|
|
99
|
+
startSpinner("Waiting for your upload in the browser…");
|
|
100
|
+
try {
|
|
101
|
+
const deadline = Date.now() + timeoutMs;
|
|
102
|
+
let seen = [];
|
|
103
|
+
while (Date.now() < deadline) {
|
|
104
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
105
|
+
if (cancelled) {
|
|
106
|
+
stopSpinner();
|
|
107
|
+
onLog(seen.length ? `✓ Kept the ${seen.length} file(s) you uploaded.` : "Cancelled — continuing without an attachment.");
|
|
108
|
+
return seen;
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
const s = await fetch(session.statusUrl);
|
|
112
|
+
if (s.ok) {
|
|
113
|
+
const j = (await s.json());
|
|
114
|
+
if (j.files && j.files.length !== seen.length) {
|
|
115
|
+
seen = j.files;
|
|
116
|
+
updateSpinner(`${seen.length} file(s) uploaded — click "Done" in the browser when finished`);
|
|
117
|
+
}
|
|
118
|
+
else if (j.files?.length) {
|
|
119
|
+
seen = j.files;
|
|
120
|
+
}
|
|
121
|
+
if (j.done) {
|
|
122
|
+
stopSpinner();
|
|
123
|
+
onLog(`✓ Attached ${seen.length} file(s): ${seen.map((f) => f.filename).join(", ")}`);
|
|
124
|
+
return seen;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
/* keep polling */
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
stopSpinner();
|
|
133
|
+
if (seen.length) {
|
|
134
|
+
onLog(`✓ Kept the ${seen.length} file(s) you uploaded (window timed out before Done).`);
|
|
135
|
+
return seen;
|
|
136
|
+
}
|
|
137
|
+
onLog("No files uploaded (timed out). Continuing without an attachment.");
|
|
138
|
+
return [];
|
|
139
|
+
}
|
|
140
|
+
finally {
|
|
141
|
+
restore();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=attach.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attach.js","sourceRoot":"","sources":["../src/attach.ts"],"names":[],"mappings":"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"}
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model access resolution.
|
|
3
|
+
*
|
|
4
|
+
* PRODUCT DEFAULT = Managed (DECISION 2026-07-06). The customer never holds a
|
|
5
|
+
* real Anthropic key. They authenticate with their 8ContentPro LICENSE, which
|
|
6
|
+
* the CLI passes as a Bearer token (ANTHROPIC_AUTH_TOKEN) to the WeActive8
|
|
7
|
+
* metering proxy (ANTHROPIC_BASE_URL). The proxy validates the license, meters
|
|
8
|
+
* credits, injects our real Anthropic key server-side, and forwards to
|
|
9
|
+
* api.anthropic.com. Our key never ships in the CLI.
|
|
10
|
+
*
|
|
11
|
+
* Verified against @anthropic-ai/claude-agent-sdk ^0.3.201: the SDK honors
|
|
12
|
+
* ANTHROPIC_BASE_URL and ANTHROPIC_AUTH_TOKEN (-> `Authorization: Bearer`),
|
|
13
|
+
* which takes precedence over ANTHROPIC_API_KEY. Auth travels in the header,
|
|
14
|
+
* not query params (avoids SDK base-URL query-encoding bug, gh issue #195).
|
|
15
|
+
*
|
|
16
|
+
* BYOC (the client's own Claude Pro/Max) is deferred until Anthropic approves
|
|
17
|
+
* third-party subscription-login.
|
|
18
|
+
*
|
|
19
|
+
* INTERNAL/DEV mode: if a real ANTHROPIC_API_KEY is present and no license, the
|
|
20
|
+
* CLI talks straight to Anthropic (WeActive8's own instance / local dev), so we
|
|
21
|
+
* can build and test M1-M3 before the proxy (M4) exists.
|
|
22
|
+
*/
|
|
23
|
+
import { loadAuthToken } from "./brand.js";
|
|
24
|
+
// The managed gateway also serves the account portal + device-login on the same host.
|
|
25
|
+
const DEFAULT_PROXY_URL = "https://8cp.wa8.us/v1";
|
|
26
|
+
export class AccessError extends Error {
|
|
27
|
+
}
|
|
28
|
+
/** Sets the env the Agent SDK reads, and returns the resolved access mode. */
|
|
29
|
+
export function resolveModelAccess() {
|
|
30
|
+
// Managed auth = the token from `8contentpro login` (or a CONTENTPRO_LICENSE override).
|
|
31
|
+
const token = process.env.CONTENTPRO_LICENSE?.trim() || loadAuthToken();
|
|
32
|
+
if (token) {
|
|
33
|
+
const proxyUrl = process.env.CONTENTPRO_PROXY_URL?.trim() || DEFAULT_PROXY_URL;
|
|
34
|
+
process.env.ANTHROPIC_BASE_URL = proxyUrl;
|
|
35
|
+
process.env.ANTHROPIC_AUTH_TOKEN = token; // -> Authorization: Bearer <token>
|
|
36
|
+
delete process.env.ANTHROPIC_API_KEY; // never let a stray key bypass the proxy
|
|
37
|
+
return { mode: "managed", proxyUrl };
|
|
38
|
+
}
|
|
39
|
+
if (process.env.ANTHROPIC_API_KEY?.trim()) {
|
|
40
|
+
// WeActive8 internal / local dev: straight to Anthropic on our own key.
|
|
41
|
+
return { mode: "dev-direct" };
|
|
42
|
+
}
|
|
43
|
+
// LOCAL mode: no license, no API key. Let the Agent SDK resolve whatever ambient
|
|
44
|
+
// Claude credential exists — your Claude Code / Max login on your own machine.
|
|
45
|
+
// This is how the founder tests 8ContentPro in their terminal for free (own plan).
|
|
46
|
+
// The SDK surfaces its own clear auth error if no credential is available.
|
|
47
|
+
return { mode: "local" };
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=auth.js.map
|
package/dist/auth.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C,sFAAsF;AACtF,MAAM,iBAAiB,GAAG,uBAAuB,CAAC;AAOlD,MAAM,OAAO,WAAY,SAAQ,KAAK;CAAG;AAEzC,8EAA8E;AAC9E,MAAM,UAAU,kBAAkB;IAChC,wFAAwF;IACxF,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,EAAE,IAAI,EAAE,IAAI,aAAa,EAAE,CAAC;IACxE,IAAI,KAAK,EAAE,CAAC;QACV,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,oBAAoB,EAAE,IAAI,EAAE,IAAI,iBAAiB,CAAC;QAC/E,OAAO,CAAC,GAAG,CAAC,kBAAkB,GAAG,QAAQ,CAAC;QAC1C,OAAO,CAAC,GAAG,CAAC,oBAAoB,GAAG,KAAK,CAAC,CAAC,mCAAmC;QAC7E,OAAO,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC,yCAAyC;QAC/E,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC;IACvC,CAAC;IAED,IAAI,OAAO,CAAC,GAAG,CAAC,iBAAiB,EAAE,IAAI,EAAE,EAAE,CAAC;QAC1C,wEAAwE;QACxE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC;IAChC,CAAC;IAED,iFAAiF;IACjF,+EAA+E;IAC/E,mFAAmF;IACnF,2EAA2E;IAC3E,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;AAC3B,CAAC"}
|
package/dist/brand.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/** Terminal branding for 8ContentPro — a distinctive, WeActive8-branded splash.
|
|
2
|
+
* White-labelable: an org can carry its own ASCII mark (derived from its logo) + color +
|
|
3
|
+
* company name, so the product reads as "{Company} Content Pro" while the ENGINE name
|
|
4
|
+
* ("8ContentPro") stays fixed everywhere — it's the model, not renameable. */
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
8
|
+
/** The fixed engine/model name — shown in every splash, never white-labeled. */
|
|
9
|
+
export const ENGINE_NAME = "8ContentPro";
|
|
10
|
+
/** Our own default mark (the WeActive8 "8"). */
|
|
11
|
+
export const DEFAULT_MARK = [" ▄████▄ ", " ██▀ ▀██", " ▀██▄▄██▀", " ██▀ ▀██", " ▀████▀ "];
|
|
12
|
+
function configPath() {
|
|
13
|
+
const dir = join(homedir(), ".8contentpro");
|
|
14
|
+
mkdirSync(dir, { recursive: true });
|
|
15
|
+
return join(dir, "config.json");
|
|
16
|
+
}
|
|
17
|
+
function readConfig() {
|
|
18
|
+
try {
|
|
19
|
+
return JSON.parse(readFileSync(configPath(), "utf8"));
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return {};
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function writeConfig(c) {
|
|
26
|
+
writeFileSync(configPath(), JSON.stringify(c, null, 2), "utf8");
|
|
27
|
+
}
|
|
28
|
+
/** Load the local brand (a stand-in until the org's branding arrives from the account). */
|
|
29
|
+
export function loadBrand() {
|
|
30
|
+
const b = readConfig().brand;
|
|
31
|
+
return b && typeof b === "object" ? b : {};
|
|
32
|
+
}
|
|
33
|
+
export function saveBrand(b) {
|
|
34
|
+
const c = readConfig();
|
|
35
|
+
c.brand = { ...(c.brand || {}), ...b };
|
|
36
|
+
writeConfig(c);
|
|
37
|
+
}
|
|
38
|
+
/** The product name shown to their team: "{Company} Content Pro" (engine name if unbranded). */
|
|
39
|
+
export function productName(b = loadBrand()) {
|
|
40
|
+
return b.companyName ? `${b.companyName} Content Pro` : ENGINE_NAME;
|
|
41
|
+
}
|
|
42
|
+
/** The access token from `8contentpro login` (the Bearer the CLI sends to the gateway). */
|
|
43
|
+
export function loadAuthToken() {
|
|
44
|
+
const t = readConfig().authToken;
|
|
45
|
+
return typeof t === "string" && t ? t : undefined;
|
|
46
|
+
}
|
|
47
|
+
export function saveAuthToken(token) {
|
|
48
|
+
const c = readConfig();
|
|
49
|
+
c.authToken = token;
|
|
50
|
+
writeConfig(c);
|
|
51
|
+
}
|
|
52
|
+
export function clearAuthToken() {
|
|
53
|
+
const c = readConfig();
|
|
54
|
+
delete c.authToken;
|
|
55
|
+
delete c.brand; // branding is org-owned; forget it on logout
|
|
56
|
+
writeConfig(c);
|
|
57
|
+
}
|
|
58
|
+
const useColor = Boolean(process.stdout.isTTY) && !process.env.NO_COLOR;
|
|
59
|
+
const E = {
|
|
60
|
+
reset: "\x1b[0m",
|
|
61
|
+
bold: "\x1b[1m",
|
|
62
|
+
dim: "\x1b[2m",
|
|
63
|
+
cyan: "\x1b[36m",
|
|
64
|
+
brightCyan: "\x1b[96m",
|
|
65
|
+
white: "\x1b[97m",
|
|
66
|
+
gray: "\x1b[90m",
|
|
67
|
+
};
|
|
68
|
+
function c(code, s) {
|
|
69
|
+
return useColor ? code + s + E.reset : s;
|
|
70
|
+
}
|
|
71
|
+
/** Map a brand color name to an ANSI escape (falls back to bright cyan). */
|
|
72
|
+
const ANSI = {
|
|
73
|
+
cyan: "\x1b[96m",
|
|
74
|
+
green: "\x1b[92m",
|
|
75
|
+
blue: "\x1b[94m",
|
|
76
|
+
red: "\x1b[91m",
|
|
77
|
+
magenta: "\x1b[95m",
|
|
78
|
+
yellow: "\x1b[93m",
|
|
79
|
+
white: "\x1b[97m",
|
|
80
|
+
gray: "\x1b[90m",
|
|
81
|
+
};
|
|
82
|
+
/** Full branded splash — shown on `--help` / no command / start of onboarding. */
|
|
83
|
+
export function splash() {
|
|
84
|
+
const b = loadBrand();
|
|
85
|
+
const mark = b.mark?.length ? b.mark : DEFAULT_MARK;
|
|
86
|
+
const markColor = ANSI[b.color ?? ""] ?? E.brightCyan;
|
|
87
|
+
const right = [
|
|
88
|
+
"",
|
|
89
|
+
c(markColor + E.bold, productName(b)),
|
|
90
|
+
c(E.gray, "content that reads human"),
|
|
91
|
+
c(E.gray, `${ENGINE_NAME} engine`),
|
|
92
|
+
b.companyName ? "" : c(E.dim, "by ") + c(E.cyan, "WeActive8"),
|
|
93
|
+
];
|
|
94
|
+
const lines = mark.map((m, i) => " " + c(markColor, m) + " " + (right[i] ?? ""));
|
|
95
|
+
return "\n" + lines.join("\n") + "\n";
|
|
96
|
+
}
|
|
97
|
+
/** Compact one-line header shown above a running command. */
|
|
98
|
+
export function tag(text) {
|
|
99
|
+
return c(E.cyan + E.bold, " ● 8ContentPro") + c(E.gray, " " + text);
|
|
100
|
+
}
|
|
101
|
+
/** A step/status line in the branded style (left accent bar). */
|
|
102
|
+
export function step(text) {
|
|
103
|
+
return c(E.cyan, " ▎ ") + text;
|
|
104
|
+
}
|
|
105
|
+
/** Clear the terminal (and scrollback) — used on restart so it feels fresh. */
|
|
106
|
+
export function clearScreen() {
|
|
107
|
+
if (process.stdout.isTTY)
|
|
108
|
+
process.stdout.write("\x1b[2J\x1b[3J\x1b[H");
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=brand.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"brand.js","sourceRoot":"","sources":["../src/brand.ts"],"names":[],"mappings":"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"}
|
package/dist/brandgen.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { extname } from "node:path";
|
|
4
|
+
import { resolveModelAccess } from "./auth.js";
|
|
5
|
+
import { MODELS } from "./models.js";
|
|
6
|
+
/**
|
|
7
|
+
* White-label mark generation. The org uploads its logo ONCE; a vision pass reads it and
|
|
8
|
+
* redraws the core symbol as a tiny monospace ASCII mark (like our "8"), and picks the
|
|
9
|
+
* brand's primary color. That mark + color replace the 8 in the splash. The raster logo
|
|
10
|
+
* itself is kept separately for the web pages + gated-PDF covers.
|
|
11
|
+
*
|
|
12
|
+
* This is a one-time, org-level step (branding is shared by all seats), so the cost is
|
|
13
|
+
* negligible and never on the article path.
|
|
14
|
+
*/
|
|
15
|
+
const IMG_MEDIA = { ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".webp": "image/webp" };
|
|
16
|
+
const COLORS = ["cyan", "green", "blue", "red", "magenta", "yellow", "white"];
|
|
17
|
+
const FENCE = /```json\s*([\s\S]*?)```|(\{[\s\S]*\})/;
|
|
18
|
+
async function imageBlock(source) {
|
|
19
|
+
let bytes;
|
|
20
|
+
let media;
|
|
21
|
+
if (/^https?:\/\//i.test(source)) {
|
|
22
|
+
const r = await fetch(source);
|
|
23
|
+
bytes = Buffer.from(await r.arrayBuffer());
|
|
24
|
+
media = r.headers.get("content-type")?.split(";")[0] || "image/png";
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
bytes = readFileSync(source);
|
|
28
|
+
media = IMG_MEDIA[extname(source).toLowerCase()] || "image/png";
|
|
29
|
+
}
|
|
30
|
+
return { type: "image", source: { type: "base64", media_type: media, data: bytes.toString("base64") } };
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Read a logo image (local path or URL) and return an ASCII mark + brand color. The mark
|
|
34
|
+
* is 5 rows tall and ~9 columns wide, using block glyphs, capturing the logo's core
|
|
35
|
+
* SYMBOL (not its wordmark) so it reads as the brand at a glance in a terminal.
|
|
36
|
+
*/
|
|
37
|
+
export async function logoToAsciiMark(source, model = MODELS.ideate) {
|
|
38
|
+
resolveModelAccess();
|
|
39
|
+
const img = await imageBlock(source);
|
|
40
|
+
const systemPrompt = [
|
|
41
|
+
"You turn a company logo into a tiny terminal ASCII mark, the way a retro terminal would draw a brand.",
|
|
42
|
+
"Look at the logo and focus on its core SYMBOL / icon (a checkmark, a shield, a circle, a monogram) — NOT the text wordmark.",
|
|
43
|
+
"Redraw that symbol as monospace ASCII art that is EXACTLY 5 lines tall and 9 characters wide per line (pad with spaces).",
|
|
44
|
+
"Use only these block glyphs and space: █ ▀ ▄ ▌ ▐ ▖ ▗ ▘ ▝ ▚ ▞ ◤ ◥ ◣ ◢ . It must read as the symbol at a glance.",
|
|
45
|
+
`Also pick the brand's single primary color from EXACTLY this list: ${COLORS.join(", ")}.`,
|
|
46
|
+
'Respond with ONLY JSON: {"mark": ["line1","line2","line3","line4","line5"], "color": "<one of the list>"}. Each mark line must be 9 chars.',
|
|
47
|
+
].join("\n");
|
|
48
|
+
const prompt = (async function* () {
|
|
49
|
+
yield { type: "user", message: { role: "user", content: [{ type: "text", text: "Draw this logo's symbol as a 5x9 ASCII mark and give its primary color." }, img] }, parent_tool_use_id: null };
|
|
50
|
+
})();
|
|
51
|
+
let out = "";
|
|
52
|
+
for await (const m of query({ prompt, options: { systemPrompt, model, allowedTools: [], permissionMode: "acceptEdits" } })) {
|
|
53
|
+
if (m.type === "result" && m.subtype === "success")
|
|
54
|
+
out = m.result;
|
|
55
|
+
}
|
|
56
|
+
const raw = out.match(FENCE);
|
|
57
|
+
const json = raw ? (raw[1] || raw[2]) : "";
|
|
58
|
+
let parsed;
|
|
59
|
+
try {
|
|
60
|
+
parsed = JSON.parse(json);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
parsed = {};
|
|
64
|
+
}
|
|
65
|
+
const color = COLORS.includes((parsed.color ?? "")) ? parsed.color : "cyan";
|
|
66
|
+
const mark = (parsed.mark ?? []).slice(0, 5).map((l) => (l || "").slice(0, 9).padEnd(9, " "));
|
|
67
|
+
if (mark.length !== 5)
|
|
68
|
+
throw new Error("Could not derive a mark from that logo — try a clearer square image of just the icon.");
|
|
69
|
+
return { mark, color };
|
|
70
|
+
}
|
|
71
|
+
/** Build the full org brand from a company name + logo image. */
|
|
72
|
+
export async function buildBrand(companyName, logoSource) {
|
|
73
|
+
const { mark, color } = await logoToAsciiMark(logoSource);
|
|
74
|
+
return { companyName: companyName.trim(), mark, color, logoPath: logoSource };
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=brandgen.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"brandgen.js","sourceRoot":"","sources":["../src/brandgen.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-info.js","sourceRoot":"","sources":["../src/build-info.ts"],"names":[],"mappings":"AAAA,mEAAmE;AACnE,MAAM,CAAC,MAAM,KAAK,GAAyD,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,0BAA0B,EAAE,CAAC"}
|