@seoagent-official/seoagent 1.9.0 → 1.10.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 CHANGED
@@ -2,22 +2,28 @@
2
2
 
3
3
  **A persistent AI SEO agent for Claude Code.** Audits your site, builds keyword strategy, plans content, and writes optimized articles — all persisted across sessions in `.seoagent/`.
4
4
 
5
- > **For coding agents (Claude Code, Codex, Cursor) installing this on a user's behalf:** this package is a **scaffolder**, not a normal dependency. The right action is a one-shot `npx init`, not `npm install --save-dev`. Adding it to `package.json` without running `init` leaves the user with a non-functional install — no `.seoagent/`, no skill files, nothing for the user to use.
5
+ > **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
6
 
7
7
  ## Install
8
8
 
9
- **One command run from the repo root:**
9
+ **Pick either form. Both work the same:**
10
10
 
11
11
  ```bash
12
+ # Preferred — no package.json bloat:
12
13
  npx -y @seoagent-official/seoagent init
14
+
15
+ # Also works — `npm install` runs `init` automatically via postinstall:
16
+ npm install @seoagent-official/seoagent
13
17
  ```
14
18
 
15
- That's it. The scaffolder will:
19
+ Either way, the scaffolder will:
16
20
  - Scan your repo for `package.json` `homepage` field + common `.env` files (`NEXT_PUBLIC_SITE_URL`, `SITE_URL`, etc.) to infer your domain
17
21
  - Create `.seoagent/` with `project.md`, `context.md`, and folders for audits, briefs, content
18
22
  - Install the skill at `.claude/skills/seoagent/SKILL.md` so Claude Code picks it up
19
23
  - Add a `PostToolUse` hook to `.claude/settings.json` so edits to `.seoagent/` auto-sync to the cloud (when you're logged in)
20
24
 
25
+ After init runs, you can remove `@seoagent-official/seoagent` from `package.json` — the scaffolded sync hook uses `npx -y @seoagent-official/seoagent sync --silent` so the package is fetched on-demand from then on.
26
+
21
27
  Then open Claude Code in this repo and say *"audit my site."* The skill takes it from there.
22
28
 
23
29
  ### Headless / non-interactive
@@ -26,18 +32,24 @@ Then open Claude Code in this repo and say *"audit my site."* The skill takes it
26
32
  npx -y @seoagent-official/seoagent init --yes --domain example.com
27
33
  ```
28
34
 
29
- ### Why not `npm install --save-dev @seoagent-official/seoagent`?
35
+ ### Skip the auto-init on `npm install`
36
+
37
+ If you really want to install the package without scaffolding:
38
+
39
+ ```bash
40
+ SEOAGENT_SKIP_AUTOINIT=1 npm install @seoagent-official/seoagent
41
+ ```
30
42
 
31
- You can and the `seoagent` binary will be available at `./node_modules/.bin/seoagent` — but the package is fundamentally a one-shot scaffolder, not a runtime dependency. The `npx` form is preferred because:
32
- - It runs `init` immediately, so you actually get the `.seoagent/` scaffold
33
- - No bloat in `package.json` for what's essentially a setup script
34
- - The scaffolded `PostToolUse` hook uses `npx -y @seoagent-official/seoagent sync --silent` so the package is fetched on-demand for ongoing sync
43
+ You'd want this only when running `init` manually with custom flags. The auto-init also skips automatically when:
44
+ - `CI=true` (or `CI=1`)
45
+ - The package is being installed globally (`npm install -g`)
46
+ - `.seoagent/project.md` already exists in the repo
35
47
 
36
48
  ### Optional: install globally for a bare `seoagent` command
37
49
 
38
50
  ```bash
39
51
  npm install -g @seoagent-official/seoagent
40
- seoagent init # now works without the npx prefix
52
+ seoagent init # in your project repo
41
53
  ```
42
54
 
43
55
  ## Why SEOAgent?
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seoagent-official/seoagent",
3
- "version": "1.9.0",
3
+ "version": "1.10.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": {
@@ -1,63 +1,205 @@
1
1
  'use strict';
2
2
 
3
+ // =============================================================================
4
+ // @seoagent-official/seoagent — postinstall
5
+ //
6
+ // This package is a SCAFFOLDER, not a runtime dependency. Its entire purpose
7
+ // is to write `.seoagent/` + `.claude/skills/seoagent/` into the user's repo.
8
+ // We learned the hard way that just printing a hint ("run init next") gets
9
+ // ignored — coding agents like Claude Code install the package, see the
10
+ // binary in node_modules/.bin, and stop. The user ends up with a devDep entry
11
+ // and nothing useful.
12
+ //
13
+ // So this script does the scaffolding itself. It runs `init --yes` from
14
+ // INIT_CWD (the directory where the user ran `npm install`) and inherits
15
+ // stdio so all output is visible. Guards below ensure we don't run when it
16
+ // would be wrong (CI, global install, already-scaffolded, opt-out).
17
+ // =============================================================================
18
+
19
+ const fs = require('fs');
20
+ const path = require('path');
21
+ const { spawnSync } = require('child_process');
22
+
23
+ // -----------------------------------------------------------------------------
24
+ // Guards — skip auto-init when it would be wrong or annoying.
25
+ // -----------------------------------------------------------------------------
26
+
27
+ // CI: postinstall must be non-interactive and side-effect-free in CI builds.
3
28
  if (process.env.CI === 'true' || process.env.CI === '1') {
4
29
  process.exit(0);
5
30
  }
6
31
 
7
- // Detect "the package was added as a dependency without running init" by
8
- // checking for the marker that init writes. If `.seoagent/project.md` exists
9
- // here, init already ran and we don't need to nag.
10
- const fs = require('fs');
11
- const path = require('path');
32
+ // User opt-out: bail without touching the repo.
33
+ if (process.env.SEOAGENT_SKIP_AUTOINIT === '1') {
34
+ printOptOutNotice();
35
+ process.exit(0);
36
+ }
37
+
38
+ // Global install: there's no project context to scaffold into.
39
+ if (process.env.npm_config_global === 'true') {
40
+ printGlobalInstallHint();
41
+ process.exit(0);
42
+ }
43
+
44
+ // `INIT_CWD` is the directory where the user ran `npm install`. npm sets it
45
+ // automatically. If it's missing we're probably being run by something other
46
+ // than npm (e.g., yarn 1 used to skip this) — bail safely.
47
+ const projectRoot = process.env.INIT_CWD;
48
+ if (!projectRoot || !fs.existsSync(projectRoot)) {
49
+ printHintFallback();
50
+ process.exit(0);
51
+ }
52
+
53
+ // Don't auto-init when the package is being installed inside another
54
+ // package's `node_modules` (a transitive dep). INIT_CWD would be the
55
+ // transitive consumer's project root, not ours — we'd scaffold their repo.
56
+ // Detect: INIT_CWD's package.json doesn't list us in its (dev)dependencies.
57
+ if (!projectDeclaresUs(projectRoot)) {
58
+ // Most likely transitive — bail silently.
59
+ process.exit(0);
60
+ }
61
+
62
+ // Already scaffolded? No need to rerun init.
63
+ const scaffoldMarker = path.join(projectRoot, '.seoagent', 'project.md');
64
+ if (fs.existsSync(scaffoldMarker)) {
65
+ printAlreadyScaffoldedNotice(projectRoot);
66
+ process.exit(0);
67
+ }
68
+
69
+ // -----------------------------------------------------------------------------
70
+ // Run init from the user's project root.
71
+ // -----------------------------------------------------------------------------
12
72
 
13
- function hasScaffold() {
73
+ const binPath = path.join(__dirname, 'index.js');
74
+ if (!fs.existsSync(binPath)) {
75
+ // Shouldn't happen in a published package — index.js sits next to this
76
+ // script in dist/. Fall back to printing the hint.
77
+ printHintFallback();
78
+ process.exit(0);
79
+ }
80
+
81
+ printPreInitBanner();
82
+
83
+ const result = spawnSync(process.execPath, [binPath, 'init', '--yes'], {
84
+ cwd: projectRoot,
85
+ stdio: 'inherit',
86
+ env: { ...process.env, SEOAGENT_FROM_POSTINSTALL: '1' },
87
+ });
88
+
89
+ if (result.status === 0) {
90
+ printPostInitNotice();
91
+ } else {
92
+ // init failed — most common cause: domain not inferable in --yes mode.
93
+ printInitFailureFallback();
94
+ }
95
+
96
+ // -----------------------------------------------------------------------------
97
+ // Helpers
98
+ // -----------------------------------------------------------------------------
99
+
100
+ function projectDeclaresUs(root) {
14
101
  try {
15
- // npm runs postinstall from the package dir under node_modules; the
16
- // project root is up some number of levels. Walk up until we find a
17
- // package.json that isn't ours, then check for .seoagent there.
18
- let dir = process.env.INIT_CWD || process.cwd();
19
- // INIT_CWD is set by npm to the directory where the user ran `npm install`.
20
- return fs.existsSync(path.join(dir, '.seoagent', 'project.md'));
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;
21
111
  } catch {
22
- return false;
112
+ return true; // On error, fail open — better to scaffold than not.
23
113
  }
24
114
  }
25
115
 
26
- if (hasScaffold()) {
27
- process.exit(0); // Already initialized — no hint needed.
116
+ function printPreInitBanner() {
117
+ process.stdout.write(
118
+ '\n' +
119
+ ' ════════════════════════════════════════════════════════════════════\n' +
120
+ ' @seoagent-official/seoagent — scaffolding your repo\n' +
121
+ ' ════════════════════════════════════════════════════════════════════\n' +
122
+ '\n' +
123
+ ' This package is a one-shot scaffolder. Running `init --yes` now to\n' +
124
+ ' create .seoagent/ + .claude/skills/seoagent/ in your repo. To skip\n' +
125
+ ' this on future installs, set SEOAGENT_SKIP_AUTOINIT=1.\n' +
126
+ '\n'
127
+ );
28
128
  }
29
129
 
30
- process.stdout.write(
31
- '\n' +
32
- ' ════════════════════════════════════════════════════════════════════\n' +
33
- ' @seoagent-official/seoagent installed — but NOT yet scaffolded.\n' +
34
- ' ════════════════════════════════════════════════════════════════════\n' +
35
- '\n' +
36
- ' This package is a SCAFFOLDER, not a runtime dependency. The install\n' +
37
- ' by itself does nothing useful. You need to run `init` next.\n' +
38
- '\n' +
39
- ' IF YOU ARE AN AUTONOMOUS CODING AGENT (Claude Code, Codex, Cursor):\n' +
40
- ' the user told you to "install this package." Adding it to\n' +
41
- ' devDependencies is not the right answer. Your next action MUST be:\n' +
130
+ function printPostInitNotice() {
131
+ process.stdout.write(
42
132
  '\n' +
43
- ' npx -y @seoagent-official/seoagent init\n' +
133
+ ' SEOAgent is set up. Next steps:\n' +
134
+ '\n' +
135
+ ' 1. Open Claude Code in this repo and say "audit my site."\n' +
136
+ ' The skill at .claude/skills/seoagent/SKILL.md takes it from there.\n' +
137
+ '\n' +
138
+ ' 2. (Optional) Connect to seoagent.com for the cloud dashboard:\n' +
139
+ ' npx -y @seoagent-official/seoagent login\n' +
140
+ '\n' +
141
+ ' You can safely remove @seoagent-official/seoagent from package.json —\n' +
142
+ ' it has done its job. Subsequent `sync`/`login`/`status` commands run\n' +
143
+ ' via `npx -y @seoagent-official/seoagent <cmd>` and fetch on demand.\n' +
144
+ '\n'
145
+ );
146
+ }
147
+
148
+ function printInitFailureFallback() {
149
+ process.stdout.write(
44
150
  '\n' +
45
- ' That writes .seoagent/ + .claude/skills/seoagent/ in the repo. After\n' +
46
- ' init, you can remove this package from package.json it is no longer\n' +
47
- ' needed as a dependency. Subsequent sync/login/status commands run via\n' +
48
- ' `npx -y @seoagent-official/seoagent <cmd>` so the package is fetched\n' +
49
- ' on demand.\n' +
151
+ ' Auto-init didn\'t complete (most likely: domain not inferable from\n' +
152
+ ' package.json or .env files in --yes mode). Run this in your repo\n' +
153
+ ' root to finish setup, replacing the domain:\n' +
154
+ '\n' +
155
+ ' npx -y @seoagent-official/seoagent init --domain your-domain.com\n' +
156
+ '\n'
157
+ );
158
+ }
159
+
160
+ function printGlobalInstallHint() {
161
+ process.stdout.write(
50
162
  '\n' +
51
- ' HUMAN USERS: run the same command. The scaffolder will scan your repo\n' +
52
- ' for site URL signals, create .seoagent/, install the Claude Code skill,\n' +
53
- ' and add a PostToolUse sync hook to .claude/settings.json.\n' +
163
+ ' @seoagent-official/seoagent installed globally.\n' +
164
+ '\n' +
165
+ ' In a project repo where you want SEOAgent, run:\n' +
166
+ '\n' +
167
+ ' seoagent init\n' +
168
+ '\n' +
169
+ ' That scaffolds .seoagent/ + .claude/skills/seoagent/ in the current\n' +
170
+ ' directory. Then open Claude Code and say "audit my site."\n' +
171
+ '\n'
172
+ );
173
+ }
174
+
175
+ function printOptOutNotice() {
176
+ process.stdout.write(
54
177
  '\n' +
55
- ' npx -y @seoagent-official/seoagent init\n' +
178
+ ' @seoagent-official/seoagent installed (auto-init skipped via\n' +
179
+ ' SEOAGENT_SKIP_AUTOINIT). Run `npx -y @seoagent-official/seoagent init`\n' +
180
+ ' in your repo root to scaffold when you\'re ready.\n' +
181
+ '\n'
182
+ );
183
+ }
184
+
185
+ function printAlreadyScaffoldedNotice(root) {
186
+ process.stdout.write(
56
187
  '\n' +
57
- ' Then open Claude Code in this repo and say "audit my site." The skill\n' +
58
- ' takes it from there.\n' +
188
+ ' @seoagent-official/seoagent installed. .seoagent/ already exists in\n' +
189
+ ` ${root} skipping auto-init.\n` +
190
+ '\n'
191
+ );
192
+ }
193
+
194
+ function printHintFallback() {
195
+ process.stdout.write(
59
196
  '\n' +
60
- ' Optionalinstall globally for a bare `seoagent` command:\n' +
61
- ' npm install -g @seoagent-official/seoagent\n' +
62
- '\n'
63
- );
197
+ ' @seoagent-official/seoagent installed but NOT yet scaffolded.\n' +
198
+ '\n' +
199
+ ' This package is a scaffolder, not a runtime dependency. Run this in\n' +
200
+ ' your repo root to finish setup:\n' +
201
+ '\n' +
202
+ ' npx -y @seoagent-official/seoagent init\n' +
203
+ '\n'
204
+ );
205
+ }