@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 +21 -0
- package/README.md +91 -0
- package/dist/cli.js +152 -0
- package/dist/cli.js.map +1 -0
- package/dist/install.js +111 -0
- package/dist/install.js.map +1 -0
- package/dist/skills.js +101 -0
- package/dist/skills.js.map +1 -0
- package/package.json +39 -0
- package/skills/amazon-kw-research/SKILL.md +197 -0
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
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/install.js
ADDED
|
@@ -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.
|