hive-rank 3.0.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 +76 -0
- package/bin/install.js +519 -0
- package/commands/audit-site.md +47 -0
- package/commands/audit.md +22 -0
- package/commands/baseline.md +39 -0
- package/commands/competitors.md +37 -0
- package/commands/content.md +39 -0
- package/commands/grow.md +40 -0
- package/commands/help.md +67 -0
- package/commands/keywords.md +48 -0
- package/commands/kickstart.md +43 -0
- package/commands/learn.md +80 -0
- package/commands/rankings.md +22 -0
- package/commands/report.md +40 -0
- package/commands/spy.md +48 -0
- package/commands/status.md +45 -0
- package/commands/trends.md +44 -0
- package/dist/hooks/capture-seo-data.js +419 -0
- package/dist/hooks/config.json +5 -0
- package/package.json +45 -0
package/README.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# hive-rank
|
|
2
|
+
|
|
3
|
+
Crowdsourced SEO intelligence for AI agents.
|
|
4
|
+
|
|
5
|
+
## What is this?
|
|
6
|
+
|
|
7
|
+
Hive Rank aggregates anonymized search data from Claude Code agents into a shared ranking dataset. Every participant benefits from the collective intelligence of the network.
|
|
8
|
+
|
|
9
|
+
**AI agents are searching for your product right now. Do you know where you rank?**
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx hive-rank
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Or add the MCP server directly:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
claude mcp add --transport sse hive-rank https://mcp.hive-rank.com/mcp
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## What you get
|
|
24
|
+
|
|
25
|
+
### 6 MCP Tools
|
|
26
|
+
|
|
27
|
+
| Tool | Description |
|
|
28
|
+
|------|-------------|
|
|
29
|
+
| `hive_rankings` | Aggregated rankings from the network for a query |
|
|
30
|
+
| `hive_trending` | Trending queries across the network |
|
|
31
|
+
| `hive_domain` | Network intelligence for a domain |
|
|
32
|
+
| `hive_stats` | Network-wide statistics |
|
|
33
|
+
| `hive_contributors` | Contributor activity stats |
|
|
34
|
+
| `hive_search` | Full-text search across network queries |
|
|
35
|
+
|
|
36
|
+
### 15 Slash Commands
|
|
37
|
+
|
|
38
|
+
Research commands that generate data for the network:
|
|
39
|
+
|
|
40
|
+
- `/hive:kickstart [domain]` — Bootstrap your SEO research
|
|
41
|
+
- `/hive:baseline [domain] [keywords...]` — Establish baseline rankings
|
|
42
|
+
- `/hive:grow [domain]` — Weekly growth check
|
|
43
|
+
- `/hive:spy [competitor]` — Deep competitor analysis
|
|
44
|
+
- `/hive:content [topic]` — Generate SEO content brief
|
|
45
|
+
|
|
46
|
+
Network intelligence commands:
|
|
47
|
+
|
|
48
|
+
- `/hive:rankings [query]` — Check network ranking data
|
|
49
|
+
- `/hive:trends` — See what's trending
|
|
50
|
+
- `/hive:status` — Network status and your contribution stats
|
|
51
|
+
- `/hive:help` — Full command reference
|
|
52
|
+
|
|
53
|
+
## How it works
|
|
54
|
+
|
|
55
|
+
1. **You search** — Every WebSearch and WebFetch captures SEO data
|
|
56
|
+
2. **Hook contributes** — Anonymized data flows to the network
|
|
57
|
+
3. **Network grows** — Aggregated insights become available
|
|
58
|
+
4. **Everyone benefits** — Richer data for all agents
|
|
59
|
+
|
|
60
|
+
## Privacy
|
|
61
|
+
|
|
62
|
+
- **Identity**: SHA-256 hash of a random UUID. No accounts, no emails.
|
|
63
|
+
- **Timestamps**: Bucketed to date-only (YYYY-MM-DD). No session correlation.
|
|
64
|
+
- **Data**: Only public search results — queries, URLs, positions, titles, snippets.
|
|
65
|
+
|
|
66
|
+
We couldn't identify you even if we wanted to.
|
|
67
|
+
|
|
68
|
+
## Links
|
|
69
|
+
|
|
70
|
+
- **Dashboard**: https://www.hive-rank.com/dashboard
|
|
71
|
+
- **Documentation**: https://www.hive-rank.com/docs
|
|
72
|
+
- **GitHub**: https://github.com/lil-serp/grow-your-shit
|
|
73
|
+
|
|
74
|
+
## License
|
|
75
|
+
|
|
76
|
+
MIT
|
package/bin/install.js
ADDED
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Hive Rank — Installer
|
|
4
|
+
*
|
|
5
|
+
* Pure Node.js installer (zero external deps).
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* node bin/install.js # from repo clone
|
|
9
|
+
* npx hive-rank # from npm (if published)
|
|
10
|
+
*
|
|
11
|
+
* Installs Hive Rank globally to ~/.hive-rank/ and configures
|
|
12
|
+
* Claude Code hooks, remote MCP server, and slash commands.
|
|
13
|
+
*
|
|
14
|
+
* No native dependencies — no npm install needed.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import fs from 'fs';
|
|
18
|
+
import path from 'path';
|
|
19
|
+
import os from 'os';
|
|
20
|
+
import { spawnSync } from 'child_process';
|
|
21
|
+
import readline from 'readline';
|
|
22
|
+
import crypto from 'crypto';
|
|
23
|
+
|
|
24
|
+
// ── Constants ──────────────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
const HOME = os.homedir();
|
|
27
|
+
const INSTALL_DIR = path.join(HOME, '.hive-rank');
|
|
28
|
+
const CLAUDE_DIR = path.join(HOME, '.claude');
|
|
29
|
+
const SETTINGS_FILE = path.join(CLAUDE_DIR, 'settings.json');
|
|
30
|
+
const COMMANDS_DIR = path.join(CLAUDE_DIR, 'commands', 'hive');
|
|
31
|
+
const PKG_ROOT = path.resolve(new URL('..', import.meta.url).pathname);
|
|
32
|
+
|
|
33
|
+
const HOOK_CMD = `node ${path.join(INSTALL_DIR, 'dist', 'hooks', 'capture-seo-data.js')}`;
|
|
34
|
+
const MCP_NAME = 'hive-rank';
|
|
35
|
+
const MCP_URL = 'https://mcp.hive-rank.com/mcp';
|
|
36
|
+
|
|
37
|
+
// ── Helpers ────────────────────────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
function log(msg) { console.log(` ${msg}`); }
|
|
40
|
+
function success(msg) { console.log(` [ok] ${msg}`); }
|
|
41
|
+
function warn(msg) { console.log(` [warn] ${msg}`); }
|
|
42
|
+
function fail(msg) { console.error(` [error] ${msg}`); process.exit(1); }
|
|
43
|
+
function dryLog(msg) { console.log(` [dry-run] ${msg}`); }
|
|
44
|
+
|
|
45
|
+
function fileExists(p) {
|
|
46
|
+
try { fs.accessSync(p); return true; } catch { return false; }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function readJson(p) {
|
|
50
|
+
return JSON.parse(fs.readFileSync(p, 'utf-8'));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function writeJsonAtomic(p, obj) {
|
|
54
|
+
const tmp = p + '.tmp.' + crypto.randomBytes(4).toString('hex');
|
|
55
|
+
fs.writeFileSync(tmp, JSON.stringify(obj, null, 2) + '\n', 'utf-8');
|
|
56
|
+
fs.renameSync(tmp, p);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function copyRecursive(src, dest) {
|
|
60
|
+
if (!fileExists(src)) return;
|
|
61
|
+
const stat = fs.statSync(src);
|
|
62
|
+
if (stat.isDirectory()) {
|
|
63
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
64
|
+
for (const entry of fs.readdirSync(src)) {
|
|
65
|
+
copyRecursive(path.join(src, entry), path.join(dest, entry));
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
69
|
+
fs.copyFileSync(src, dest);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function ask(question) {
|
|
74
|
+
if (!process.stdin.isTTY) return null;
|
|
75
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
76
|
+
return new Promise((resolve) => {
|
|
77
|
+
rl.question(` ${question} `, (answer) => {
|
|
78
|
+
rl.close();
|
|
79
|
+
resolve(answer.trim());
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function hasClaudeCli() {
|
|
85
|
+
try {
|
|
86
|
+
const result = spawnSync('claude', ['--version'], { stdio: 'pipe', timeout: 5000 });
|
|
87
|
+
return result.status === 0;
|
|
88
|
+
} catch {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function registerMcpServer() {
|
|
94
|
+
if (hasClaudeCli()) {
|
|
95
|
+
// Remove old entries from all scopes
|
|
96
|
+
for (const oldName of [MCP_NAME, 'gys-local', 'hive-seo', 'grow-your-shit']) {
|
|
97
|
+
spawnSync('claude', ['mcp', 'remove', '--scope', 'user', oldName], { stdio: 'pipe' });
|
|
98
|
+
spawnSync('claude', ['mcp', 'remove', oldName], { stdio: 'pipe' });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Register remote MCP server via SSE
|
|
102
|
+
const result = spawnSync(
|
|
103
|
+
'claude',
|
|
104
|
+
['mcp', 'add', '--scope', 'user', '--transport', 'sse', MCP_NAME, MCP_URL],
|
|
105
|
+
{ stdio: 'pipe', timeout: 10000 }
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
if (result.status === 0) return 'cli';
|
|
109
|
+
warn(`claude mcp add failed: ${result.stderr?.toString().trim()}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Fallback: write to ~/.claude.json
|
|
113
|
+
const claudeJson = path.join(HOME, '.claude.json');
|
|
114
|
+
let config = {};
|
|
115
|
+
if (fileExists(claudeJson)) {
|
|
116
|
+
try {
|
|
117
|
+
config = readJson(claudeJson);
|
|
118
|
+
} catch {
|
|
119
|
+
warn('~/.claude.json exists but is not valid JSON. Skipping MCP registration.');
|
|
120
|
+
return 'skipped';
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Clean up old entries
|
|
125
|
+
if (config.mcpServers) {
|
|
126
|
+
for (const oldName of ['gys-local', 'hive-seo', 'grow-your-shit', MCP_NAME]) {
|
|
127
|
+
delete config.mcpServers[oldName];
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (!config.mcpServers) config.mcpServers = {};
|
|
132
|
+
config.mcpServers[MCP_NAME] = {
|
|
133
|
+
type: 'sse',
|
|
134
|
+
url: MCP_URL
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
writeJsonAtomic(claudeJson, config);
|
|
138
|
+
return 'direct';
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function removeMcpServer() {
|
|
142
|
+
if (hasClaudeCli()) {
|
|
143
|
+
for (const oldName of [MCP_NAME, 'gys-local', 'hive-seo', 'grow-your-shit']) {
|
|
144
|
+
spawnSync('claude', ['mcp', 'remove', '--scope', 'user', oldName], { stdio: 'pipe' });
|
|
145
|
+
spawnSync('claude', ['mcp', 'remove', oldName], { stdio: 'pipe' });
|
|
146
|
+
}
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const claudeJson = path.join(HOME, '.claude.json');
|
|
151
|
+
if (fileExists(claudeJson)) {
|
|
152
|
+
try {
|
|
153
|
+
const config = readJson(claudeJson);
|
|
154
|
+
let changed = false;
|
|
155
|
+
if (config.mcpServers) {
|
|
156
|
+
for (const oldName of [MCP_NAME, 'gys-local', 'hive-seo', 'grow-your-shit']) {
|
|
157
|
+
if (config.mcpServers[oldName]) { delete config.mcpServers[oldName]; changed = true; }
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (changed) writeJsonAtomic(claudeJson, config);
|
|
161
|
+
} catch { /* ignore */ }
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function configureHook() {
|
|
166
|
+
fs.mkdirSync(CLAUDE_DIR, { recursive: true });
|
|
167
|
+
|
|
168
|
+
let settings = {};
|
|
169
|
+
if (fileExists(SETTINGS_FILE)) {
|
|
170
|
+
try {
|
|
171
|
+
fs.copyFileSync(SETTINGS_FILE, SETTINGS_FILE + '.backup');
|
|
172
|
+
settings = readJson(SETTINGS_FILE);
|
|
173
|
+
} catch {
|
|
174
|
+
warn('Could not parse ~/.claude/settings.json — creating backup and starting fresh');
|
|
175
|
+
if (fileExists(SETTINGS_FILE)) {
|
|
176
|
+
fs.copyFileSync(SETTINGS_FILE, SETTINGS_FILE + '.corrupt.' + Date.now());
|
|
177
|
+
}
|
|
178
|
+
settings = {};
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Clean up old mcpServers (shouldn't be in settings.json)
|
|
183
|
+
if (settings.mcpServers) {
|
|
184
|
+
for (const oldName of ['gys-local', 'hive-seo', MCP_NAME]) {
|
|
185
|
+
delete settings.mcpServers[oldName];
|
|
186
|
+
}
|
|
187
|
+
if (Object.keys(settings.mcpServers).length === 0) delete settings.mcpServers;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Remove old hook entries
|
|
191
|
+
if (settings.hooks?.PostToolUse) {
|
|
192
|
+
settings.hooks.PostToolUse = settings.hooks.PostToolUse.filter((entry) => {
|
|
193
|
+
const cmd = entry.command || '';
|
|
194
|
+
const nestedCmds = (entry.hooks || []).map(h => h.command || '').join(' ');
|
|
195
|
+
const allCmds = cmd + ' ' + nestedCmds;
|
|
196
|
+
return !allCmds.includes('hive-seo') &&
|
|
197
|
+
!allCmds.includes('hive-rank') &&
|
|
198
|
+
!allCmds.includes('grow-your-shit');
|
|
199
|
+
});
|
|
200
|
+
if (settings.hooks.PostToolUse.length === 0) delete settings.hooks.PostToolUse;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Add hook
|
|
204
|
+
if (!settings.hooks) settings.hooks = {};
|
|
205
|
+
if (!settings.hooks.PostToolUse) settings.hooks.PostToolUse = [];
|
|
206
|
+
settings.hooks.PostToolUse.push({
|
|
207
|
+
matcher: 'WebSearch|WebFetch',
|
|
208
|
+
hooks: [{ type: 'command', command: HOOK_CMD }]
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
writeJsonAtomic(SETTINGS_FILE, settings);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function removeHook() {
|
|
215
|
+
if (!fileExists(SETTINGS_FILE)) return;
|
|
216
|
+
|
|
217
|
+
try {
|
|
218
|
+
const settings = readJson(SETTINGS_FILE);
|
|
219
|
+
if (settings.hooks?.PostToolUse) {
|
|
220
|
+
settings.hooks.PostToolUse = settings.hooks.PostToolUse.filter((entry) => {
|
|
221
|
+
const cmd = entry.command || '';
|
|
222
|
+
const nestedCmds = (entry.hooks || []).map(h => h.command || '').join(' ');
|
|
223
|
+
const allCmds = cmd + ' ' + nestedCmds;
|
|
224
|
+
return !allCmds.includes('hive-seo') &&
|
|
225
|
+
!allCmds.includes('hive-rank') &&
|
|
226
|
+
!allCmds.includes('grow-your-shit');
|
|
227
|
+
});
|
|
228
|
+
if (settings.hooks.PostToolUse.length === 0) delete settings.hooks.PostToolUse;
|
|
229
|
+
if (settings.hooks && Object.keys(settings.hooks).length === 0) delete settings.hooks;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (settings.mcpServers) {
|
|
233
|
+
for (const oldName of ['gys-local', 'hive-seo', MCP_NAME]) {
|
|
234
|
+
delete settings.mcpServers[oldName];
|
|
235
|
+
}
|
|
236
|
+
if (Object.keys(settings.mcpServers).length === 0) delete settings.mcpServers;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
writeJsonAtomic(SETTINGS_FILE, settings);
|
|
240
|
+
} catch { /* ignore */ }
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ── Parse args ─────────────────────────────────────────────────────────────────
|
|
244
|
+
|
|
245
|
+
const args = process.argv.slice(2);
|
|
246
|
+
const FLAG_FORCE = args.includes('--force');
|
|
247
|
+
const FLAG_UNINSTALL = args.includes('--uninstall');
|
|
248
|
+
const FLAG_DRY_RUN = args.includes('--dry-run');
|
|
249
|
+
const FLAG_HELP = args.includes('--help') || args.includes('-h');
|
|
250
|
+
|
|
251
|
+
if (FLAG_HELP) {
|
|
252
|
+
console.log(`
|
|
253
|
+
Hive Rank — Crowdsourced SEO intelligence for AI agents
|
|
254
|
+
|
|
255
|
+
Usage:
|
|
256
|
+
node bin/install.js Install from repo clone
|
|
257
|
+
node bin/install.js --force Reinstall (overwrite existing)
|
|
258
|
+
node bin/install.js --uninstall Remove Hive Rank completely
|
|
259
|
+
node bin/install.js --dry-run Preview changes without writing
|
|
260
|
+
node bin/install.js --help Show this help
|
|
261
|
+
|
|
262
|
+
What it does:
|
|
263
|
+
1. Copies dist + commands to ~/.hive-rank/
|
|
264
|
+
2. Registers hooks in ~/.claude/settings.json
|
|
265
|
+
3. Registers remote MCP server via \`claude mcp add\` (SSE transport)
|
|
266
|
+
4. Installs slash commands to ~/.claude/commands/hive/
|
|
267
|
+
|
|
268
|
+
Config locations:
|
|
269
|
+
Hooks: ~/.claude/settings.json
|
|
270
|
+
MCP server: ~/.claude.json (via \`claude mcp add --scope user\`)
|
|
271
|
+
Commands: ~/.claude/commands/hive/*.md
|
|
272
|
+
`);
|
|
273
|
+
process.exit(0);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// ── Uninstall ──────────────────────────────────────────────────────────────────
|
|
277
|
+
|
|
278
|
+
if (FLAG_UNINSTALL) {
|
|
279
|
+
if (FLAG_DRY_RUN) {
|
|
280
|
+
console.log('\n Uninstalling Hive Rank — DRY RUN (no changes will be made)\n');
|
|
281
|
+
dryLog('Would remove hooks from ' + SETTINGS_FILE);
|
|
282
|
+
dryLog('Would remove MCP server');
|
|
283
|
+
if (fileExists(COMMANDS_DIR)) dryLog('Would remove slash commands: ' + COMMANDS_DIR);
|
|
284
|
+
if (fileExists(INSTALL_DIR)) dryLog('Would remove ' + INSTALL_DIR + '/');
|
|
285
|
+
console.log('\n Dry run complete — no changes were made.\n');
|
|
286
|
+
process.exit(0);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
console.log('\n Uninstalling Hive Rank...\n');
|
|
290
|
+
|
|
291
|
+
removeHook();
|
|
292
|
+
success('Removed hooks from settings.json');
|
|
293
|
+
|
|
294
|
+
removeMcpServer();
|
|
295
|
+
success('Removed MCP server');
|
|
296
|
+
|
|
297
|
+
// Remove old gys commands too
|
|
298
|
+
const oldCommandsDir = path.join(CLAUDE_DIR, 'commands', 'gys');
|
|
299
|
+
if (fileExists(oldCommandsDir)) {
|
|
300
|
+
fs.rmSync(oldCommandsDir, { recursive: true, force: true });
|
|
301
|
+
success('Removed old /gys:* commands');
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (fileExists(COMMANDS_DIR)) {
|
|
305
|
+
fs.rmSync(COMMANDS_DIR, { recursive: true, force: true });
|
|
306
|
+
success('Removed slash commands');
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (fileExists(INSTALL_DIR)) {
|
|
310
|
+
fs.rmSync(INSTALL_DIR, { recursive: true, force: true });
|
|
311
|
+
success('Removed ~/.hive-rank/');
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Clean up old install dirs
|
|
315
|
+
for (const oldDir of ['.grow-your-shit', '.hive-seo']) {
|
|
316
|
+
const oldPath = path.join(HOME, oldDir);
|
|
317
|
+
if (fileExists(oldPath)) {
|
|
318
|
+
fs.rmSync(oldPath, { recursive: true, force: true });
|
|
319
|
+
success(`Removed ~/${oldDir}/`);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
console.log('\n Hive Rank has been uninstalled. Restart Claude Code to apply.\n');
|
|
324
|
+
process.exit(0);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// ── Install ────────────────────────────────────────────────────────────────────
|
|
328
|
+
|
|
329
|
+
async function install() {
|
|
330
|
+
if (FLAG_DRY_RUN) {
|
|
331
|
+
console.log('\n Hive Rank — DRY RUN (no changes will be made)\n');
|
|
332
|
+
} else {
|
|
333
|
+
console.log('\n Hive Rank — Crowdsourced SEO intelligence for AI agents\n');
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Preflight: check dist/ exists
|
|
337
|
+
const distDir = path.join(PKG_ROOT, 'dist');
|
|
338
|
+
if (!fileExists(distDir)) {
|
|
339
|
+
fail('dist/ not found. Run `pnpm run build` first, then `node bin/install.js`.');
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Check for existing installation
|
|
343
|
+
if (fileExists(INSTALL_DIR) && !FLAG_FORCE && !FLAG_DRY_RUN) {
|
|
344
|
+
const answer = await ask('Existing installation found. Overwrite? (y/N)');
|
|
345
|
+
if (!answer || answer.toLowerCase() !== 'y') {
|
|
346
|
+
log('Use --force to overwrite. Aborting.');
|
|
347
|
+
process.exit(0);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Preserve existing contributorId
|
|
352
|
+
let existingContributorId = null;
|
|
353
|
+
const configPaths = [
|
|
354
|
+
path.join(INSTALL_DIR, 'dist', 'hooks', 'config.json'),
|
|
355
|
+
path.join(HOME, '.grow-your-shit', 'dist', 'hooks', 'config.json'),
|
|
356
|
+
path.join(HOME, '.hive-seo', 'dist', 'hooks', 'config.json')
|
|
357
|
+
];
|
|
358
|
+
for (const configPath of configPaths) {
|
|
359
|
+
if (!existingContributorId && fileExists(configPath)) {
|
|
360
|
+
try {
|
|
361
|
+
const existingConfig = readJson(configPath);
|
|
362
|
+
existingContributorId = existingConfig?.contributorId ||
|
|
363
|
+
existingConfig?.hive?.contributorId;
|
|
364
|
+
} catch { /* ignore */ }
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// ── Step 1: Copy package files ──
|
|
369
|
+
|
|
370
|
+
if (FLAG_DRY_RUN) {
|
|
371
|
+
dryLog(`Would copy dist/ to ${INSTALL_DIR}/dist/`);
|
|
372
|
+
dryLog(`Would copy commands/ to ${INSTALL_DIR}/commands/`);
|
|
373
|
+
dryLog(`Would copy bin/install.js to ${INSTALL_DIR}/bin/install.js`);
|
|
374
|
+
} else {
|
|
375
|
+
log('Copying files to ~/.hive-rank/ ...');
|
|
376
|
+
|
|
377
|
+
if (fileExists(INSTALL_DIR)) {
|
|
378
|
+
fs.rmSync(INSTALL_DIR, { recursive: true, force: true });
|
|
379
|
+
}
|
|
380
|
+
fs.mkdirSync(INSTALL_DIR, { recursive: true });
|
|
381
|
+
|
|
382
|
+
copyRecursive(path.join(PKG_ROOT, 'dist'), path.join(INSTALL_DIR, 'dist'));
|
|
383
|
+
copyRecursive(path.join(PKG_ROOT, 'commands'), path.join(INSTALL_DIR, 'commands'));
|
|
384
|
+
|
|
385
|
+
const installerDest = path.join(INSTALL_DIR, 'bin');
|
|
386
|
+
fs.mkdirSync(installerDest, { recursive: true });
|
|
387
|
+
fs.copyFileSync(
|
|
388
|
+
path.join(PKG_ROOT, 'bin', 'install.js'),
|
|
389
|
+
path.join(installerDest, 'install.js')
|
|
390
|
+
);
|
|
391
|
+
|
|
392
|
+
const pkgVersion = readJson(path.join(PKG_ROOT, 'package.json')).version || '0.0.0';
|
|
393
|
+
writeJsonAtomic(path.join(INSTALL_DIR, 'version.json'), {
|
|
394
|
+
version: pkgVersion,
|
|
395
|
+
installedAt: new Date().toISOString()
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
success('Files copied');
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// ── Step 2: Write hooks config ──
|
|
402
|
+
|
|
403
|
+
const hooksConfigDir = path.join(INSTALL_DIR, 'dist', 'hooks');
|
|
404
|
+
const hooksConfigPath = path.join(hooksConfigDir, 'config.json');
|
|
405
|
+
const contributorId = existingContributorId || crypto.randomUUID();
|
|
406
|
+
|
|
407
|
+
if (FLAG_DRY_RUN) {
|
|
408
|
+
dryLog(`Would write hook config to ${hooksConfigPath}`);
|
|
409
|
+
dryLog(` contributorId: ${existingContributorId ? 'preserved' : 'new'}`);
|
|
410
|
+
} else {
|
|
411
|
+
fs.mkdirSync(hooksConfigDir, { recursive: true });
|
|
412
|
+
writeJsonAtomic(hooksConfigPath, {
|
|
413
|
+
enabled: true,
|
|
414
|
+
endpoint: 'https://mcp.hive-rank.com/api/contribute',
|
|
415
|
+
contributorId: contributorId
|
|
416
|
+
});
|
|
417
|
+
success('Hook config written');
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// ── Step 3: Register MCP server ──
|
|
421
|
+
|
|
422
|
+
if (FLAG_DRY_RUN) {
|
|
423
|
+
if (hasClaudeCli()) {
|
|
424
|
+
dryLog(`Would run: claude mcp add --scope user --transport sse ${MCP_NAME} ${MCP_URL}`);
|
|
425
|
+
} else {
|
|
426
|
+
dryLog('Would register MCP server in ~/.claude.json');
|
|
427
|
+
}
|
|
428
|
+
} else {
|
|
429
|
+
log('Registering remote MCP server...');
|
|
430
|
+
const mcpMethod = registerMcpServer();
|
|
431
|
+
if (mcpMethod === 'cli') {
|
|
432
|
+
success('MCP server registered via `claude mcp add` (SSE transport)');
|
|
433
|
+
} else if (mcpMethod === 'direct') {
|
|
434
|
+
success('MCP server registered in ~/.claude.json');
|
|
435
|
+
} else {
|
|
436
|
+
warn('MCP server registration skipped — run manually after install');
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// ── Step 4: Configure hook ──
|
|
441
|
+
|
|
442
|
+
if (FLAG_DRY_RUN) {
|
|
443
|
+
dryLog(`Would add PostToolUse hook to ${SETTINGS_FILE}`);
|
|
444
|
+
} else {
|
|
445
|
+
log('Configuring PostToolUse hook...');
|
|
446
|
+
configureHook();
|
|
447
|
+
success('Hook configured in ~/.claude/settings.json');
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// ── Step 5: Install slash commands ──
|
|
451
|
+
|
|
452
|
+
// Remove old gys commands
|
|
453
|
+
const oldCommandsDir = path.join(CLAUDE_DIR, 'commands', 'gys');
|
|
454
|
+
if (!FLAG_DRY_RUN && fileExists(oldCommandsDir)) {
|
|
455
|
+
fs.rmSync(oldCommandsDir, { recursive: true, force: true });
|
|
456
|
+
log('Removed old /gys:* commands');
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
if (FLAG_DRY_RUN) {
|
|
460
|
+
const commandsSource = path.join(PKG_ROOT, 'commands');
|
|
461
|
+
if (fileExists(commandsSource)) {
|
|
462
|
+
const cmdFiles = fs.readdirSync(commandsSource).filter(f => f.endsWith('.md'));
|
|
463
|
+
dryLog(`Would install ${cmdFiles.length} slash commands to ${COMMANDS_DIR}/`);
|
|
464
|
+
}
|
|
465
|
+
} else {
|
|
466
|
+
log('Installing slash commands...');
|
|
467
|
+
fs.mkdirSync(COMMANDS_DIR, { recursive: true });
|
|
468
|
+
|
|
469
|
+
const commandsSource = path.join(INSTALL_DIR, 'commands');
|
|
470
|
+
if (fileExists(commandsSource)) {
|
|
471
|
+
for (const file of fs.readdirSync(commandsSource)) {
|
|
472
|
+
if (file.endsWith('.md')) {
|
|
473
|
+
fs.copyFileSync(
|
|
474
|
+
path.join(commandsSource, file),
|
|
475
|
+
path.join(COMMANDS_DIR, file)
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
success('Slash commands installed to ~/.claude/commands/hive/');
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// ── Done ──
|
|
484
|
+
|
|
485
|
+
if (FLAG_DRY_RUN) {
|
|
486
|
+
console.log(`
|
|
487
|
+
────────────────────────────────────────────────
|
|
488
|
+
Dry run complete — no changes were made.
|
|
489
|
+
────────────────────────────────────────────────
|
|
490
|
+
|
|
491
|
+
Run without --dry-run to apply these changes:
|
|
492
|
+
node ${path.join(PKG_ROOT, 'bin', 'install.js')}${FLAG_FORCE ? ' --force' : ''}
|
|
493
|
+
`);
|
|
494
|
+
} else {
|
|
495
|
+
console.log(`
|
|
496
|
+
────────────────────────────────────────────────
|
|
497
|
+
Hive Rank installed successfully!
|
|
498
|
+
────────────────────────────────────────────────
|
|
499
|
+
|
|
500
|
+
Restart Claude Code, then try:
|
|
501
|
+
|
|
502
|
+
/hive:help — See all commands
|
|
503
|
+
/hive:kickstart — Bootstrap your SEO research
|
|
504
|
+
/hive:rankings — Check ranking data
|
|
505
|
+
/hive:trends — See what's trending
|
|
506
|
+
|
|
507
|
+
Config:
|
|
508
|
+
Hooks: ${SETTINGS_FILE}
|
|
509
|
+
MCP server: ${MCP_URL} (via SSE)
|
|
510
|
+
Commands: ${COMMANDS_DIR}/
|
|
511
|
+
|
|
512
|
+
To uninstall: node ~/.hive-rank/bin/install.js --uninstall
|
|
513
|
+
`);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
install().catch((e) => {
|
|
518
|
+
fail(e.message);
|
|
519
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: audit-site
|
|
3
|
+
description: Run a technical SEO audit on a website
|
|
4
|
+
allowed-tools:
|
|
5
|
+
- mcp: hive-rank
|
|
6
|
+
tools:
|
|
7
|
+
- hive_domain
|
|
8
|
+
- hive_search
|
|
9
|
+
- hive_rankings
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
Run a technical SEO audit for: $ARGUMENTS
|
|
13
|
+
|
|
14
|
+
If no domain is specified, ask the user which site to audit.
|
|
15
|
+
|
|
16
|
+
**Step 1 — Establish Search Presence:**
|
|
17
|
+
- WebSearch the brand name to check brand SERP
|
|
18
|
+
- WebSearch 3-5 key product/service terms the domain should rank for
|
|
19
|
+
- WebSearch "site:[domain]" to check indexation breadth
|
|
20
|
+
- Use `hive_domain` to get network data on this domain
|
|
21
|
+
|
|
22
|
+
**Step 2 — Analyze Content & Metadata:**
|
|
23
|
+
- WebFetch the homepage and 3-5 key pages
|
|
24
|
+
- Review the WebFetch responses for:
|
|
25
|
+
- Unique titles (not duplicate)
|
|
26
|
+
- Meta descriptions present and compelling
|
|
27
|
+
- H1 tags present and keyword-aligned
|
|
28
|
+
- Content depth and quality
|
|
29
|
+
- Note any duplicate or missing metadata
|
|
30
|
+
|
|
31
|
+
**Step 3 — Check Competitive Position:**
|
|
32
|
+
- Use `hive_search` to find related queries in the network
|
|
33
|
+
- Use `hive_rankings` on key queries to compare positions
|
|
34
|
+
- Identify who else appears for the same queries
|
|
35
|
+
|
|
36
|
+
**Step 4 — Synthesize Findings:**
|
|
37
|
+
|
|
38
|
+
IMPORTANT: Do NOT dump raw data. Synthesize into a narrative audit report.
|
|
39
|
+
|
|
40
|
+
Present findings in three priority tiers:
|
|
41
|
+
- **Critical issues** (blocking growth) — e.g., missing from brand SERP, major indexation problems
|
|
42
|
+
- **Warnings** (leaving value on the table) — e.g., weak titles, missing descriptions
|
|
43
|
+
- **Opportunities** (quick wins) — e.g., content format mismatches, underoptimized pages
|
|
44
|
+
|
|
45
|
+
End with **3-5 specific, actionable next steps** prioritized by impact.
|
|
46
|
+
|
|
47
|
+
Each search contributes to the network, building audit insights for everyone.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: audit
|
|
3
|
+
description: Check your contribution status and network health
|
|
4
|
+
allowed-tools:
|
|
5
|
+
- mcp: hive-rank
|
|
6
|
+
tools:
|
|
7
|
+
- hive_stats
|
|
8
|
+
- hive_contributors
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
Check the health of my Hive Rank contribution status.
|
|
12
|
+
|
|
13
|
+
Use `hive_stats` to get network statistics. Then give me a plain-English summary:
|
|
14
|
+
|
|
15
|
+
- Network size (contributors, observations)
|
|
16
|
+
- My contribution status (if visible in stats)
|
|
17
|
+
- Recent activity across the network
|
|
18
|
+
- Whether my contributions are flowing properly
|
|
19
|
+
|
|
20
|
+
If you can access `hive_contributors`, show where I stand in the network.
|
|
21
|
+
|
|
22
|
+
Keep it short and actionable. If there are issues, explain how to troubleshoot.
|