@seoagent-official/seoagent 1.42.1 → 1.42.2

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
@@ -35,27 +35,21 @@
35
35
 
36
36
  ---
37
37
 
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.
38
+ > **This package is a one-shot scaffolder, not a runtime dependency.** Run `init` once to scaffold `.seoagent/` + the Claude Code skill in your repo. Nothing to keep in `package.json` afterwards. No `postinstall` script — installs are silent and play nicely with npm 11+.
39
39
 
40
40
  ## Install
41
41
 
42
- **Pick either form. Both work the same:**
43
-
44
42
  ```bash
45
- # Preferred — no package.json bloat:
46
43
  npx -y @seoagent-official/seoagent init
47
-
48
- # Also works — `npm install` runs `init` automatically via postinstall:
49
- npm install @seoagent-official/seoagent
50
44
  ```
51
45
 
52
- Either way, the scaffolder will:
46
+ The scaffolder will:
53
47
  - Scan your repo for `package.json` `homepage` field + common `.env` files (`NEXT_PUBLIC_SITE_URL`, `SITE_URL`, etc.) to infer your domain
54
48
  - Create `.seoagent/` with `project.md`, `context.md`, and folders for audits, briefs, content
55
49
  - Install the skill at `.claude/skills/seoagent/SKILL.md` so Claude Code picks it up
56
50
  - Add a `PostToolUse` hook to `.claude/settings.json` so edits to `.seoagent/` auto-sync to the cloud (when you're logged in)
57
51
 
58
- 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.
52
+ The scaffolded sync hook uses `npx -y @seoagent-official/seoagent sync --silent` so the package is fetched on demand from then on — no `package.json` entry needed.
59
53
 
60
54
  Then open Claude Code in this repo and say *"audit my site."* The skill takes it from there.
61
55
 
@@ -70,19 +64,6 @@ Then open Claude Code in this repo and say *"audit my site."* The skill takes it
70
64
  npx -y @seoagent-official/seoagent init --yes --domain example.com
71
65
  ```
72
66
 
73
- ### Skip the auto-init on `npm install`
74
-
75
- If you really want to install the package without scaffolding:
76
-
77
- ```bash
78
- SEOAGENT_SKIP_AUTOINIT=1 npm install @seoagent-official/seoagent
79
- ```
80
-
81
- You'd want this only when running `init` manually with custom flags. The auto-init also skips automatically when:
82
- - `CI=true` (or `CI=1`)
83
- - The package is being installed globally (`npm install -g`)
84
- - `.seoagent/project.md` already exists in the repo
85
-
86
67
  ### Optional: install globally for a bare `seoagent` command
87
68
 
88
69
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seoagent-official/seoagent",
3
- "version": "1.42.1",
3
+ "version": "1.42.2",
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,9 +9,7 @@
9
9
  "files": [
10
10
  "index.js",
11
11
  "skills",
12
- "assets",
13
- "postinstall-hint.cjs",
14
- "postinstall-lib.cjs"
12
+ "assets"
15
13
  ],
16
14
  "dependencies": {
17
15
  "@clack/prompts": "^0.9.0",
@@ -31,9 +29,6 @@
31
29
  "type": "git",
32
30
  "url": "git+https://github.com/Baxter-Inc/seoagent-npm.git"
33
31
  },
34
- "scripts": {
35
- "postinstall": "node ./postinstall-hint.cjs"
36
- },
37
32
  "publishConfig": {
38
33
  "access": "public"
39
34
  }
@@ -1,200 +0,0 @@
1
- 'use strict';
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
- const { isDirectInstall } = require('./postinstall-lib.cjs');
23
-
24
- // -----------------------------------------------------------------------------
25
- // Guards — skip auto-init when it would be wrong or annoying.
26
- // -----------------------------------------------------------------------------
27
-
28
- // CI: postinstall must be non-interactive and side-effect-free in CI builds.
29
- if (process.env.CI === 'true' || process.env.CI === '1') {
30
- process.exit(0);
31
- }
32
-
33
- // User opt-out: bail without touching the repo.
34
- if (process.env.SEOAGENT_SKIP_AUTOINIT === '1') {
35
- printOptOutNotice();
36
- process.exit(0);
37
- }
38
-
39
- // Global install: there's no project context to scaffold into.
40
- if (process.env.npm_config_global === 'true') {
41
- printGlobalInstallHint();
42
- process.exit(0);
43
- }
44
-
45
- // `INIT_CWD` is the directory where the user ran `npm install`. npm sets it
46
- // automatically. If it's missing we're probably being run by something other
47
- // than npm (e.g., yarn 1 used to skip this) — bail safely.
48
- const projectRoot = process.env.INIT_CWD;
49
- if (!projectRoot || !fs.existsSync(projectRoot)) {
50
- printHintFallback();
51
- process.exit(0);
52
- }
53
-
54
- // Don't auto-init when the package is being installed inside another
55
- // package's `node_modules` (a transitive dep). INIT_CWD would be the
56
- // transitive consumer's project root, not ours — we'd scaffold their repo.
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)) {
71
- // Most likely transitive — bail silently.
72
- process.exit(0);
73
- }
74
-
75
- // Already scaffolded? No need to rerun init.
76
- const scaffoldMarker = path.join(projectRoot, '.seoagent', 'project.md');
77
- if (fs.existsSync(scaffoldMarker)) {
78
- printAlreadyScaffoldedNotice(projectRoot);
79
- process.exit(0);
80
- }
81
-
82
- // -----------------------------------------------------------------------------
83
- // Run init from the user's project root.
84
- // -----------------------------------------------------------------------------
85
-
86
- const binPath = path.join(__dirname, 'index.js');
87
- if (!fs.existsSync(binPath)) {
88
- // Shouldn't happen in a published package — index.js sits next to this
89
- // script in dist/. Fall back to printing the hint.
90
- printHintFallback();
91
- process.exit(0);
92
- }
93
-
94
- printPreInitBanner();
95
-
96
- const result = spawnSync(process.execPath, [binPath, 'init', '--yes'], {
97
- cwd: projectRoot,
98
- stdio: 'inherit',
99
- env: { ...process.env, SEOAGENT_FROM_POSTINSTALL: '1' },
100
- });
101
-
102
- if (result.status === 0) {
103
- printPostInitNotice();
104
- } else {
105
- // init failed — most common cause: domain not inferable in --yes mode.
106
- printInitFailureFallback();
107
- }
108
-
109
- // -----------------------------------------------------------------------------
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.
113
- // -----------------------------------------------------------------------------
114
-
115
- function printPreInitBanner() {
116
- process.stdout.write(
117
- '\n' +
118
- ' ════════════════════════════════════════════════════════════════════\n' +
119
- ' @seoagent-official/seoagent — scaffolding your repo\n' +
120
- ' ════════════════════════════════════════════════════════════════════\n' +
121
- '\n' +
122
- ' This package is a one-shot scaffolder. Running `init --yes` now to\n' +
123
- ' create .seoagent/ + .claude/skills/seoagent/ in your repo. To skip\n' +
124
- ' this on future installs, set SEOAGENT_SKIP_AUTOINIT=1.\n' +
125
- '\n'
126
- );
127
- }
128
-
129
- function printPostInitNotice() {
130
- // init's outro already printed the full next-steps prompt for both
131
- // agents and humans. This is just the postinstall-specific cleanup
132
- // hint that wouldn't be relevant to someone running `npx init` directly.
133
- process.stdout.write(
134
- '\n' +
135
- ' Note for AGENTS and HUMANS: @seoagent-official/seoagent was added to\n' +
136
- ' your package.json (devDependencies) by `npm install`. You can safely\n' +
137
- ' remove it — its job is done. Subsequent `sync`/`login`/`status` calls\n' +
138
- ' run via `npx -y @seoagent-official/seoagent <cmd>` and fetch on demand.\n' +
139
- '\n'
140
- );
141
- }
142
-
143
- function printInitFailureFallback() {
144
- process.stdout.write(
145
- '\n' +
146
- ' ⚠ Auto-init didn\'t complete (most likely: domain not inferable from\n' +
147
- ' package.json or .env files in --yes mode). Run this in your repo\n' +
148
- ' root to finish setup, replacing the domain:\n' +
149
- '\n' +
150
- ' npx -y @seoagent-official/seoagent init --domain your-domain.com\n' +
151
- '\n'
152
- );
153
- }
154
-
155
- function printGlobalInstallHint() {
156
- process.stdout.write(
157
- '\n' +
158
- ' @seoagent-official/seoagent installed globally.\n' +
159
- '\n' +
160
- ' In a project repo where you want SEOAgent, run:\n' +
161
- '\n' +
162
- ' seoagent init\n' +
163
- '\n' +
164
- ' That scaffolds .seoagent/ + .claude/skills/seoagent/ in the current\n' +
165
- ' directory. Then open Claude Code and say "audit my site."\n' +
166
- '\n'
167
- );
168
- }
169
-
170
- function printOptOutNotice() {
171
- process.stdout.write(
172
- '\n' +
173
- ' @seoagent-official/seoagent installed (auto-init skipped via\n' +
174
- ' SEOAGENT_SKIP_AUTOINIT). Run `npx -y @seoagent-official/seoagent init`\n' +
175
- ' in your repo root to scaffold when you\'re ready.\n' +
176
- '\n'
177
- );
178
- }
179
-
180
- function printAlreadyScaffoldedNotice(root) {
181
- process.stdout.write(
182
- '\n' +
183
- ' @seoagent-official/seoagent installed. .seoagent/ already exists in\n' +
184
- ` ${root} — skipping auto-init.\n` +
185
- '\n'
186
- );
187
- }
188
-
189
- function printHintFallback() {
190
- process.stdout.write(
191
- '\n' +
192
- ' @seoagent-official/seoagent installed — but NOT yet scaffolded.\n' +
193
- '\n' +
194
- ' This package is a scaffolder, not a runtime dependency. Run this in\n' +
195
- ' your repo root to finish setup:\n' +
196
- '\n' +
197
- ' npx -y @seoagent-official/seoagent init\n' +
198
- '\n'
199
- );
200
- }
@@ -1,117 +0,0 @@
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
- };