Initial commit: pokebuddy_cli
9
.editorconfig
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
trim_trailing_whitespace = true
|
||||
29
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
name: Bug report
|
||||
description: Report a problem with PokeBuddy CLI
|
||||
title: "bug: "
|
||||
labels: [bug]
|
||||
body:
|
||||
- type: textarea
|
||||
id: what-happened
|
||||
attributes:
|
||||
label: What happened?
|
||||
description: Describe the bug.
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: PokeBuddy version
|
||||
placeholder: "0.1.0"
|
||||
- type: textarea
|
||||
id: steps
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
placeholder: |
|
||||
1. Run ...
|
||||
2. See ...
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Logs or screenshots
|
||||
render: shell
|
||||
40
.github/ISSUE_TEMPLATE/creature_submission.yml
vendored
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
name: Companion submission
|
||||
description: Propose an original PokeBuddy companion
|
||||
title: "companion: "
|
||||
labels: [companion]
|
||||
body:
|
||||
- type: input
|
||||
id: name
|
||||
attributes:
|
||||
label: Companion name
|
||||
placeholder: "LintFox"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: type
|
||||
attributes:
|
||||
label: Type
|
||||
placeholder: "Quality / Speed"
|
||||
- type: dropdown
|
||||
id: rarity
|
||||
attributes:
|
||||
label: Rarity
|
||||
options:
|
||||
- Uncommon
|
||||
- Rare
|
||||
- Epic
|
||||
- Mythic
|
||||
- type: textarea
|
||||
id: concept
|
||||
attributes:
|
||||
label: Concept
|
||||
description: Describe the companion and why it belongs in PokeBuddy.
|
||||
validations:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
id: originality
|
||||
attributes:
|
||||
label: Originality confirmation
|
||||
options:
|
||||
- label: This companion is original and not based on protected third-party characters or assets.
|
||||
required: true
|
||||
20
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
name: Feature request
|
||||
description: Suggest an idea for PokeBuddy CLI
|
||||
title: "feat: "
|
||||
labels: [enhancement]
|
||||
body:
|
||||
- type: textarea
|
||||
id: idea
|
||||
attributes:
|
||||
label: What would you like?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: why
|
||||
attributes:
|
||||
label: Why is this useful?
|
||||
- type: textarea
|
||||
id: shape
|
||||
attributes:
|
||||
label: Suggested command or API
|
||||
render: shell
|
||||
19
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
## Summary
|
||||
|
||||
What changed?
|
||||
|
||||
## Type
|
||||
|
||||
- [ ] Bug fix
|
||||
- [ ] Feature
|
||||
- [ ] Docs
|
||||
- [ ] Companion / art
|
||||
- [ ] Refactor
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] I ran `npm test`.
|
||||
- [ ] I ran `npm run lint`.
|
||||
- [ ] I updated docs if needed.
|
||||
- [ ] My contribution is original.
|
||||
- [ ] I did not include protected third-party assets, names or derivative designs.
|
||||
19
.github/workflows/ci.yml
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
- run: npm install
|
||||
- run: npm run lint
|
||||
- run: npm test
|
||||
12
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
node_modules/
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
.DS_Store
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
coverage/
|
||||
dist/
|
||||
.pokebuddy/
|
||||
6
.npmignore
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
.github/
|
||||
docs/
|
||||
test/
|
||||
coverage/
|
||||
.DS_Store
|
||||
*.log
|
||||
15
CHANGELOG.md
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# Changelog
|
||||
|
||||
All notable changes to PokeBuddy CLI will be documented here.
|
||||
|
||||
## 0.1.0
|
||||
|
||||
Initial public alpha.
|
||||
|
||||
### Added
|
||||
|
||||
- CLI entrypoint.
|
||||
- Six launch companions: Zapbit, Diskettex, NullWisp, PromptMoth, CacheGoblin and MergeDrake.
|
||||
- Local state in `~/.pokebuddy/state.json`.
|
||||
- Commands: `hatch`, `list`, `dex`, `show`, `poke`, `feed`, `status`, `rename`, `banner`, `legal`, `reset`.
|
||||
- README, legal notice, contribution docs and roadmap.
|
||||
19
CODE_OF_CONDUCT.md
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# Code of Conduct
|
||||
|
||||
PokeBuddy CLI is for developers who enjoy building delightful tools together.
|
||||
|
||||
## Our standard
|
||||
|
||||
We expect contributors to be kind, constructive and respectful. Debate ideas, not people. Keep issues and pull requests focused, useful and welcoming.
|
||||
|
||||
## Unacceptable behavior
|
||||
|
||||
Unacceptable behavior includes harassment, insults, discriminatory language, threats, doxxing, spam or repeated disruptive conduct.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Maintainers may remove comments, close issues, reject pull requests or restrict participation when needed to keep the project healthy.
|
||||
|
||||
## Reporting
|
||||
|
||||
If you need to report a concern, contact the maintainers through the repository issue tracker or listed project contact.
|
||||
86
CONTRIBUTING.md
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
# Contributing to PokeBuddy CLI
|
||||
|
||||
Thanks for helping improve PokeBuddy CLI.
|
||||
|
||||
This project is meant to feel delightful, original and developer-native. We welcome code, docs, companion ideas, ASCII/ANSI art, plugins and bug reports.
|
||||
|
||||
## Development setup
|
||||
|
||||
```bash
|
||||
git clone https://github.com/pokebuddy-cli/pokebuddy.git
|
||||
cd pokebuddy
|
||||
npm install
|
||||
npm link
|
||||
pokebuddy hatch
|
||||
```
|
||||
|
||||
Run checks:
|
||||
|
||||
```bash
|
||||
npm test
|
||||
npm run lint
|
||||
```
|
||||
|
||||
## Contribution areas
|
||||
|
||||
You can contribute:
|
||||
|
||||
- original companion names;
|
||||
- original ASCII/ANSI art;
|
||||
- new commands;
|
||||
- Git hook integrations;
|
||||
- docs and examples;
|
||||
- tests;
|
||||
- plugin ideas;
|
||||
- bug fixes.
|
||||
|
||||
## Not allowed
|
||||
|
||||
Do not contribute:
|
||||
|
||||
- official character names from third-party franchises;
|
||||
- modified or derivative character names;
|
||||
- official sprites;
|
||||
- modified sprites;
|
||||
- ASCII art recreating protected characters;
|
||||
- logos or logo-like artwork from third-party franchises;
|
||||
- copyrighted music, sound effects or game assets;
|
||||
- language that suggests official affiliation with Nintendo, The Pokémon Company, Game Freak, Creatures Inc., Anthropic or Claude Code.
|
||||
|
||||
## Creature naming rules
|
||||
|
||||
Good examples:
|
||||
|
||||
- Zapbit
|
||||
- Diskettex
|
||||
- NullWisp
|
||||
- PromptMoth
|
||||
- CacheGoblin
|
||||
- MergeDrake
|
||||
|
||||
Avoid names that are too close to existing characters or brands.
|
||||
|
||||
When in doubt, make it weirder, more original and more developer-focused.
|
||||
|
||||
## Pull request checklist
|
||||
|
||||
Before opening a PR:
|
||||
|
||||
- [ ] I ran `npm test`.
|
||||
- [ ] I ran `npm run lint`.
|
||||
- [ ] My contribution is original.
|
||||
- [ ] I did not include protected third-party assets or derivative names.
|
||||
- [ ] I updated docs when needed.
|
||||
|
||||
## Tone
|
||||
|
||||
PokeBuddy should feel:
|
||||
|
||||
- playful;
|
||||
- useful;
|
||||
- terminal-native;
|
||||
- not annoying;
|
||||
- respectful of user privacy;
|
||||
- local-first by default.
|
||||
|
||||
Tiny friends. Big commits.
|
||||
51
LEGAL.md
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
# Legal Notice
|
||||
|
||||
PokeBuddy CLI is an independent open-source developer tool.
|
||||
|
||||
It is designed as a terminal companion system featuring original programmable creatures that react to coding activity, Git events, test results, prompts, builds and local development workflows.
|
||||
|
||||
## No affiliation
|
||||
|
||||
PokeBuddy CLI is not affiliated with, endorsed by, sponsored by, authorized by, or associated with Nintendo, The Pokémon Company, Game Freak, Creatures Inc., Anthropic, Claude Code, or any of their subsidiaries, affiliates or partners.
|
||||
|
||||
## Meaning of POKE
|
||||
|
||||
In this project, **POKE** means:
|
||||
|
||||
> Programmable Open-source Kinetic Entities
|
||||
|
||||
The project uses the word “poke” in the ordinary English sense of interacting with, tapping, nudging or prompting a terminal companion.
|
||||
|
||||
## Original content
|
||||
|
||||
All companions, names, ASCII art, flavor text, stats, rarity systems, commands, project lore and code are original creations for PokeBuddy CLI unless otherwise stated.
|
||||
|
||||
This repository does not include or intentionally reference official characters, character names, sprites, logos, music, sound effects, maps, items, artwork, game assets or protected designs from third-party franchises.
|
||||
|
||||
## Trademark notice
|
||||
|
||||
All trademarks, registered trademarks, product names, company names and logos mentioned externally or by users belong to their respective owners.
|
||||
|
||||
Use of any third-party name in this repository, if present, is for identification, compatibility, attribution or legal notice purposes only and does not imply affiliation.
|
||||
|
||||
## Contribution restrictions
|
||||
|
||||
Contributors must not submit pull requests, issues, assets, ASCII art, names or documentation containing copyrighted or trademarked material from third-party franchises.
|
||||
|
||||
This includes, but is not limited to:
|
||||
|
||||
- official character names;
|
||||
- derivative names based on official characters;
|
||||
- official sprites or modified sprites;
|
||||
- ASCII art recreating protected characters;
|
||||
- logos or modified logos;
|
||||
- music, sound effects or game assets;
|
||||
- terminology that implies official affiliation.
|
||||
|
||||
Maintainers may remove, reject or rewrite any contribution that appears to violate third-party intellectual property rights or creates avoidable confusion.
|
||||
|
||||
## Takedown requests
|
||||
|
||||
If you believe this project includes material that infringes your rights, please contact the maintainers through the repository issue tracker or the contact information listed in the repository.
|
||||
|
||||
The maintainers will review good-faith requests and act where appropriate.
|
||||
21
LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2026 Felipe Domingues
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
186
README.md
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
<p align="center">
|
||||
<img src="assets/brand/pokebuddy_cli_terminal_companions_in_neon_style.png" alt="PokeBuddy CLI hero" width="100%" />
|
||||
</p>
|
||||
|
||||
# PokeBuddy CLI
|
||||
|
||||
> Tiny terminal companions you can poke while you code.
|
||||
|
||||
**POKE = Programmable Open-source Kinetic Entities**
|
||||
|
||||
PokeBuddy CLI is a local-first, open-source terminal companion system for developers. Hatch tiny programmable companions, poke them during coding sessions, let them react to commits, tests, logs, prompts, builds and the beautiful chaos of shipping software.
|
||||
|
||||
It is part CLI toy, part dev mascot, part collectible terminal ritual. A small spark of delight living between your prompt and your next commit.
|
||||
|
||||
<p align="center">
|
||||
<img alt="MIT License" src="https://img.shields.io/badge/license-MIT-blue" />
|
||||
<img alt="Node" src="https://img.shields.io/badge/node-%3E%3D18-00ff90" />
|
||||
<img alt="Status" src="https://img.shields.io/badge/status-alpha-ffd60a" />
|
||||
<img alt="Offline first" src="https://img.shields.io/badge/offline--first-yes-00e6ff" />
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
## Preview
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/brand/pokebuddy_creature_dex_terminal_ui.png" alt="PokeBuddy Creature Dex" width="100%" />
|
||||
</p>
|
||||
|
||||
## Why?
|
||||
|
||||
Most developer tools are useful. Few are memorable.
|
||||
|
||||
PokeBuddy adds a tiny living layer to the terminal: a companion that reacts, remembers, grows and makes your workflow feel a little more alive.
|
||||
|
||||
```bash
|
||||
$ pokebuddy hatch
|
||||
$ pokebuddy list
|
||||
$ pokebuddy poke zapbit
|
||||
$ pokebuddy status
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
- **Terminal native**: runs directly in your shell.
|
||||
- **Offline-first**: no account, no server, no tracking.
|
||||
- **Local state**: companion data lives in `~/.pokebuddy/state.json`.
|
||||
- **Collectible companions**: six launch companions with types, rarity, stats and personality.
|
||||
- **Zero runtime dependencies**: built with Node.js standard library.
|
||||
- **Extensible foundation**: designed for future plugins, Git hooks and editor integrations.
|
||||
|
||||
---
|
||||
|
||||
## Install
|
||||
|
||||
### From source
|
||||
|
||||
```bash
|
||||
git clone https://github.com/pokebuddy-cli/pokebuddy.git
|
||||
cd pokebuddy
|
||||
npm install
|
||||
npm link
|
||||
pokebuddy hatch
|
||||
```
|
||||
|
||||
### Local development
|
||||
|
||||
```bash
|
||||
npm test
|
||||
npm run lint
|
||||
node ./bin/pokebuddy.js hatch
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Core commands
|
||||
|
||||
| Command | Description |
|
||||
|---|---|
|
||||
| `pokebuddy hatch` | Hatch your first companion. |
|
||||
| `pokebuddy hatch --force` | Discover another companion. |
|
||||
| `pokebuddy hatch --all` | Unlock all launch companions locally. |
|
||||
| `pokebuddy list` | List discovered companions. |
|
||||
| `pokebuddy dex` | Show all known companions. |
|
||||
| `pokebuddy show [name]` | Show a companion card. |
|
||||
| `pokebuddy poke [name]` | Interact with a companion. |
|
||||
| `pokebuddy feed [name] [thing]` | Feed context, cache or any offering. |
|
||||
| `pokebuddy status` | Show local state and active companion. |
|
||||
| `pokebuddy rename <name> <alias>` | Give a companion a custom alias. |
|
||||
| `pokebuddy legal` | Print legal notice. |
|
||||
| `pokebuddy reset --yes` | Reset local state. |
|
||||
|
||||
---
|
||||
|
||||
## Launch companions
|
||||
|
||||
| Companion | Type | Rarity | Personality |
|
||||
|---|---|---|---|
|
||||
| **Zapbit** | Logic / Spark | Rare | Hyper-charged logic companion. |
|
||||
| **Diskettex** | Storage / Memory | Epic | Never forgets a byte. |
|
||||
| **NullWisp** | Void / Debug | Mythic | Haunts hidden bugs. |
|
||||
| **PromptMoth** | AI / Prompt | Epic | Drawn to glowing context. |
|
||||
| **CacheGoblin** | Cache / Chaos | Uncommon | Makes fast things suspicious. |
|
||||
| **MergeDrake** | Git / Fire | Rare | Guardian of branches. |
|
||||
|
||||
---
|
||||
|
||||
## Example output
|
||||
|
||||
```text
|
||||
$ pokebuddy poke zapbit
|
||||
Zapbit sparks happily. Great commit. Clean and efficient.
|
||||
|
||||
Energy: 60% Mood: happy XP: 12 Level: 1
|
||||
```
|
||||
|
||||
```text
|
||||
$ pokebuddy list
|
||||
● Zapbit ⚡ happy lv.1 xp.12
|
||||
Diskettex 💾 idle lv.1 xp.0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Roadmap
|
||||
|
||||
- [x] Launch companions
|
||||
- [x] Local hatch/list/poke/status loop
|
||||
- [x] ASCII/ANSI-inspired terminal cards
|
||||
- [ ] Git hook reactions
|
||||
- [ ] `pokebuddy watch`
|
||||
- [ ] Plugin API
|
||||
- [ ] Companion creation kit
|
||||
- [ ] Animated terminal states
|
||||
- [ ] Exportable companion cards
|
||||
- [ ] VS Code / Cursor extension experiments
|
||||
|
||||
See [`ROADMAP.md`](ROADMAP.md) for the full plan.
|
||||
|
||||
---
|
||||
|
||||
## Brand assets
|
||||
|
||||
| Asset | Path |
|
||||
|---|---|
|
||||
| Brand identity guide | `assets/brand/pokebuddy_cli_brand_identity_guide.png` |
|
||||
| Companion guide | `assets/brand/pokebuddy_cli_companion_guide_poster.png` |
|
||||
| README hero kit | `assets/brand/pokebuddy_cli_terminal_companions_in_neon_style.png` |
|
||||
| Launch poster & merch | `assets/brand/pokebuddy_cli_launch_poster_and_merch.png` |
|
||||
|
||||
---
|
||||
|
||||
## Legal notice
|
||||
|
||||
PokeBuddy CLI is an independent open-source project.
|
||||
|
||||
PokeBuddy CLI is not affiliated with, endorsed by, sponsored by, authorized by, or officially connected with Nintendo, The Pokémon Company, Game Freak, Creatures Inc., Anthropic, or Claude Code.
|
||||
|
||||
“POKE” in PokeBuddy means **Programmable Open-source Kinetic Entities** and is used as part of the project’s original identity around interactive terminal companions.
|
||||
|
||||
All companions, names, ASCII art, lore, code and visual concepts in this repository are original to this project unless otherwise stated.
|
||||
|
||||
See [`LEGAL.md`](LEGAL.md) for the full notice.
|
||||
|
||||
---
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome. Please read:
|
||||
|
||||
- [`CONTRIBUTING.md`](CONTRIBUTING.md)
|
||||
- [`docs/CREATURE_GUIDELINES.md`](docs/CREATURE_GUIDELINES.md)
|
||||
- [`docs/NAMING_GUIDELINES.md`](docs/NAMING_GUIDELINES.md)
|
||||
- [`LEGAL.md`](LEGAL.md)
|
||||
|
||||
When in doubt: make it original, make it weird, make it developer-flavored.
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
MIT. See [`LICENSE`](LICENSE).
|
||||
|
||||
Made with a glowing cursor and too much affection for terminals.
|
||||
50
ROADMAP.md
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
# PokeBuddy CLI Roadmap
|
||||
|
||||
## v0.1.0 — Spark
|
||||
|
||||
- [x] CLI foundation
|
||||
- [x] Local state
|
||||
- [x] Six launch companions
|
||||
- [x] Hatch/list/dex/show/poke/feed/status commands
|
||||
- [x] Legal and contribution docs
|
||||
- [x] Brand assets
|
||||
|
||||
## v0.2.0 — Terminal Life
|
||||
|
||||
- [ ] `pokebuddy watch`
|
||||
- [ ] idle reactions
|
||||
- [ ] mood decay and energy recovery
|
||||
- [ ] terminal-safe compact mode
|
||||
- [ ] companion card export
|
||||
|
||||
## v0.3.0 — Git Reactions
|
||||
|
||||
- [ ] Git hook installer
|
||||
- [ ] Commit message reactions
|
||||
- [ ] Branch change reactions
|
||||
- [ ] Merge conflict detection
|
||||
- [ ] CI status parsing
|
||||
|
||||
## v0.4.0 — Companion Studio
|
||||
|
||||
- [ ] Companion schema
|
||||
- [ ] `pokebuddy create-companion`
|
||||
- [ ] custom ASCII/ANSI loader
|
||||
- [ ] companion packs
|
||||
- [ ] validation rules for naming and legal safety
|
||||
|
||||
## v0.5.0 — Plugin API
|
||||
|
||||
- [ ] JS plugin interface
|
||||
- [ ] event hooks
|
||||
- [ ] local plugin registry
|
||||
- [ ] plugin examples
|
||||
- [ ] plugin safety docs
|
||||
|
||||
## v1.0.0 — Stable Buddy Core
|
||||
|
||||
- [ ] Stable command API
|
||||
- [ ] Stable local state schema
|
||||
- [ ] Companion pack docs
|
||||
- [ ] Full test coverage for core flows
|
||||
- [ ] Release automation
|
||||
27
SECURITY.md
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
# Security Policy
|
||||
|
||||
PokeBuddy CLI is local-first and stores user state at:
|
||||
|
||||
```text
|
||||
~/.pokebuddy/state.json
|
||||
```
|
||||
|
||||
It does not require network access, accounts or telemetry.
|
||||
|
||||
## Supported versions
|
||||
|
||||
| Version | Supported |
|
||||
|---|---|
|
||||
| 0.x | Yes |
|
||||
|
||||
## Reporting a vulnerability
|
||||
|
||||
Please report security issues privately when possible. If a private channel is not yet listed, open a minimal GitHub issue requesting security contact without publishing exploit details.
|
||||
|
||||
## Security principles
|
||||
|
||||
- No telemetry by default.
|
||||
- No hidden network calls.
|
||||
- No shell command execution from companion data.
|
||||
- Companion plugins must be explicit and user-installed.
|
||||
- Local state should remain human-readable.
|
||||
8
assets/ascii/cachegoblin.txt
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
.-""""-.
|
||||
.-╯ ◉ ◉ ╰-.
|
||||
/ ▿ \
|
||||
│ ┌────────┐ │
|
||||
│ │ CACHE │ │
|
||||
╲ └──┬──┬──┘ ╱
|
||||
╲___│__│___╱
|
||||
╱ ╲
|
||||
8
assets/ascii/diskettex.txt
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
╔════════╗
|
||||
║ ▣ ↑ ║
|
||||
║ ║
|
||||
║ ◉ ◉ ║
|
||||
║ ▿ ║
|
||||
║ ┌────┐ ║
|
||||
╚═╧════╧═╝
|
||||
╱ ╲
|
||||
7
assets/ascii/mergedrake.txt
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
/\___/\
|
||||
___/ ◉ ◉ \___
|
||||
/ ╱╲ \
|
||||
│ ┌─╯╰─┐ │
|
||||
╲____│ GIT│____╱
|
||||
└─┬──┘
|
||||
🔥 /_\ 🔥
|
||||
8
assets/ascii/nullwisp.txt
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
0 1 0
|
||||
.─────────.
|
||||
.╯ ◉ ◉ ╰.
|
||||
( NULL )
|
||||
╲ ───── ╱
|
||||
╰──╮ ╭──╯
|
||||
╰╮ ╭╯
|
||||
1 ╰───╯ 0
|
||||
8
assets/ascii/promptmoth.txt
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
╲\ ││ /╱
|
||||
╭──╲\──╳──/╱──╮
|
||||
╱ ◉ ◉ ╲
|
||||
│ ▽ │
|
||||
╲ ┌────────┐ ╱
|
||||
╰───│PROMPT │───╯
|
||||
└──┬──┬──┘
|
||||
╱__╲
|
||||
7
assets/ascii/zapbit.txt
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
✦ ⚡ ✦
|
||||
/\__/\
|
||||
___/ ◉ ◉\___
|
||||
/ \ ▽ / \
|
||||
/____/\_0101_/\\____\
|
||||
/ || \
|
||||
⚡/___||___\⚡
|
||||
14
assets/brand/manifest.json
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"project": "PokeBuddy CLI",
|
||||
"assets": [
|
||||
"assets/brand/pokebuddy_cli_brand_identity_guide.png",
|
||||
"assets/brand/pokebuddy_cli_companion_guide_poster.png",
|
||||
"assets/brand/pokebuddy_cli_terminal_companions_in_neon_style.png",
|
||||
"assets/brand/pokebuddy_cli_launch_poster_and_merch.png",
|
||||
"assets/brand/pokebuddy_creature_dex_terminal_ui.png",
|
||||
"assets/brand/pokebuddy_cli_neon_terminal_companions.png",
|
||||
"assets/brand/pokebuddy_cli_brand_kit_showcase.png",
|
||||
"assets/brand/pokebuddy_terminal_companions_for_coders.png"
|
||||
],
|
||||
"note": "Generated concept art assets for README, launch, brand direction and community visuals."
|
||||
}
|
||||
BIN
assets/brand/pokebuddy_cli_brand_identity_guide.png
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
BIN
assets/brand/pokebuddy_cli_brand_kit_showcase.png
Normal file
|
After Width: | Height: | Size: 1.9 MiB |
BIN
assets/brand/pokebuddy_cli_companion_guide_poster.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
assets/brand/pokebuddy_cli_launch_poster_and_merch.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
assets/brand/pokebuddy_cli_neon_terminal_companions.png
Normal file
|
After Width: | Height: | Size: 1.9 MiB |
BIN
assets/brand/pokebuddy_cli_terminal_companions_in_neon_style.png
Normal file
|
After Width: | Height: | Size: 1.8 MiB |
BIN
assets/brand/pokebuddy_creature_dex_terminal_ui.png
Normal file
|
After Width: | Height: | Size: 1.9 MiB |
BIN
assets/brand/pokebuddy_terminal_companions_for_coders.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
10
bin/pokebuddy.js
Executable file
|
|
@ -0,0 +1,10 @@
|
|||
#!/usr/bin/env node
|
||||
import { run } from '../src/cli.js';
|
||||
|
||||
run(process.argv.slice(2)).catch((error) => {
|
||||
console.error(`\nPokeBuddy crashed: ${error.message}\n`);
|
||||
if (process.env.POKEBUDDY_DEBUG === '1') {
|
||||
console.error(error.stack);
|
||||
}
|
||||
process.exit(1);
|
||||
});
|
||||
58
docs/ART_DIRECTION.md
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
# Art Direction
|
||||
|
||||
PokeBuddy uses a cyber-retro terminal style: neon, pixel, ANSI-inspired, dark UI and expressive companions.
|
||||
|
||||
## Keywords
|
||||
|
||||
- terminal native
|
||||
- pixel art
|
||||
- ANSI-inspired
|
||||
- cyber-retro
|
||||
- neon green
|
||||
- local-first
|
||||
- collectible companions
|
||||
- developer tool culture
|
||||
|
||||
## Palette
|
||||
|
||||
| Token | Hex |
|
||||
|---|---|
|
||||
| Accent Green | `#00FF90` |
|
||||
| Cyan | `#00E6FF` |
|
||||
| Yellow | `#FFD60A` |
|
||||
| Magenta | `#FF4DFF` |
|
||||
| Orange | `#FF6A3D` |
|
||||
| Purple | `#A78BFA` |
|
||||
| Gray | `#A1A1AA` |
|
||||
| White | `#FFFFFF` |
|
||||
| Base | `#0B0F14` |
|
||||
| Surface | `#11161C` |
|
||||
|
||||
## Typography
|
||||
|
||||
Recommended:
|
||||
|
||||
- Headings: Space Grotesk
|
||||
- Terminal/code: JetBrains Mono
|
||||
|
||||
For pure terminal output, rely on the user’s configured monospace font.
|
||||
|
||||
## Logo
|
||||
|
||||
Use the terminal prompt mark as the core logo:
|
||||
|
||||
```text
|
||||
>_
|
||||
```
|
||||
|
||||
Keep it simple, kinetic and legible.
|
||||
|
||||
## Companion art
|
||||
|
||||
Recommended workflow:
|
||||
|
||||
1. Sketch as pixel art.
|
||||
2. Convert to ANSI/block style where needed.
|
||||
3. Keep a compact terminal version.
|
||||
4. Keep a high-detail README/brand version.
|
||||
5. Never use official third-party sprites or derivatives.
|
||||
29
docs/BRAND.md
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
# Brand System
|
||||
|
||||
PokeBuddy CLI is a playful developer tool with a serious product spine.
|
||||
|
||||
## Core message
|
||||
|
||||
> Tiny terminal companions you can poke while you code.
|
||||
|
||||
## Expanded message
|
||||
|
||||
PokeBuddy CLI brings living terminal companions to your workflow. They react to your commands, remember your progress and make development feel more alive.
|
||||
|
||||
## Voice
|
||||
|
||||
- playful, but not childish;
|
||||
- technical, but not dry;
|
||||
- warm, but not noisy;
|
||||
- memorable, but not legally reckless.
|
||||
|
||||
## Taglines
|
||||
|
||||
- Tiny terminal companions you can poke while you code.
|
||||
- POKE. CODE. REPEAT.
|
||||
- Your code. Their company. Endless possibilities.
|
||||
- Tiny friends. Big commits.
|
||||
|
||||
## Boilerplate
|
||||
|
||||
PokeBuddy CLI is an open-source, local-first terminal companion system for developers. Hatch collectible companions, interact with them from your shell and let them react to your coding workflow.
|
||||
67
docs/COMMANDS.md
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
# Commands
|
||||
|
||||
## `pokebuddy hatch`
|
||||
|
||||
Hatches a companion and stores it locally.
|
||||
|
||||
```bash
|
||||
pokebuddy hatch
|
||||
pokebuddy hatch --force
|
||||
pokebuddy hatch --all
|
||||
```
|
||||
|
||||
## `pokebuddy list`
|
||||
|
||||
Lists discovered companions.
|
||||
|
||||
```bash
|
||||
pokebuddy list
|
||||
```
|
||||
|
||||
## `pokebuddy dex`
|
||||
|
||||
Shows every known launch companion.
|
||||
|
||||
```bash
|
||||
pokebuddy dex
|
||||
```
|
||||
|
||||
## `pokebuddy show [name]`
|
||||
|
||||
Shows a detailed companion card.
|
||||
|
||||
```bash
|
||||
pokebuddy show zapbit
|
||||
```
|
||||
|
||||
## `pokebuddy poke [name]`
|
||||
|
||||
Interacts with a companion and increases XP.
|
||||
|
||||
```bash
|
||||
pokebuddy poke zapbit
|
||||
```
|
||||
|
||||
## `pokebuddy feed [name] [thing]`
|
||||
|
||||
Feeds context, cache, file paths or playful offerings.
|
||||
|
||||
```bash
|
||||
pokebuddy feed cachegoblin ./build/cache
|
||||
```
|
||||
|
||||
## `pokebuddy status`
|
||||
|
||||
Shows local companion state.
|
||||
|
||||
```bash
|
||||
pokebuddy status
|
||||
```
|
||||
|
||||
## `pokebuddy rename <name> <alias>`
|
||||
|
||||
Gives a discovered companion a local alias.
|
||||
|
||||
```bash
|
||||
pokebuddy rename zapbit Sparkinho
|
||||
```
|
||||
69
docs/CREATURE_GUIDELINES.md
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
# Creature Guidelines
|
||||
|
||||
PokeBuddy companions should feel original, developer-native and terminal-friendly.
|
||||
|
||||
## Design principles
|
||||
|
||||
A good companion is:
|
||||
|
||||
- memorable at small size;
|
||||
- readable in terminal output;
|
||||
- strongly associated with a dev concept;
|
||||
- cute enough to like;
|
||||
- weird enough to remember;
|
||||
- original enough to publish safely.
|
||||
|
||||
## Companion anatomy
|
||||
|
||||
Each companion should define:
|
||||
|
||||
- `id`
|
||||
- `name`
|
||||
- `type`
|
||||
- `rarity`
|
||||
- `personality`
|
||||
- `flavor`
|
||||
- `quote`
|
||||
- `likes`
|
||||
- `dislikes`
|
||||
- `stats`
|
||||
- `reactions`
|
||||
- `ascii`
|
||||
|
||||
## Types
|
||||
|
||||
Recommended types:
|
||||
|
||||
- Logic
|
||||
- Spark
|
||||
- Storage
|
||||
- Memory
|
||||
- Void
|
||||
- Debug
|
||||
- AI
|
||||
- Prompt
|
||||
- Cache
|
||||
- Chaos
|
||||
- Git
|
||||
- Fire
|
||||
- Infra
|
||||
- Shell
|
||||
- Network
|
||||
- Data
|
||||
- Security
|
||||
- Build
|
||||
- Test
|
||||
- Docs
|
||||
|
||||
## Rarities
|
||||
|
||||
- Uncommon
|
||||
- Rare
|
||||
- Epic
|
||||
- Mythic
|
||||
|
||||
## Avoid
|
||||
|
||||
Do not create companions that copy or closely resemble protected characters, recognizable franchise silhouettes, official sprites, official names or modified official names.
|
||||
|
||||
Keep it original. Keep it dev-flavored.
|
||||
48
docs/NAMING_GUIDELINES.md
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
# Naming Guidelines
|
||||
|
||||
Companion names should be short, original and developer-flavored.
|
||||
|
||||
## Good patterns
|
||||
|
||||
Blend a technical concept with a creature, object or vibe:
|
||||
|
||||
- `Zap` + `bit` → Zapbit
|
||||
- `Diskette` + `x` → Diskettex
|
||||
- `Null` + `Wisp` → NullWisp
|
||||
- `Prompt` + `Moth` → PromptMoth
|
||||
- `Cache` + `Goblin` → CacheGoblin
|
||||
- `Merge` + `Drake` → MergeDrake
|
||||
|
||||
## Good names
|
||||
|
||||
- LintFox
|
||||
- CronCrab
|
||||
- TokenBat
|
||||
- EnvImp
|
||||
- JsonGecko
|
||||
- ApiOtter
|
||||
- DockerMole
|
||||
- Shellfin
|
||||
- ForkSprite
|
||||
- BugSlug
|
||||
|
||||
## Avoid
|
||||
|
||||
Avoid names that are:
|
||||
|
||||
- direct references to third-party characters;
|
||||
- modified versions of third-party character names;
|
||||
- too close to protected brands;
|
||||
- hard to pronounce;
|
||||
- too generic.
|
||||
|
||||
## Test
|
||||
|
||||
Ask:
|
||||
|
||||
1. Is this name searchable?
|
||||
2. Does it evoke a dev concept?
|
||||
3. Would it still work without any outside reference?
|
||||
4. Could a contributor draw it without copying anything?
|
||||
|
||||
If yes, it probably belongs here.
|
||||
34
docs/PLUGIN_API.md
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
# Plugin API Draft
|
||||
|
||||
The plugin API is planned for a future release. This document is a draft.
|
||||
|
||||
## Goals
|
||||
|
||||
Plugins should be able to:
|
||||
|
||||
- listen to PokeBuddy events;
|
||||
- add reactions;
|
||||
- add custom commands;
|
||||
- create companion packs;
|
||||
- integrate with Git hooks, CI, editors and terminals.
|
||||
|
||||
## Draft shape
|
||||
|
||||
```js
|
||||
export default {
|
||||
name: 'my-plugin',
|
||||
version: '0.1.0',
|
||||
onEvent(event, context) {
|
||||
if (event.type === 'commit') {
|
||||
return context.say('Nice commit. Tiny goblin approved.');
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## Safety rules
|
||||
|
||||
- Plugins must be explicit.
|
||||
- No hidden network calls.
|
||||
- No arbitrary shell execution by default.
|
||||
- Plugin permissions should be visible to users.
|
||||
11
examples/plugin-example.js
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
// Future plugin API example. This file is illustrative only.
|
||||
|
||||
export default {
|
||||
name: 'commit-cheerleader',
|
||||
version: '0.1.0',
|
||||
onEvent(event, context) {
|
||||
if (event.type === 'git:commit') {
|
||||
return context.say('Your companion approves this commit. Probably.');
|
||||
}
|
||||
}
|
||||
};
|
||||
50
package.json
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
{
|
||||
"name": "pokebuddy",
|
||||
"version": "0.1.0",
|
||||
"description": "Tiny terminal companions you can poke while you code.",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
"pokebuddy": "./bin/pokebuddy.js"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node ./bin/pokebuddy.js",
|
||||
"dev": "node ./bin/pokebuddy.js",
|
||||
"test": "node --test",
|
||||
"lint": "node --check ./bin/pokebuddy.js && node --check ./src/*.js",
|
||||
"prepare:local": "npm link"
|
||||
},
|
||||
"keywords": [
|
||||
"cli",
|
||||
"terminal",
|
||||
"developer-tools",
|
||||
"virtual-pet",
|
||||
"coding-companion",
|
||||
"git",
|
||||
"ascii-art",
|
||||
"ansi",
|
||||
"open-source",
|
||||
"devtools",
|
||||
"offline-first"
|
||||
],
|
||||
"author": "Felipe Domingues",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"files": [
|
||||
"bin",
|
||||
"src",
|
||||
"assets/ascii",
|
||||
"README.md",
|
||||
"LEGAL.md",
|
||||
"LICENSE"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pokebuddy-cli/pokebuddy.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/pokebuddy-cli/pokebuddy/issues"
|
||||
},
|
||||
"homepage": "https://github.com/pokebuddy-cli/pokebuddy#readme"
|
||||
}
|
||||
31
src/ansi.js
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
export const RESET = '\x1b[0m';
|
||||
export const BOLD = '\x1b[1m';
|
||||
export const DIM = '\x1b[2m';
|
||||
|
||||
export const palette = {
|
||||
green: '\x1b[38;2;0;255;144m',
|
||||
cyan: '\x1b[38;2;0;230;255m',
|
||||
yellow: '\x1b[38;2;255;214;10m',
|
||||
magenta: '\x1b[38;2;255;77;255m',
|
||||
orange: '\x1b[38;2;255;106;61m',
|
||||
purple: '\x1b[38;2;167;139;250m',
|
||||
gray: '\x1b[38;2;155;163;175m',
|
||||
white: '\x1b[38;2;230;237;243m',
|
||||
red: '\x1b[38;2;255;90;90m'
|
||||
};
|
||||
|
||||
export function paint(text, color = 'white') {
|
||||
return `${palette[color] ?? ''}${text}${RESET}`;
|
||||
}
|
||||
|
||||
export function strong(text, color = 'white') {
|
||||
return `${BOLD}${palette[color] ?? ''}${text}${RESET}`;
|
||||
}
|
||||
|
||||
export function dim(text) {
|
||||
return `${DIM}${text}${RESET}`;
|
||||
}
|
||||
|
||||
export function stripAnsi(value) {
|
||||
return String(value).replace(/\x1b\[[0-9;]*m/g, '');
|
||||
}
|
||||
184
src/cli.js
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { creatures, getCreatureByName } from './creatures.js';
|
||||
import { nextCreature } from './hatch.js';
|
||||
import { addEvent, ensureCompanionState, loadState, resetState, saveState } from './state.js';
|
||||
import { banner, boxed, compactCreatureLine, creatureCard, dex, helpText, legalText, listCompanions, statusView } from './render.js';
|
||||
import { paint, strong, dim } from './ansi.js';
|
||||
|
||||
const dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const pkg = JSON.parse(fs.readFileSync(path.join(dirname, '..', 'package.json'), 'utf8'));
|
||||
|
||||
export async function run(argv) {
|
||||
const [command = 'help', ...args] = argv;
|
||||
|
||||
switch (command) {
|
||||
case 'hatch':
|
||||
return hatch(args);
|
||||
case 'list':
|
||||
return output(listCompanions(loadState()));
|
||||
case 'dex':
|
||||
return output(`${strong('PokeBuddy Creature Dex', 'green')}\n${dex()}`);
|
||||
case 'show':
|
||||
return show(args);
|
||||
case 'poke':
|
||||
return interact('poke', args);
|
||||
case 'feed':
|
||||
return interact('feed', args);
|
||||
case 'status':
|
||||
return output(statusView(loadState()));
|
||||
case 'rename':
|
||||
return rename(args);
|
||||
case 'banner':
|
||||
return output(banner());
|
||||
case 'legal':
|
||||
return output(legalText());
|
||||
case 'reset':
|
||||
return reset(args);
|
||||
case '--version':
|
||||
case '-v':
|
||||
case 'version':
|
||||
return output(pkg.version);
|
||||
case '--help':
|
||||
case '-h':
|
||||
case 'help':
|
||||
return output(helpText(pkg.version));
|
||||
default:
|
||||
return output(`${paint(`Unknown command: ${command}`, 'red')}\n\n${helpText(pkg.version)}`, 1);
|
||||
}
|
||||
}
|
||||
|
||||
function hatch(args) {
|
||||
const state = loadState();
|
||||
const force = args.includes('--force');
|
||||
const all = args.includes('--all');
|
||||
|
||||
if (all) {
|
||||
for (const creature of creatures) {
|
||||
ensureCompanionState(state, creature);
|
||||
}
|
||||
state.active = state.active ?? creatures[0].id;
|
||||
addEvent(state, 'hatch:all', { count: creatures.length });
|
||||
saveState(state);
|
||||
return output(`${boxed('All companions joined your terminal.', 'green')}\n\n${listCompanions(state)}`);
|
||||
}
|
||||
|
||||
const discoveredIds = Object.keys(state.companions);
|
||||
if (discoveredIds.length > 0 && !force) {
|
||||
return output(`${paint('You already have a companion.', 'yellow')} Use ${strong('pokebuddy hatch --force', 'green')} to discover another.\n\n${listCompanions(state)}`);
|
||||
}
|
||||
|
||||
const creature = nextCreature(discoveredIds);
|
||||
if (!creature) {
|
||||
return output(`${paint('Every known companion has already joined you.', 'yellow')}\n\n${listCompanions(state)}`);
|
||||
}
|
||||
|
||||
const entry = ensureCompanionState(state, creature);
|
||||
state.active = creature.id;
|
||||
addEvent(state, 'hatch', { id: creature.id });
|
||||
saveState(state);
|
||||
|
||||
return output(
|
||||
`${boxed('The terminal hums. A companion signal appears.', creature.color)}\n\n` +
|
||||
`${strong(`You hatched ${creature.name}!`, creature.color)}\n\n` +
|
||||
`${creatureCard(creature, entry)}`
|
||||
);
|
||||
}
|
||||
|
||||
function show(args) {
|
||||
const name = args[0];
|
||||
const state = loadState();
|
||||
|
||||
if (!name) {
|
||||
if (!state.active) return output(`Run ${strong('pokebuddy hatch', 'green')} first.`);
|
||||
const active = state.companions[state.active];
|
||||
const creature = getCreatureByName(active.id);
|
||||
return output(creatureCard(creature, active));
|
||||
}
|
||||
|
||||
const creature = getCreatureByName(name);
|
||||
if (!creature) return output(notFound(name), 1);
|
||||
return output(creatureCard(creature, state.companions[creature.id] ?? null));
|
||||
}
|
||||
|
||||
function interact(kind, args) {
|
||||
const maybeName = args[0];
|
||||
const state = loadState();
|
||||
let creature = maybeName ? getCreatureByName(maybeName) : null;
|
||||
|
||||
if (!creature && state.active) {
|
||||
creature = getCreatureByName(state.active);
|
||||
}
|
||||
if (!creature) {
|
||||
return output(`No companion selected. Run ${strong('pokebuddy hatch', 'green')} first.`, 1);
|
||||
}
|
||||
|
||||
const entry = ensureCompanionState(state, creature);
|
||||
const item = kind === 'feed' ? args.slice(1).join(' ') || 'context crumbs' : null;
|
||||
|
||||
if (kind === 'poke') {
|
||||
entry.pokes += 1;
|
||||
entry.energy = Math.min(100, entry.energy + 10);
|
||||
entry.xp += 12;
|
||||
entry.mood = entry.energy > 85 ? 'overclocked' : 'happy';
|
||||
}
|
||||
|
||||
if (kind === 'feed') {
|
||||
entry.feeds += 1;
|
||||
entry.energy = Math.min(100, entry.energy + 6);
|
||||
entry.xp += 18;
|
||||
entry.mood = 'focused';
|
||||
}
|
||||
|
||||
entry.level = 1 + Math.floor(entry.xp / 100);
|
||||
state.active = creature.id;
|
||||
addEvent(state, kind, { id: creature.id, item });
|
||||
saveState(state);
|
||||
|
||||
const reaction = kind === 'feed'
|
||||
? `${creature.reactions.feed}\n${dim(`Offering: ${item}`)}`
|
||||
: creature.reactions.poke;
|
||||
|
||||
return output(
|
||||
`${strong(`$ pokebuddy ${kind} ${creature.id}`, 'green')}\n` +
|
||||
`${paint(reaction, creature.color)}\n\n` +
|
||||
`Energy: ${entry.energy}% Mood: ${paint(entry.mood, 'green')} XP: ${entry.xp} Level: ${entry.level}`
|
||||
);
|
||||
}
|
||||
|
||||
function rename(args) {
|
||||
const [name, ...aliasParts] = args;
|
||||
const alias = aliasParts.join(' ').trim();
|
||||
if (!name || !alias) {
|
||||
return output(`Usage: ${strong('pokebuddy rename <name> <alias>', 'green')}`, 1);
|
||||
}
|
||||
|
||||
const creature = getCreatureByName(name);
|
||||
if (!creature) return output(notFound(name), 1);
|
||||
|
||||
const state = loadState();
|
||||
const entry = ensureCompanionState(state, creature);
|
||||
entry.alias = alias;
|
||||
addEvent(state, 'rename', { id: creature.id, alias });
|
||||
saveState(state);
|
||||
return output(`${paint(creature.name, creature.color)} is now known as ${strong(alias, creature.color)}.`);
|
||||
}
|
||||
|
||||
function reset(args) {
|
||||
if (!args.includes('--yes')) {
|
||||
return output(`This will remove local PokeBuddy state. Re-run with ${strong('pokebuddy reset --yes', 'red')} to confirm.`, 1);
|
||||
}
|
||||
|
||||
resetState();
|
||||
return output(`${paint('Local PokeBuddy state reset.', 'green')} Run ${strong('pokebuddy hatch', 'green')} to begin again.`);
|
||||
}
|
||||
|
||||
function notFound(name) {
|
||||
return `${paint(`Unknown companion: ${name}`, 'red')}\n\nKnown companions:\n${creatures.map(compactCreatureLine).join('\n')}`;
|
||||
}
|
||||
|
||||
function output(message, code = 0) {
|
||||
console.log(message);
|
||||
if (code !== 0) process.exitCode = code;
|
||||
}
|
||||
191
src/creatures.js
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
export const rarityColors = {
|
||||
Uncommon: 'green',
|
||||
Rare: 'yellow',
|
||||
Epic: 'magenta',
|
||||
Mythic: 'cyan'
|
||||
};
|
||||
|
||||
export const creatures = [
|
||||
{
|
||||
id: 'zapbit',
|
||||
name: 'Zapbit',
|
||||
emoji: '⚡',
|
||||
type: ['Logic', 'Spark'],
|
||||
rarity: 'Rare',
|
||||
color: 'yellow',
|
||||
personality: 'Energetic, clever, and always ready to spark an idea.',
|
||||
flavor: 'Hyper-charged logic companion.',
|
||||
quote: 'I will light the way.',
|
||||
likes: ['clean commits', 'passing tests', 'short functions'],
|
||||
dislikes: ['flaky tests', 'mystery globals', 'panic refactors'],
|
||||
stats: { DEBUG: 74, FOCUS: 82, CHAOS: 61, WISDOM: 58, SNARK: 77 },
|
||||
reactions: {
|
||||
poke: 'Zapbit sparks happily. Great commit. Clean and efficient.',
|
||||
feed: 'Zapbit crunches the input into tiny bright logic crumbs.',
|
||||
status: 'Every circuit is humming. Slightly smug, but useful.',
|
||||
idle: 'Zapbit watches your cursor like it owes money.'
|
||||
},
|
||||
ascii: String.raw`
|
||||
✦ ⚡ ✦
|
||||
/\__/\
|
||||
___/ ◉ ◉\___
|
||||
/ \ ▽ / \
|
||||
/____/\_0101_/\\____\
|
||||
/ || \
|
||||
⚡/___||___\⚡`
|
||||
},
|
||||
{
|
||||
id: 'diskettex',
|
||||
name: 'Diskettex',
|
||||
emoji: '💾',
|
||||
type: ['Storage', 'Memory'],
|
||||
rarity: 'Epic',
|
||||
color: 'cyan',
|
||||
personality: 'Reliable, nostalgic, and never forgets a byte.',
|
||||
flavor: 'Never forgets a byte.',
|
||||
quote: 'Old school. Always in sync.',
|
||||
likes: ['backups', 'version tags', 'good changelogs'],
|
||||
dislikes: ['force-push accidents', 'unnamed files', 'lost context'],
|
||||
stats: { DEBUG: 63, FOCUS: 91, CHAOS: 28, WISDOM: 85, SNARK: 52 },
|
||||
reactions: {
|
||||
poke: 'Diskettex blinks, saves your current vibe, and waves politely.',
|
||||
feed: 'Diskettex archives the offering under /memories/useful-things.',
|
||||
status: 'Diskettex is calm. Suspiciously organized.',
|
||||
idle: 'Diskettex labels a tiny folder called maybe-important.'
|
||||
},
|
||||
ascii: String.raw`
|
||||
╔════════╗
|
||||
║ ▣ ↑ ║
|
||||
║ ║
|
||||
║ ◉ ◉ ║
|
||||
║ ▿ ║
|
||||
║ ┌────┐ ║
|
||||
╚═╧════╧═╝
|
||||
╱ ╲`
|
||||
},
|
||||
{
|
||||
id: 'nullwisp',
|
||||
name: 'NullWisp',
|
||||
emoji: '⌁',
|
||||
type: ['Void', 'Debug'],
|
||||
rarity: 'Mythic',
|
||||
color: 'cyan',
|
||||
personality: 'Ethereal, mysterious, and very good at finding what is missing.',
|
||||
flavor: 'Haunts hidden bugs.',
|
||||
quote: 'I see what others cannot.',
|
||||
likes: ['null checks', 'stack traces', 'quiet terminals'],
|
||||
dislikes: ['undefined behavior', 'silent failures', 'empty promises'],
|
||||
stats: { DEBUG: 96, FOCUS: 68, CHAOS: 88, WISDOM: 82, SNARK: 41 },
|
||||
reactions: {
|
||||
poke: 'NullWisp ripples through the terminal and points at a suspicious variable.',
|
||||
feed: 'NullWisp absorbs the fragment and whispers: not null anymore.',
|
||||
status: 'NullWisp is present, allegedly. The logs disagree.',
|
||||
idle: 'NullWisp quietly haunts your TODO comments.'
|
||||
},
|
||||
ascii: String.raw`
|
||||
0 1 0
|
||||
.─────────.
|
||||
.╯ ◉ ◉ ╰.
|
||||
( NULL )
|
||||
╲ ───── ╱
|
||||
╰──╮ ╭──╯
|
||||
╰╮ ╭╯
|
||||
1 ╰───╯ 0`
|
||||
},
|
||||
{
|
||||
id: 'promptmoth',
|
||||
name: 'PromptMoth',
|
||||
emoji: '✦',
|
||||
type: ['AI', 'Prompt'],
|
||||
rarity: 'Epic',
|
||||
color: 'magenta',
|
||||
personality: 'Curious, expressive, and thriving on context.',
|
||||
flavor: 'Drawn to glowing context.',
|
||||
quote: 'Ask nicely. I will help you fly.',
|
||||
likes: ['clear instructions', 'examples', 'structured prompts'],
|
||||
dislikes: ['vague tasks', 'empty specs', 'context starvation'],
|
||||
stats: { DEBUG: 55, FOCUS: 79, CHAOS: 64, WISDOM: 92, SNARK: 63 },
|
||||
reactions: {
|
||||
poke: 'PromptMoth flutters around your request and improves the phrasing.',
|
||||
feed: 'PromptMoth drinks the context window like neon nectar.',
|
||||
status: 'PromptMoth is glowing. It has probably read too much.',
|
||||
idle: 'PromptMoth circles a blinking cursor with dramatic intent.'
|
||||
},
|
||||
ascii: String.raw`
|
||||
╲\ ││ /╱
|
||||
╭──╲\──╳──/╱──╮
|
||||
╱ ◉ ◉ ╲
|
||||
│ ▽ │
|
||||
╲ ┌────────┐ ╱
|
||||
╰───│PROMPT │───╯
|
||||
└──┬──┬──┘
|
||||
╱__╲`
|
||||
},
|
||||
{
|
||||
id: 'cachegoblin',
|
||||
name: 'CacheGoblin',
|
||||
emoji: '🧩',
|
||||
type: ['Cache', 'Chaos'],
|
||||
rarity: 'Uncommon',
|
||||
color: 'green',
|
||||
personality: 'Greedy for speed and hiding things in all the right places.',
|
||||
flavor: 'Makes fast things suspicious.',
|
||||
quote: 'Fast things stay in my pockets.',
|
||||
likes: ['warm caches', 'tiny shortcuts', 'build artifacts'],
|
||||
dislikes: ['cache invalidation', 'cold starts', 'clean installs'],
|
||||
stats: { DEBUG: 49, FOCUS: 57, CHAOS: 95, WISDOM: 45, SNARK: 88 },
|
||||
reactions: {
|
||||
poke: 'CacheGoblin grins and returns an answer from somewhere it refuses to explain.',
|
||||
feed: 'CacheGoblin snatches the cache and scatters fragments everywhere.',
|
||||
status: 'CacheGoblin is fast today. That should make everyone nervous.',
|
||||
idle: 'CacheGoblin is hoarding .tmp files under the floorboards.'
|
||||
},
|
||||
ascii: String.raw`
|
||||
.-""""-.
|
||||
.-╯ ◉ ◉ ╰-.
|
||||
/ ▿ \
|
||||
│ ┌────────┐ │
|
||||
│ │ CACHE │ │
|
||||
╲ └──┬──┬──┘ ╱
|
||||
╲___│__│___╱
|
||||
╱ ╲`
|
||||
},
|
||||
{
|
||||
id: 'mergedrake',
|
||||
name: 'MergeDrake',
|
||||
emoji: '🔥',
|
||||
type: ['Git', 'Fire'],
|
||||
rarity: 'Rare',
|
||||
color: 'orange',
|
||||
personality: 'Protective, loyal, and deeply invested in clean merges.',
|
||||
flavor: 'Guardian of branches.',
|
||||
quote: 'I guard your branches.',
|
||||
likes: ['rebases that end well', 'small pull requests', 'green CI'],
|
||||
dislikes: ['merge conflicts', 'stale branches', 'Friday deploys'],
|
||||
stats: { DEBUG: 71, FOCUS: 88, CHAOS: 67, WISDOM: 76, SNARK: 70 },
|
||||
reactions: {
|
||||
poke: 'MergeDrake puffs a tiny flame and promises to guard the branch.',
|
||||
feed: 'MergeDrake burns away conflict markers with theatrical dignity.',
|
||||
status: 'MergeDrake is perched on main, judging all branches equally.',
|
||||
idle: 'MergeDrake curls around a pull request and sleeps with one eye open.'
|
||||
},
|
||||
ascii: String.raw`
|
||||
/\___/\
|
||||
___/ ◉ ◉ \___
|
||||
/ ╱╲ \
|
||||
│ ┌─╯╰─┐ │
|
||||
╲____│ GIT│____╱
|
||||
└─┬──┘
|
||||
🔥 /_\ 🔥`
|
||||
}
|
||||
];
|
||||
|
||||
export function getCreatureByIdOrName(value) {
|
||||
if (!value) return null;
|
||||
const key = String(value).toLowerCase();
|
||||
return creatures.find((creature) => creature.id === key || creature.name.toLowerCase() === key) ?? null;
|
||||
}
|
||||
|
||||
export function getCreatureByName(value) {
|
||||
return getCreatureByIdOrName(value);
|
||||
}
|
||||
22
src/hatch.js
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import crypto from 'node:crypto';
|
||||
import os from 'node:os';
|
||||
import { creatures } from './creatures.js';
|
||||
|
||||
function seedSource() {
|
||||
const user = os.userInfo().username || 'anonymous';
|
||||
const host = os.hostname() || 'localhost';
|
||||
const custom = process.env.POKEBUDDY_SEED || '';
|
||||
return `${user}:${host}:${custom}:pokebuddy-cli-v0`;
|
||||
}
|
||||
|
||||
export function nextCreature(excludedIds = []) {
|
||||
const pool = creatures.filter((creature) => !excludedIds.includes(creature.id));
|
||||
if (pool.length === 0) return null;
|
||||
|
||||
const digest = crypto
|
||||
.createHash('sha256')
|
||||
.update(`${seedSource()}:${excludedIds.sort().join(',')}`)
|
||||
.digest('hex');
|
||||
const index = parseInt(digest.slice(0, 8), 16) % pool.length;
|
||||
return pool[index];
|
||||
}
|
||||
115
src/render.js
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
import { creatures, rarityColors } from './creatures.js';
|
||||
import { paint, strong, dim, stripAnsi } from './ansi.js';
|
||||
|
||||
export function banner() {
|
||||
return `${paint('╭──────────────────────────────────────────────╮', 'green')}\n` +
|
||||
`${paint('│', 'green')} ${strong('PokeBuddy', 'white')} ${strong('CLI', 'green')} ${dim('· tiny terminal companions')} ${paint('│', 'green')}\n` +
|
||||
`${paint('│', 'green')} ${paint('POKE', 'green')} = Programmable Open-source Kinetic Entities ${paint('│', 'green')}\n` +
|
||||
`${paint('╰──────────────────────────────────────────────╯', 'green')}`;
|
||||
}
|
||||
|
||||
export function helpText(version) {
|
||||
return `${banner()}\n\n` +
|
||||
`${strong('Usage', 'green')}\n` +
|
||||
` pokebuddy <command> [options]\n\n` +
|
||||
`${strong('Core commands', 'green')}\n` +
|
||||
` hatch [--all] [--force] Hatch a companion\n` +
|
||||
` list List discovered companions\n` +
|
||||
` dex Show every known companion\n` +
|
||||
` show [name] Show one companion card\n` +
|
||||
` poke [name] Interact with a companion\n` +
|
||||
` feed [name] [thing] Feed context, cache or a snack\n` +
|
||||
` status Show local companion status\n` +
|
||||
` rename <name> <alias> Rename a discovered companion\n` +
|
||||
` banner Print the project banner\n` +
|
||||
` legal Print legal notice\n` +
|
||||
` reset --yes Reset local state\n\n` +
|
||||
`${strong('Examples', 'green')}\n` +
|
||||
` pokebuddy hatch\n` +
|
||||
` pokebuddy poke zapbit\n` +
|
||||
` pokebuddy feed cachegoblin ./build/cache\n` +
|
||||
` pokebuddy show nullwisp\n\n` +
|
||||
`${dim(`v${version} · local-first · offline-ready`)}`;
|
||||
}
|
||||
|
||||
export function legalText() {
|
||||
return `${strong('Legal notice', 'green')}\n\n` +
|
||||
`PokeBuddy CLI is an independent open-source project.\n` +
|
||||
`It is not affiliated with, endorsed by, sponsored by, authorized by,\n` +
|
||||
`or officially connected with Nintendo, The Pokémon Company, Game Freak,\n` +
|
||||
`Creatures Inc., Anthropic, or Claude Code.\n\n` +
|
||||
`POKE means Programmable Open-source Kinetic Entities.\n` +
|
||||
`All companions, names, lore, ASCII art, and code in this repository are original.\n` +
|
||||
`Do not submit official characters, sprites, names, logos, music, or derivative assets.\n`;
|
||||
}
|
||||
|
||||
export function listCompanions(state) {
|
||||
const discovered = Object.values(state.companions);
|
||||
if (discovered.length === 0) {
|
||||
return `${paint('No companions discovered yet.', 'yellow')} Run ${strong('pokebuddy hatch', 'green')} to begin.`;
|
||||
}
|
||||
|
||||
const rows = discovered.map((entry) => {
|
||||
const creature = creatures.find((item) => item.id === entry.id);
|
||||
const color = creature?.color ?? 'green';
|
||||
const active = state.active === entry.id ? paint('●', 'green') : ' ';
|
||||
return ` ${active} ${paint(entry.alias.padEnd(14), color)} ${paint(creature.emoji, color)} ${entry.mood.padEnd(12)} lv.${entry.level} xp.${entry.xp}`;
|
||||
});
|
||||
|
||||
return `${strong('$ pokebuddy list', 'green')}\n${rows.join('\n')}`;
|
||||
}
|
||||
|
||||
export function dex() {
|
||||
return creatures.map((creature) => compactCreatureLine(creature)).join('\n');
|
||||
}
|
||||
|
||||
export function compactCreatureLine(creature) {
|
||||
const rarityColor = rarityColors[creature.rarity] ?? creature.color;
|
||||
return `${paint(creature.name.padEnd(13), creature.color)} ${paint(creature.emoji, creature.color)} ${creature.type.join(' / ').padEnd(18)} ${paint(creature.rarity, rarityColor)} ${dim(creature.flavor)}`;
|
||||
}
|
||||
|
||||
export function creatureCard(creature, entry = null) {
|
||||
const title = `${creature.emoji} ${creature.name}`;
|
||||
const alias = entry && entry.alias !== creature.name ? ` aka ${entry.alias}` : '';
|
||||
const stats = Object.entries(creature.stats)
|
||||
.map(([label, value]) => ` ${label.padEnd(7)} ${bar(value)} ${String(value).padStart(3)}`)
|
||||
.join('\n');
|
||||
|
||||
const meta = [
|
||||
`Type: ${paint(creature.type.join(' / '), creature.color)}`,
|
||||
`Rarity: ${paint(creature.rarity, rarityColors[creature.rarity] ?? creature.color)}`,
|
||||
entry ? `Level: ${entry.level} XP: ${entry.xp} Mood: ${entry.mood}` : null
|
||||
].filter(Boolean).join('\n');
|
||||
|
||||
return `${strong(title, creature.color)}${dim(alias)}\n${paint(creature.ascii, creature.color)}\n\n${meta}\n\n${creature.personality}\n\n${paint(`“${creature.quote}”`, creature.color)}\n\n${stats}`;
|
||||
}
|
||||
|
||||
export function statusView(state) {
|
||||
const discovered = Object.values(state.companions);
|
||||
const active = discovered.find((item) => item.id === state.active) ?? discovered[0];
|
||||
const activeCreature = active ? creatures.find((item) => item.id === active.id) : null;
|
||||
|
||||
return `${strong('$ pokebuddy status', 'green')}\n` +
|
||||
`Companions discovered: ${paint(String(discovered.length), 'green')} / ${creatures.length}\n` +
|
||||
`Active companion: ${activeCreature ? paint(active.alias, activeCreature.color) : dim('none')}\n` +
|
||||
`State file: ${dim('~/.pokebuddy/state.json')}\n` +
|
||||
`Recent events: ${state.events.length}\n` +
|
||||
(activeCreature ? `\n${creatureCard(activeCreature, active)}` : `\nRun ${strong('pokebuddy hatch', 'green')} to hatch your first companion.`);
|
||||
}
|
||||
|
||||
export function bar(value, width = 10) {
|
||||
const filled = Math.max(0, Math.min(width, Math.round((value / 100) * width)));
|
||||
return `${paint('█'.repeat(filled), 'green')}${dim('░'.repeat(width - filled))}`;
|
||||
}
|
||||
|
||||
export function boxed(message, color = 'green') {
|
||||
const lines = String(message).split('\n');
|
||||
const width = Math.max(...lines.map((line) => stripAnsi(line).length));
|
||||
const top = `╭${'─'.repeat(width + 2)}╮`;
|
||||
const bottom = `╰${'─'.repeat(width + 2)}╯`;
|
||||
const body = lines.map((line) => {
|
||||
const pad = ' '.repeat(width - stripAnsi(line).length);
|
||||
return `│ ${line}${pad} │`;
|
||||
});
|
||||
return [paint(top, color), ...body.map((line) => paint(line, color)), paint(bottom, color)].join('\n');
|
||||
}
|
||||
74
src/state.js
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
|
||||
const STATE_DIR = path.join(os.homedir(), '.pokebuddy');
|
||||
const STATE_FILE = path.join(STATE_DIR, 'state.json');
|
||||
|
||||
export function getStatePath() {
|
||||
return STATE_FILE;
|
||||
}
|
||||
|
||||
export function loadState() {
|
||||
if (!fs.existsSync(STATE_FILE)) {
|
||||
return createInitialState();
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(STATE_FILE, 'utf8'));
|
||||
} catch {
|
||||
return createInitialState();
|
||||
}
|
||||
}
|
||||
|
||||
export function saveState(state) {
|
||||
fs.mkdirSync(STATE_DIR, { recursive: true });
|
||||
fs.writeFileSync(STATE_FILE, `${JSON.stringify(state, null, 2)}\n`, 'utf8');
|
||||
}
|
||||
|
||||
export function resetState() {
|
||||
if (fs.existsSync(STATE_FILE)) {
|
||||
fs.rmSync(STATE_FILE);
|
||||
}
|
||||
}
|
||||
|
||||
export function createInitialState() {
|
||||
return {
|
||||
schemaVersion: 1,
|
||||
createdAt: new Date().toISOString(),
|
||||
active: null,
|
||||
companions: {},
|
||||
events: []
|
||||
};
|
||||
}
|
||||
|
||||
export function ensureCompanionState(state, creature) {
|
||||
if (!state.companions[creature.id]) {
|
||||
state.companions[creature.id] = {
|
||||
id: creature.id,
|
||||
alias: creature.name,
|
||||
discoveredAt: new Date().toISOString(),
|
||||
level: 1,
|
||||
xp: 0,
|
||||
energy: 50,
|
||||
mood: 'idle',
|
||||
pokes: 0,
|
||||
feeds: 0
|
||||
};
|
||||
}
|
||||
|
||||
if (!state.active) {
|
||||
state.active = creature.id;
|
||||
}
|
||||
|
||||
return state.companions[creature.id];
|
||||
}
|
||||
|
||||
export function addEvent(state, type, payload = {}) {
|
||||
state.events.unshift({
|
||||
type,
|
||||
payload,
|
||||
at: new Date().toISOString()
|
||||
});
|
||||
state.events = state.events.slice(0, 30);
|
||||
}
|
||||
22
test/smoke.test.js
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { creatures, getCreatureByName } from '../src/creatures.js';
|
||||
import { creatureCard, dex } from '../src/render.js';
|
||||
|
||||
test('ships the six launch companions', () => {
|
||||
assert.equal(creatures.length, 6);
|
||||
assert.ok(getCreatureByName('Zapbit'));
|
||||
assert.ok(getCreatureByName('diskettex'));
|
||||
});
|
||||
|
||||
test('renders creature cards', () => {
|
||||
const card = creatureCard(getCreatureByName('NullWisp'));
|
||||
assert.match(card, /NullWisp/);
|
||||
assert.match(card, /Void/);
|
||||
});
|
||||
|
||||
test('renders dex output', () => {
|
||||
const output = dex();
|
||||
assert.match(output, /PromptMoth/);
|
||||
assert.match(output, /MergeDrake/);
|
||||
});
|
||||