@professorragna/pokeapi-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/README.md +101 -0
- package/bin/pkmn.js +2 -0
- package/dist/cli.js +70 -0
- package/dist/cli.js.map +1 -0
- package/dist/core/errors.js +44 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/http.js +91 -0
- package/dist/core/http.js.map +1 -0
- package/dist/core/output.js +4 -0
- package/dist/core/output.js.map +1 -0
- package/dist/resources/ability.js +21 -0
- package/dist/resources/ability.js.map +1 -0
- package/dist/resources/item.js +21 -0
- package/dist/resources/item.js.map +1 -0
- package/dist/resources/move.js +21 -0
- package/dist/resources/move.js.map +1 -0
- package/dist/resources/pokemon-species.js +21 -0
- package/dist/resources/pokemon-species.js.map +1 -0
- package/dist/resources/pokemon.js +21 -0
- package/dist/resources/pokemon.js.map +1 -0
- package/dist/resources/type.js +21 -0
- package/dist/resources/type.js.map +1 -0
- package/dist/util/normalize.js +9 -0
- package/dist/util/normalize.js.map +1 -0
- package/dist/util/version.js +10 -0
- package/dist/util/version.js.map +1 -0
- package/package.json +47 -0
- package/skills/pokeapi-cli/SKILL.md +163 -0
package/README.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# pokeapi-cli
|
|
2
|
+
|
|
3
|
+
Command-line interface for the [PokeAPI](https://pokeapi.co/docs/v2). Fetches Pokemon data and prints the **exact, untransformed JSON** response from the API.
|
|
4
|
+
|
|
5
|
+
**Binary:** `pkmn` · **Package:** `pokeapi-cli` on npm
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# Global install (pick one)
|
|
11
|
+
npm i -g pokeapi-cli
|
|
12
|
+
pnpm add -g pokeapi-cli
|
|
13
|
+
yarn global add pokeapi-cli
|
|
14
|
+
|
|
15
|
+
# Run without global install
|
|
16
|
+
npx pokeapi-cli pkmn pokemon pikachu
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Prerequisites
|
|
20
|
+
|
|
21
|
+
- **Node.js** >= 18.17
|
|
22
|
+
|
|
23
|
+
### Verify
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pkmn --version
|
|
27
|
+
pkmn --help
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Commands
|
|
31
|
+
|
|
32
|
+
| Command | Endpoint | Description |
|
|
33
|
+
|---------|----------|-------------|
|
|
34
|
+
| `pkmn pokemon <nameOrId>` | `GET /pokemon/{name}` | Battle data: types, base stats, abilities, moves, sprites |
|
|
35
|
+
| `pkmn pokemon-species <nameOrId>` | `GET /pokemon-species/{name}` | Species data: evolution, egg groups, flavor text, legendary flags |
|
|
36
|
+
| `pkmn ability <nameOrId>` | `GET /ability/{name}` | Ability data: effect text, Pokemon with the ability, flavor text |
|
|
37
|
+
| `pkmn item <nameOrId>` | `GET /item/{name}` | Item data: cost, fling, attributes, category, effect/flavor text, held-by |
|
|
38
|
+
| `pkmn move <nameOrId>` | `GET /move/{name}` | Move data: power, PP, accuracy, type, damage class, effect, stat changes |
|
|
39
|
+
| `pkmn type <nameOrId>` | `GET /type/{name}` | Type data: damage relations (offensive/defensive), generation, move damage class, Pokemon and moves of the type |
|
|
40
|
+
|
|
41
|
+
## Usage
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
# By name
|
|
45
|
+
pkmn pokemon incineroar
|
|
46
|
+
pkmn pokemon-species wormadam
|
|
47
|
+
pkmn ability intimidate
|
|
48
|
+
pkmn item potion
|
|
49
|
+
pkmn move flamethrower
|
|
50
|
+
pkmn type fire
|
|
51
|
+
|
|
52
|
+
# By national dex id
|
|
53
|
+
pkmn pokemon 727
|
|
54
|
+
pkmn pokemon-species 413
|
|
55
|
+
pkmn ability 22
|
|
56
|
+
pkmn item 1
|
|
57
|
+
pkmn move 53
|
|
58
|
+
pkmn type 10
|
|
59
|
+
|
|
60
|
+
# Hyphenated names (spaces/underscores normalized automatically)
|
|
61
|
+
pkmn pokemon "flutter mane"
|
|
62
|
+
pkmn pokemon flutter-mane
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Output
|
|
66
|
+
|
|
67
|
+
Commands print the **raw PokeAPI JSON** to stdout with 2-space indentation. Keys and values are exactly as returned by the API (snake_case, no transformation). Errors are written to stderr as JSON with a non-zero exit code.
|
|
68
|
+
|
|
69
|
+
Pipe to [jq](https://jqlang.github.io/jq/) for field selection:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pkmn pokemon pikachu | jq '.types'
|
|
73
|
+
pkmn pokemon-species pikachu | jq '.genera'
|
|
74
|
+
pkmn ability intimidate | jq '.effect_entries'
|
|
75
|
+
pkmn item potion | jq '.effect_entries'
|
|
76
|
+
pkmn move flamethrower | jq '.effect_entries'
|
|
77
|
+
pkmn type fire | jq '.damage_relations'
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Agent skill
|
|
81
|
+
|
|
82
|
+
This CLI ships an agent skill at [`skills/pokeapi-cli/SKILL.md`](skills/pokeapi-cli/SKILL.md) for any harness that supports the [Agent Skills specification](https://agentskills.io) (Claude Code, Cursor, Codex, and others).
|
|
83
|
+
|
|
84
|
+
Install it with the [skills CLI](https://github.com/vercel-labs/skills):
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
npx skills add jpbullalayao/pokeapi-cli
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Development
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
cd general/pokeapi-cli
|
|
94
|
+
npm install
|
|
95
|
+
npm run build
|
|
96
|
+
node bin/pkmn.js pokemon pikachu
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## License
|
|
100
|
+
|
|
101
|
+
MIT
|
package/bin/pkmn.js
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { isCliError, writeCliError } from './core/errors.js';
|
|
4
|
+
import { registerAbility } from './resources/ability.js';
|
|
5
|
+
import { registerItem } from './resources/item.js';
|
|
6
|
+
import { registerMove } from './resources/move.js';
|
|
7
|
+
import { registerPokemonSpecies } from './resources/pokemon-species.js';
|
|
8
|
+
import { registerPokemon } from './resources/pokemon.js';
|
|
9
|
+
import { registerType } from './resources/type.js';
|
|
10
|
+
import { getVersion } from './util/version.js';
|
|
11
|
+
const program = new Command();
|
|
12
|
+
program.version(getVersion(), '-V, --version');
|
|
13
|
+
program
|
|
14
|
+
.name('pkmn')
|
|
15
|
+
.description('Command-line interface for the PokeAPI (pokemon, pokemon-species, ability, item, move, and type endpoints).\n\nDocs: https://pokeapi.co/docs/v2');
|
|
16
|
+
registerPokemon(program);
|
|
17
|
+
registerPokemonSpecies(program);
|
|
18
|
+
registerAbility(program);
|
|
19
|
+
registerItem(program);
|
|
20
|
+
registerMove(program);
|
|
21
|
+
registerType(program);
|
|
22
|
+
program.addHelpText('after', `
|
|
23
|
+
Output:
|
|
24
|
+
Commands print the raw PokeAPI JSON response to stdout (2-space indentation).
|
|
25
|
+
Errors are written to stderr as JSON with a non-zero exit code.
|
|
26
|
+
|
|
27
|
+
Examples:
|
|
28
|
+
$ pkmn pokemon pikachu
|
|
29
|
+
$ pkmn pokemon-species pikachu
|
|
30
|
+
$ pkmn ability intimidate
|
|
31
|
+
$ pkmn item potion
|
|
32
|
+
$ pkmn move flamethrower
|
|
33
|
+
$ pkmn type fire
|
|
34
|
+
`);
|
|
35
|
+
program
|
|
36
|
+
.command('help [topic]')
|
|
37
|
+
.description('Show help (alias: pkmn --help)')
|
|
38
|
+
.action((topic) => {
|
|
39
|
+
if (topic) {
|
|
40
|
+
process.stderr.write(`Use: pkmn ${topic} --help (or: pkmn --help ${topic})\n`);
|
|
41
|
+
}
|
|
42
|
+
program.help();
|
|
43
|
+
});
|
|
44
|
+
program.action(() => {
|
|
45
|
+
program.help();
|
|
46
|
+
});
|
|
47
|
+
function printError(e) {
|
|
48
|
+
if (isCliError(e)) {
|
|
49
|
+
writeCliError(e);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (e instanceof Error) {
|
|
53
|
+
process.stderr.write(`${JSON.stringify({ error: { code: 'generic-error', message: e.message, exit: 1 } }, null, 2)}\n`);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
process.stderr.write(`${JSON.stringify({ error: { code: 'generic-error', message: 'Unknown error', exit: 1 } }, null, 2)}\n`);
|
|
57
|
+
}
|
|
58
|
+
process.exitCode = 1;
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
await program.parseAsync(process.argv, { from: 'node' });
|
|
62
|
+
}
|
|
63
|
+
catch (e) {
|
|
64
|
+
printError(e);
|
|
65
|
+
if (process.exitCode == null || process.exitCode === undefined) {
|
|
66
|
+
process.exitCode = 1;
|
|
67
|
+
}
|
|
68
|
+
process.exit(process.exitCode);
|
|
69
|
+
}
|
|
70
|
+
//# 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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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const exitCodeByKind = {
|
|
2
|
+
'generic-error': 1,
|
|
3
|
+
'usage-error': 2,
|
|
4
|
+
'not-found': 1,
|
|
5
|
+
'validation-error': 4,
|
|
6
|
+
'api-error': 5,
|
|
7
|
+
'network-error': 6,
|
|
8
|
+
};
|
|
9
|
+
export class CliError extends Error {
|
|
10
|
+
code;
|
|
11
|
+
exit;
|
|
12
|
+
hint;
|
|
13
|
+
docsUrl;
|
|
14
|
+
cause;
|
|
15
|
+
constructor(message, options) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.name = 'CliError';
|
|
18
|
+
this.code = options.code;
|
|
19
|
+
this.hint = options.hint;
|
|
20
|
+
this.docsUrl = options.docsUrl;
|
|
21
|
+
this.cause = options.cause;
|
|
22
|
+
this.exit = exitCodeByKind[options.code];
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export function isCliError(e) {
|
|
26
|
+
return e instanceof CliError;
|
|
27
|
+
}
|
|
28
|
+
export function formatJsonError(e) {
|
|
29
|
+
return {
|
|
30
|
+
error: {
|
|
31
|
+
code: e.code,
|
|
32
|
+
message: e.message,
|
|
33
|
+
...(e.hint ? { hint: e.hint } : {}),
|
|
34
|
+
exit: e.exit,
|
|
35
|
+
...(e.docsUrl ? { docs: e.docsUrl } : {}),
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/** Writes a CliError to stderr and sets `process.exitCode`. */
|
|
40
|
+
export function writeCliError(e) {
|
|
41
|
+
process.stderr.write(`${JSON.stringify(formatJsonError(e), null, 2)}\n`);
|
|
42
|
+
process.exitCode = e.exit;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"AAQA,MAAM,cAAc,GAA6C;IAC/D,eAAe,EAAE,CAAC;IAClB,aAAa,EAAE,CAAC;IAChB,WAAW,EAAE,CAAC;IACd,kBAAkB,EAAE,CAAC;IACrB,WAAW,EAAE,CAAC;IACd,eAAe,EAAE,CAAC;CACnB,CAAC;AAEF,MAAM,OAAO,QAAS,SAAQ,KAAK;IACxB,IAAI,CAAY;IAChB,IAAI,CAAwB;IAC5B,IAAI,CAAU;IACd,OAAO,CAAU;IACjB,KAAK,CAAW;IAEzB,YACE,OAAe,EACf,OAKC;QAED,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC;QACvB,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;QACzB,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;QACzB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;QAC/B,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;QAC3B,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3C,CAAC;CACF;AAED,MAAM,UAAU,UAAU,CAAC,CAAU;IACnC,OAAO,CAAC,YAAY,QAAQ,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,CAAW;IAGzC,OAAO;QACL,KAAK,EAAE;YACL,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,OAAO,EAAE,CAAC,CAAC,OAAO;YAClB,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACnC,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC1C;KACF,CAAC;AACJ,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,aAAa,CAAC,CAAW;IACvC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;IACzE,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC,IAAI,CAAC;AAC5B,CAAC"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { getVersion } from '../util/version.js';
|
|
2
|
+
import { CliError } from './errors.js';
|
|
3
|
+
const API_BASE = 'https://pokeapi.co/api/v2';
|
|
4
|
+
const MAX_RETRIES = 3;
|
|
5
|
+
function sleep(ms) {
|
|
6
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
7
|
+
}
|
|
8
|
+
function jitterMs(base) {
|
|
9
|
+
return base + Math.floor(Math.random() * 200);
|
|
10
|
+
}
|
|
11
|
+
function buildUrl(path) {
|
|
12
|
+
const p = path.startsWith('/') ? path : `/${path}`;
|
|
13
|
+
const base = API_BASE.endsWith('/') ? API_BASE.slice(0, -1) : API_BASE;
|
|
14
|
+
return base + p;
|
|
15
|
+
}
|
|
16
|
+
function userAgent() {
|
|
17
|
+
return `pokeapi-cli/${getVersion()} node/${process.version}`;
|
|
18
|
+
}
|
|
19
|
+
export class ApiClient {
|
|
20
|
+
async getJson(path, signal) {
|
|
21
|
+
const url = buildUrl(path);
|
|
22
|
+
const requestSignal = signal ?? AbortSignal.timeout(30_000);
|
|
23
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
24
|
+
let res;
|
|
25
|
+
try {
|
|
26
|
+
res = await fetch(url, {
|
|
27
|
+
method: 'GET',
|
|
28
|
+
headers: {
|
|
29
|
+
Accept: 'application/json',
|
|
30
|
+
'User-Agent': userAgent(),
|
|
31
|
+
},
|
|
32
|
+
signal: requestSignal,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
catch (e) {
|
|
36
|
+
const name = e?.name;
|
|
37
|
+
const isAbort = name === 'AbortError';
|
|
38
|
+
if (isAbort) {
|
|
39
|
+
throw new CliError('Request timed out', { code: 'network-error', cause: e });
|
|
40
|
+
}
|
|
41
|
+
if (attempt >= MAX_RETRIES) {
|
|
42
|
+
throw new CliError(e instanceof Error ? e.message : 'Network error', {
|
|
43
|
+
code: 'network-error',
|
|
44
|
+
cause: e,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
await sleep(jitterMs(200 * 2 ** attempt));
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (res.status === 404) {
|
|
51
|
+
throw new CliError('Pokemon not found', {
|
|
52
|
+
code: 'not-found',
|
|
53
|
+
hint: `No resource at ${path}`,
|
|
54
|
+
docsUrl: 'https://pokeapi.co/docs/v2',
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
if (res.status === 429 || (res.status >= 500 && res.status < 600)) {
|
|
58
|
+
if (attempt >= MAX_RETRIES) {
|
|
59
|
+
const body = await this.safeReadText(res);
|
|
60
|
+
throw new CliError(`API request failed: ${res.status} ${res.statusText}${body ? ` — ${body.slice(0, 200)}` : ''}`, { code: 'api-error' });
|
|
61
|
+
}
|
|
62
|
+
const ra = res.headers.get('retry-after');
|
|
63
|
+
const waitSec = ra ? Number.parseInt(ra, 10) : Number.NaN;
|
|
64
|
+
const delay = Number.isFinite(waitSec) && waitSec > 0 ? waitSec * 1000 : jitterMs(200 * 2 ** attempt);
|
|
65
|
+
await sleep(delay);
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (!res.ok) {
|
|
69
|
+
const body = await this.safeReadText(res);
|
|
70
|
+
throw new CliError(`API error: ${res.status} ${res.statusText}${body ? ` — ${body.slice(0, 200)}` : ''}`, { code: 'api-error' });
|
|
71
|
+
}
|
|
72
|
+
const text = await res.text();
|
|
73
|
+
try {
|
|
74
|
+
return text ? JSON.parse(text) : null;
|
|
75
|
+
}
|
|
76
|
+
catch (e) {
|
|
77
|
+
throw new CliError('Invalid JSON in API response', { code: 'validation-error', cause: e });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
throw new CliError('Request failed after retries', { code: 'network-error' });
|
|
81
|
+
}
|
|
82
|
+
async safeReadText(res) {
|
|
83
|
+
try {
|
|
84
|
+
return await res.text();
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return '';
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=http.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../../src/core/http.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"output.js","sourceRoot":"","sources":["../../src/core/output.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,SAAS,CAAC,IAAa;IACrC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;AAC7D,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ApiClient } from '../core/http.js';
|
|
2
|
+
import { printJson } from '../core/output.js';
|
|
3
|
+
import { normalizePokemonInput } from '../util/normalize.js';
|
|
4
|
+
const http = new ApiClient();
|
|
5
|
+
export function registerAbility(program) {
|
|
6
|
+
program
|
|
7
|
+
.command('ability <nameOrId>')
|
|
8
|
+
.description('Fetch an ability by name or id (GET /ability/{name})')
|
|
9
|
+
.addHelpText('after', `
|
|
10
|
+
Examples:
|
|
11
|
+
$ pkmn ability intimidate
|
|
12
|
+
$ pkmn ability 22
|
|
13
|
+
$ pkmn ability flame-body
|
|
14
|
+
`)
|
|
15
|
+
.action(async (nameOrId) => {
|
|
16
|
+
const id = normalizePokemonInput(nameOrId);
|
|
17
|
+
const data = await http.getJson(`/ability/${encodeURIComponent(id)}`);
|
|
18
|
+
printJson(data);
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=ability.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ability.js","sourceRoot":"","sources":["../../src/resources/ability.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,MAAM,IAAI,GAAG,IAAI,SAAS,EAAE,CAAC;AAE7B,MAAM,UAAU,eAAe,CAAC,OAAgB;IAC9C,OAAO;SACJ,OAAO,CAAC,oBAAoB,CAAC;SAC7B,WAAW,CAAC,sDAAsD,CAAC;SACnE,WAAW,CACV,OAAO,EACP;;;;;CAKL,CACI;SACA,MAAM,CAAC,KAAK,EAAE,QAAgB,EAAE,EAAE;QACjC,MAAM,EAAE,GAAG,qBAAqB,CAAC,QAAQ,CAAC,CAAC;QAC3C,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,YAAY,kBAAkB,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QACtE,SAAS,CAAC,IAAI,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACP,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ApiClient } from '../core/http.js';
|
|
2
|
+
import { printJson } from '../core/output.js';
|
|
3
|
+
import { normalizePokemonInput } from '../util/normalize.js';
|
|
4
|
+
const http = new ApiClient();
|
|
5
|
+
export function registerItem(program) {
|
|
6
|
+
program
|
|
7
|
+
.command('item <nameOrId>')
|
|
8
|
+
.description('Fetch an item by name or id (GET /item/{name})')
|
|
9
|
+
.addHelpText('after', `
|
|
10
|
+
Examples:
|
|
11
|
+
$ pkmn item potion
|
|
12
|
+
$ pkmn item 1
|
|
13
|
+
$ pkmn item master-ball
|
|
14
|
+
`)
|
|
15
|
+
.action(async (nameOrId) => {
|
|
16
|
+
const id = normalizePokemonInput(nameOrId);
|
|
17
|
+
const data = await http.getJson(`/item/${encodeURIComponent(id)}`);
|
|
18
|
+
printJson(data);
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=item.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"item.js","sourceRoot":"","sources":["../../src/resources/item.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,MAAM,IAAI,GAAG,IAAI,SAAS,EAAE,CAAC;AAE7B,MAAM,UAAU,YAAY,CAAC,OAAgB;IAC3C,OAAO;SACJ,OAAO,CAAC,iBAAiB,CAAC;SAC1B,WAAW,CAAC,gDAAgD,CAAC;SAC7D,WAAW,CACV,OAAO,EACP;;;;;CAKL,CACI;SACA,MAAM,CAAC,KAAK,EAAE,QAAgB,EAAE,EAAE;QACjC,MAAM,EAAE,GAAG,qBAAqB,CAAC,QAAQ,CAAC,CAAC;QAC3C,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,SAAS,kBAAkB,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QACnE,SAAS,CAAC,IAAI,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACP,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ApiClient } from '../core/http.js';
|
|
2
|
+
import { printJson } from '../core/output.js';
|
|
3
|
+
import { normalizePokemonInput } from '../util/normalize.js';
|
|
4
|
+
const http = new ApiClient();
|
|
5
|
+
export function registerMove(program) {
|
|
6
|
+
program
|
|
7
|
+
.command('move <nameOrId>')
|
|
8
|
+
.description('Fetch a move by name or id (GET /move/{name})')
|
|
9
|
+
.addHelpText('after', `
|
|
10
|
+
Examples:
|
|
11
|
+
$ pkmn move flamethrower
|
|
12
|
+
$ pkmn move 53
|
|
13
|
+
$ pkmn move close-combat
|
|
14
|
+
`)
|
|
15
|
+
.action(async (nameOrId) => {
|
|
16
|
+
const id = normalizePokemonInput(nameOrId);
|
|
17
|
+
const data = await http.getJson(`/move/${encodeURIComponent(id)}`);
|
|
18
|
+
printJson(data);
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=move.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"move.js","sourceRoot":"","sources":["../../src/resources/move.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,MAAM,IAAI,GAAG,IAAI,SAAS,EAAE,CAAC;AAE7B,MAAM,UAAU,YAAY,CAAC,OAAgB;IAC3C,OAAO;SACJ,OAAO,CAAC,iBAAiB,CAAC;SAC1B,WAAW,CAAC,+CAA+C,CAAC;SAC5D,WAAW,CACV,OAAO,EACP;;;;;CAKL,CACI;SACA,MAAM,CAAC,KAAK,EAAE,QAAgB,EAAE,EAAE;QACjC,MAAM,EAAE,GAAG,qBAAqB,CAAC,QAAQ,CAAC,CAAC;QAC3C,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,SAAS,kBAAkB,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QACnE,SAAS,CAAC,IAAI,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACP,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ApiClient } from '../core/http.js';
|
|
2
|
+
import { printJson } from '../core/output.js';
|
|
3
|
+
import { normalizePokemonInput } from '../util/normalize.js';
|
|
4
|
+
const http = new ApiClient();
|
|
5
|
+
export function registerPokemonSpecies(program) {
|
|
6
|
+
program
|
|
7
|
+
.command('pokemon-species <nameOrId>')
|
|
8
|
+
.description('Fetch a Pokemon species by name or id (GET /pokemon-species/{name})')
|
|
9
|
+
.addHelpText('after', `
|
|
10
|
+
Examples:
|
|
11
|
+
$ pkmn pokemon-species wormadam
|
|
12
|
+
$ pkmn pokemon-species 413
|
|
13
|
+
$ pkmn pokemon-species flutter-mane
|
|
14
|
+
`)
|
|
15
|
+
.action(async (nameOrId) => {
|
|
16
|
+
const id = normalizePokemonInput(nameOrId);
|
|
17
|
+
const data = await http.getJson(`/pokemon-species/${encodeURIComponent(id)}`);
|
|
18
|
+
printJson(data);
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=pokemon-species.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pokemon-species.js","sourceRoot":"","sources":["../../src/resources/pokemon-species.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,MAAM,IAAI,GAAG,IAAI,SAAS,EAAE,CAAC;AAE7B,MAAM,UAAU,sBAAsB,CAAC,OAAgB;IACrD,OAAO;SACJ,OAAO,CAAC,4BAA4B,CAAC;SACrC,WAAW,CAAC,qEAAqE,CAAC;SAClF,WAAW,CACV,OAAO,EACP;;;;;CAKL,CACI;SACA,MAAM,CAAC,KAAK,EAAE,QAAgB,EAAE,EAAE;QACjC,MAAM,EAAE,GAAG,qBAAqB,CAAC,QAAQ,CAAC,CAAC;QAC3C,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,oBAAoB,kBAAkB,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QAC9E,SAAS,CAAC,IAAI,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACP,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ApiClient } from '../core/http.js';
|
|
2
|
+
import { printJson } from '../core/output.js';
|
|
3
|
+
import { normalizePokemonInput } from '../util/normalize.js';
|
|
4
|
+
const http = new ApiClient();
|
|
5
|
+
export function registerPokemon(program) {
|
|
6
|
+
program
|
|
7
|
+
.command('pokemon <nameOrId>')
|
|
8
|
+
.description('Fetch a Pokemon by name or id (GET /pokemon/{name})')
|
|
9
|
+
.addHelpText('after', `
|
|
10
|
+
Examples:
|
|
11
|
+
$ pkmn pokemon incineroar
|
|
12
|
+
$ pkmn pokemon 727
|
|
13
|
+
$ pkmn pokemon flutter-mane
|
|
14
|
+
`)
|
|
15
|
+
.action(async (nameOrId) => {
|
|
16
|
+
const id = normalizePokemonInput(nameOrId);
|
|
17
|
+
const data = await http.getJson(`/pokemon/${encodeURIComponent(id)}`);
|
|
18
|
+
printJson(data);
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=pokemon.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pokemon.js","sourceRoot":"","sources":["../../src/resources/pokemon.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,MAAM,IAAI,GAAG,IAAI,SAAS,EAAE,CAAC;AAE7B,MAAM,UAAU,eAAe,CAAC,OAAgB;IAC9C,OAAO;SACJ,OAAO,CAAC,oBAAoB,CAAC;SAC7B,WAAW,CAAC,qDAAqD,CAAC;SAClE,WAAW,CACV,OAAO,EACP;;;;;CAKL,CACI;SACA,MAAM,CAAC,KAAK,EAAE,QAAgB,EAAE,EAAE;QACjC,MAAM,EAAE,GAAG,qBAAqB,CAAC,QAAQ,CAAC,CAAC;QAC3C,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,YAAY,kBAAkB,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QACtE,SAAS,CAAC,IAAI,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACP,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ApiClient } from '../core/http.js';
|
|
2
|
+
import { printJson } from '../core/output.js';
|
|
3
|
+
import { normalizePokemonInput } from '../util/normalize.js';
|
|
4
|
+
const http = new ApiClient();
|
|
5
|
+
export function registerType(program) {
|
|
6
|
+
program
|
|
7
|
+
.command('type <nameOrId>')
|
|
8
|
+
.description('Fetch a type by name or id (GET /type/{name})')
|
|
9
|
+
.addHelpText('after', `
|
|
10
|
+
Examples:
|
|
11
|
+
$ pkmn type fire
|
|
12
|
+
$ pkmn type 10
|
|
13
|
+
$ pkmn type ground
|
|
14
|
+
`)
|
|
15
|
+
.action(async (nameOrId) => {
|
|
16
|
+
const id = normalizePokemonInput(nameOrId);
|
|
17
|
+
const data = await http.getJson(`/type/${encodeURIComponent(id)}`);
|
|
18
|
+
printJson(data);
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=type.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"type.js","sourceRoot":"","sources":["../../src/resources/type.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,MAAM,IAAI,GAAG,IAAI,SAAS,EAAE,CAAC;AAE7B,MAAM,UAAU,YAAY,CAAC,OAAgB;IAC3C,OAAO;SACJ,OAAO,CAAC,iBAAiB,CAAC;SAC1B,WAAW,CAAC,+CAA+C,CAAC;SAC5D,WAAW,CACV,OAAO,EACP;;;;;CAKL,CACI;SACA,MAAM,CAAC,KAAK,EAAE,QAAgB,EAAE,EAAE;QACjC,MAAM,EAAE,GAAG,qBAAqB,CAAC,QAAQ,CAAC,CAAC;QAC3C,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,SAAS,kBAAkB,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QACnE,SAAS,CAAC,IAAI,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACP,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Normalize a Pokemon name or id for PokeAPI lookup. */
|
|
2
|
+
export function normalizePokemonInput(input) {
|
|
3
|
+
const trimmed = input.trim();
|
|
4
|
+
if (/^\d+$/.test(trimmed)) {
|
|
5
|
+
return trimmed;
|
|
6
|
+
}
|
|
7
|
+
return trimmed.toLowerCase().replace(/[\s_]+/g, '-');
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=normalize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalize.js","sourceRoot":"","sources":["../../src/util/normalize.ts"],"names":[],"mappings":"AAAA,yDAAyD;AACzD,MAAM,UAAU,qBAAqB,CAAC,KAAa;IACjD,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,IAAI,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QAC1B,OAAO,OAAO,CAAC;IACjB,CAAC;IACD,OAAO,OAAO,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;AACvD,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
5
|
+
export function getVersion() {
|
|
6
|
+
const pkgPath = join(__dirname, '../../package.json');
|
|
7
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
8
|
+
return pkg.version;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=version.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/util/version.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAE1D,MAAM,UAAU,UAAU;IACxB,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,EAAE,oBAAoB,CAAC,CAAC;IACtD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAwB,CAAC;IAC7E,OAAO,GAAG,CAAC,OAAO,CAAC;AACrB,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@professorragna/pokeapi-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI for the PokeAPI (pokemon and pokemon-species endpoints).",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=18.17"
|
|
8
|
+
},
|
|
9
|
+
"bin": {
|
|
10
|
+
"pkmn": "./bin/pkmn.js"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist",
|
|
14
|
+
"bin",
|
|
15
|
+
"README.md",
|
|
16
|
+
"skills"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc -p .",
|
|
20
|
+
"dev": "tsc -w -p .",
|
|
21
|
+
"lint": "biome check ./src",
|
|
22
|
+
"format": "biome format --write ./src",
|
|
23
|
+
"typecheck": "tsc --noEmit -p ."
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"pokeapi-cli",
|
|
27
|
+
"pokeapi",
|
|
28
|
+
"pokemon",
|
|
29
|
+
"cli"
|
|
30
|
+
],
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "https://github.com/jpbullalayao/pokeapi-cli.git"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"commander": "^12.1.0"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@biomejs/biome": "^1.9.4",
|
|
44
|
+
"@types/node": "^22.9.0",
|
|
45
|
+
"typescript": "^5.6.2"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pokeapi-cli
|
|
3
|
+
description: >-
|
|
4
|
+
PokeAPI CLI reference for fetching Pokemon, Pokemon species, ability, item, move,
|
|
5
|
+
and type data from pokeapi.co. Use when the user mentions pokeapi-cli, pkmn, Pokemon base
|
|
6
|
+
stats, typing, type matchups, weaknesses, resistances, abilities, moves, items, evolution,
|
|
7
|
+
egg groups, or needs to look up canonical Pokemon data from the command line or in agent workflows.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# PokeAPI CLI (`pkmn`)
|
|
11
|
+
|
|
12
|
+
Command-line interface for the [PokeAPI](https://pokeapi.co/docs/v2): fetches Pokemon data and prints the exact JSON response.
|
|
13
|
+
|
|
14
|
+
**API documentation:** [pokeapi.co/docs/v2](https://pokeapi.co/docs/v2)
|
|
15
|
+
|
|
16
|
+
**Package:** `pokeapi-cli` on npm · **Binary:** `pkmn` · **Current version:** 0.1.0 (see `pkmn --version` after install)
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Authentication
|
|
21
|
+
|
|
22
|
+
PokeAPI is public and requires **no API key**.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## CLI structure
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
pkmn # Root (default action shows help)
|
|
30
|
+
├── pokemon <nameOrId> # GET /pokemon/{name}
|
|
31
|
+
├── pokemon-species <nameOrId> # GET /pokemon-species/{name}
|
|
32
|
+
├── ability <nameOrId> # GET /ability/{name}
|
|
33
|
+
├── item <nameOrId> # GET /item/{name}
|
|
34
|
+
├── move <nameOrId> # GET /move/{name}
|
|
35
|
+
└── type <nameOrId> # GET /type/{name}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Commands
|
|
41
|
+
|
|
42
|
+
### `pkmn pokemon <nameOrId>`
|
|
43
|
+
|
|
44
|
+
Fetch a Pokemon by name or national dex id. Returns battle data from `GET /pokemon/{name}`.
|
|
45
|
+
|
|
46
|
+
**Includes:** types, base stats, abilities, moves, sprites, held items, cries, game indices.
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pkmn pokemon incineroar
|
|
50
|
+
pkmn pokemon 727
|
|
51
|
+
pkmn pokemon flutter-mane
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### `pkmn pokemon-species <nameOrId>`
|
|
55
|
+
|
|
56
|
+
Fetch a Pokemon species by name or id. Returns species data from `GET /pokemon-species/{name}`.
|
|
57
|
+
|
|
58
|
+
**Includes:** gender rate, capture rate, egg groups, evolution chain, flavor text, genera, varieties, legendary/mythical flags.
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
pkmn pokemon-species wormadam
|
|
62
|
+
pkmn pokemon-species 413
|
|
63
|
+
pkmn pokemon-species pikachu
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### `pkmn ability <nameOrId>`
|
|
67
|
+
|
|
68
|
+
Fetch an ability by name or id. Returns ability data from `GET /ability/{name}`.
|
|
69
|
+
|
|
70
|
+
**Includes:** effect text, Pokemon that can have the ability, flavor text entries, generation, main-series flag.
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
pkmn ability intimidate
|
|
74
|
+
pkmn ability 22
|
|
75
|
+
pkmn ability flame-body
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### `pkmn item <nameOrId>`
|
|
79
|
+
|
|
80
|
+
Fetch an item by name or id. Returns item data from `GET /item/{name}`.
|
|
81
|
+
|
|
82
|
+
**Includes:** cost, fling power/effect, attributes, category, effect/flavor text, held-by Pokemon.
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
pkmn item potion
|
|
86
|
+
pkmn item 1
|
|
87
|
+
pkmn item master-ball
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### `pkmn move <nameOrId>`
|
|
91
|
+
|
|
92
|
+
Fetch a move by name or id. Returns move data from `GET /move/{name}`.
|
|
93
|
+
|
|
94
|
+
**Includes:** power, PP, accuracy, priority, type, damage class, effect entries, stat changes, target, learned-by Pokemon.
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
pkmn move flamethrower
|
|
98
|
+
pkmn move 53
|
|
99
|
+
pkmn move close-combat
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### `pkmn type <nameOrId>`
|
|
103
|
+
|
|
104
|
+
Fetch a type by name or id. Returns type data from `GET /type/{name}`.
|
|
105
|
+
|
|
106
|
+
**Includes:** damage relations (offensive/defensive), past damage relations, generation, move damage class, Pokemon of the type, moves of the type.
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
pkmn type fire
|
|
110
|
+
pkmn type 10
|
|
111
|
+
pkmn type ground
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Output
|
|
117
|
+
|
|
118
|
+
- **Success:** raw PokeAPI JSON to stdout (2-space indentation). Keys are snake_case exactly as the API returns them. No transformation.
|
|
119
|
+
- **Error:** JSON object to stderr with `error.code`, `error.message`, and non-zero exit code.
|
|
120
|
+
- **404:** `error.code` is `not-found` when the name or id does not exist.
|
|
121
|
+
|
|
122
|
+
Use `jq` to select fields from the response:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
pkmn pokemon pikachu | jq '.stats'
|
|
126
|
+
pkmn pokemon-species pikachu | jq '.evolution_chain.url'
|
|
127
|
+
pkmn ability intimidate | jq '.effect_entries'
|
|
128
|
+
pkmn item potion | jq '.effect_entries'
|
|
129
|
+
pkmn move flamethrower | jq '.effect_entries'
|
|
130
|
+
pkmn type fire | jq '.damage_relations'
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Agent routing
|
|
136
|
+
|
|
137
|
+
When the user asks about…
|
|
138
|
+
|
|
139
|
+
| Topic | Action |
|
|
140
|
+
|-------|--------|
|
|
141
|
+
| Pokemon typing, base stats, abilities, moves | `pkmn pokemon <name>` |
|
|
142
|
+
| Evolution, egg groups, legendary/mythical, flavor text | `pkmn pokemon-species <name>` |
|
|
143
|
+
| Ability effect text, which Pokemon have an ability | `pkmn ability <name>` |
|
|
144
|
+
| Item cost, effects, attributes, which Pokemon hold an item | `pkmn item <name>` |
|
|
145
|
+
| Move power, PP, accuracy, type, damage class, effects | `pkmn move <name>` |
|
|
146
|
+
| Type matchups, weaknesses, resistances, Pokemon/moves of a type | `pkmn type <name>` |
|
|
147
|
+
|
|
148
|
+
**Hard rule:** never guess base stats, typings, abilities, or learnsets — run `pkmn` and read the JSON.
|
|
149
|
+
|
|
150
|
+
Name normalization: spaces and underscores become hyphens; names are lowercased. Numeric ids pass through unchanged.
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Exit codes
|
|
155
|
+
|
|
156
|
+
| Code | Meaning |
|
|
157
|
+
|------|---------|
|
|
158
|
+
| 0 | Success |
|
|
159
|
+
| 1 | Not found or generic error |
|
|
160
|
+
| 2 | Usage error |
|
|
161
|
+
| 4 | Validation error (malformed API response) |
|
|
162
|
+
| 5 | API error |
|
|
163
|
+
| 6 | Network error / timeout |
|