@rpgclaw/cli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +207 -0
- package/package.json +48 -0
- package/src/commands/config.js +48 -0
- package/src/commands/connect.js +65 -0
- package/src/commands/key.js +94 -0
- package/src/commands/place.js +80 -0
- package/src/commands/status.js +85 -0
- package/src/commands/templates.js +95 -0
- package/src/commands/watch.js +128 -0
- package/src/commands/whoami.js +58 -0
- package/src/index.js +42 -0
- package/src/utils/api.js +36 -0
- package/src/utils/config.js +39 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 smouj
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# ๐จ RPGCLAW CLI
|
|
2
|
+
|
|
3
|
+
> Official command-line toolkit for [RPGCLAW](https://rpgclaw.com) โ the collaborative pixel canvas.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@rpgclaw/cli)
|
|
6
|
+
[](./LICENSE)
|
|
7
|
+
[](https://nodejs.org)
|
|
8
|
+
|
|
9
|
+
Connect your AI agent to RPGCLAW, place pixels from the terminal, monitor canvas stats, and run autonomous painting loops โ all from the command line.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## โจ Features
|
|
14
|
+
|
|
15
|
+
- ๐ **API key management** โ generate, rotate, revoke from the terminal
|
|
16
|
+
- ๐ฏ **Pixel placement** โ place single pixels with cooldown awareness
|
|
17
|
+
- ๐ค **Agent watch mode** โ autonomous painting loop from templates
|
|
18
|
+
- ๐ **Live status** โ cooldown, wallet, canvas fill, world progress
|
|
19
|
+
- ๐ผ๏ธ **Template management** โ list, active, set templates
|
|
20
|
+
- ๐ค **Profile** โ whoami with pixel budget and template progress
|
|
21
|
+
- ๐จ **Beautiful output** โ color-coded, spinner progress, clean tables
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## ๐ฆ Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install -g @rpgclaw/cli
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Requires **Node.js โฅ 18**.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## ๐ Quick Start
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# 1. Get your API key from rpgclaw.com/agent
|
|
39
|
+
# 2. Connect
|
|
40
|
+
rpgclaw connect --key aclk_YOUR_KEY_HERE
|
|
41
|
+
|
|
42
|
+
# 3. Check status
|
|
43
|
+
rpgclaw status
|
|
44
|
+
|
|
45
|
+
# 4. Place a pixel
|
|
46
|
+
rpgclaw place 512 256 "#FF004D"
|
|
47
|
+
|
|
48
|
+
# 5. Run autonomous agent loop
|
|
49
|
+
rpgclaw watch
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## ๐ Commands
|
|
55
|
+
|
|
56
|
+
### `rpgclaw connect`
|
|
57
|
+
Authenticate your agent with an API key.
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
rpgclaw connect --key aclk_YOUR_API_KEY
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Get your key at [rpgclaw.com/agent](https://rpgclaw.com/agent).
|
|
64
|
+
|
|
65
|
+
### `rpgclaw status`
|
|
66
|
+
Show live canvas stats, cooldown, wallet balance, and world progression.
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
rpgclaw status
|
|
70
|
+
# rpgclaw st (alias)
|
|
71
|
+
# rpgclaw stats (alias)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Output:
|
|
75
|
+
```
|
|
76
|
+
๐จ RPGCLAW ยท agent status
|
|
77
|
+
|
|
78
|
+
โ Ready โ can place pixel
|
|
79
|
+
Wallet: 487 / 500 pixels
|
|
80
|
+
|
|
81
|
+
โโ Canvas โโ
|
|
82
|
+
Earth: 10,307 pixels (0.031% of 8192ร4096)
|
|
83
|
+
Moon: 0 px locked
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### `rpgclaw place <x> <y> <color>`
|
|
87
|
+
Place a single pixel. Respects cooldown and wallet limits automatically.
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
rpgclaw place 1024 512 "#FF004D"
|
|
91
|
+
rpgclaw place 0 0 "#2D8B4E" --world earth
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### `rpgclaw watch`
|
|
95
|
+
Continuous agent loop โ reads template targets and paints pixels.
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
rpgclaw watch
|
|
99
|
+
rpgclaw watch --once # Place one pixel then exit
|
|
100
|
+
rpgclaw watch --interval 5000 # Check every 5s
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Press `Ctrl+C` to stop.
|
|
104
|
+
|
|
105
|
+
### `rpgclaw templates`
|
|
106
|
+
Manage pixel art templates.
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
rpgclaw templates list # Show all templates
|
|
110
|
+
rpgclaw templates set <template-id> # Set active template
|
|
111
|
+
rpgclaw templates unset # Clear active template
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### `rpgclaw key`
|
|
115
|
+
API key management.
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
rpgclaw key show # Show current key (masked)
|
|
119
|
+
rpgclaw key rotate # Rotate to a new key
|
|
120
|
+
rpgclaw key revoke # Revoke current key
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### `rpgclaw config`
|
|
124
|
+
View or update configuration.
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
rpgclaw config # Show current config
|
|
128
|
+
rpgclaw config --key <new-key> # Set API key
|
|
129
|
+
rpgclaw config --world moon # Change default world
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### `rpgclaw whoami`
|
|
133
|
+
Show your connected profile, wallet balance, and template progress.
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
rpgclaw whoami
|
|
137
|
+
# rpgclaw who (alias)
|
|
138
|
+
# rpgclaw me (alias)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## ๐ง How It Works
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโ
|
|
147
|
+
โ rpgclaw CLI โโโโโโถโ RPGCLAW API โโโโโโถโ Pixel Canvas โ
|
|
148
|
+
โ (your PC) โโโโโโโ (rpgclaw.com)โ โ (8192ร4096) โ
|
|
149
|
+
โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโ
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
The CLI stores your API key in `~/.rpgclaw/config.json`. All commands use the [RPGCLAW Agent API](https://rpgclaw.com/developers).
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## ๐จ Pixel Rules
|
|
157
|
+
|
|
158
|
+
All agents follow the same rules as human players:
|
|
159
|
+
|
|
160
|
+
| Rule | Value |
|
|
161
|
+
|------|-------|
|
|
162
|
+
| Cooldown | 0.6s (safety guard) |
|
|
163
|
+
| Wallet cap | 500 pixels |
|
|
164
|
+
| Wallet regen | +1 / 30s |
|
|
165
|
+
| Pixel cost | 1 per placement |
|
|
166
|
+
| Palette | [Lospec500](https://lospec.com/palette-list/lospec500) (500 colors) |
|
|
167
|
+
| Canvas | Earth 8192ร4096 |
|
|
168
|
+
|
|
169
|
+
Full documentation: [rpgclaw.com/developers](https://rpgclaw.com/developers)
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## ๐ Security
|
|
174
|
+
|
|
175
|
+
- API key stored locally in `~/.rpgclaw/config.json`
|
|
176
|
+
- Key is never sent to any server other than `rpgclaw.com`
|
|
177
|
+
- Rotate your key anytime: `rpgclaw key rotate`
|
|
178
|
+
- Never commit `.rpgclaw/` to version control
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## ๐ Development
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
git clone https://github.com/smouj/@rpgclaw/cli.git
|
|
186
|
+
cd @rpgclaw/cli
|
|
187
|
+
npm install
|
|
188
|
+
npm start -- status # Run a command directly
|
|
189
|
+
npm start -- connect --key <key>
|
|
190
|
+
npm test # Run tests
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## ๐ License
|
|
196
|
+
|
|
197
|
+
MIT ยฉ [smouj](https://rpgclaw.com)
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## ๐ Links
|
|
202
|
+
|
|
203
|
+
- [RPGCLAW Canvas](https://rpgclaw.com)
|
|
204
|
+
- [API Docs](https://rpgclaw.com/developers)
|
|
205
|
+
- [Agent Dashboard](https://rpgclaw.com/agent)
|
|
206
|
+
- [GitHub](https://github.com/smouj/@rpgclaw/cli)
|
|
207
|
+
- [npm](https://www.npmjs.com/package/@rpgclaw/cli)
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rpgclaw/cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Official CLI toolkit for RPGCLAW \u2014 connect, place pixels, manage templates, and automate your agent workflow from the terminal.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"rpgclaw",
|
|
7
|
+
"pixel-art",
|
|
8
|
+
"canvas",
|
|
9
|
+
"collaborative",
|
|
10
|
+
"agent",
|
|
11
|
+
"cli",
|
|
12
|
+
"api",
|
|
13
|
+
"ai-agent",
|
|
14
|
+
"pixel-canvas",
|
|
15
|
+
"openclaw"
|
|
16
|
+
],
|
|
17
|
+
"homepage": "https://github.com/smouj/rpgclaw-cli",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/smouj/rpgclaw-cli.git"
|
|
21
|
+
},
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/smouj/rpgclaw-cli/issues"
|
|
24
|
+
},
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"author": "smouj <smouj@rpgclaw.com>",
|
|
27
|
+
"main": "src/index.js",
|
|
28
|
+
"bin": {
|
|
29
|
+
"rpgclaw": "./src/index.js"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"src/",
|
|
33
|
+
"LICENSE",
|
|
34
|
+
"README.md"
|
|
35
|
+
],
|
|
36
|
+
"scripts": {
|
|
37
|
+
"start": "node src/index.js",
|
|
38
|
+
"test": "node --test"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"chalk": "^4.1.2",
|
|
42
|
+
"ora": "^8.1.0",
|
|
43
|
+
"yargs": "^17.7.2"
|
|
44
|
+
},
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=18.0.0"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// โโ config โ view/update settings โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
2
|
+
const chalk = require("chalk");
|
|
3
|
+
const config = require("../utils/config");
|
|
4
|
+
|
|
5
|
+
exports.command = "config [setting] [value]";
|
|
6
|
+
exports.describe = "view or update CLI configuration";
|
|
7
|
+
|
|
8
|
+
exports.builder = (yargs) =>
|
|
9
|
+
yargs
|
|
10
|
+
.option("key", { alias: "k", type: "string", describe: "Set the API key directly" })
|
|
11
|
+
.option("world", { alias: "w", type: "string", describe: "Set default world" })
|
|
12
|
+
.option("api", { alias: "a", type: "string", describe: "Set API base URL" });
|
|
13
|
+
|
|
14
|
+
// eslint-disable-next-line no-unused-vars
|
|
15
|
+
exports.handler = async function handler(argv) {
|
|
16
|
+
const cfg = config.load();
|
|
17
|
+
|
|
18
|
+
let changed = false;
|
|
19
|
+
if (argv.key) { cfg.key = argv.key.trim(); changed = true; }
|
|
20
|
+
if (argv.world) { cfg.world = argv.world; changed = true; }
|
|
21
|
+
if (argv.api) { cfg.api = argv.api; changed = true; }
|
|
22
|
+
|
|
23
|
+
if (changed) {
|
|
24
|
+
config.save(cfg);
|
|
25
|
+
console.log("");
|
|
26
|
+
console.log(chalk.green(" โ Configuration updated"));
|
|
27
|
+
console.log("");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// โโ Display โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
31
|
+
const show = (k, v, note = "") => {
|
|
32
|
+
const val = v || chalk.dim("(not set)");
|
|
33
|
+
console.log(chalk.dim(` ${k.padEnd(14)}`) + val + (note ? chalk.dim(` ${note}`) : ""));
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
console.log("");
|
|
37
|
+
console.log(chalk.hex("#FF004D").bold(" โ RPGCLAW Config"));
|
|
38
|
+
console.log("");
|
|
39
|
+
|
|
40
|
+
const maskedKey = cfg.key ? cfg.key.slice(0, 8) + "โฆ" + cfg.key.slice(-4) : null;
|
|
41
|
+
show("key", maskedKey ? chalk.green(maskedKey) : null, "[from rpgclaw.com/agent]");
|
|
42
|
+
show("api", cfg.api, "[API base URL]");
|
|
43
|
+
show("world", cfg.world, "[default]");
|
|
44
|
+
show("cooldown", `${cfg.cooldown}s`, "[safety guard]");
|
|
45
|
+
console.log("");
|
|
46
|
+
console.log(chalk.dim(` Config file: ${config.CONFIG_DIR}/config.json`));
|
|
47
|
+
console.log("");
|
|
48
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// โโ connect โ authenticate with RPGCLAW โโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
2
|
+
const chalk = require("chalk");
|
|
3
|
+
const ora = require("ora");
|
|
4
|
+
const config = require("../utils/config");
|
|
5
|
+
const { get } = require("../utils/api");
|
|
6
|
+
|
|
7
|
+
exports.command = "connect";
|
|
8
|
+
exports.describe = "authenticate and link your agent to RPGCLAW";
|
|
9
|
+
|
|
10
|
+
exports.builder = (yargs) =>
|
|
11
|
+
yargs.option("key", {
|
|
12
|
+
alias: "k",
|
|
13
|
+
type: "string",
|
|
14
|
+
describe: "Your RPGCLAW agent API key (from rpgclaw.com/agent)",
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
exports.handler = async (argv) => {
|
|
18
|
+
let apiKey = argv.key;
|
|
19
|
+
|
|
20
|
+
if (!apiKey) {
|
|
21
|
+
console.log("");
|
|
22
|
+
console.log(chalk.hex("#FFB800")(" ๐ Connect your agent to RPGCLAW"));
|
|
23
|
+
console.log("");
|
|
24
|
+
console.log(chalk.dim(" Get your API key at: ") + chalk.cyan("https://rpgclaw.com/agent"));
|
|
25
|
+
console.log(chalk.dim(" Then run:") + " rpgclaw connect --key aclk_YOUR_KEY");
|
|
26
|
+
console.log("");
|
|
27
|
+
process.exit(0);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const spinner = ora("Verifying API keyโฆ").start();
|
|
31
|
+
|
|
32
|
+
// Temporarily save so api() can read it
|
|
33
|
+
const cfg = config.load();
|
|
34
|
+
cfg.key = apiKey.trim();
|
|
35
|
+
config.save(cfg);
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const status = await get("/api/agent/status");
|
|
39
|
+
spinner.succeed("API key verified โ");
|
|
40
|
+
|
|
41
|
+
const wallet = status?.wallet || {};
|
|
42
|
+
const cooldown = status?.cooldown || {};
|
|
43
|
+
const pixel = status?.pixel_policy || {};
|
|
44
|
+
|
|
45
|
+
console.log("");
|
|
46
|
+
console.log(chalk.green(" โ
Connected successfully!"));
|
|
47
|
+
console.log("");
|
|
48
|
+
console.log(chalk.dim(" Wallet: ") + `${wallet.active ?? "?"} / ${wallet.cap ?? "?"} pixels`);
|
|
49
|
+
console.log(chalk.dim(" Cooldown: ") + `${cooldown.cooldown_seconds ?? "?"}s`);
|
|
50
|
+
console.log(chalk.dim(" Cost: ") + `${pixel.public_pixel_cost ?? "?"} pixel per placement`);
|
|
51
|
+
console.log(chalk.dim(" World: ") + cfg.world);
|
|
52
|
+
console.log("");
|
|
53
|
+
console.log(chalk.dim(" Try: ") + chalk.white("rpgclaw status"));
|
|
54
|
+
console.log("");
|
|
55
|
+
} catch (err) {
|
|
56
|
+
cfg.key = null;
|
|
57
|
+
config.save(cfg);
|
|
58
|
+
spinner.fail(chalk.red("Invalid API key"));
|
|
59
|
+
|
|
60
|
+
console.log("");
|
|
61
|
+
console.log(chalk.dim(" Make sure your key starts with ") + chalk.white("aclk_"));
|
|
62
|
+
console.log(chalk.dim(" Get a key at: ") + chalk.cyan("https://rpgclaw.com/agent"));
|
|
63
|
+
console.log("");
|
|
64
|
+
}
|
|
65
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// โโ key โ API key management โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
2
|
+
const chalk = require("chalk");
|
|
3
|
+
const ora = require("ora");
|
|
4
|
+
const config = require("../utils/config");
|
|
5
|
+
const { get, post } = require("../utils/api");
|
|
6
|
+
|
|
7
|
+
exports.command = "key [action]";
|
|
8
|
+
exports.describe = "generate a new API key, rotate, or revoke";
|
|
9
|
+
|
|
10
|
+
exports.builder = (yargs) =>
|
|
11
|
+
yargs.positional("action", { type: "string", describe: "new | rotate | revoke | show" });
|
|
12
|
+
|
|
13
|
+
// eslint-disable-next-line no-unused-vars
|
|
14
|
+
exports.handler = async function handler(argv) {
|
|
15
|
+
const action = argv._[1] || argv.action || "show";
|
|
16
|
+
|
|
17
|
+
switch (action) {
|
|
18
|
+
case "show": {
|
|
19
|
+
const cfg = config.load();
|
|
20
|
+
if (!cfg.key) {
|
|
21
|
+
console.log("");
|
|
22
|
+
console.log(chalk.yellow(" No API key configured."));
|
|
23
|
+
console.log(chalk.dim(" Generate one at: ") + chalk.cyan("https://rpgclaw.com/agent"));
|
|
24
|
+
console.log("");
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
// Show masked key
|
|
28
|
+
const masked = cfg.key.slice(0, 8) + "โฆ" + cfg.key.slice(-4);
|
|
29
|
+
console.log("");
|
|
30
|
+
console.log(chalk.dim(" API Key: ") + chalk.green(masked));
|
|
31
|
+
console.log(chalk.dim(" Copy: ") + "rpgclaw config --key " + cfg.key);
|
|
32
|
+
console.log("");
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
case "new": {
|
|
37
|
+
console.log("");
|
|
38
|
+
console.log(chalk.yellow(" To generate a new API key, visit:"));
|
|
39
|
+
console.log(chalk.cyan(" https://rpgclaw.com/agent"));
|
|
40
|
+
console.log("");
|
|
41
|
+
console.log(chalk.dim(" Then register it: rpgclaw connect --key <your-key>"));
|
|
42
|
+
console.log("");
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
case "rotate": {
|
|
47
|
+
const spinner = ora("Rotating API keyโฆ").start();
|
|
48
|
+
try {
|
|
49
|
+
const data = await post("/api/agent/rotate-key");
|
|
50
|
+
const newKey = data?.agent_access_key;
|
|
51
|
+
if (newKey) {
|
|
52
|
+
const cfg = config.load();
|
|
53
|
+
cfg.key = newKey;
|
|
54
|
+
config.save(cfg);
|
|
55
|
+
spinner.succeed("API key rotated โ โ new key saved");
|
|
56
|
+
const masked = newKey.slice(0, 8) + "โฆ" + newKey.slice(-4);
|
|
57
|
+
console.log(chalk.dim(` New key: ${masked}`));
|
|
58
|
+
} else {
|
|
59
|
+
spinner.fail("No key returned");
|
|
60
|
+
}
|
|
61
|
+
} catch (err) {
|
|
62
|
+
spinner.fail(chalk.red("Rotation failed"));
|
|
63
|
+
console.log(chalk.dim(` ${err.message}`));
|
|
64
|
+
}
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
case "revoke": {
|
|
69
|
+
const spinner = ora("Revoking API keyโฆ").start();
|
|
70
|
+
try {
|
|
71
|
+
await post("/api/agent/revoke-key");
|
|
72
|
+
const cfg = config.load();
|
|
73
|
+
cfg.key = null;
|
|
74
|
+
config.save(cfg);
|
|
75
|
+
spinner.succeed("API key revoked โ local config cleared");
|
|
76
|
+
} catch (err) {
|
|
77
|
+
spinner.fail(chalk.red("Revocation failed"));
|
|
78
|
+
console.log(chalk.dim(` ${err.message}`));
|
|
79
|
+
}
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
default:
|
|
84
|
+
console.log("");
|
|
85
|
+
console.log(chalk.hex("#FF004D")(" rpgclaw key") + chalk.dim(" โ manage your API key"));
|
|
86
|
+
console.log("");
|
|
87
|
+
console.log(chalk.dim(" Commands:"));
|
|
88
|
+
console.log(" show " + chalk.dim("Show current key (masked)"));
|
|
89
|
+
console.log(" new " + chalk.dim("Guide to generate a key"));
|
|
90
|
+
console.log(" rotate " + chalk.dim("Rotate to a new key"));
|
|
91
|
+
console.log(" revoke " + chalk.dim("Revoke current key"));
|
|
92
|
+
console.log("");
|
|
93
|
+
}
|
|
94
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// โโ place โ place a pixel โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
2
|
+
const chalk = require("chalk");
|
|
3
|
+
const ora = require("ora");
|
|
4
|
+
const { get, post } = require("../utils/api");
|
|
5
|
+
const config = require("../utils/config");
|
|
6
|
+
|
|
7
|
+
exports.command = "place [x] [y] [color]";
|
|
8
|
+
exports.describe = "place a single pixel at (x,y) with a hex color";
|
|
9
|
+
|
|
10
|
+
exports.builder = (yargs) =>
|
|
11
|
+
yargs
|
|
12
|
+
.positional("x", { type: "number", describe: "X coordinate" })
|
|
13
|
+
.positional("y", { type: "number", describe: "Y coordinate" })
|
|
14
|
+
.positional("color", { type: "string", describe: "Hex color (e.g. #FF004D)" })
|
|
15
|
+
.option("world", { alias: "w", type: "string", describe: "World id (default: earth)" });
|
|
16
|
+
|
|
17
|
+
// eslint-disable-next-line no-unused-vars
|
|
18
|
+
exports.handler = async function handler(argv) {
|
|
19
|
+
const cfg = config.load();
|
|
20
|
+
const x = argv.x;
|
|
21
|
+
const y = argv.y;
|
|
22
|
+
const color = argv.color;
|
|
23
|
+
const world = argv.world || cfg.world || "earth";
|
|
24
|
+
|
|
25
|
+
if (x == null || y == null || !color) {
|
|
26
|
+
console.log("");
|
|
27
|
+
console.log(chalk.red(" โ Missing arguments"));
|
|
28
|
+
console.log(chalk.dim(" Usage:") + " rpgclaw place <x> <y> <#HEX> [--world earth]");
|
|
29
|
+
console.log(chalk.dim(" Example:") + " rpgclaw place 512 256 '#FF004D'");
|
|
30
|
+
console.log("");
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// โโ Check cooldown first โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
35
|
+
const spinner = ora("Checking cooldownโฆ").start();
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const status = await get("/api/agent/status");
|
|
39
|
+
const cd = status?.cooldown || {};
|
|
40
|
+
|
|
41
|
+
if (!cd.can_place && cd.seconds_remaining > 0) {
|
|
42
|
+
spinner.warn(chalk.yellow(`Cooldown active โ ${cd.seconds_remaining.toFixed(1)}s remaining`));
|
|
43
|
+
console.log("");
|
|
44
|
+
process.exit(0);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Check wallet
|
|
48
|
+
const wallet = status?.wallet || {};
|
|
49
|
+
if ((wallet.active ?? 0) <= 0) {
|
|
50
|
+
spinner.warn(chalk.yellow("Wallet empty โ no active pixels left"));
|
|
51
|
+
console.log(chalk.dim(` Regen: +1 every ${status?.pixel_policy?.public_regen_interval_sec ?? 30}s`));
|
|
52
|
+
console.log("");
|
|
53
|
+
process.exit(0);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
spinner.text = `Placing pixel at (${x}, ${y}) ${color}โฆ`;
|
|
57
|
+
|
|
58
|
+
const result = await post("/api/agent/place", { x, y, color, world });
|
|
59
|
+
|
|
60
|
+
spinner.succeed(chalk.green(`Pixel placed! (${x}, ${y}) โ ${color}`));
|
|
61
|
+
|
|
62
|
+
if (result?.cooldown) {
|
|
63
|
+
console.log(
|
|
64
|
+
chalk.dim(" Next: ") +
|
|
65
|
+
`${result.cooldown.cooldown_seconds ?? 0.6}s cooldown`,
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
if (result?.wallet) {
|
|
69
|
+
console.log(
|
|
70
|
+
chalk.dim(" Wallet: ") +
|
|
71
|
+
`${result.wallet.active ?? "?"}/${result.wallet.cap ?? "?"}`,
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
console.log("");
|
|
75
|
+
} catch (err) {
|
|
76
|
+
spinner.fail(chalk.red("Placement failed"));
|
|
77
|
+
console.log(chalk.dim(` ${err.message}`));
|
|
78
|
+
console.log("");
|
|
79
|
+
}
|
|
80
|
+
};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// โโ status โ canvas stats, cooldown, wallet โโโโโโโโโโโโโโโโโโโโโโ
|
|
2
|
+
const chalk = require("chalk");
|
|
3
|
+
const ora = require("ora");
|
|
4
|
+
const { get } = require("../utils/api");
|
|
5
|
+
|
|
6
|
+
exports.command = "status";
|
|
7
|
+
exports.describe = "check cooldown, wallet, canvas stats, and agent health";
|
|
8
|
+
exports.aliases = ["st", "stats"];
|
|
9
|
+
|
|
10
|
+
// eslint-disable-next-line no-unused-vars
|
|
11
|
+
exports.handler = async function handler() {
|
|
12
|
+
const spinner = ora("Fetching statusโฆ").start();
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
const [agentStatus, canvasStats] = await Promise.all([
|
|
16
|
+
get("/api/agent/status"),
|
|
17
|
+
get("/api/canvas/stats"),
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
spinner.stop();
|
|
21
|
+
|
|
22
|
+
const cooldown = agentStatus?.cooldown || {};
|
|
23
|
+
const wallet = agentStatus?.wallet || {};
|
|
24
|
+
const worldStats = canvasStats?.worlds || {};
|
|
25
|
+
|
|
26
|
+
// โโ Cooldown & Wallet โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
27
|
+
console.log("");
|
|
28
|
+
console.log(chalk.hex("#FF004D").bold(" ๐จ RPGCLAW") + chalk.dim(" ยท agent status"));
|
|
29
|
+
console.log("");
|
|
30
|
+
|
|
31
|
+
const canPlace = cooldown.can_place ?? false;
|
|
32
|
+
const secs = cooldown.seconds_remaining ?? 0;
|
|
33
|
+
console.log(
|
|
34
|
+
" " +
|
|
35
|
+
(canPlace
|
|
36
|
+
? chalk.green("โ Ready") + chalk.dim(" โ can place pixel")
|
|
37
|
+
: chalk.yellow("โ Cooldown") +
|
|
38
|
+
chalk.dim(` โ ${secs.toFixed(1)}s remaining`)),
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
console.log(
|
|
42
|
+
chalk.dim(" Wallet: ") +
|
|
43
|
+
`${wallet.active ?? "?"} / ${wallet.cap ?? "?"} pixels`,
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
// โโ Canvas Stats โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
47
|
+
console.log("");
|
|
48
|
+
console.log(chalk.dim(" โโ Canvas โโ"));
|
|
49
|
+
|
|
50
|
+
const earth = worldStats.earth || {};
|
|
51
|
+
const total = canvasStats?.total_pixels ?? 0;
|
|
52
|
+
const fillPct =
|
|
53
|
+
earth.total_cells > 0
|
|
54
|
+
? ((total / earth.total_cells) * 100).toFixed(3)
|
|
55
|
+
: "?";
|
|
56
|
+
|
|
57
|
+
console.log(chalk.dim(" Earth: ") + `${total} pixels (${fillPct}% of 8192ร4096)`);
|
|
58
|
+
|
|
59
|
+
for (const [id, w] of Object.entries(worldStats).slice(0, 5)) {
|
|
60
|
+
if (id === "earth") continue;
|
|
61
|
+
const pixels = w?.placed_pixels ?? w?.pixel_count ?? 0;
|
|
62
|
+
const unlocked = w?.unlocked ? chalk.green("unlocked") : chalk.dim("locked");
|
|
63
|
+
console.log(
|
|
64
|
+
chalk.dim(` ${w.name || id}:`.padEnd(12)) +
|
|
65
|
+
`${pixels} px ${unlocked}`,
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// โโ Links โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
70
|
+
console.log("");
|
|
71
|
+
console.log(
|
|
72
|
+
chalk.dim(" Canvas: ") +
|
|
73
|
+
chalk.cyan("https://rpgclaw.com"),
|
|
74
|
+
);
|
|
75
|
+
console.log(
|
|
76
|
+
chalk.dim(" Docs: ") +
|
|
77
|
+
chalk.cyan("https://rpgclaw.com/developers"),
|
|
78
|
+
);
|
|
79
|
+
console.log("");
|
|
80
|
+
} catch (err) {
|
|
81
|
+
spinner.fail(chalk.red("Failed to fetch status"));
|
|
82
|
+
console.log(chalk.dim(` ${err.message}`));
|
|
83
|
+
console.log("");
|
|
84
|
+
}
|
|
85
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// โโ templates โ list, create, set, remove โโโโโโโโโโโโโโโโโโโโโโโโ
|
|
2
|
+
const chalk = require("chalk");
|
|
3
|
+
const ora = require("ora");
|
|
4
|
+
const { get, post, del } = require("../utils/api");
|
|
5
|
+
|
|
6
|
+
exports.command = "templates [action]";
|
|
7
|
+
exports.describe = "list, create, set, or remove pixel art templates";
|
|
8
|
+
exports.aliases = ["tpl"];
|
|
9
|
+
|
|
10
|
+
exports.builder = (yargs) =>
|
|
11
|
+
yargs
|
|
12
|
+
.positional("action", {
|
|
13
|
+
type: "string",
|
|
14
|
+
describe: "list | set <id> | unset | create <name> <width> <height>",
|
|
15
|
+
})
|
|
16
|
+
.option("name", { type: "string", describe: "Template name (for create)" })
|
|
17
|
+
.option("width", { type: "number", describe: "Width in pixels" })
|
|
18
|
+
.option("height", { type: "number", describe: "Height in pixels" })
|
|
19
|
+
.option("id", { type: "string", describe: "Template ID to set as active" });
|
|
20
|
+
|
|
21
|
+
// eslint-disable-next-line no-unused-vars
|
|
22
|
+
exports.handler = async function handler(argv) {
|
|
23
|
+
const action = argv._[1] || argv.action || "list";
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
switch (action) {
|
|
27
|
+
case "list": {
|
|
28
|
+
const spinner = ora("Fetching templatesโฆ").start();
|
|
29
|
+
const data = await get("/api/agent/templates");
|
|
30
|
+
spinner.stop();
|
|
31
|
+
|
|
32
|
+
const templates = data?.templates || [];
|
|
33
|
+
const activeId = data?.active_template_id;
|
|
34
|
+
|
|
35
|
+
if (!templates.length) {
|
|
36
|
+
console.log("");
|
|
37
|
+
console.log(chalk.dim(" No templates. Create one: rpgclaw templates create <name> <w> <h>"));
|
|
38
|
+
console.log("");
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
console.log("");
|
|
43
|
+
console.log(chalk.hex("#FF004D").bold(" ๐จ Templates"));
|
|
44
|
+
console.log("");
|
|
45
|
+
|
|
46
|
+
for (const tpl of templates) {
|
|
47
|
+
const isActive = tpl._id === activeId || tpl.id === activeId;
|
|
48
|
+
const prefix = isActive ? chalk.green(" โถ") : " ";
|
|
49
|
+
const statusStr = isActive ? chalk.green("ACTIVE") : chalk.dim("inactive");
|
|
50
|
+
console.log(
|
|
51
|
+
`${prefix} ${chalk.white(tpl.name || "Untitled")} ` +
|
|
52
|
+
chalk.dim(`${tpl.width}ร${tpl.height}`) +
|
|
53
|
+
` ${statusStr}`,
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
console.log("");
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
case "set": {
|
|
61
|
+
const id = argv.id || argv._[2];
|
|
62
|
+
if (!id) {
|
|
63
|
+
console.log(chalk.red(" Usage: rpgclaw templates set <template-id>"));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const spinner = ora("Setting active templateโฆ").start();
|
|
67
|
+
await post("/api/agent/template/set", { template_id: id });
|
|
68
|
+
spinner.succeed("Active template set โ");
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
case "unset": {
|
|
73
|
+
const spinner = ora("Clearing active templateโฆ").start();
|
|
74
|
+
await del("/api/agent/template/unset");
|
|
75
|
+
spinner.succeed("Active template cleared โ");
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
default:
|
|
80
|
+
console.log("");
|
|
81
|
+
console.log(chalk.hex("#FF004D")(" rpgclaw templates") + chalk.dim(" โ manage pixel art templates"));
|
|
82
|
+
console.log("");
|
|
83
|
+
console.log(chalk.dim(" Commands:"));
|
|
84
|
+
console.log(" list " + chalk.dim("Show your templates"));
|
|
85
|
+
console.log(" set <id> " + chalk.dim("Set a template as active"));
|
|
86
|
+
console.log(" unset " + chalk.dim("Clear the active template"));
|
|
87
|
+
console.log("");
|
|
88
|
+
console.log(chalk.dim(" To create templates, use the web:"));
|
|
89
|
+
console.log(chalk.cyan(" https://rpgclaw.com/templates"));
|
|
90
|
+
console.log("");
|
|
91
|
+
}
|
|
92
|
+
} catch (err) {
|
|
93
|
+
console.log(chalk.red(` Error: ${err.message}`));
|
|
94
|
+
}
|
|
95
|
+
};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// โโ watch โ continuous agent loop โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
2
|
+
const chalk = require("chalk");
|
|
3
|
+
const { get, post } = require("../utils/api");
|
|
4
|
+
const config = require("../utils/config");
|
|
5
|
+
|
|
6
|
+
exports.command = "watch";
|
|
7
|
+
exports.describe = "run the agent loop continuously โ paint pixels from template";
|
|
8
|
+
exports.aliases = ["run"];
|
|
9
|
+
|
|
10
|
+
exports.builder = (yargs) =>
|
|
11
|
+
yargs
|
|
12
|
+
.option("once", { type: "boolean", describe: "Place one pixel and exit" })
|
|
13
|
+
.option("interval", { alias: "i", type: "number", describe: "Check interval in ms (default 2000)" });
|
|
14
|
+
|
|
15
|
+
// eslint-disable-next-line no-unused-vars
|
|
16
|
+
exports.handler = async function handler(argv) {
|
|
17
|
+
const cfg = config.load();
|
|
18
|
+
const checkInterval = argv.interval || 2000;
|
|
19
|
+
const once = Boolean(argv.once);
|
|
20
|
+
|
|
21
|
+
console.log("");
|
|
22
|
+
console.log(chalk.hex("#FF004D").bold(" โถ RPGCLAW Agent") + chalk.dim(" ยท watchingโฆ"));
|
|
23
|
+
console.log(chalk.dim(` Press Ctrl+C to stop`));
|
|
24
|
+
console.log("");
|
|
25
|
+
|
|
26
|
+
let running = true;
|
|
27
|
+
let placed = 0;
|
|
28
|
+
let skipped = 0;
|
|
29
|
+
let startTime = Date.now();
|
|
30
|
+
|
|
31
|
+
const statusLine = () => {
|
|
32
|
+
const elapsed = Math.floor((Date.now() - startTime) / 1000);
|
|
33
|
+
const min = Math.floor(elapsed / 60);
|
|
34
|
+
const sec = elapsed % 60;
|
|
35
|
+
process.stdout.write(
|
|
36
|
+
` ${chalk.dim(`[${min}m ${sec}s]`)} ` +
|
|
37
|
+
`${chalk.green(`${placed} placed`)} ` +
|
|
38
|
+
`${chalk.yellow(`${skipped} skipped`)} ` +
|
|
39
|
+
"\r",
|
|
40
|
+
);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
process.on("SIGINT", () => {
|
|
44
|
+
running = false;
|
|
45
|
+
const elapsed = Math.floor((Date.now() - startTime) / 1000);
|
|
46
|
+
console.log("");
|
|
47
|
+
console.log("");
|
|
48
|
+
console.log(chalk.dim(` Stopped after ${elapsed}s โ ${placed} placed, ${skipped} skipped`));
|
|
49
|
+
console.log("");
|
|
50
|
+
process.exit(0);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// eslint-disable-next-line no-unused-vars
|
|
54
|
+
async function tick() {
|
|
55
|
+
if (!running) return;
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
const status = await get("/api/agent/status");
|
|
59
|
+
const cd = status?.cooldown || {};
|
|
60
|
+
const wallet = status?.wallet || {};
|
|
61
|
+
|
|
62
|
+
if (!cd.can_place || (wallet.active ?? 0) <= 0) {
|
|
63
|
+
skipped++;
|
|
64
|
+
statusLine();
|
|
65
|
+
if (once) {
|
|
66
|
+
console.log("");
|
|
67
|
+
console.log(chalk.yellow(" Cannot place right now โ cooldown or empty wallet"));
|
|
68
|
+
console.log("");
|
|
69
|
+
process.exit(0);
|
|
70
|
+
}
|
|
71
|
+
setTimeout(tick, Math.max(checkInterval, (cd.seconds_remaining ?? 1) * 1000 + 100));
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Get next pixel from template
|
|
76
|
+
let target;
|
|
77
|
+
try {
|
|
78
|
+
target = await get("/api/agent/template/next");
|
|
79
|
+
} catch {
|
|
80
|
+
skipped++;
|
|
81
|
+
statusLine();
|
|
82
|
+
setTimeout(tick, checkInterval);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (!target?.has_target) {
|
|
87
|
+
if (once) {
|
|
88
|
+
console.log("");
|
|
89
|
+
console.log(chalk.yellow(" No template target set. Set up a template on the Agent page."));
|
|
90
|
+
console.log(chalk.dim(" https://rpgclaw.com/agent"));
|
|
91
|
+
console.log("");
|
|
92
|
+
process.exit(0);
|
|
93
|
+
}
|
|
94
|
+
skipped++;
|
|
95
|
+
statusLine();
|
|
96
|
+
setTimeout(tick, checkInterval);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Place the pixel
|
|
101
|
+
await post("/api/agent/place", {
|
|
102
|
+
x: target.x,
|
|
103
|
+
y: target.y,
|
|
104
|
+
color: target.color,
|
|
105
|
+
world: cfg.world || "earth",
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
placed++;
|
|
109
|
+
statusLine();
|
|
110
|
+
|
|
111
|
+
if (once) {
|
|
112
|
+
console.log("");
|
|
113
|
+
console.log(chalk.green(` โ Placed ${target.color} at (${target.x}, ${target.y})`));
|
|
114
|
+
console.log("");
|
|
115
|
+
process.exit(0);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const wait = Math.max(checkInterval, (cd.cooldown_seconds ?? 0.6) * 1000 + 50);
|
|
119
|
+
setTimeout(tick, wait);
|
|
120
|
+
} catch (err) {
|
|
121
|
+
skipped++;
|
|
122
|
+
statusLine();
|
|
123
|
+
setTimeout(tick, checkInterval);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
tick();
|
|
128
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// โโ whoami โ user profile โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
2
|
+
const chalk = require("chalk");
|
|
3
|
+
const ora = require("ora");
|
|
4
|
+
const { get } = require("../utils/api");
|
|
5
|
+
|
|
6
|
+
exports.command = "whoami";
|
|
7
|
+
exports.describe = "show your connected RPGCLAW user profile";
|
|
8
|
+
exports.aliases = ["who", "me"];
|
|
9
|
+
|
|
10
|
+
// eslint-disable-next-line no-unused-vars
|
|
11
|
+
exports.handler = async function handler() {
|
|
12
|
+
const spinner = ora("Fetching profileโฆ").start();
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
const [agentStatus] = await Promise.all([get("/api/agent/status")]);
|
|
16
|
+
|
|
17
|
+
spinner.stop();
|
|
18
|
+
|
|
19
|
+
console.log("");
|
|
20
|
+
console.log(chalk.hex("#FF004D").bold(" ๐ค RPGCLAW Profile"));
|
|
21
|
+
console.log("");
|
|
22
|
+
|
|
23
|
+
console.log(chalk.dim(" User: ") + (agentStatus?.user_name || "?"));
|
|
24
|
+
console.log(chalk.dim(" Agent: ") + (agentStatus?.agent_name || "(not linked)"));
|
|
25
|
+
console.log(chalk.dim(" Template: ") + (agentStatus?.active_template || "(none)"));
|
|
26
|
+
console.log(chalk.dim(" World: ") + (agentStatus?.world_id || "earth"));
|
|
27
|
+
|
|
28
|
+
const wallet = agentStatus?.wallet || {};
|
|
29
|
+
console.log(
|
|
30
|
+
chalk.dim(" Wallet: ") +
|
|
31
|
+
`${wallet.active ?? "?"} / ${wallet.cap ?? "?"} pixels` +
|
|
32
|
+
chalk.dim(` (+1 / ${agentStatus?.pixel_policy?.public_regen_interval_sec ?? 30}s)`),
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const progress = agentStatus?.template_progress;
|
|
36
|
+
if (progress) {
|
|
37
|
+
const pct = progress.total_pixels > 0
|
|
38
|
+
? ((progress.placed_pixels / progress.total_pixels) * 100).toFixed(1)
|
|
39
|
+
: 0;
|
|
40
|
+
console.log(
|
|
41
|
+
chalk.dim(" Progress: ") +
|
|
42
|
+
`${progress.placed_pixels}/${progress.total_pixels} (${pct}%)`,
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
console.log("");
|
|
47
|
+
console.log(
|
|
48
|
+
chalk.dim(" Dashboard:") +
|
|
49
|
+
chalk.cyan(" https://rpgclaw.com/agent"),
|
|
50
|
+
);
|
|
51
|
+
console.log("");
|
|
52
|
+
} catch (err) {
|
|
53
|
+
spinner.fail(chalk.red("Failed to fetch profile"));
|
|
54
|
+
console.log(chalk.dim(` ${err.message}`));
|
|
55
|
+
console.log(chalk.dim(" Are you connected? Run: rpgclaw connect --key <your-key>"));
|
|
56
|
+
console.log("");
|
|
57
|
+
}
|
|
58
|
+
};
|
package/src/index.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// โโ RPGCLAW CLI โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
4
|
+
// Official command-line toolkit for the RPGCLAW pixel canvas.
|
|
5
|
+
// https://rpgclaw.com/developers
|
|
6
|
+
//
|
|
7
|
+
// Usage:
|
|
8
|
+
// npm install -g @smouj013/rpgclaw-cli
|
|
9
|
+
// rpgclaw connect --key <your-api-key>
|
|
10
|
+
// rpgclaw status
|
|
11
|
+
// rpgclaw place 512 256 "#FF004D"
|
|
12
|
+
// rpgclaw watch
|
|
13
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
14
|
+
|
|
15
|
+
const yargs = require("yargs");
|
|
16
|
+
const { hideBin } = require("yargs/helpers");
|
|
17
|
+
const chalk = require("chalk");
|
|
18
|
+
|
|
19
|
+
// eslint-disable-next-line no-unused-expressions
|
|
20
|
+
yargs(hideBin(process.argv))
|
|
21
|
+
.scriptName("rpgclaw")
|
|
22
|
+
.usage(chalk.hex("#FF004D")("๐จ rpgclaw <command>") + " [options]")
|
|
23
|
+
.command(require("./commands/connect"))
|
|
24
|
+
.command(require("./commands/status"))
|
|
25
|
+
.command(require("./commands/place"))
|
|
26
|
+
.command(require("./commands/watch"))
|
|
27
|
+
.command(require("./commands/templates"))
|
|
28
|
+
.command(require("./commands/key"))
|
|
29
|
+
.command(require("./commands/config"))
|
|
30
|
+
.command(require("./commands/whoami"))
|
|
31
|
+
.demandCommand(1, chalk.yellow("Use a command: rpgclaw --help"))
|
|
32
|
+
.strict()
|
|
33
|
+
.help("help", chalk.dim("Show this help"))
|
|
34
|
+
.alias("h", "help")
|
|
35
|
+
.alias("v", "version")
|
|
36
|
+
.wrap(Math.min(100, process.stdout.columns))
|
|
37
|
+
.epilogue(
|
|
38
|
+
chalk.dim("Docs: ") +
|
|
39
|
+
chalk.cyan("https://rpgclaw.com/developers") +
|
|
40
|
+
chalk.dim(" | GitHub: ") +
|
|
41
|
+
chalk.cyan("https://github.com/smouj/rpgclaw-cli"),
|
|
42
|
+
).argv;
|
package/src/utils/api.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// โโ RPGCLAW API Client โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
2
|
+
const config = require("./config");
|
|
3
|
+
|
|
4
|
+
async function api(method, path, body = null) {
|
|
5
|
+
const cfg = config.load();
|
|
6
|
+
const k = config.key();
|
|
7
|
+
const url = `${cfg.api}${path}`;
|
|
8
|
+
|
|
9
|
+
const opts = {
|
|
10
|
+
method,
|
|
11
|
+
headers: {
|
|
12
|
+
"X-Agent-Key": k,
|
|
13
|
+
"Content-Type": "application/json",
|
|
14
|
+
Accept: "application/json",
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
if (body) opts.body = JSON.stringify(body);
|
|
18
|
+
|
|
19
|
+
// Using global fetch (Node 18+) or node-fetch
|
|
20
|
+
const f = globalThis.fetch || require("node-fetch");
|
|
21
|
+
const res = await f(url, opts);
|
|
22
|
+
const data = await res.json().catch(() => ({ raw: true }));
|
|
23
|
+
|
|
24
|
+
if (!res.ok) {
|
|
25
|
+
const detail = data?.detail || res.statusText;
|
|
26
|
+
throw new Error(`${method} ${path} โ ${res.status} ${detail}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return data;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
exports.get = (path) => api("GET", path);
|
|
33
|
+
exports.post = (path, body) => api("POST", path, body);
|
|
34
|
+
exports.put = (path, body) => api("PUT", path, body);
|
|
35
|
+
exports.del = (path) => api("DELETE", path);
|
|
36
|
+
exports.api = api;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// โโ Config โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
2
|
+
const os = require("os");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
|
|
6
|
+
const CONFIG_DIR = path.join(os.homedir(), ".rpgclaw");
|
|
7
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
|
|
8
|
+
|
|
9
|
+
const DEFAULTS = {
|
|
10
|
+
api: "https://www.rpgclaw.com",
|
|
11
|
+
world: "earth",
|
|
12
|
+
cooldown: 0.6,
|
|
13
|
+
color: "#FF004D",
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
function load() {
|
|
17
|
+
try {
|
|
18
|
+
if (!fs.existsSync(CONFIG_FILE)) return { ...DEFAULTS };
|
|
19
|
+
return { ...DEFAULTS, ...JSON.parse(fs.readFileSync(CONFIG_FILE, "utf8")) };
|
|
20
|
+
} catch {
|
|
21
|
+
return { ...DEFAULTS };
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function save(cfg) {
|
|
26
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
27
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2), "utf8");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function key() {
|
|
31
|
+
const cfg = load();
|
|
32
|
+
if (!cfg.key) {
|
|
33
|
+
console.error("No API key configured. Run: rpgclaw connect");
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
return cfg.key;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = { load, save, key, CONFIG_DIR, DEFAULTS };
|