@growthub/cli 0.3.56 → 0.3.58
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/assets/worker-kits/growthub-zernio-social-v1/QUICKSTART.md +209 -0
- package/assets/worker-kits/growthub-zernio-social-v1/brands/NEW-CLIENT.md +74 -0
- package/assets/worker-kits/growthub-zernio-social-v1/brands/_template/brand-kit.md +131 -0
- package/assets/worker-kits/growthub-zernio-social-v1/brands/growthub/brand-kit.md +141 -0
- package/assets/worker-kits/growthub-zernio-social-v1/bundles/growthub-zernio-social-v1.json +55 -0
- package/assets/worker-kits/growthub-zernio-social-v1/docs/ai-caption-layer.md +132 -0
- package/assets/worker-kits/growthub-zernio-social-v1/docs/local-adapters.md +123 -0
- package/assets/worker-kits/growthub-zernio-social-v1/docs/platform-coverage.md +112 -0
- package/assets/worker-kits/growthub-zernio-social-v1/docs/postiz-ui-shell-integration.md +166 -0
- package/assets/worker-kits/growthub-zernio-social-v1/docs/posts-and-queues-layer.md +208 -0
- package/assets/worker-kits/growthub-zernio-social-v1/docs/zernio-api-integration.md +265 -0
- package/assets/worker-kits/growthub-zernio-social-v1/examples/analytics-brief-sample.md +97 -0
- package/assets/worker-kits/growthub-zernio-social-v1/examples/client-proposal-sample.md +106 -0
- package/assets/worker-kits/growthub-zernio-social-v1/examples/content-calendar-sample.md +74 -0
- package/assets/worker-kits/growthub-zernio-social-v1/examples/social-campaign-sample.md +105 -0
- package/assets/worker-kits/growthub-zernio-social-v1/growthub-meta/README.md +146 -0
- package/assets/worker-kits/growthub-zernio-social-v1/growthub-meta/kit-standard.md +120 -0
- package/assets/worker-kits/growthub-zernio-social-v1/kit.json +104 -0
- package/assets/worker-kits/growthub-zernio-social-v1/output/README.md +63 -0
- package/assets/worker-kits/growthub-zernio-social-v1/output-standards.md +132 -0
- package/assets/worker-kits/growthub-zernio-social-v1/runtime-assumptions.md +170 -0
- package/assets/worker-kits/growthub-zernio-social-v1/setup/check-deps.mjs +117 -0
- package/assets/worker-kits/growthub-zernio-social-v1/setup/check-deps.sh +86 -0
- package/assets/worker-kits/growthub-zernio-social-v1/setup/install-mcp.mjs +177 -0
- package/assets/worker-kits/growthub-zernio-social-v1/setup/setup.mjs +247 -0
- package/assets/worker-kits/growthub-zernio-social-v1/setup/verify-env.mjs +138 -0
- package/assets/worker-kits/growthub-zernio-social-v1/skills.md +332 -0
- package/assets/worker-kits/growthub-zernio-social-v1/templates/analytics-brief.md +101 -0
- package/assets/worker-kits/growthub-zernio-social-v1/templates/caption-copy-deck.md +105 -0
- package/assets/worker-kits/growthub-zernio-social-v1/templates/client-proposal.md +98 -0
- package/assets/worker-kits/growthub-zernio-social-v1/templates/content-calendar.md +70 -0
- package/assets/worker-kits/growthub-zernio-social-v1/templates/platform-publishing-plan.md +86 -0
- package/assets/worker-kits/growthub-zernio-social-v1/templates/scheduling-manifest.md +92 -0
- package/assets/worker-kits/growthub-zernio-social-v1/templates/social-campaign-brief.md +102 -0
- package/assets/worker-kits/growthub-zernio-social-v1/validation-checklist.md +85 -0
- package/assets/worker-kits/growthub-zernio-social-v1/workers/zernio-social-operator/CLAUDE.md +307 -0
- package/package.json +1 -1
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// setup.mjs — one-command cross-platform bootstrap for the Zernio Social Media Studio
|
|
3
|
+
//
|
|
4
|
+
// Works identically on macOS, Linux, and Windows (PowerShell, cmd, WSL, git-bash).
|
|
5
|
+
// Usage:
|
|
6
|
+
// node setup/setup.mjs # full bootstrap (deps + env + verify)
|
|
7
|
+
// node setup/setup.mjs --skip-deps # skip dependency check
|
|
8
|
+
// node setup/setup.mjs --skip-verify # skip live API reachability check
|
|
9
|
+
// node setup/setup.mjs --yes # no-prompt mode (CI-safe)
|
|
10
|
+
//
|
|
11
|
+
// What it does (in order):
|
|
12
|
+
// 1. Prints the kit banner and detects the host OS.
|
|
13
|
+
// 2. Runs the cross-platform dependency check (Node 18+, curl optional, git optional).
|
|
14
|
+
// 3. If .env is missing, copies .env.example to .env verbatim.
|
|
15
|
+
// 4. Runs verify-env.mjs to confirm ZERNIO_API_KEY format + reachability.
|
|
16
|
+
// 5. Prints the exact next step for the user's OS.
|
|
17
|
+
//
|
|
18
|
+
// No external dependencies. Pure Node stdlib only.
|
|
19
|
+
|
|
20
|
+
import { existsSync, copyFileSync, readFileSync, statSync } from "node:fs";
|
|
21
|
+
import { join, dirname, resolve } from "node:path";
|
|
22
|
+
import { fileURLToPath } from "node:url";
|
|
23
|
+
import { spawn } from "node:child_process";
|
|
24
|
+
import { platform, release } from "node:os";
|
|
25
|
+
|
|
26
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
27
|
+
const __dirname = dirname(__filename);
|
|
28
|
+
const KIT_ROOT = resolve(__dirname, "..");
|
|
29
|
+
|
|
30
|
+
const argv = new Set(process.argv.slice(2));
|
|
31
|
+
const SKIP_DEPS = argv.has("--skip-deps");
|
|
32
|
+
const SKIP_VERIFY = argv.has("--skip-verify");
|
|
33
|
+
const YES = argv.has("--yes");
|
|
34
|
+
|
|
35
|
+
const RED = "\x1b[31m";
|
|
36
|
+
const GREEN = "\x1b[32m";
|
|
37
|
+
const YELLOW = "\x1b[33m";
|
|
38
|
+
const BLUE = "\x1b[34m";
|
|
39
|
+
const BOLD = "\x1b[1m";
|
|
40
|
+
const DIM = "\x1b[2m";
|
|
41
|
+
const RESET = "\x1b[0m";
|
|
42
|
+
|
|
43
|
+
function log(msg) { console.log(msg); }
|
|
44
|
+
function step(n, label) { log(""); log(`${BOLD}${BLUE}[${n}/4]${RESET} ${BOLD}${label}${RESET}`); }
|
|
45
|
+
function ok(msg) { log(` ${GREEN}\u2713${RESET} ${msg}`); }
|
|
46
|
+
function warn(msg) { log(` ${YELLOW}!${RESET} ${msg}`); }
|
|
47
|
+
function fail(msg) { log(` ${RED}\u2717${RESET} ${msg}`); }
|
|
48
|
+
|
|
49
|
+
function detectOs() {
|
|
50
|
+
const p = platform();
|
|
51
|
+
if (p === "darwin") return { id: "mac", pretty: "macOS" };
|
|
52
|
+
if (p === "win32") return { id: "windows", pretty: "Windows" };
|
|
53
|
+
if (p === "linux") return { id: "linux", pretty: "Linux" };
|
|
54
|
+
return { id: p, pretty: p };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function run(cmd, args, opts = {}) {
|
|
58
|
+
return new Promise((resolvePromise) => {
|
|
59
|
+
const child = spawn(cmd, args, { stdio: "inherit", shell: false, ...opts });
|
|
60
|
+
child.on("close", (code) => resolvePromise(code ?? 1));
|
|
61
|
+
child.on("error", () => resolvePromise(1));
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function printBanner(osInfo) {
|
|
66
|
+
log("");
|
|
67
|
+
log(`${BOLD}Growthub Zernio Social Media Studio${RESET} \u2014 one-command setup`);
|
|
68
|
+
log(`${DIM}kit: growthub-zernio-social-v1 \u00b7 host: ${osInfo.pretty} (${platform()} ${release()}) \u00b7 node: ${process.version}${RESET}`);
|
|
69
|
+
log("");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// Step 1 \u2014 Dependency check (cross-platform, node-native)
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
function checkNodeVersion() {
|
|
77
|
+
const major = Number.parseInt(process.versions.node.split(".")[0], 10);
|
|
78
|
+
if (!Number.isFinite(major) || major < 18) {
|
|
79
|
+
fail(`Node.js ${process.version} detected \u2014 Node 18+ required. Install from https://nodejs.org`);
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
ok(`Node.js ${process.version} (>= 18 required)`);
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function which(binary) {
|
|
87
|
+
// Cross-platform "which": spawn a lookup with proper extension handling.
|
|
88
|
+
return new Promise((resolvePromise) => {
|
|
89
|
+
const isWin = platform() === "win32";
|
|
90
|
+
const lookup = isWin ? "where" : "which";
|
|
91
|
+
const child = spawn(lookup, [binary], { shell: false });
|
|
92
|
+
let out = "";
|
|
93
|
+
child.stdout?.on("data", (d) => (out += String(d)));
|
|
94
|
+
child.on("close", (code) => resolvePromise(code === 0 ? out.trim().split(/\r?\n/)[0] : null));
|
|
95
|
+
child.on("error", () => resolvePromise(null));
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function runDepsCheck() {
|
|
100
|
+
step(1, "Checking dependencies");
|
|
101
|
+
if (SKIP_DEPS) {
|
|
102
|
+
warn("--skip-deps passed \u2014 skipping dependency check");
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
let allGood = true;
|
|
106
|
+
if (!checkNodeVersion()) allGood = false;
|
|
107
|
+
|
|
108
|
+
const curlPath = await which("curl");
|
|
109
|
+
if (curlPath) ok(`curl at ${curlPath}`);
|
|
110
|
+
else warn("curl not found \u2014 not required (Node's built-in fetch is used), but recommended for manual API testing");
|
|
111
|
+
|
|
112
|
+
const gitPath = await which("git");
|
|
113
|
+
if (gitPath) ok(`git at ${gitPath}`);
|
|
114
|
+
else warn("git not found \u2014 not required for the kit, but recommended for your own project versioning");
|
|
115
|
+
|
|
116
|
+
return allGood;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
// Step 2 \u2014 Ensure .env exists
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
|
|
123
|
+
function ensureEnvFile() {
|
|
124
|
+
step(2, "Ensuring .env is in place");
|
|
125
|
+
const envPath = join(KIT_ROOT, ".env");
|
|
126
|
+
const examplePath = join(KIT_ROOT, ".env.example");
|
|
127
|
+
|
|
128
|
+
if (existsSync(envPath)) {
|
|
129
|
+
const size = statSync(envPath).size;
|
|
130
|
+
ok(`.env already exists (${size} bytes) \u2014 not overwriting`);
|
|
131
|
+
return { created: false, envPath };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (existsSync(examplePath)) {
|
|
135
|
+
copyFileSync(examplePath, envPath);
|
|
136
|
+
ok(`Copied .env.example \u2192 .env`);
|
|
137
|
+
warn(`Open .env and fill in ZERNIO_API_KEY + ZERNIO_PROFILE_ID before running the operator`);
|
|
138
|
+
return { created: true, envPath };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// No .env.example \u2014 guide the user to create .env directly.
|
|
142
|
+
warn(".env not found \u2014 create it with your Zernio credentials:");
|
|
143
|
+
log(` ${DIM}ZERNIO_API_KEY=sk_<your-64-hex-key>${RESET}`);
|
|
144
|
+
log(` ${DIM}ZERNIO_API_URL=https://zernio.com/api/v1${RESET}`);
|
|
145
|
+
log(` ${DIM}ZERNIO_PROFILE_ID=<your-profile-id>${RESET}`);
|
|
146
|
+
log(` ${DIM}ZERNIO_TIMEZONE=America/New_York # optional${RESET}`);
|
|
147
|
+
log(` ${DIM}ANTHROPIC_API_KEY=sk-ant-... # optional, for hybrid caption mode${RESET}`);
|
|
148
|
+
log("");
|
|
149
|
+
log(` Skip env entirely for agent-only mode: ${BOLD}node setup/setup.mjs --skip-verify${RESET}`);
|
|
150
|
+
return { created: false, envPath: null };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ---------------------------------------------------------------------------
|
|
154
|
+
// Step 3 \u2014 Run verify-env.mjs (same file we already ship)
|
|
155
|
+
// ---------------------------------------------------------------------------
|
|
156
|
+
|
|
157
|
+
async function runVerifyEnv() {
|
|
158
|
+
step(3, "Verifying environment (format + reachability)");
|
|
159
|
+
if (SKIP_VERIFY) {
|
|
160
|
+
warn("--skip-verify passed \u2014 skipping environment verification");
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
loadDotEnvInto(process.env);
|
|
164
|
+
const verifyPath = join(__dirname, "verify-env.mjs");
|
|
165
|
+
if (!existsSync(verifyPath)) {
|
|
166
|
+
fail("setup/verify-env.mjs missing \u2014 kit payload is incomplete");
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
const code = await run(process.execPath, [verifyPath]);
|
|
170
|
+
// verify-env.mjs returns 0 even on warnings; non-zero only if it crashes
|
|
171
|
+
return code === 0;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Minimal inline .env loader \u2014 no dotenv dependency.
|
|
175
|
+
function loadDotEnvInto(target) {
|
|
176
|
+
const envPath = join(KIT_ROOT, ".env");
|
|
177
|
+
if (!existsSync(envPath)) return;
|
|
178
|
+
const raw = readFileSync(envPath, "utf8");
|
|
179
|
+
for (const rawLine of raw.split(/\r?\n/)) {
|
|
180
|
+
const line = rawLine.trim();
|
|
181
|
+
if (!line || line.startsWith("#")) continue;
|
|
182
|
+
const eq = line.indexOf("=");
|
|
183
|
+
if (eq < 0) continue;
|
|
184
|
+
const key = line.slice(0, eq).trim();
|
|
185
|
+
let value = line.slice(eq + 1).trim();
|
|
186
|
+
if ((value.startsWith("\"") && value.endsWith("\"")) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
187
|
+
value = value.slice(1, -1);
|
|
188
|
+
}
|
|
189
|
+
if (target[key] === undefined) target[key] = value;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// ---------------------------------------------------------------------------
|
|
194
|
+
// Step 4 \u2014 Print OS-specific next steps
|
|
195
|
+
// ---------------------------------------------------------------------------
|
|
196
|
+
|
|
197
|
+
function printNextSteps(osInfo, envCreated) {
|
|
198
|
+
step(4, "Next steps");
|
|
199
|
+
const open = osInfo.id === "mac" ? "open" : osInfo.id === "windows" ? "start" : "xdg-open";
|
|
200
|
+
if (envCreated) {
|
|
201
|
+
log(` 1. Fill in your Zernio key in .env:`);
|
|
202
|
+
if (osInfo.id === "windows") {
|
|
203
|
+
log(` ${DIM}notepad .env${RESET} (or any editor)`);
|
|
204
|
+
log(` ${DIM}code .env${RESET} (VS Code)`);
|
|
205
|
+
} else {
|
|
206
|
+
log(` ${DIM}${open} .env${RESET} (system default editor)`);
|
|
207
|
+
log(` ${DIM}nano .env${RESET} (terminal editor)`);
|
|
208
|
+
log(` ${DIM}code .env${RESET} (VS Code)`);
|
|
209
|
+
}
|
|
210
|
+
log(` 2. Re-run: ${BOLD}node setup/setup.mjs${RESET} to re-verify.`);
|
|
211
|
+
log(` 3. Then open Claude Code / Codex / Cursor at this folder as the Working Directory.`);
|
|
212
|
+
} else {
|
|
213
|
+
log(` 1. Open Claude Code / Codex / Cursor.`);
|
|
214
|
+
log(` 2. Point the Working Directory at:`);
|
|
215
|
+
log(` ${BOLD}${KIT_ROOT}${RESET}`);
|
|
216
|
+
log(` 3. The ${BOLD}zernio-social-operator${RESET} agent entrypoint is:`);
|
|
217
|
+
log(` ${DIM}workers/zernio-social-operator/CLAUDE.md${RESET}`);
|
|
218
|
+
log(` 4. Try: ${BOLD}"/zernio campaign"${RESET} to run the full 10-step workflow.`);
|
|
219
|
+
}
|
|
220
|
+
log("");
|
|
221
|
+
log(`${DIM}For full setup guidance see QUICKSTART.md. Agent-only mode is always valid \u2014 Zernio reachability does not block planning.${RESET}`);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// ---------------------------------------------------------------------------
|
|
225
|
+
// Main
|
|
226
|
+
// ---------------------------------------------------------------------------
|
|
227
|
+
|
|
228
|
+
async function main() {
|
|
229
|
+
const osInfo = detectOs();
|
|
230
|
+
printBanner(osInfo);
|
|
231
|
+
|
|
232
|
+
const depsOk = await runDepsCheck();
|
|
233
|
+
const envResult = ensureEnvFile();
|
|
234
|
+
await runVerifyEnv();
|
|
235
|
+
printNextSteps(osInfo, envResult.created);
|
|
236
|
+
|
|
237
|
+
if (!depsOk) {
|
|
238
|
+
log("");
|
|
239
|
+
log(`${RED}${BOLD}One or more dependency checks failed. Fix the issues above and re-run.${RESET}`);
|
|
240
|
+
process.exit(1);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
main().catch((err) => {
|
|
245
|
+
console.error(err);
|
|
246
|
+
process.exit(1);
|
|
247
|
+
});
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// verify-env.mjs — Verify Zernio API key + profile + reachability for the kit
|
|
3
|
+
// Usage: node setup/verify-env.mjs
|
|
4
|
+
|
|
5
|
+
const RED = "\x1b[31m";
|
|
6
|
+
const GREEN = "\x1b[32m";
|
|
7
|
+
const YELLOW = "\x1b[33m";
|
|
8
|
+
const RESET = "\x1b[0m";
|
|
9
|
+
|
|
10
|
+
let allPassed = true;
|
|
11
|
+
const results = [];
|
|
12
|
+
|
|
13
|
+
function check(label, passed, detail = "") {
|
|
14
|
+
const icon = passed ? `${GREEN}✓${RESET}` : `${RED}✗${RESET}`;
|
|
15
|
+
results.push({ label, passed, detail, icon });
|
|
16
|
+
if (!passed) allPassed = false;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function warn(label, detail = "") {
|
|
20
|
+
results.push({ label, passed: null, detail, icon: `${YELLOW}⚠${RESET}` });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
console.log("Growthub Zernio Social Media Studio — Environment Verification");
|
|
24
|
+
console.log("=".repeat(64));
|
|
25
|
+
console.log("");
|
|
26
|
+
|
|
27
|
+
// --- Check 1: ZERNIO_API_KEY format ---
|
|
28
|
+
const ZERNIO_KEY_RE = /^sk_[0-9a-fA-F]{64}$/;
|
|
29
|
+
const zernioKey = process.env.ZERNIO_API_KEY;
|
|
30
|
+
let zernioKeyValid = false;
|
|
31
|
+
if (!zernioKey || zernioKey === "your_zernio_api_key_here") {
|
|
32
|
+
warn("ZERNIO_API_KEY not set", "Set it in .env. Agent-only mode is still available.");
|
|
33
|
+
} else if (!ZERNIO_KEY_RE.test(zernioKey)) {
|
|
34
|
+
check("ZERNIO_API_KEY format is valid", false, "Key must match sk_ + 64 hex characters");
|
|
35
|
+
} else {
|
|
36
|
+
check("ZERNIO_API_KEY format is valid", true, zernioKey.slice(0, 8) + "...");
|
|
37
|
+
zernioKeyValid = true;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// --- Check 2: ZERNIO_API_URL present ---
|
|
41
|
+
const zernioApiUrl = process.env.ZERNIO_API_URL ?? "https://zernio.com/api/v1";
|
|
42
|
+
check("ZERNIO_API_URL is set", Boolean(zernioApiUrl), zernioApiUrl);
|
|
43
|
+
|
|
44
|
+
// --- Check 3: Zernio API reachable (only if a plausible key exists) ---
|
|
45
|
+
let apiReachable = false;
|
|
46
|
+
if (zernioKeyValid) {
|
|
47
|
+
try {
|
|
48
|
+
const controller = new AbortController();
|
|
49
|
+
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
50
|
+
const res = await fetch(`${zernioApiUrl}/profiles`, {
|
|
51
|
+
signal: controller.signal,
|
|
52
|
+
headers: { Authorization: `Bearer ${zernioKey}` },
|
|
53
|
+
});
|
|
54
|
+
clearTimeout(timeout);
|
|
55
|
+
apiReachable = res.ok;
|
|
56
|
+
if (!res.ok) {
|
|
57
|
+
check("Zernio API accepts the key", false, `HTTP ${res.status} from GET ${zernioApiUrl}/profiles`);
|
|
58
|
+
} else {
|
|
59
|
+
check("Zernio API accepts the key", true, `HTTP 200 from GET ${zernioApiUrl}/profiles`);
|
|
60
|
+
}
|
|
61
|
+
} catch (err) {
|
|
62
|
+
check("Zernio API is reachable", false, `Could not reach ${zernioApiUrl} — ${String(err?.message ?? err).slice(0, 120)}`);
|
|
63
|
+
}
|
|
64
|
+
} else {
|
|
65
|
+
warn("Zernio API reachability skipped", "No valid ZERNIO_API_KEY — agent-only mode only");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// --- Check 4: ZERNIO_PROFILE_ID ---
|
|
69
|
+
const profileId = process.env.ZERNIO_PROFILE_ID;
|
|
70
|
+
if (!profileId || profileId === "your_zernio_profile_id_here") {
|
|
71
|
+
warn("ZERNIO_PROFILE_ID not set", "Required for scheduling — find it in the Zernio dashboard or via GET /api/v1/profiles");
|
|
72
|
+
} else if (zernioKeyValid && apiReachable) {
|
|
73
|
+
try {
|
|
74
|
+
const controller = new AbortController();
|
|
75
|
+
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
76
|
+
const res = await fetch(`${zernioApiUrl}/profiles/${encodeURIComponent(profileId)}`, {
|
|
77
|
+
signal: controller.signal,
|
|
78
|
+
headers: { Authorization: `Bearer ${zernioKey}` },
|
|
79
|
+
});
|
|
80
|
+
clearTimeout(timeout);
|
|
81
|
+
if (res.ok) {
|
|
82
|
+
check("ZERNIO_PROFILE_ID exists on the account", true, profileId.slice(0, 16) + "...");
|
|
83
|
+
} else if (res.status === 404) {
|
|
84
|
+
check("ZERNIO_PROFILE_ID exists on the account", false, "Profile not found for this API key");
|
|
85
|
+
} else {
|
|
86
|
+
warn("ZERNIO_PROFILE_ID lookup returned non-2xx", `HTTP ${res.status}`);
|
|
87
|
+
}
|
|
88
|
+
} catch (err) {
|
|
89
|
+
warn("ZERNIO_PROFILE_ID lookup failed", String(err?.message ?? err).slice(0, 120));
|
|
90
|
+
}
|
|
91
|
+
} else {
|
|
92
|
+
check("ZERNIO_PROFILE_ID is set", true, "Format check only — API not reached");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// --- Check 5: ZERNIO_TIMEZONE plausibility (optional) ---
|
|
96
|
+
const tz = process.env.ZERNIO_TIMEZONE;
|
|
97
|
+
if (tz) {
|
|
98
|
+
// minimum sanity: looks like "Area/City" or a UTC offset tag
|
|
99
|
+
const plausible = /^[A-Za-z]+\/[A-Za-z_]+/.test(tz) || /^UTC[+-]?\d*$/i.test(tz);
|
|
100
|
+
if (plausible) {
|
|
101
|
+
check("ZERNIO_TIMEZONE format is plausible", true, tz);
|
|
102
|
+
} else {
|
|
103
|
+
check("ZERNIO_TIMEZONE format is plausible", false, `Unexpected value: ${tz}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// --- Check 6: Anthropic API key (optional) ---
|
|
108
|
+
const anthropicKey = process.env.ANTHROPIC_API_KEY;
|
|
109
|
+
if (!anthropicKey || anthropicKey === "your_anthropic_key_here") {
|
|
110
|
+
warn("ANTHROPIC_API_KEY not set", "Optional — used only for enhanced caption drafting in hybrid mode");
|
|
111
|
+
} else if (anthropicKey.startsWith("sk-ant-")) {
|
|
112
|
+
check("ANTHROPIC_API_KEY format is valid", true);
|
|
113
|
+
} else {
|
|
114
|
+
check("ANTHROPIC_API_KEY format is valid", false, "Key does not start with sk-ant- — check your Anthropic console");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// --- Print results ---
|
|
118
|
+
console.log("Results:");
|
|
119
|
+
for (const { icon, label, detail } of results) {
|
|
120
|
+
const detailStr = detail ? ` → ${detail}` : "";
|
|
121
|
+
console.log(` ${icon} ${label}${detailStr}`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
console.log("");
|
|
125
|
+
|
|
126
|
+
if (allPassed) {
|
|
127
|
+
console.log(`${GREEN}All checks passed. Ready for api-live or hybrid mode.${RESET}`);
|
|
128
|
+
} else {
|
|
129
|
+
const warnings = results.filter((r) => r.passed === null);
|
|
130
|
+
const failures = results.filter((r) => r.passed === false);
|
|
131
|
+
if (failures.length > 0) {
|
|
132
|
+
console.log(`${RED}${failures.length} check(s) failed. See details above.${RESET}`);
|
|
133
|
+
console.log(`\n Agent-only mode remains available for campaign planning and dry-run manifests.`);
|
|
134
|
+
}
|
|
135
|
+
if (warnings.length > 0 && failures.length === 0) {
|
|
136
|
+
console.log(`${YELLOW}Checks passed with warnings. Agent-only mode is available.${RESET}`);
|
|
137
|
+
}
|
|
138
|
+
}
|