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.
- package/LICENSE +11 -0
- package/README.md +57 -0
- package/bin/index.mjs +183 -0
- 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
|
+
}
|