@dragonbot-skills/cli 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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ballistic Brands
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,91 @@
1
+ # @dragonbot-skills/cli
2
+
3
+ Install [Claude skills](https://docs.anthropic.com/) from the DragonBot
4
+ catalog into your local Claude environment.
5
+
6
+ ```bash
7
+ npx @dragonbot-skills/cli install amazon-kw-research
8
+ ```
9
+
10
+ That single command drops `amazon-kw-research/SKILL.md` (plus any
11
+ references and scripts) into `~/.claude/skills/amazon-kw-research/`,
12
+ where Claude Code (and other Claude clients that follow the same
13
+ convention) will pick it up automatically.
14
+
15
+ ## Commands
16
+
17
+ ```bash
18
+ # What's in the catalog?
19
+ dragonbot-skills list
20
+
21
+ # Install a skill (user-scope: ~/.claude/skills/<slug>/)
22
+ dragonbot-skills install amazon-kw-research
23
+
24
+ # Install for one repo only (./.claude/skills/<slug>/)
25
+ dragonbot-skills install amazon-kw-research --project
26
+
27
+ # Install somewhere custom
28
+ dragonbot-skills install amazon-kw-research --target ~/my-claude-skills
29
+
30
+ # Reinstall over an existing copy
31
+ dragonbot-skills install amazon-kw-research --force
32
+
33
+ # Remove
34
+ dragonbot-skills uninstall amazon-kw-research
35
+ ```
36
+
37
+ `-h` / `--help` prints the full usage.
38
+
39
+ ## Skills
40
+
41
+ | Slug | What it does |
42
+ |---|---|
43
+ | `amazon-kw-research` | Amazon keyword research → workbook + PPC setup (uses Keepa + Jungle Scout via the DragonBot MCP). |
44
+
45
+ More skills will be added here — file a PR or run `dragonbot-skills list`
46
+ after upgrading.
47
+
48
+ ## Where it installs
49
+
50
+ | Scope | Path | When to use |
51
+ |---|---|---|
52
+ | `user` *(default)* | `~/.claude/skills/<slug>/` | one install, every project on this machine sees it |
53
+ | `--project` | `<cwd>/.claude/skills/<slug>/` | this repo only |
54
+ | `--target <dir>` | `<dir>/<slug>/` | custom — for clients that use a different skills directory |
55
+
56
+ Skills installed here are picked up by Claude Code automatically. Other
57
+ Claude clients (Desktop, Cowork, etc.) that follow the `.claude/skills/`
58
+ convention work the same way; if yours reads from a different path, use
59
+ `--target`.
60
+
61
+ ## Updating
62
+
63
+ Skills are bundled with this package, so updating is just `npx`-ing the
64
+ latest version:
65
+
66
+ ```bash
67
+ npx @dragonbot-skills/cli@latest install amazon-kw-research --force
68
+ ```
69
+
70
+ `--force` is needed because we won't silently overwrite an existing
71
+ install. (No, we don't auto-update — installs are explicit.)
72
+
73
+ ## Development
74
+
75
+ ```bash
76
+ npm install
77
+ npm run typecheck
78
+ npm test
79
+ npm run build # → dist/cli.js (chmod +x)
80
+ node dist/cli.js list
81
+ ```
82
+
83
+ Skills live in `skills/<slug>/SKILL.md` at the package root. Adding a
84
+ new skill = adding a new folder there with a `SKILL.md` containing
85
+ frontmatter (`name:`, `description:`) and a markdown body. The
86
+ `bundledRoot` test override pattern in `src/install.test.ts` lets you
87
+ unit-test against synthetic skills without touching the real catalog.
88
+
89
+ ## License
90
+
91
+ MIT — see [LICENSE](LICENSE).
package/dist/cli.js ADDED
@@ -0,0 +1,152 @@
1
+ #!/usr/bin/env node
2
+ // @dragonbot-skills/cli — installer for DragonBot's Claude skills.
3
+ //
4
+ // Usage:
5
+ // npx @dragonbot-skills/cli list
6
+ // npx @dragonbot-skills/cli install <slug> [--project] [--target <dir>] [--force]
7
+ // npx @dragonbot-skills/cli uninstall <slug> [--project] [--target <dir>]
8
+ //
9
+ // Keep the dispatch dumb on purpose — three commands, flat flag
10
+ // parsing, no commander/yargs dep. If we ever grow past ~5 commands,
11
+ // reach for one.
12
+ import { listBundledSkills } from "./skills.js";
13
+ import { install, uninstall } from "./install.js";
14
+ const USAGE = `dragonbot-skills — install Claude skills from the DragonBot catalog
15
+
16
+ Usage:
17
+ dragonbot-skills list
18
+ dragonbot-skills install <slug> [--project] [--target <dir>] [--force]
19
+ dragonbot-skills uninstall <slug> [--project] [--target <dir>]
20
+
21
+ Scope:
22
+ (default) install to ~/.claude/skills/<slug>/ (user-scope)
23
+ --project install to ./.claude/skills/<slug>/ (current repo only)
24
+ --target <dir> install to <dir>/<slug>/ (any custom path)
25
+
26
+ Flags:
27
+ --force overwrite an existing install
28
+ -h, --help show this help
29
+
30
+ Examples:
31
+ dragonbot-skills install amazon-kw-research
32
+ dragonbot-skills install amazon-kw-research --project --force
33
+ dragonbot-skills list
34
+ `;
35
+ function main(argv) {
36
+ const args = argv.slice(2);
37
+ if (args.length === 0 || args[0] === "-h" || args[0] === "--help") {
38
+ process.stdout.write(USAGE);
39
+ return 0;
40
+ }
41
+ const command = args[0];
42
+ const rest = args.slice(1);
43
+ switch (command) {
44
+ case "list":
45
+ return runList();
46
+ case "install":
47
+ return runInstall(rest);
48
+ case "uninstall":
49
+ return runUninstall(rest);
50
+ default:
51
+ process.stderr.write(`Unknown command: ${command}\n\n${USAGE}`);
52
+ return 2;
53
+ }
54
+ }
55
+ function runList() {
56
+ const skills = listBundledSkills();
57
+ if (skills.length === 0) {
58
+ process.stdout.write("No skills bundled in this version of the package.\n");
59
+ return 0;
60
+ }
61
+ process.stdout.write(`Available skills (${skills.length}):\n\n`);
62
+ const slugWidth = Math.max(...skills.map((s) => s.slug.length));
63
+ for (const s of skills) {
64
+ const padded = s.slug.padEnd(slugWidth, " ");
65
+ process.stdout.write(` ${padded} ${s.description}\n`);
66
+ }
67
+ process.stdout.write("\nInstall one with: dragonbot-skills install <slug>\n");
68
+ return 0;
69
+ }
70
+ function runInstall(rest) {
71
+ const { positionals, flags } = parseFlags(rest);
72
+ const slug = positionals[0];
73
+ if (!slug) {
74
+ process.stderr.write("install: missing <slug>\n\n" + USAGE);
75
+ return 2;
76
+ }
77
+ const scope = flags.project ? "project" : "user";
78
+ try {
79
+ const result = install({
80
+ slug,
81
+ scope,
82
+ targetDir: flags.target,
83
+ force: flags.force,
84
+ });
85
+ process.stdout.write(`✓ Installed ${slug} → ${result.installedTo} (${result.scope}-scope)\n`);
86
+ return 0;
87
+ }
88
+ catch (err) {
89
+ process.stderr.write(`${err.message}\n`);
90
+ return 1;
91
+ }
92
+ }
93
+ function runUninstall(rest) {
94
+ const { positionals, flags } = parseFlags(rest);
95
+ const slug = positionals[0];
96
+ if (!slug) {
97
+ process.stderr.write("uninstall: missing <slug>\n\n" + USAGE);
98
+ return 2;
99
+ }
100
+ const scope = flags.project ? "project" : "user";
101
+ try {
102
+ const result = uninstall({
103
+ slug,
104
+ scope,
105
+ targetDir: flags.target,
106
+ });
107
+ if (result.removed) {
108
+ process.stdout.write(`✓ Removed ${slug} from ${result.path}\n`);
109
+ }
110
+ else {
111
+ process.stdout.write(`(no-op) ${result.path} was not installed\n`);
112
+ }
113
+ return 0;
114
+ }
115
+ catch (err) {
116
+ process.stderr.write(`${err.message}\n`);
117
+ return 1;
118
+ }
119
+ }
120
+ function parseFlags(args) {
121
+ const positionals = [];
122
+ const flags = {};
123
+ for (let i = 0; i < args.length; i++) {
124
+ const arg = args[i];
125
+ if (!arg.startsWith("--")) {
126
+ positionals.push(arg);
127
+ continue;
128
+ }
129
+ const eq = arg.indexOf("=");
130
+ const key = eq < 0 ? arg.slice(2) : arg.slice(2, eq);
131
+ const inlineValue = eq < 0 ? undefined : arg.slice(eq + 1);
132
+ switch (key) {
133
+ case "project":
134
+ flags.project = true;
135
+ break;
136
+ case "force":
137
+ flags.force = true;
138
+ break;
139
+ case "target":
140
+ flags.target = inlineValue ?? args[++i];
141
+ if (!flags.target) {
142
+ throw new Error("--target needs a directory path");
143
+ }
144
+ break;
145
+ default:
146
+ throw new Error(`Unknown flag: --${key}`);
147
+ }
148
+ }
149
+ return { positionals, flags };
150
+ }
151
+ process.exit(main(process.argv));
152
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,111 @@
1
+ // Install / uninstall skills into the user's local Claude skills
2
+ // directory.
3
+ //
4
+ // Claude Code reads skills from two locations:
5
+ // user-scope ~/.claude/skills/<slug>/SKILL.md
6
+ // project-scope <cwd>/.claude/skills/<slug>/SKILL.md
7
+ //
8
+ // We default to user-scope (one install, every project sees it) and
9
+ // expose `--project` for the per-repo variant. `--target <dir>`
10
+ // overrides both — useful for other Claude clients (Cowork, Desktop)
11
+ // that put their skills directory somewhere else.
12
+ //
13
+ // Install is atomic-ish: copy to a temp directory next to the
14
+ // destination, then rename into place. If the destination already
15
+ // exists we refuse without `--force` — a real reinstall should be
16
+ // explicit, not silent.
17
+ import fs from "node:fs";
18
+ import os from "node:os";
19
+ import path from "node:path";
20
+ import { findBundledSkill } from "./skills.js";
21
+ /**
22
+ * Resolve the skills directory the CLI should write into for a
23
+ * given scope. Doesn't create the directory — install() does that.
24
+ */
25
+ export function resolveSkillsRoot(opts) {
26
+ if (opts.targetDir) {
27
+ return { root: path.resolve(opts.targetDir), scope: "custom" };
28
+ }
29
+ const scope = opts.scope ?? "user";
30
+ if (scope === "project") {
31
+ const cwd = opts.cwd ?? process.cwd();
32
+ return { root: path.join(cwd, ".claude", "skills"), scope: "project" };
33
+ }
34
+ const home = opts.home ?? os.homedir();
35
+ return { root: path.join(home, ".claude", "skills"), scope: "user" };
36
+ }
37
+ /**
38
+ * Copy the bundled skill folder for `slug` into the chosen skills
39
+ * directory. Throws when:
40
+ * - the slug isn't bundled in this package
41
+ * - the destination exists and `force` is false
42
+ */
43
+ export function install(opts) {
44
+ const src = findBundledSkill(opts.slug, opts.bundledRoot);
45
+ if (!src) {
46
+ throw new Error(`Skill not found in this package: ${opts.slug}\n` +
47
+ `Run \`dragonbot-skills list\` to see what's available.`);
48
+ }
49
+ const { root, scope } = resolveSkillsRoot({
50
+ scope: opts.scope,
51
+ targetDir: opts.targetDir,
52
+ cwd: opts.cwd,
53
+ home: opts.home,
54
+ });
55
+ const dest = path.join(root, opts.slug);
56
+ if (fs.existsSync(dest)) {
57
+ if (!opts.force) {
58
+ throw new Error(`${dest} already exists. Pass --force to overwrite.`);
59
+ }
60
+ fs.rmSync(dest, { recursive: true, force: true });
61
+ }
62
+ fs.mkdirSync(root, { recursive: true });
63
+ // Stage in a sibling tmp dir, then rename. Avoids leaving a half-
64
+ // copied skill folder behind if the copy is interrupted.
65
+ const staging = fs.mkdtempSync(path.join(root, `.${opts.slug}-staging-`));
66
+ try {
67
+ copyDirRecursive(src, staging);
68
+ fs.renameSync(staging, dest);
69
+ }
70
+ catch (err) {
71
+ fs.rmSync(staging, { recursive: true, force: true });
72
+ throw err;
73
+ }
74
+ return { installedTo: dest, scope };
75
+ }
76
+ /**
77
+ * Remove the installed skill folder. Idempotent: returns
78
+ * `removed: false` (no error) when the folder isn't present.
79
+ */
80
+ export function uninstall(opts) {
81
+ const { root, scope } = resolveSkillsRoot({
82
+ scope: opts.scope,
83
+ targetDir: opts.targetDir,
84
+ cwd: opts.cwd,
85
+ home: opts.home,
86
+ });
87
+ const dest = path.join(root, opts.slug);
88
+ if (!fs.existsSync(dest)) {
89
+ return { removed: false, path: dest, scope };
90
+ }
91
+ fs.rmSync(dest, { recursive: true, force: true });
92
+ return { removed: true, path: dest, scope };
93
+ }
94
+ // Minimal recursive copy — no externals, follows directories and
95
+ // regular files only. Symlinks aren't expected in skills today; if
96
+ // we ever ship one, switch to fs.cp({ recursive: true, verbatimSymlinks: true }).
97
+ function copyDirRecursive(src, dest) {
98
+ fs.mkdirSync(dest, { recursive: true });
99
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
100
+ const s = path.join(src, entry.name);
101
+ const d = path.join(dest, entry.name);
102
+ if (entry.isDirectory()) {
103
+ copyDirRecursive(s, d);
104
+ }
105
+ else if (entry.isFile()) {
106
+ fs.copyFileSync(s, d);
107
+ }
108
+ // Symlinks + special files: skip silently.
109
+ }
110
+ }
111
+ //# sourceMappingURL=install.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.js","sourceRoot":"","sources":["../src/install.ts"],"names":[],"mappings":"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"}
package/dist/skills.js ADDED
@@ -0,0 +1,101 @@
1
+ // Discovery of skills bundled inside this package.
2
+ //
3
+ // The CLI doesn't fetch from a registry yet — every skill we
4
+ // distribute lives under `skills/<slug>/` inside the package itself.
5
+ // `npm install`-ing @dragonbot-skills/cli (or running it via npx)
6
+ // gives the user the full catalog locally; the install command just
7
+ // copies the requested folder out to the user's Claude skills dir.
8
+ import fs from "node:fs";
9
+ import path from "node:path";
10
+ import { fileURLToPath } from "node:url";
11
+ /**
12
+ * Absolute path to the bundled `skills/` directory.
13
+ *
14
+ * Resolves relative to THIS source file, not to process.cwd() —
15
+ * users run `npx @dragonbot-skills/cli` from wherever they happen
16
+ * to be, so cwd is irrelevant. We walk up from `dist/skills.js`
17
+ * (compiled) to the package root and then into `skills/`.
18
+ */
19
+ export function bundledSkillsDir() {
20
+ const here = path.dirname(fileURLToPath(import.meta.url));
21
+ return path.resolve(here, "..", "skills");
22
+ }
23
+ /**
24
+ * Return every skill bundled in this package: the directory name
25
+ * (slug) plus the `name:` and `description:` fields parsed from its
26
+ * SKILL.md frontmatter. Sorted by slug for stable output.
27
+ *
28
+ * Skips any subdirectory that doesn't contain a SKILL.md (it's not
29
+ * a skill).
30
+ */
31
+ export function listBundledSkills(rootOverride) {
32
+ const dir = rootOverride ?? bundledSkillsDir();
33
+ if (!fs.existsSync(dir))
34
+ return [];
35
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
36
+ const skills = [];
37
+ for (const entry of entries) {
38
+ if (!entry.isDirectory())
39
+ continue;
40
+ const slug = entry.name;
41
+ const skillFile = path.join(dir, slug, "SKILL.md");
42
+ if (!fs.existsSync(skillFile))
43
+ continue;
44
+ const body = fs.readFileSync(skillFile, "utf8");
45
+ const meta = parseFrontmatter(body);
46
+ skills.push({
47
+ slug,
48
+ name: meta.name ?? slug,
49
+ description: meta.description ?? "",
50
+ });
51
+ }
52
+ skills.sort((a, b) => a.slug.localeCompare(b.slug));
53
+ return skills;
54
+ }
55
+ /**
56
+ * Resolve the on-disk path of a single bundled skill folder.
57
+ * Returns null when the slug isn't present (or the folder lacks a
58
+ * SKILL.md, in which case it isn't really a skill).
59
+ */
60
+ export function findBundledSkill(slug, rootOverride) {
61
+ const root = rootOverride ?? bundledSkillsDir();
62
+ const dir = path.join(root, slug);
63
+ const skillFile = path.join(dir, "SKILL.md");
64
+ if (!fs.existsSync(skillFile))
65
+ return null;
66
+ const stat = fs.statSync(dir);
67
+ if (!stat.isDirectory())
68
+ return null;
69
+ return dir;
70
+ }
71
+ function parseFrontmatter(source) {
72
+ if (!source.startsWith("---"))
73
+ return {};
74
+ const end = source.indexOf("\n---", 3);
75
+ if (end < 0)
76
+ return {};
77
+ const header = source.slice(3, end).trim();
78
+ const out = {};
79
+ for (const rawLine of header.split("\n")) {
80
+ if (rawLine.startsWith(" ") || rawLine.startsWith("\t"))
81
+ continue;
82
+ const line = rawLine.trim();
83
+ if (!line || line.startsWith("#"))
84
+ continue;
85
+ const colon = line.indexOf(":");
86
+ if (colon < 0)
87
+ continue;
88
+ const key = line.slice(0, colon).trim();
89
+ let value = line.slice(colon + 1).trim();
90
+ if ((value.startsWith('"') && value.endsWith('"')) ||
91
+ (value.startsWith("'") && value.endsWith("'"))) {
92
+ value = value.slice(1, -1);
93
+ }
94
+ if (key === "name")
95
+ out.name = value;
96
+ if (key === "description")
97
+ out.description = value;
98
+ }
99
+ return out;
100
+ }
101
+ //# sourceMappingURL=skills.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skills.js","sourceRoot":"","sources":["../src/skills.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@dragonbot-skills/cli",
3
+ "version": "0.1.0",
4
+ "description": "Install DragonBot skills (Amazon keyword research, etc.) into your local Claude environment.",
5
+ "type": "module",
6
+ "bin": {
7
+ "dragonbot-skills": "dist/cli.js"
8
+ },
9
+ "files": [
10
+ "dist/",
11
+ "skills/",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "scripts": {
16
+ "build": "tsc && chmod +x dist/cli.js",
17
+ "prepublishOnly": "npm run build",
18
+ "typecheck": "tsc --noEmit",
19
+ "test": "node --test --import tsx 'src/**/*.test.ts'",
20
+ "dev": "tsx src/cli.ts"
21
+ },
22
+ "keywords": [
23
+ "claude",
24
+ "claude-skills",
25
+ "anthropic",
26
+ "amazon",
27
+ "keyword-research",
28
+ "dragonbot"
29
+ ],
30
+ "license": "MIT",
31
+ "engines": {
32
+ "node": ">=20"
33
+ },
34
+ "devDependencies": {
35
+ "@types/node": "^22.0.0",
36
+ "tsx": "^4.19.0",
37
+ "typescript": "^5.7.0"
38
+ }
39
+ }
@@ -0,0 +1,197 @@
1
+ ---
2
+ name: amazon-keyword-research
3
+ description: Amazon keyword research for product listings and PPC. Use when asked to do keyword research, find keywords for a product, build keyword lists, or analyze search terms for Amazon products.
4
+ requires:
5
+ connections:
6
+ - keepa
7
+ - jungle_scout
8
+ - google_drive
9
+ - google_sheets
10
+ ---
11
+
12
+ # Amazon Keyword Research Skill
13
+
14
+ ## Overview
15
+ Conduct keyword research for Amazon products using Keepa (for ASIN/competitor data) and Jungle Scout (for keyword metrics). Output a structured Google Sheet workbook with raw data, filtered keywords, root keyword analysis, and PPC keyword lists.
16
+
17
+ > **Before any API call here, read the per-service skills first** — they contain the canonical endpoint specs and curl examples you need:
18
+ > - **Keepa:** see `skills/keepa/SKILL.md` (vended key via gateway, `?key=`-based auth).
19
+ > - **Jungle Scout:** see `skills/jungle_scout/SKILL.md` (transparent reverse proxy, `Authorization` header + extra headers).
20
+ >
21
+ > Each skill has a `toolspec` YAML block listing every endpoint, method, params, and the exact auth flow — parse those for ground truth instead of guessing endpoint paths.
22
+
23
+ ## Process
24
+
25
+ ### 1. Identify Competitor ASINs
26
+ - Use Keepa (see `skills/keepa/SKILL.md`) to find top competitors for the product category — start with `/search` by keyword, or `/bestsellers` for a category, then `/product` for details.
27
+ - Select 5-10 ASINs that are direct competitors (same product type, similar price range)
28
+ - Include a mix: top sellers + rising products
29
+
30
+ ### 2. Pull Keyword Data
31
+ - Use Jungle Scout's `keywords_by_asin` endpoint (see `skills/jungle_scout/SKILL.md`) for each competitor ASIN
32
+ - For exploration around a known phrase, use `keywords_by_keyword` instead
33
+ - Export: keyword phrase, search volume, SV trend, PPC bids, competing products, sponsored ASINs, ranking competitors
34
+
35
+ ### 3. Build the Workbook
36
+
37
+ #### Sheet: Raw Data
38
+ - All keywords from Cerebro, unfiltered
39
+ - Include all columns from the source
40
+
41
+ #### Sheet: Filtered Keywords
42
+ - Only includes **relevant** keywords — filtered by the Ranking Competitors relevancy test
43
+ - **Relevancy test:** A keyword is relevant if ≥30% of the competitor ASINs rank for it. With 10 competitors that's ≥3; with 7-8 competitors that's ≥2. Adjust threshold based on competitor count.
44
+ - "Raw Data" = full Cerebro dump (all columns, untouched). "Filtered Keywords" = relevant keywords only, simplified columns + root keyword classification + color coding.
45
+ - Add a "Root Keyword" column that classifies each keyword by its root keyword group
46
+ - Color-code rows by root keyword (matching the Root Keywords sheet colors)
47
+ - Unclassified keywords stay white — this shows RKW coverage gaps
48
+
49
+ **Why filter by Ranking Competitors:**
50
+ - Cerebro pulls keywords from competitor ASINs, but competitors sell multiple product types
51
+ - A competitor might rank for "card sleeves" but that doesn't make it relevant for deck boxes
52
+ - Ranking Competitors count tells you how many of YOUR selected competitors actually rank for that keyword
53
+ - If only 1 out of 7 competitors ranks for it, it's probably not relevant to the product category
54
+
55
+ #### Sheet: Root Keywords
56
+ - Group keywords into root keyword categories
57
+ - Each root keyword gets a distinct pastel background color
58
+ - Columns: Root Keyword, Total Search Volume, # Variants, Top 5 Variants, Avg PPC Bid, Notes
59
+
60
+ **Root Keyword Rules:**
61
+ - Root keywords must be **specific to the product type** — not generic category terms
62
+ - ❌ BAD: "mtg accessories", "magic the gathering accessories", "card protector" (too generic, captures unrelated products)
63
+ - ❌ BAD: "card sleeves", "booster box" (adjacent product categories, not the product being sold)
64
+ - ✅ GOOD: "deck box", "mtg deck box", "commander deck box", "card storage box", "magnetic box"
65
+ - Root keywords should describe **the product you're selling**, not adjacent products that share competitors
66
+ - **NO catch-all "other" bucket.** Leave unmatched keywords with an empty Root Keyword field (uncolored). This makes gaps immediately visible — if too many rows are uncolored, you know you're missing root keywords.
67
+ - Check the "Top 5 Variants" — if they include unrelated products (e.g., mahjong when researching MTG), the root keyword is too broad
68
+ - When a keyword matches multiple root keywords, assign it to the **most specific** (longest match)
69
+ - Unclassified keywords stay white/uncolored in Raw Keywords — this is a feature, not a bug. It shows coverage gaps at a glance.
70
+
71
+ #### Sheet: Never Keywords (NKWs)
72
+ - **Single words** (not phrases) that should be negated in BROAD PPC campaigns
73
+ - A word is an NKW if we're SURE that a search term containing it does NOT describe our product — meaning the searcher would not buy our product
74
+ - NKWs are about **product fit**, not niche. Example for a deck box:
75
+ - ✅ NKW: "dual" (our deck box holds one deck, not two)
76
+ - ✅ NKW: "binder" (completely different product format)
77
+ - ❌ NOT NKW: "pokemon" (someone could use our deck box for pokemon cards)
78
+ - ❌ NOT NKW: "yugioh" (same logic — different game, but same product use case)
79
+ - Columns: Word, Reason, Source Keywords (examples of keywords containing this word)
80
+ - **Competitor brand names** are NKWs — if someone searches "gamegenic deck box" they want that specific brand, not ours
81
+ - Be conservative — when in doubt, do NOT add as NKW. False negatives (missing an NKW) waste some ad spend; false positives (wrongly blocking a word) lose sales.
82
+
83
+ **How NKWs are used in PPC:**
84
+ - EXACT campaign: targets each Filtered Keyword as exact match
85
+ - BROAD campaign: targets each Filtered Keyword as broad match, negating:
86
+ 1. All Filtered Keywords (already in EXACT)
87
+ 2. All Never Keywords (would produce irrelevant search terms)
88
+ - Goal of BROAD: discover new long-tail keywords based on Master List words
89
+
90
+ #### Sheet: Master List
91
+ - The final curated keyword list that feeds directly into PPC campaigns
92
+ - Formula: `master_list = filtered_keywords × root_keywords / never_keywords`
93
+ - `×` (multiply/overlap): only Filtered Keywords that ARE classified under a Root Keyword
94
+ - `/` (divide/remove): exclude any keyword containing a word from the Never Keywords list
95
+ - Contains the same data columns as Filtered Keywords (minus the Root Keyword column)
96
+ - This is the sheet that gets used for EXACT and BROAD PPC campaigns
97
+
98
+ #### Sheet: Single Words
99
+ - Individual word frequency analysis from the **Master List** (not raw data)
100
+ - Columns: Word, Count (how many Master List keywords contain this word)
101
+ - Sorted by count descending
102
+ - Helps identify core vocabulary for listings and potential missing NKWs
103
+
104
+ #### Sheet: Amazon Listing
105
+ - Suggested Amazon product title and 5 bullet points, optimized for keyword coverage
106
+ - **Goal:** Include as many Single Words as possible while keeping the text readable, relevant, and compliant with Amazon guidelines
107
+
108
+ **Title:**
109
+ - Amazon max ~200 characters — aim for 190-200
110
+ - Must contain all high-count words from Single Words (the most important keywords)
111
+ - Higher-count words should appear earlier in the title
112
+ - Format: `Brand - Product Type - Key Features - Use Case/Compatibility`
113
+ - Must be readable and make sense — not keyword-stuffed
114
+
115
+ **Bullet Points (5):**
116
+ - Each bullet covers a different angle: specs, protection, compatibility, design, portability/gifting
117
+ - Work in remaining Single Words that didn't fit in the title
118
+ - Still must be readable and relevant to actual product features
119
+ - Each bullet: ~200-350 chars
120
+
121
+ **Counters & Tracking (built into the sheet):**
122
+ - Title character count (vs Amazon max)
123
+ - Title word count
124
+ - Each bullet's character count
125
+ - Total bullet characters
126
+ - **Keyword Coverage section:** Total Single Words, Used in Title, Used in Bullets Only, Used Anywhere, NOT Used (with percentages)
127
+ - **Top Unused Words:** Quick-reference list of highest-count unused words
128
+ - **Full Word Usage Detail table:** Every Single Word with columns: Word, Count, In Title? (Yes/No), In Bullets? (Yes/No)
129
+ - Green row = used in title
130
+ - Yellow row = used in bullets only
131
+ - Red row = unused
132
+ - Yes/No cells: green background for Yes, red for No
133
+ - **Notes column for unused words** explaining why each is unused:
134
+ - "Variant X already in listing" — e.g. "boxes" when "box" is present, "sleeves" when "sleeved" is used
135
+ - "Franchise/set name" — lorwyn, avatar, spiderman, etc.
136
+ - "Competitor brand" — shouldn't be in our listing
137
+ - "Color" — listing covers multiple variants
138
+ - "Material not applicable" — doesn't describe our product
139
+ - "Low priority / niche term" — catch-all for rare terms
140
+ - Feature-specific flags — "check if product has this feature" (e.g. dice tray, window)
141
+
142
+ **Formatting:**
143
+ - Dark blue section headers with white bold text
144
+ - Light blue for title, light green for bullets, light yellow for stats
145
+ - Orange for top unused quick-reference
146
+ - Dark red separator for unused words section
147
+ - Column B wide (800px) with text wrap for bullet readability
148
+ - Column E (Notes) at 400px
149
+
150
+ #### Sheet: PPC Setup
151
+ - Complete PPC campaign setup ready for implementation
152
+ - **Sections:**
153
+ 1. **Campaign Structure** — overview table showing both campaigns, their match types, targeting, negatives, and goals
154
+ 2. **EXACT Campaign Keywords** — all Master List keywords sorted by search volume, with suggested PPC bids (min/max/suggested)
155
+ 3. **BROAD Campaign Keywords** — same keywords as EXACT (reference note)
156
+ 4. **BROAD Campaign — Exact Negative Keywords** — all Master List keywords as exact negatives (to avoid overlap with EXACT campaign)
157
+ 5. **BROAD Campaign — Phrase Negative Keywords** — all Never Keywords as phrase negatives (to block irrelevant search terms)
158
+ 6. **Summary** — total counts for each section + bid statistics (avg/min/max)
159
+ - **Formatting:**
160
+ - Dark blue section headers
161
+ - Green alternating rows for EXACT keywords
162
+ - Purple for BROAD campaign references
163
+ - Orange alternating rows for exact negatives
164
+ - Red alternating rows for phrase negatives
165
+ - Yellow for summary stats
166
+
167
+ ### 4. Color Coding
168
+ - Assign each root keyword a distinct **pastel** color (HSV: saturation ~0.25, value 1.0)
169
+ - Apply the color to the root keyword's row in the Root Keywords sheet
170
+ - Apply the same color to all rows in Filtered Keywords that belong to that root keyword
171
+ - If a keyword belongs to multiple root keywords, assign it to one (most specific) and color accordingly
172
+
173
+ ### 5. Upload to Google Drive
174
+ - Create as a Google Sheet (not xlsx) directly via API
175
+ - Place in the appropriate product folder (e.g., `Brands / [Brand] / Product line - [Product] /`)
176
+ - Name format: "Keyword research - [Product name]"
177
+ - Match parent folder permissions when sharing
178
+
179
+ ### 6. Sheet Formatting
180
+ - **Freeze row 1 and column A** on all sheets (for easy scrolling)
181
+ - **Add filters** on all columns of every sheet
182
+ - **Numbers must be numbers** — use `valueInputOption='USER_ENTERED'` when writing to Google Sheets so numeric values are treated as numbers, not text. Never write numbers as strings (no leading apostrophe). This enables proper sorting/filtering.
183
+ - These are standard — apply to every workbook, every time
184
+
185
+ ## Quality Checks
186
+ - [ ] Filtered Keywords only contains relevant keywords (≥30% of competitors ranking)
187
+ - [ ] Raw Data contains the full unfiltered Cerebro dump
188
+ - [ ] Root keywords are specific to the product, not generic category terms
189
+ - [ ] Top 5 Variants for each root keyword are all relevant to the actual product
190
+ - [ ] No "other" catch-all bucket — unclassified keywords stay blank/uncolored
191
+ - [ ] Colors are applied consistently between Root Keywords and Filtered Keywords
192
+ - [ ] Sheet is in the correct Drive folder with proper permissions
193
+
194
+ ---
195
+ > **Core skill** (read-only, updated by the platform).
196
+ > If you need to add customer-specific knowledge, IDs, preferences, or learnings for this skill,
197
+ > create an extension at `workspace/skills/amazon-kw-research/SKILL.md` — do not modify this file.