create-lovable-seo-agent 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 (4) hide show
  1. package/LICENSE +11 -0
  2. package/README.md +57 -0
  3. package/bin/index.mjs +183 -0
  4. package/package.json +32 -0
package/LICENSE ADDED
@@ -0,0 +1,11 @@
1
+ Copyright (c) 2026 nooncodes
2
+
3
+ PROPRIETARY LICENSE
4
+
5
+ This software is licensed, not sold. Use is permitted only after purchasing a
6
+ valid license key and activating it via the installer. Sharing, republishing,
7
+ redistributing, or reverse-engineering the installer or the downloaded bundle
8
+ is strictly prohibited and results in immediate license revocation.
9
+
10
+ Refunds available within 14 days of purchase if no license activation has
11
+ occurred. Contact support@nooncodes.net.
package/README.md ADDED
@@ -0,0 +1,57 @@
1
+ # create-lovable-seo-agent
2
+
3
+ > Install the full **SEO Agent** (edge functions + MCP server + database schema) into any Lovable / Supabase project — with one command.
4
+
5
+ ## Usage
6
+
7
+ ```bash
8
+ cd my-lovable-project
9
+ npx create-lovable-seo-agent
10
+ ```
11
+
12
+ You'll be prompted for:
13
+ 1. **Project path** (defaults to current directory)
14
+ 2. **License key** — format `LSA-XXXX-XXXX-XXXX`
15
+
16
+ The installer verifies your license, activates it on this machine, downloads the bundle, and extracts it into your project.
17
+
18
+ ## What gets installed
19
+
20
+ - `supabase/functions/seo-agent*` — 5 edge functions for review, autopilot, chat, rollback, scheduled
21
+ - `supabase/functions/gsc-*` — 2 Google Search Console functions
22
+ - `supabase/functions/mcp` — MCP server exposing 8 tools
23
+ - `src/lib/mcp/` — MCP tool definitions
24
+ - `supabase/migrations/*_seo_agent_install.sql` — schema (7 tables)
25
+
26
+ ## After installation
27
+
28
+ Open your project in Lovable and say:
29
+
30
+ > شغّل الـ migrations الجديدة، وأضف mcpPlugin() فى vite.config.ts، وأضف route /.lovable/oauth/consent
31
+
32
+ Then add these secrets in **Project Settings → Secrets**:
33
+ - `LOVABLE_API_KEY` (managed automatically)
34
+ - `GOOGLE_SERVICE_ACCOUNT_JSON` (optional, for GSC data)
35
+
36
+ ## Requirements
37
+
38
+ - Node.js ≥ 18
39
+ - A Lovable project with `blog_posts` table (see `docs/SCHEMA.md` in the bundle)
40
+ - A valid license key — [get one at nooncodes.net](https://nooncodes.net)
41
+
42
+ ## Plans
43
+
44
+ | Plan | Machines | Price |
45
+ |----------|----------|-------|
46
+ | Personal | 1 | $49 |
47
+ | Team | 5 | $149 |
48
+ | Agency | Unlimited | $499 |
49
+
50
+ ## License
51
+
52
+ Proprietary — see LICENSE. One key per plan; sharing keys revokes access for all activations.
53
+
54
+ ## Support
55
+
56
+ - Docs: https://nooncodes.net/seo-agent/docs
57
+ - Email: support@nooncodes.net
package/bin/index.mjs ADDED
@@ -0,0 +1,183 @@
1
+ #!/usr/bin/env node
2
+ // ============================================================================
3
+ // create-lovable-seo-agent — installer CLI
4
+ // Flow: license prompt → verify → activate → download bundle → extract
5
+ // ============================================================================
6
+ import { createHash } from "node:crypto";
7
+ import { hostname, platform, arch, homedir } from "node:os";
8
+ import { mkdirSync, existsSync, readFileSync, writeFileSync } from "node:fs";
9
+ import { join, resolve } from "node:path";
10
+ import { pipeline } from "node:stream/promises";
11
+ import { Readable } from "node:stream";
12
+ import { spawn } from "node:child_process";
13
+ import prompts from "prompts";
14
+ import pc from "picocolors";
15
+ import * as tar from "tar";
16
+
17
+ const LICENSE_SERVER =
18
+ process.env.LOVABLE_SEO_LICENSE_SERVER ??
19
+ "https://yntavqukvncoiutdfndd.supabase.co/functions/v1/license-server";
20
+
21
+ const CONFIG_DIR = join(homedir(), ".lovable-seo-agent");
22
+ const CONFIG_FILE = join(CONFIG_DIR, "config.json");
23
+
24
+ // ── helpers ────────────────────────────────────────────────────────────────
25
+ function readSavedConfig() {
26
+ try {
27
+ return JSON.parse(readFileSync(CONFIG_FILE, "utf8"));
28
+ } catch { return {}; }
29
+ }
30
+
31
+ function saveConfig(cfg) {
32
+ mkdirSync(CONFIG_DIR, { recursive: true });
33
+ const merged = { ...readSavedConfig(), ...cfg };
34
+ writeFileSync(CONFIG_FILE, JSON.stringify(merged, null, 2), "utf8");
35
+ }
36
+
37
+ function machineFingerprint() {
38
+ // Stable per (hostname + platform + arch + node-major)
39
+ const raw = `${hostname()}|${platform()}|${arch()}|${process.versions.node.split(".")[0]}`;
40
+ return createHash("sha256").update(raw).digest("hex").slice(0, 32);
41
+ }
42
+
43
+ async function apiPost(path, body) {
44
+ const res = await fetch(`${LICENSE_SERVER}${path}`, {
45
+ method: "POST",
46
+ headers: { "Content-Type": "application/json" },
47
+ body: JSON.stringify(body),
48
+ });
49
+ const text = await res.text();
50
+ let data;
51
+ try { data = JSON.parse(text); } catch { data = { error: text }; }
52
+ return { status: res.status, data };
53
+ }
54
+
55
+ function die(msg, code = 1) {
56
+ console.error(pc.red(`\n✖ ${msg}\n`));
57
+ process.exit(code);
58
+ }
59
+
60
+ // ── main ───────────────────────────────────────────────────────────────────
61
+ async function main() {
62
+ console.log(pc.bold(pc.yellow("\n 🤖 Lovable SEO Agent — Installer\n")));
63
+ console.log(pc.dim(" Install the full SEO Agent (edge functions + MCP server + schema)\n into your Lovable / Supabase project.\n"));
64
+
65
+ const saved = readSavedConfig();
66
+ const fingerprint = machineFingerprint();
67
+
68
+ // 1. Ask for target directory
69
+ const cwd = process.cwd();
70
+ const { targetDir } = await prompts({
71
+ type: "text",
72
+ name: "targetDir",
73
+ message: "Path to your Lovable project root:",
74
+ initial: cwd,
75
+ validate: (v) => existsSync(v) ? true : "Directory does not exist",
76
+ });
77
+ if (!targetDir) die("Cancelled.");
78
+
79
+ const target = resolve(targetDir);
80
+ if (!existsSync(join(target, "package.json"))) {
81
+ console.log(pc.yellow(" ⚠ No package.json found — is this really a Lovable project?"));
82
+ const { confirm } = await prompts({ type: "confirm", name: "confirm", message: "Continue anyway?", initial: false });
83
+ if (!confirm) die("Cancelled.");
84
+ }
85
+
86
+ // 2. Ask for license key
87
+ const { licenseKey } = await prompts({
88
+ type: "text",
89
+ name: "licenseKey",
90
+ message: "Enter your license key (LSA-XXXX-XXXX-XXXX):",
91
+ initial: saved.licenseKey ?? "",
92
+ validate: (v) => /^LSA-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$/i.test(v.trim())
93
+ ? true : "Invalid format. Expected LSA-XXXX-XXXX-XXXX.",
94
+ });
95
+ if (!licenseKey) die("Cancelled.");
96
+ const key = licenseKey.trim().toUpperCase();
97
+
98
+ // 3. Verify
99
+ console.log(pc.dim("\n → Verifying license..."));
100
+ const v = await apiPost("/verify", { license_key: key, machine_fingerprint: fingerprint });
101
+ if (v.status !== 200 || !v.data?.valid) {
102
+ const reason = v.data?.error ?? "unknown";
103
+ die(`License invalid: ${reason}`);
104
+ }
105
+ console.log(pc.green(` ✓ License valid (plan: ${v.data.plan}${v.data.expires_at ? `, expires ${new Date(v.data.expires_at).toLocaleDateString()}` : ""})`));
106
+
107
+ // 4. Activate on this machine (if not already)
108
+ if (!v.data.already_activated) {
109
+ console.log(pc.dim(" → Activating on this machine..."));
110
+ const a = await apiPost("/activate", {
111
+ license_key: key,
112
+ machine_fingerprint: fingerprint,
113
+ hostname: hostname(),
114
+ platform: `${platform()}-${arch()}`,
115
+ project_path: target,
116
+ });
117
+ if (a.status !== 200 || !a.data?.success) {
118
+ die(`Activation failed: ${a.data?.error ?? "unknown"}`);
119
+ }
120
+ console.log(pc.green(` ✓ Activated (${a.data.activations_remaining ?? "?"} slots remaining)`));
121
+ } else {
122
+ console.log(pc.green(" ✓ Already activated on this machine"));
123
+ }
124
+
125
+ saveConfig({ licenseKey: key });
126
+
127
+ // 5. Download bundle
128
+ console.log(pc.dim("\n → Downloading SEO Agent bundle..."));
129
+ const dlRes = await fetch(`${LICENSE_SERVER}/download`, {
130
+ method: "POST",
131
+ headers: { "Content-Type": "application/json" },
132
+ body: JSON.stringify({ license_key: key, machine_fingerprint: fingerprint }),
133
+ });
134
+ if (!dlRes.ok || !dlRes.body) {
135
+ const errText = await dlRes.text().catch(() => "");
136
+ die(`Download failed (${dlRes.status}): ${errText.slice(0, 200)}`);
137
+ }
138
+ const ct = dlRes.headers.get("content-type") ?? "";
139
+ if (!ct.includes("gzip") && !ct.includes("octet-stream") && !ct.includes("tar")) {
140
+ const errText = await dlRes.text().catch(() => "");
141
+ die(`Unexpected response: ${errText.slice(0, 200)}`);
142
+ }
143
+
144
+ // 6. Extract tar.gz into target
145
+ console.log(pc.dim(" → Extracting into your project..."));
146
+ await pipeline(
147
+ Readable.fromWeb(dlRes.body),
148
+ tar.x({ cwd: target, strip: 0 }),
149
+ );
150
+ console.log(pc.green(" ✓ Files installed"));
151
+
152
+ // 7. Read the MANIFEST that shipped in the bundle so we can show what's included
153
+ let manifest = null;
154
+ try { manifest = JSON.parse(readFileSync(join(target, "MANIFEST.json"), "utf8")); } catch {}
155
+ const planInfo = manifest?.plans?.[v.data.plan];
156
+ if (planInfo) {
157
+ console.log(pc.bold(`\n 📦 ${planInfo.name} Plan — features included:`));
158
+ for (const f of planInfo.features) console.log(pc.dim(` • ${f}`));
159
+ }
160
+
161
+ // 8. Run the interactive setup wizard (shipped inside the bundle as setup-wizard.mjs)
162
+ const wizardPath = join(target, "setup-wizard.mjs");
163
+ if (existsSync(wizardPath)) {
164
+ console.log(pc.dim("\n → Launching setup wizard...\n"));
165
+ await new Promise((resolveP, rejectP) => {
166
+ const child = spawn(process.execPath, [wizardPath, target], { stdio: "inherit" });
167
+ child.on("exit", code => code === 0 ? resolveP() : rejectP(new Error(`Wizard exited ${code}`)));
168
+ }).catch(err => console.log(pc.yellow(` ⚠ Wizard skipped: ${err.message}`)));
169
+ }
170
+
171
+ // 9. Done
172
+ console.log(pc.bold(pc.green("\n ✅ Installation complete!\n")));
173
+ console.log(pc.bold(" Next steps:"));
174
+ console.log(` 1. Copy values from ${pc.cyan(".env.seo")} into your Supabase Edge Function secrets`);
175
+ console.log(` 2. Run: ${pc.cyan("supabase db push")}`);
176
+ console.log(` 3. Run: ${pc.cyan("supabase functions deploy --project-ref YOUR_REF")}`);
177
+ console.log(` 4. Open Lovable and reload the preview\n`);
178
+ }
179
+
180
+ main().catch((err) => {
181
+ console.error(pc.red(`\n✖ Unexpected error: ${err?.message ?? err}\n`));
182
+ process.exit(1);
183
+ });
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "create-lovable-seo-agent",
3
+ "version": "0.1.0",
4
+ "description": "Install the SEO Agent (edge functions + MCP server + migrations) into any Lovable / Supabase project with a license key.",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-lovable-seo-agent": "./bin/index.mjs"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "README.md",
12
+ "LICENSE"
13
+ ],
14
+ "keywords": [
15
+ "lovable",
16
+ "seo",
17
+ "seo-agent",
18
+ "supabase",
19
+ "mcp",
20
+ "cli"
21
+ ],
22
+ "author": "nooncodes",
23
+ "license": "SEE LICENSE IN LICENSE",
24
+ "engines": {
25
+ "node": ">=18"
26
+ },
27
+ "dependencies": {
28
+ "prompts": "^2.4.2",
29
+ "picocolors": "^1.0.1",
30
+ "tar": "^7.4.3"
31
+ }
32
+ }