@seoagent-official/seoagent 1.11.0 → 1.13.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 +36 -3
- package/assets/hero.svg +73 -0
- package/package.json +4 -2
- package/postinstall-hint.cjs +18 -19
- package/postinstall-lib.cjs +117 -0
package/README.md
CHANGED
|
@@ -1,6 +1,39 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<a href="https://seoagent.com?ref=npm-readme-hero">
|
|
3
|
+
<img src="https://unpkg.com/@seoagent-official/seoagent/assets/hero.svg" alt="SEOAgent — the persistent AI SEO agent for Claude Code" width="900" />
|
|
4
|
+
</a>
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
<h1 align="center">SEOAgent</h1>
|
|
8
|
+
|
|
9
|
+
<p align="center">
|
|
10
|
+
<strong>The persistent AI SEO agent for <a href="https://www.anthropic.com/claude-code">Claude Code</a>.</strong><br/>
|
|
11
|
+
Audits • Keyword strategy • Content briefs • Optimized articles — all persisted in <code>.seoagent/</code>.
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
<p align="center">
|
|
15
|
+
<a href="https://www.npmjs.com/package/@seoagent-official/seoagent"><img alt="npm version" src="https://img.shields.io/npm/v/@seoagent-official/seoagent?color=cb3837&label=npm&logo=npm&logoColor=white"></a>
|
|
16
|
+
<a href="https://www.npmjs.com/package/@seoagent-official/seoagent"><img alt="downloads" src="https://img.shields.io/npm/dm/@seoagent-official/seoagent?color=22c55e&label=downloads"></a>
|
|
17
|
+
<a href="https://github.com/Baxter-Inc/seoagent-npm/actions"><img alt="CI" src="https://img.shields.io/github/actions/workflow/status/Baxter-Inc/seoagent-npm/publish-npm.yml?branch=main&label=CI&logo=github"></a>
|
|
18
|
+
<a href="https://github.com/Baxter-Inc/seoagent-npm/blob/main/LICENSE"><img alt="license MIT" src="https://img.shields.io/npm/l/@seoagent-official/seoagent?color=3b82f6&label=license"></a>
|
|
19
|
+
<a href="https://seoagent.com?ref=npm-readme-badge"><img alt="SEOAgent Cloud" src="https://img.shields.io/badge/cloud-seoagent.com-7c3aed?logo=cloudflare&logoColor=white"></a>
|
|
20
|
+
</p>
|
|
21
|
+
|
|
22
|
+
<p align="center">
|
|
23
|
+
<img alt="Claude Code" src="https://img.shields.io/badge/CLAUDE%20CODE-5b54f4?style=flat-square&logo=anthropic&logoColor=white">
|
|
24
|
+
<img alt="TypeScript" src="https://img.shields.io/badge/TYPESCRIPT-3178c6?style=flat-square&logo=typescript&logoColor=white">
|
|
25
|
+
<img alt="Node 18+" src="https://img.shields.io/badge/NODE%20%E2%89%A5%2018-339933?style=flat-square&logo=node.js&logoColor=white">
|
|
26
|
+
<img alt="SEO Autopilot" src="https://img.shields.io/badge/SEO-AUTOPILOT-f97316?style=flat-square">
|
|
27
|
+
<img alt="Images via OpenAI · fal.ai · Replicate" src="https://img.shields.io/badge/IMAGES-OpenAI%20%C2%B7%20fal.ai%20%C2%B7%20Replicate-ec4899?style=flat-square">
|
|
28
|
+
</p>
|
|
29
|
+
|
|
30
|
+
<p align="center">
|
|
31
|
+
Built and maintained by <a href="https://seoagent.com?ref=npm-readme-builtby"><strong>SEOAgent.com</strong></a> ·
|
|
32
|
+
<a href="https://seoagent.com/pricing?ref=npm-readme">Cloud pricing</a> ·
|
|
33
|
+
<a href="https://seoagent.com?ref=npm-readme-docs">Docs & dashboard</a>
|
|
34
|
+
</p>
|
|
35
|
+
|
|
36
|
+
---
|
|
4
37
|
|
|
5
38
|
> **This package is a scaffolder, not a runtime dependency.** Both forms below do the same thing — scaffold `.seoagent/` + the Claude Code skill in your repo. You don't need to keep it in `package.json` after init runs.
|
|
6
39
|
|
package/assets/hero.svg
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 360" width="1200" height="360" role="img" aria-label="SEOAgent — The persistent AI SEO agent for Claude Code">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
|
|
4
|
+
<stop offset="0%" stop-color="#0d0d1f"/>
|
|
5
|
+
<stop offset="55%" stop-color="#1a1147"/>
|
|
6
|
+
<stop offset="100%" stop-color="#2d1b69"/>
|
|
7
|
+
</linearGradient>
|
|
8
|
+
<linearGradient id="accent" x1="0" y1="0" x2="1" y2="0">
|
|
9
|
+
<stop offset="0%" stop-color="#a78bfa"/>
|
|
10
|
+
<stop offset="100%" stop-color="#7c3aed"/>
|
|
11
|
+
</linearGradient>
|
|
12
|
+
<radialGradient id="glow" cx="0.85" cy="0.15" r="0.7">
|
|
13
|
+
<stop offset="0%" stop-color="#7c3aed" stop-opacity="0.55"/>
|
|
14
|
+
<stop offset="60%" stop-color="#7c3aed" stop-opacity="0.08"/>
|
|
15
|
+
<stop offset="100%" stop-color="#7c3aed" stop-opacity="0"/>
|
|
16
|
+
</radialGradient>
|
|
17
|
+
<filter id="soft" x="-20%" y="-20%" width="140%" height="140%">
|
|
18
|
+
<feGaussianBlur stdDeviation="0.6"/>
|
|
19
|
+
</filter>
|
|
20
|
+
</defs>
|
|
21
|
+
|
|
22
|
+
<rect width="1200" height="360" rx="20" ry="20" fill="url(#bg)"/>
|
|
23
|
+
<rect width="1200" height="360" rx="20" ry="20" fill="url(#glow)"/>
|
|
24
|
+
|
|
25
|
+
<g opacity="0.18" stroke="#a78bfa" stroke-width="1" fill="none">
|
|
26
|
+
<path d="M0 280 C 220 240, 420 320, 640 270 S 1020 220, 1200 260"/>
|
|
27
|
+
<path d="M0 305 C 220 270, 420 345, 640 295 S 1020 250, 1200 285"/>
|
|
28
|
+
<path d="M0 330 C 220 300, 420 365, 640 320 S 1020 280, 1200 310"/>
|
|
29
|
+
</g>
|
|
30
|
+
|
|
31
|
+
<g transform="translate(70 120)">
|
|
32
|
+
<circle cx="32" cy="32" r="32" fill="url(#accent)"/>
|
|
33
|
+
<text x="32" y="44" text-anchor="middle" font-family="Inter, ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif" font-size="32" font-weight="800" fill="#0d0d1f">S</text>
|
|
34
|
+
</g>
|
|
35
|
+
|
|
36
|
+
<text x="160" y="170" font-family="Inter, ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif" font-size="78" font-weight="800" letter-spacing="-2" fill="#ffffff">
|
|
37
|
+
SEO<tspan fill="url(#accent)">Agent</tspan>
|
|
38
|
+
</text>
|
|
39
|
+
|
|
40
|
+
<text x="160" y="215" font-family="Inter, ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif" font-size="22" font-weight="500" fill="#cbd5f5" filter="url(#soft)">
|
|
41
|
+
The persistent AI SEO agent for Claude Code · Manifest-ready autopilot for audits, briefs & articles
|
|
42
|
+
</text>
|
|
43
|
+
|
|
44
|
+
<g font-family="ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace" font-size="14" font-weight="600">
|
|
45
|
+
<g transform="translate(160 255)">
|
|
46
|
+
<rect width="146" height="32" rx="16" fill="#ffffff" fill-opacity="0.08" stroke="#a78bfa" stroke-opacity="0.35"/>
|
|
47
|
+
<text x="73" y="21" text-anchor="middle" fill="#e9d5ff">persistent .seoagent/</text>
|
|
48
|
+
</g>
|
|
49
|
+
<g transform="translate(316 255)">
|
|
50
|
+
<rect width="138" height="32" rx="16" fill="#ffffff" fill-opacity="0.08" stroke="#a78bfa" stroke-opacity="0.35"/>
|
|
51
|
+
<text x="69" y="21" text-anchor="middle" fill="#e9d5ff">hub & spoke clusters</text>
|
|
52
|
+
</g>
|
|
53
|
+
<g transform="translate(464 255)">
|
|
54
|
+
<rect width="120" height="32" rx="16" fill="#ffffff" fill-opacity="0.08" stroke="#a78bfa" stroke-opacity="0.35"/>
|
|
55
|
+
<text x="60" y="21" text-anchor="middle" fill="#e9d5ff">JSON-LD ready</text>
|
|
56
|
+
</g>
|
|
57
|
+
<g transform="translate(594 255)">
|
|
58
|
+
<rect width="120" height="32" rx="16" fill="#ffffff" fill-opacity="0.08" stroke="#a78bfa" stroke-opacity="0.35"/>
|
|
59
|
+
<text x="60" y="21" text-anchor="middle" fill="#e9d5ff">image autopilot</text>
|
|
60
|
+
</g>
|
|
61
|
+
<g transform="translate(724 255)">
|
|
62
|
+
<rect width="124" height="32" rx="16" fill="#ffffff" fill-opacity="0.08" stroke="#a78bfa" stroke-opacity="0.35"/>
|
|
63
|
+
<text x="62" y="21" text-anchor="middle" fill="#e9d5ff">cloud sync hook</text>
|
|
64
|
+
</g>
|
|
65
|
+
</g>
|
|
66
|
+
|
|
67
|
+
<g transform="translate(940 70)" opacity="0.95">
|
|
68
|
+
<rect width="190" height="56" rx="10" fill="#ffffff" fill-opacity="0.06" stroke="#a78bfa" stroke-opacity="0.4"/>
|
|
69
|
+
<text x="20" y="24" font-family="ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace" font-size="11" fill="#a78bfa">$</text>
|
|
70
|
+
<text x="36" y="24" font-family="ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace" font-size="11" fill="#e9d5ff">npx -y @seoagent-</text>
|
|
71
|
+
<text x="36" y="42" font-family="ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace" font-size="11" fill="#e9d5ff">official/seoagent init</text>
|
|
72
|
+
</g>
|
|
73
|
+
</svg>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seoagent-official/seoagent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.0",
|
|
4
4
|
"description": "Scaffolder for Claude Code's SEOAgent skill. Run once: `npx -y @seoagent-official/seoagent init`. Sets up .seoagent/ for persistent audits, keyword strategy, content planning, and optimized writing. Not a runtime dependency.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
"index.js",
|
|
11
11
|
"skills",
|
|
12
|
-
"
|
|
12
|
+
"assets",
|
|
13
|
+
"postinstall-hint.cjs",
|
|
14
|
+
"postinstall-lib.cjs"
|
|
13
15
|
],
|
|
14
16
|
"dependencies": {
|
|
15
17
|
"@clack/prompts": "^0.9.0",
|
package/postinstall-hint.cjs
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
const fs = require('fs');
|
|
20
20
|
const path = require('path');
|
|
21
21
|
const { spawnSync } = require('child_process');
|
|
22
|
+
const { isDirectInstall } = require('./postinstall-lib.cjs');
|
|
22
23
|
|
|
23
24
|
// -----------------------------------------------------------------------------
|
|
24
25
|
// Guards — skip auto-init when it would be wrong or annoying.
|
|
@@ -53,8 +54,20 @@ if (!projectRoot || !fs.existsSync(projectRoot)) {
|
|
|
53
54
|
// Don't auto-init when the package is being installed inside another
|
|
54
55
|
// package's `node_modules` (a transitive dep). INIT_CWD would be the
|
|
55
56
|
// transitive consumer's project root, not ours — we'd scaffold their repo.
|
|
56
|
-
//
|
|
57
|
-
|
|
57
|
+
//
|
|
58
|
+
// Detecting "transitive" reliably is harder than it looks: when a user runs
|
|
59
|
+
// `npm install @seoagent-official/seoagent` in a fresh project, npm 7+ runs
|
|
60
|
+
// THIS postinstall BEFORE writing the new dep to INIT_CWD/package.json. So
|
|
61
|
+
// "is our package name in INIT_CWD/package.json" is false-negative-prone and
|
|
62
|
+
// silently kills the captive install moment on every fresh install — which
|
|
63
|
+
// is exactly the case we most need it to fire on.
|
|
64
|
+
//
|
|
65
|
+
// Instead: look at sibling node_modules entries. If ANY of them declares us
|
|
66
|
+
// as a dependency, we're being dragged in transitively → bail. Otherwise
|
|
67
|
+
// we're a direct install (or no one else needs us yet) → proceed.
|
|
68
|
+
// We still consult package.json as a positive signal (e.g. for repeat installs
|
|
69
|
+
// where the dep is already saved), since it strengthens the "direct" conclusion.
|
|
70
|
+
if (!isDirectInstall(projectRoot)) {
|
|
58
71
|
// Most likely transitive — bail silently.
|
|
59
72
|
process.exit(0);
|
|
60
73
|
}
|
|
@@ -94,25 +107,11 @@ if (result.status === 0) {
|
|
|
94
107
|
}
|
|
95
108
|
|
|
96
109
|
// -----------------------------------------------------------------------------
|
|
97
|
-
// Helpers
|
|
110
|
+
// Helpers — direct-install detection lives in ./postinstall-lib.cjs so it can
|
|
111
|
+
// be unit-tested without firing the side effects above. The print functions
|
|
112
|
+
// stay inline because they don't have meaningful behavior to test.
|
|
98
113
|
// -----------------------------------------------------------------------------
|
|
99
114
|
|
|
100
|
-
function projectDeclaresUs(root) {
|
|
101
|
-
try {
|
|
102
|
-
const pkgPath = path.join(root, 'package.json');
|
|
103
|
-
if (!fs.existsSync(pkgPath)) {
|
|
104
|
-
// No package.json — user ran `npm install <pkg>` in an empty dir or
|
|
105
|
-
// outside a project. That's unusual but valid for scaffolding.
|
|
106
|
-
return true;
|
|
107
|
-
}
|
|
108
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
109
|
-
const all = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
|
|
110
|
-
return '@seoagent-official/seoagent' in all;
|
|
111
|
-
} catch {
|
|
112
|
-
return true; // On error, fail open — better to scaffold than not.
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
115
|
function printPreInitBanner() {
|
|
117
116
|
process.stdout.write(
|
|
118
117
|
'\n' +
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// =============================================================================
|
|
4
|
+
// postinstall-lib — pure helpers used by postinstall-hint.cjs
|
|
5
|
+
//
|
|
6
|
+
// Lives in its own file (instead of inline in postinstall-hint.cjs) for two
|
|
7
|
+
// reasons:
|
|
8
|
+
// 1. Unit-testable. The main script has top-level side effects
|
|
9
|
+
// (process.exit, spawnSync) that fire on require — splitting the pure
|
|
10
|
+
// logic out means vitest can require THIS file safely.
|
|
11
|
+
// 2. The regression we're guarding against is subtle: npm 7+ writes the
|
|
12
|
+
// newly-installed package to the parent's package.json AFTER running
|
|
13
|
+
// the package's postinstall script. So the old "is our name in
|
|
14
|
+
// INIT_CWD/package.json" check returned false on every fresh install,
|
|
15
|
+
// silently killing the captive moment we built the package around.
|
|
16
|
+
// Tests for THIS file pin the corrected behavior in place.
|
|
17
|
+
// =============================================================================
|
|
18
|
+
|
|
19
|
+
const fs = require('fs');
|
|
20
|
+
const path = require('path');
|
|
21
|
+
|
|
22
|
+
const PKG_NAME = '@seoagent-official/seoagent';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Decide whether this postinstall is firing for a DIRECT install (user/agent
|
|
26
|
+
* explicitly added us) vs a TRANSITIVE install (we're a dep of someone else's
|
|
27
|
+
* dep). Returns true ⇒ scaffold; false ⇒ silent bail.
|
|
28
|
+
*
|
|
29
|
+
* Strategy is two-step because both signals alone are unreliable:
|
|
30
|
+
* 1. POSITIVE — INIT_CWD/package.json already lists us. Confirms direct
|
|
31
|
+
* (but is FALSE on the most common case: first-time `npm install <us>`,
|
|
32
|
+
* because npm 7+ writes package.json AFTER lifecycle scripts).
|
|
33
|
+
* 2. NEGATIVE — Look at every sibling under INIT_CWD/node_modules and check
|
|
34
|
+
* if any of THEIR package.json lists us as a dep. If yes → transitive.
|
|
35
|
+
* If no → direct (or top-level install with no other consumer yet).
|
|
36
|
+
*
|
|
37
|
+
* If both are inconclusive (e.g. no package.json at all because the user ran
|
|
38
|
+
* `npm install <us>` in an empty dir), default to "direct" — scaffolding is
|
|
39
|
+
* the whole point of the package and the existing `.seoagent/project.md`
|
|
40
|
+
* idempotency guard prevents double-scaffolding on subsequent runs.
|
|
41
|
+
*/
|
|
42
|
+
function isDirectInstall(root) {
|
|
43
|
+
if (!root) return true; // No INIT_CWD: nothing else we can check; scaffold.
|
|
44
|
+
if (declaredInProjectJson(root)) return true;
|
|
45
|
+
if (anySiblingDependsOnUs(root)) return false;
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function declaredInProjectJson(root) {
|
|
50
|
+
try {
|
|
51
|
+
const pkgPath = path.join(root, 'package.json');
|
|
52
|
+
if (!fs.existsSync(pkgPath)) return false;
|
|
53
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
54
|
+
const all = mergeAllDepKinds(pkg);
|
|
55
|
+
return PKG_NAME in all;
|
|
56
|
+
} catch {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function anySiblingDependsOnUs(root) {
|
|
62
|
+
try {
|
|
63
|
+
const nm = path.join(root, 'node_modules');
|
|
64
|
+
if (!fs.existsSync(nm)) return false;
|
|
65
|
+
for (const entry of fs.readdirSync(nm, { withFileTypes: true })) {
|
|
66
|
+
if (!entry.isDirectory() || entry.name.startsWith('.')) continue;
|
|
67
|
+
// Scoped packages: recurse one level.
|
|
68
|
+
if (entry.name.startsWith('@')) {
|
|
69
|
+
const scopeDir = path.join(nm, entry.name);
|
|
70
|
+
let scoped;
|
|
71
|
+
try {
|
|
72
|
+
scoped = fs.readdirSync(scopeDir, { withFileTypes: true });
|
|
73
|
+
} catch {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
for (const inner of scoped) {
|
|
77
|
+
if (!inner.isDirectory()) continue;
|
|
78
|
+
// Skip ourselves.
|
|
79
|
+
if (entry.name === '@seoagent-official' && inner.name === 'seoagent') continue;
|
|
80
|
+
if (pkgListsUs(path.join(scopeDir, inner.name, 'package.json'))) return true;
|
|
81
|
+
}
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (pkgListsUs(path.join(nm, entry.name, 'package.json'))) return true;
|
|
85
|
+
}
|
|
86
|
+
return false;
|
|
87
|
+
} catch {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function pkgListsUs(pkgJsonPath) {
|
|
93
|
+
try {
|
|
94
|
+
if (!fs.existsSync(pkgJsonPath)) return false;
|
|
95
|
+
const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));
|
|
96
|
+
return PKG_NAME in mergeAllDepKinds(pkg);
|
|
97
|
+
} catch {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function mergeAllDepKinds(pkg) {
|
|
103
|
+
return {
|
|
104
|
+
...(pkg.dependencies || {}),
|
|
105
|
+
...(pkg.devDependencies || {}),
|
|
106
|
+
...(pkg.optionalDependencies || {}),
|
|
107
|
+
...(pkg.peerDependencies || {}),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
module.exports = {
|
|
112
|
+
PKG_NAME,
|
|
113
|
+
isDirectInstall,
|
|
114
|
+
declaredInProjectJson,
|
|
115
|
+
anySiblingDependsOnUs,
|
|
116
|
+
pkgListsUs,
|
|
117
|
+
};
|