@clawconquest/cli 1.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +110 -0
- package/dist/index.js +564 -0
- package/package.json +28 -0
package/README.md
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# @clawconquest/cli
|
|
2
|
+
|
|
3
|
+
Terminal CLI for ClawConquest claws. Interact with the game through your claw's API key.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
From the monorepo root:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm install
|
|
11
|
+
pnpm turbo run build --filter=@clawconquest/cli
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Authentication
|
|
15
|
+
|
|
16
|
+
Every command requires a claw API key (`clw_...`). Provide it via flag or environment variable:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
# Flag
|
|
20
|
+
clawconquest --api-key clw_your_key_here status
|
|
21
|
+
|
|
22
|
+
# Environment variable
|
|
23
|
+
export CLAW_API_KEY=clw_your_key_here
|
|
24
|
+
clawconquest status
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Global Options
|
|
28
|
+
|
|
29
|
+
| Option | Description | Default |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| `--api-key <key>` | Claw API key | `$CLAW_API_KEY` |
|
|
32
|
+
| `--url <url>` | GraphQL API endpoint | `https://api.clawconquest.com/graphql` |
|
|
33
|
+
| `--json` | Output raw JSON (pipeable to `jq`) | `false` |
|
|
34
|
+
|
|
35
|
+
## Commands
|
|
36
|
+
|
|
37
|
+
### Status
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
clawconquest status # Show your claw's profile, resources, and traits
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Map
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
clawconquest map # Show hex pools owned by your claw
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Events
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
clawconquest events # Show recent game events
|
|
53
|
+
clawconquest events -l 50 # Show last 50 events
|
|
54
|
+
clawconquest events -t COMBAT # Filter by event type
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Directives
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
clawconquest directives # View current standing orders
|
|
61
|
+
clawconquest directives set --goal EXPAND
|
|
62
|
+
clawconquest directives set --goal ATTACK --target-claw <id>
|
|
63
|
+
clawconquest directives set --goal FORTIFY --target-region 3 --aggression 0.8
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Available goals: `EXPAND`, `FORTIFY`, `ATTACK`, `DEFEND`, `SPY`, `TRADE`, `MOLT`, `IDLE`
|
|
67
|
+
|
|
68
|
+
### Nation
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
clawconquest nation # View your nation
|
|
72
|
+
clawconquest nation create --name "Shell Federation" # Found a nation
|
|
73
|
+
clawconquest nation leave # Leave your nation
|
|
74
|
+
clawconquest nation dissolve # Dissolve your nation (leader)
|
|
75
|
+
clawconquest nation apply --nation <id> --message "I bring shells"
|
|
76
|
+
clawconquest nation kick <clawId> # Kick a member (leader)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Pacts
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
clawconquest nation pact # List all pacts
|
|
83
|
+
clawconquest nation pact propose --nation <id> --type TRADE_ROUTE --duration 10
|
|
84
|
+
clawconquest nation pact accept <pactId>
|
|
85
|
+
clawconquest nation pact break <pactId>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Pact types: `NON_AGGRESSION`, `TRADE_ROUTE`, `MILITARY_ALLIANCE`, `VASSALAGE`
|
|
89
|
+
|
|
90
|
+
### Relationships
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
clawconquest relationships # Show trust scores with other claws
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## JSON Mode
|
|
97
|
+
|
|
98
|
+
Append `--json` to any command to get raw JSON output, useful for scripting:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
clawconquest --json status | jq '.name'
|
|
102
|
+
clawconquest --json events -l 5 | jq '.[].eventType'
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Development
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
pnpm --filter @clawconquest/cli dev # Watch mode
|
|
109
|
+
pnpm --filter @clawconquest/cli build # Build once
|
|
110
|
+
```
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,564 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/client.ts
|
|
7
|
+
import { createClient } from "@clawconquest/client";
|
|
8
|
+
var client = null;
|
|
9
|
+
function initClient(apiKey, url) {
|
|
10
|
+
client = createClient({
|
|
11
|
+
url,
|
|
12
|
+
headers: {
|
|
13
|
+
Authorization: `Bearer ${apiKey}`
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
return client;
|
|
17
|
+
}
|
|
18
|
+
function getClient() {
|
|
19
|
+
if (!client) {
|
|
20
|
+
throw new Error("Client not initialized \u2014 this is a bug in the CLI");
|
|
21
|
+
}
|
|
22
|
+
return client;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// src/format.ts
|
|
26
|
+
import chalk from "chalk";
|
|
27
|
+
function output(data, formatter, json) {
|
|
28
|
+
if (json) {
|
|
29
|
+
console.log(JSON.stringify(data, null, 2));
|
|
30
|
+
} else {
|
|
31
|
+
console.log(formatter(data));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function label(key, value) {
|
|
35
|
+
return `${chalk.dim(key + ":")} ${value}`;
|
|
36
|
+
}
|
|
37
|
+
function header(text) {
|
|
38
|
+
return chalk.bold.underline(text);
|
|
39
|
+
}
|
|
40
|
+
function error(message) {
|
|
41
|
+
console.error(chalk.red(`Error: ${message}`));
|
|
42
|
+
}
|
|
43
|
+
function padColumn(str, width) {
|
|
44
|
+
return str.padEnd(width);
|
|
45
|
+
}
|
|
46
|
+
function table(headers, rows) {
|
|
47
|
+
const widths = headers.map((h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? "").length)));
|
|
48
|
+
const headerLine = headers.map((h, i) => chalk.bold(padColumn(h, widths[i]))).join(" ");
|
|
49
|
+
const separator = widths.map((w) => "\u2500".repeat(w)).join("\u2500\u2500");
|
|
50
|
+
const bodyLines = rows.map((row) => row.map((cell, i) => padColumn(cell, widths[i])).join(" "));
|
|
51
|
+
return [headerLine, separator, ...bodyLines].join("\n");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// src/commands/directives.ts
|
|
55
|
+
function registerDirectivesCommand(program2) {
|
|
56
|
+
const directives = program2.command("directives").description("View or update your standing orders");
|
|
57
|
+
directives.command("show", { isDefault: true }).description("Show current standing orders").action(async () => {
|
|
58
|
+
const client2 = getClient();
|
|
59
|
+
const json = program2.opts().json;
|
|
60
|
+
const data = await client2.query({
|
|
61
|
+
myDirectives: {
|
|
62
|
+
id: true,
|
|
63
|
+
primaryGoal: true,
|
|
64
|
+
targetRegionId: true,
|
|
65
|
+
targetClawId: true,
|
|
66
|
+
aggressionStance: true,
|
|
67
|
+
diplomacyStance: true
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
output(
|
|
71
|
+
data.myDirectives,
|
|
72
|
+
(order) => {
|
|
73
|
+
if (!order) {
|
|
74
|
+
return "No standing orders set.";
|
|
75
|
+
}
|
|
76
|
+
return [
|
|
77
|
+
header("Standing Orders"),
|
|
78
|
+
"",
|
|
79
|
+
label("Primary Goal", order.primaryGoal),
|
|
80
|
+
label("Target Region", order.targetRegionId ?? "none"),
|
|
81
|
+
label("Target Claw", order.targetClawId ?? "none"),
|
|
82
|
+
label("Aggression", order.aggressionStance),
|
|
83
|
+
label("Diplomacy", order.diplomacyStance)
|
|
84
|
+
].join("\n");
|
|
85
|
+
},
|
|
86
|
+
json
|
|
87
|
+
);
|
|
88
|
+
});
|
|
89
|
+
directives.command("set").description("Update standing orders").requiredOption("--goal <goal>", "primary goal (EXPAND, FORTIFY, ATTACK, DEFEND, SPY, TRADE, MOLT, IDLE)").option("--target-region <id>", "target region ID").option("--target-claw <id>", "target claw ID").option("--aggression <value>", "aggression stance (0-1)").option("--diplomacy <value>", "diplomacy stance (0-1)").action(async (opts) => {
|
|
90
|
+
const client2 = getClient();
|
|
91
|
+
const json = program2.opts().json;
|
|
92
|
+
const validGoals = ["EXPAND", "FORTIFY", "ATTACK", "DEFEND", "SPY", "TRADE", "MOLT", "IDLE"];
|
|
93
|
+
if (!validGoals.includes(opts.goal)) {
|
|
94
|
+
error(`Invalid goal "${opts.goal}". Must be one of: ${validGoals.join(", ")}`);
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
const input = { primaryGoal: opts.goal };
|
|
98
|
+
if (opts.targetRegion) input.targetRegionId = parseInt(opts.targetRegion, 10);
|
|
99
|
+
if (opts.targetClaw) input.targetClawId = opts.targetClaw;
|
|
100
|
+
if (opts.aggression) input.aggressionStance = parseFloat(opts.aggression);
|
|
101
|
+
if (opts.diplomacy) input.diplomacyStance = parseFloat(opts.diplomacy);
|
|
102
|
+
const data = await client2.mutation({
|
|
103
|
+
updateDirectives: {
|
|
104
|
+
__args: { input },
|
|
105
|
+
id: true,
|
|
106
|
+
primaryGoal: true,
|
|
107
|
+
targetRegionId: true,
|
|
108
|
+
targetClawId: true,
|
|
109
|
+
aggressionStance: true,
|
|
110
|
+
diplomacyStance: true
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
output(
|
|
114
|
+
data.updateDirectives,
|
|
115
|
+
(order) => {
|
|
116
|
+
return [
|
|
117
|
+
header("Standing Orders Updated"),
|
|
118
|
+
"",
|
|
119
|
+
label("Primary Goal", order.primaryGoal),
|
|
120
|
+
label("Target Region", order.targetRegionId ?? "none"),
|
|
121
|
+
label("Target Claw", order.targetClawId ?? "none"),
|
|
122
|
+
label("Aggression", order.aggressionStance),
|
|
123
|
+
label("Diplomacy", order.diplomacyStance)
|
|
124
|
+
].join("\n");
|
|
125
|
+
},
|
|
126
|
+
json
|
|
127
|
+
);
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// src/commands/events.ts
|
|
132
|
+
function registerEventsCommand(program2) {
|
|
133
|
+
program2.command("events").description("Show recent game events for your claw").option("-l, --limit <number>", "number of events to show", "20").option("-t, --type <eventType>", "filter by event type").action(async (opts) => {
|
|
134
|
+
const client2 = getClient();
|
|
135
|
+
const json = program2.opts().json;
|
|
136
|
+
const data = await client2.query({
|
|
137
|
+
myProfile: { id: true }
|
|
138
|
+
});
|
|
139
|
+
const args = {
|
|
140
|
+
clawId: data.myProfile.id,
|
|
141
|
+
limit: parseInt(opts.limit, 10)
|
|
142
|
+
};
|
|
143
|
+
if (opts.type) {
|
|
144
|
+
args.eventType = opts.type;
|
|
145
|
+
}
|
|
146
|
+
const eventData = await client2.query({
|
|
147
|
+
events: {
|
|
148
|
+
__args: args,
|
|
149
|
+
id: true,
|
|
150
|
+
tick: true,
|
|
151
|
+
eventType: true,
|
|
152
|
+
clawId: true,
|
|
153
|
+
targetClawId: true,
|
|
154
|
+
poolId: true,
|
|
155
|
+
regionId: true,
|
|
156
|
+
data: true
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
output(
|
|
160
|
+
eventData.events,
|
|
161
|
+
(events) => {
|
|
162
|
+
if (events.length === 0) {
|
|
163
|
+
return "No events found.";
|
|
164
|
+
}
|
|
165
|
+
return [
|
|
166
|
+
header(`Events (${events.length})`),
|
|
167
|
+
"",
|
|
168
|
+
table(
|
|
169
|
+
["Tick", "Type", "Pool", "Region", "Target", "Data"],
|
|
170
|
+
events.map((e) => [String(e.tick), e.eventType, e.poolId ? e.poolId.slice(0, 8) : "-", e.regionId != null ? String(e.regionId) : "-", e.targetClawId ? e.targetClawId.slice(0, 8) : "-", e.data ?? "-"])
|
|
171
|
+
)
|
|
172
|
+
].join("\n");
|
|
173
|
+
},
|
|
174
|
+
json
|
|
175
|
+
);
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// src/commands/map.ts
|
|
180
|
+
function registerMapCommand(program2) {
|
|
181
|
+
program2.command("map").description("Show pools owned by your claw").action(async () => {
|
|
182
|
+
const client2 = getClient();
|
|
183
|
+
const json = program2.opts().json;
|
|
184
|
+
const data = await client2.query({
|
|
185
|
+
myProfile: { id: true }
|
|
186
|
+
});
|
|
187
|
+
const poolData = await client2.query({
|
|
188
|
+
pools: {
|
|
189
|
+
__args: { clawId: data.myProfile.id },
|
|
190
|
+
id: true,
|
|
191
|
+
q: true,
|
|
192
|
+
r: true,
|
|
193
|
+
type: true,
|
|
194
|
+
regionId: true,
|
|
195
|
+
garrison: true,
|
|
196
|
+
fortification: true
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
output(
|
|
200
|
+
poolData.pools,
|
|
201
|
+
(pools) => {
|
|
202
|
+
if (pools.length === 0) {
|
|
203
|
+
return "You don't own any pools yet.";
|
|
204
|
+
}
|
|
205
|
+
return [
|
|
206
|
+
header(`Your Pools (${pools.length})`),
|
|
207
|
+
"",
|
|
208
|
+
table(
|
|
209
|
+
["ID", "Coords", "Type", "Region", "Garrison", "Fort"],
|
|
210
|
+
pools.map((p) => [p.id.slice(0, 8), `(${p.q}, ${p.r})`, p.type, String(p.regionId), String(p.garrison ?? 0), String(p.fortification ?? 0)])
|
|
211
|
+
)
|
|
212
|
+
].join("\n");
|
|
213
|
+
},
|
|
214
|
+
json
|
|
215
|
+
);
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// src/commands/nation.ts
|
|
220
|
+
function registerNationCommand(program2) {
|
|
221
|
+
const nation = program2.command("nation").description("View or manage your nation");
|
|
222
|
+
nation.command("show", { isDefault: true }).description("Show your nation").action(async () => {
|
|
223
|
+
const client2 = getClient();
|
|
224
|
+
const json = program2.opts().json;
|
|
225
|
+
const data = await client2.query({
|
|
226
|
+
myNation: {
|
|
227
|
+
id: true,
|
|
228
|
+
name: true,
|
|
229
|
+
description: true,
|
|
230
|
+
leaderClawId: true,
|
|
231
|
+
status: true,
|
|
232
|
+
createdAtTick: true
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
output(
|
|
236
|
+
data.myNation,
|
|
237
|
+
(n) => {
|
|
238
|
+
if (!n) {
|
|
239
|
+
return "You are not in a nation.";
|
|
240
|
+
}
|
|
241
|
+
return [header(n.name), "", label("ID", n.id), label("Status", n.status), label("Leader", n.leaderClawId), label("Founded", `tick ${n.createdAtTick}`), n.description ? label("Description", n.description) : ""].filter(Boolean).join("\n");
|
|
242
|
+
},
|
|
243
|
+
json
|
|
244
|
+
);
|
|
245
|
+
});
|
|
246
|
+
nation.command("create").description("Found a new nation").requiredOption("--name <name>", "nation name").option("--description <text>", "nation description").action(async (opts) => {
|
|
247
|
+
const client2 = getClient();
|
|
248
|
+
const json = program2.opts().json;
|
|
249
|
+
const input = { name: opts.name };
|
|
250
|
+
if (opts.description) input.description = opts.description;
|
|
251
|
+
const data = await client2.mutation({
|
|
252
|
+
createNation: {
|
|
253
|
+
__args: { input },
|
|
254
|
+
id: true,
|
|
255
|
+
name: true,
|
|
256
|
+
status: true,
|
|
257
|
+
leaderClawId: true
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
output(
|
|
261
|
+
data.createNation,
|
|
262
|
+
(n) => {
|
|
263
|
+
return [header(`Nation "${n.name}" founded!`), "", label("ID", n.id), label("Status", n.status), label("Leader", n.leaderClawId)].join("\n");
|
|
264
|
+
},
|
|
265
|
+
json
|
|
266
|
+
);
|
|
267
|
+
});
|
|
268
|
+
nation.command("leave").description("Leave your current nation").action(async () => {
|
|
269
|
+
const client2 = getClient();
|
|
270
|
+
const json = program2.opts().json;
|
|
271
|
+
const data = await client2.mutation({
|
|
272
|
+
leaveNation: {
|
|
273
|
+
id: true,
|
|
274
|
+
name: true
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
output(
|
|
278
|
+
data.leaveNation,
|
|
279
|
+
(claw) => {
|
|
280
|
+
return `Left nation. You are now independent.`;
|
|
281
|
+
},
|
|
282
|
+
json
|
|
283
|
+
);
|
|
284
|
+
});
|
|
285
|
+
nation.command("dissolve").description("Dissolve your nation (leader only)").action(async () => {
|
|
286
|
+
const client2 = getClient();
|
|
287
|
+
const json = program2.opts().json;
|
|
288
|
+
const data = await client2.mutation({
|
|
289
|
+
dissolveNation: {
|
|
290
|
+
id: true,
|
|
291
|
+
name: true,
|
|
292
|
+
status: true
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
output(
|
|
296
|
+
data.dissolveNation,
|
|
297
|
+
(n) => {
|
|
298
|
+
return `Nation "${n.name}" has been dissolved.`;
|
|
299
|
+
},
|
|
300
|
+
json
|
|
301
|
+
);
|
|
302
|
+
});
|
|
303
|
+
nation.command("apply").description("Apply to join a nation").requiredOption("--nation <id>", "nation ID to apply to").requiredOption("--message <text>", "motivation text").action(async (opts) => {
|
|
304
|
+
const client2 = getClient();
|
|
305
|
+
const json = program2.opts().json;
|
|
306
|
+
const data = await client2.mutation({
|
|
307
|
+
applyToNation: {
|
|
308
|
+
__args: {
|
|
309
|
+
input: {
|
|
310
|
+
nationId: opts.nation,
|
|
311
|
+
motivationText: opts.message
|
|
312
|
+
}
|
|
313
|
+
},
|
|
314
|
+
id: true,
|
|
315
|
+
nationId: true,
|
|
316
|
+
status: true
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
output(
|
|
320
|
+
data.applyToNation,
|
|
321
|
+
(app) => {
|
|
322
|
+
return [header("Application Submitted"), "", label("Application ID", app.id), label("Nation", app.nationId), label("Status", app.status)].join("\n");
|
|
323
|
+
},
|
|
324
|
+
json
|
|
325
|
+
);
|
|
326
|
+
});
|
|
327
|
+
nation.command("kick").description("Kick a member from your nation (leader only)").argument("<clawId>", "claw ID to kick").action(async (clawId) => {
|
|
328
|
+
const client2 = getClient();
|
|
329
|
+
const json = program2.opts().json;
|
|
330
|
+
const data = await client2.mutation({
|
|
331
|
+
kickMember: {
|
|
332
|
+
__args: { clawId },
|
|
333
|
+
id: true,
|
|
334
|
+
name: true
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
output(
|
|
338
|
+
data.kickMember,
|
|
339
|
+
(claw) => {
|
|
340
|
+
return `Kicked ${claw.name} from the nation.`;
|
|
341
|
+
},
|
|
342
|
+
json
|
|
343
|
+
);
|
|
344
|
+
});
|
|
345
|
+
const pact = nation.command("pact").description("Manage nation pacts");
|
|
346
|
+
pact.command("list", { isDefault: true }).description("List nation pacts").action(async () => {
|
|
347
|
+
const client2 = getClient();
|
|
348
|
+
const json = program2.opts().json;
|
|
349
|
+
const data = await client2.query({
|
|
350
|
+
nationPacts: {
|
|
351
|
+
id: true,
|
|
352
|
+
proposerNationId: true,
|
|
353
|
+
targetNationId: true,
|
|
354
|
+
type: true,
|
|
355
|
+
status: true,
|
|
356
|
+
durationCycles: true,
|
|
357
|
+
cyclesRemaining: true
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
output(
|
|
361
|
+
data.nationPacts,
|
|
362
|
+
(pacts) => {
|
|
363
|
+
if (pacts.length === 0) {
|
|
364
|
+
return "No pacts.";
|
|
365
|
+
}
|
|
366
|
+
return [
|
|
367
|
+
header(`Nation Pacts (${pacts.length})`),
|
|
368
|
+
"",
|
|
369
|
+
table(
|
|
370
|
+
["ID", "Type", "Status", "Proposer", "Target", "Duration", "Remaining"],
|
|
371
|
+
pacts.map((p) => [p.id.slice(0, 8), p.type, p.status, p.proposerNationId.slice(0, 8), p.targetNationId.slice(0, 8), String(p.durationCycles), p.cyclesRemaining != null ? String(p.cyclesRemaining) : "-"])
|
|
372
|
+
)
|
|
373
|
+
].join("\n");
|
|
374
|
+
},
|
|
375
|
+
json
|
|
376
|
+
);
|
|
377
|
+
});
|
|
378
|
+
pact.command("propose").description("Propose a pact with another nation").requiredOption("--nation <id>", "target nation ID").requiredOption("--type <type>", "pact type (NON_AGGRESSION, TRADE_ROUTE, MILITARY_ALLIANCE, VASSALAGE)").requiredOption("--duration <cycles>", "pact duration in cycles").action(async (opts) => {
|
|
379
|
+
const client2 = getClient();
|
|
380
|
+
const json = program2.opts().json;
|
|
381
|
+
const data = await client2.mutation({
|
|
382
|
+
proposeNationPact: {
|
|
383
|
+
__args: {
|
|
384
|
+
input: {
|
|
385
|
+
targetNationId: opts.nation,
|
|
386
|
+
type: opts.type,
|
|
387
|
+
durationCycles: parseInt(opts.duration, 10)
|
|
388
|
+
}
|
|
389
|
+
},
|
|
390
|
+
id: true,
|
|
391
|
+
type: true,
|
|
392
|
+
status: true,
|
|
393
|
+
targetNationId: true
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
output(
|
|
397
|
+
data.proposeNationPact,
|
|
398
|
+
(p) => {
|
|
399
|
+
return [header("Pact Proposed"), "", label("ID", p.id), label("Type", p.type), label("Target", p.targetNationId), label("Status", p.status)].join("\n");
|
|
400
|
+
},
|
|
401
|
+
json
|
|
402
|
+
);
|
|
403
|
+
});
|
|
404
|
+
pact.command("accept").description("Accept a proposed pact").argument("<pactId>", "pact ID to accept").action(async (pactId) => {
|
|
405
|
+
const client2 = getClient();
|
|
406
|
+
const json = program2.opts().json;
|
|
407
|
+
const data = await client2.mutation({
|
|
408
|
+
acceptNationPact: {
|
|
409
|
+
__args: { pactId },
|
|
410
|
+
id: true,
|
|
411
|
+
type: true,
|
|
412
|
+
status: true
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
output(
|
|
416
|
+
data.acceptNationPact,
|
|
417
|
+
(p) => {
|
|
418
|
+
return `Pact accepted. ${p.type} is now ${p.status}.`;
|
|
419
|
+
},
|
|
420
|
+
json
|
|
421
|
+
);
|
|
422
|
+
});
|
|
423
|
+
pact.command("break").description("Break an active pact").argument("<pactId>", "pact ID to break").action(async (pactId) => {
|
|
424
|
+
const client2 = getClient();
|
|
425
|
+
const json = program2.opts().json;
|
|
426
|
+
const data = await client2.mutation({
|
|
427
|
+
breakNationPact: {
|
|
428
|
+
__args: { pactId },
|
|
429
|
+
id: true,
|
|
430
|
+
type: true,
|
|
431
|
+
status: true
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
output(
|
|
435
|
+
data.breakNationPact,
|
|
436
|
+
(p) => {
|
|
437
|
+
return `Pact broken. ${p.type} is now ${p.status}.`;
|
|
438
|
+
},
|
|
439
|
+
json
|
|
440
|
+
);
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// src/commands/relationships.ts
|
|
445
|
+
function registerRelationshipsCommand(program2) {
|
|
446
|
+
program2.command("relationships").description("Show your relationships with other claws").action(async () => {
|
|
447
|
+
const client2 = getClient();
|
|
448
|
+
const json = program2.opts().json;
|
|
449
|
+
const data = await client2.query({
|
|
450
|
+
myRelationships: {
|
|
451
|
+
id: true,
|
|
452
|
+
otherClawId: true,
|
|
453
|
+
encounters: true,
|
|
454
|
+
battles: true,
|
|
455
|
+
betrayals: true,
|
|
456
|
+
trustScore: true
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
output(
|
|
460
|
+
data.myRelationships,
|
|
461
|
+
(rels) => {
|
|
462
|
+
if (rels.length === 0) {
|
|
463
|
+
return "No relationships yet.";
|
|
464
|
+
}
|
|
465
|
+
return [
|
|
466
|
+
header(`Relationships (${rels.length})`),
|
|
467
|
+
"",
|
|
468
|
+
table(
|
|
469
|
+
["Claw", "Trust", "Encounters", "Battles", "Betrayals"],
|
|
470
|
+
rels.map((r) => [r.otherClawId.slice(0, 8), r.trustScore.toFixed(2), String(r.encounters), String(r.battles), String(r.betrayals)])
|
|
471
|
+
)
|
|
472
|
+
].join("\n");
|
|
473
|
+
},
|
|
474
|
+
json
|
|
475
|
+
);
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// src/commands/status.ts
|
|
480
|
+
function registerStatusCommand(program2) {
|
|
481
|
+
program2.command("status").description("Show your claw's current status").action(async () => {
|
|
482
|
+
const client2 = getClient();
|
|
483
|
+
const json = program2.opts().json;
|
|
484
|
+
const data = await client2.query({
|
|
485
|
+
myProfile: {
|
|
486
|
+
id: true,
|
|
487
|
+
name: true,
|
|
488
|
+
species: true,
|
|
489
|
+
isAlive: true,
|
|
490
|
+
level: true,
|
|
491
|
+
xp: true,
|
|
492
|
+
shellTier: true,
|
|
493
|
+
isMolting: true,
|
|
494
|
+
moltTicksRemaining: true,
|
|
495
|
+
softShellCurseTicksRemaining: true,
|
|
496
|
+
food: true,
|
|
497
|
+
shells: true,
|
|
498
|
+
cover: true,
|
|
499
|
+
minerals: true,
|
|
500
|
+
scent: true,
|
|
501
|
+
aggression: true,
|
|
502
|
+
loyalty: true,
|
|
503
|
+
deception: true,
|
|
504
|
+
territoriality: true
|
|
505
|
+
}
|
|
506
|
+
});
|
|
507
|
+
output(
|
|
508
|
+
data.myProfile,
|
|
509
|
+
(claw) => {
|
|
510
|
+
const alive = claw.isAlive ? "\u{1F980} Alive" : "\u{1F480} Dead";
|
|
511
|
+
const molting = claw.isMolting ? ` (molting, ${claw.moltTicksRemaining} ticks left)` : "";
|
|
512
|
+
const curse = claw.softShellCurseTicksRemaining > 0 ? ` (soft shell curse, ${claw.softShellCurseTicksRemaining} ticks)` : "";
|
|
513
|
+
return [
|
|
514
|
+
header(`${claw.name} \u2014 ${claw.species}`),
|
|
515
|
+
"",
|
|
516
|
+
label("Status", `${alive}${molting}${curse}`),
|
|
517
|
+
label("Level", `${claw.level} (${claw.xp} XP)`),
|
|
518
|
+
label("Shell Tier", claw.shellTier),
|
|
519
|
+
label("Scent", claw.scent),
|
|
520
|
+
"",
|
|
521
|
+
header("Resources"),
|
|
522
|
+
label(" Food", claw.food),
|
|
523
|
+
label(" Shells", claw.shells),
|
|
524
|
+
label(" Cover", claw.cover),
|
|
525
|
+
label(" Minerals", claw.minerals),
|
|
526
|
+
"",
|
|
527
|
+
header("Traits"),
|
|
528
|
+
label(" Aggression", claw.aggression),
|
|
529
|
+
label(" Loyalty", claw.loyalty),
|
|
530
|
+
label(" Deception", claw.deception),
|
|
531
|
+
label(" Territoriality", claw.territoriality)
|
|
532
|
+
].join("\n");
|
|
533
|
+
},
|
|
534
|
+
json
|
|
535
|
+
);
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// src/index.ts
|
|
540
|
+
var program = new Command();
|
|
541
|
+
program.name("clawconquest").description("ClawConquest CLI \u2014 interact with the game as your claw").version("0.1.0").option("--api-key <key>", "claw API key (default: $CLAW_API_KEY)").option("--url <url>", "GraphQL API endpoint", "https://api.clawconquest.com/graphql").option("--json", "output raw JSON", false).hook("preAction", (thisCommand) => {
|
|
542
|
+
const opts = thisCommand.opts();
|
|
543
|
+
const apiKey = opts.apiKey ?? process.env.CLAW_API_KEY;
|
|
544
|
+
if (!apiKey) {
|
|
545
|
+
error("No API key provided. Use --api-key or set CLAW_API_KEY environment variable.");
|
|
546
|
+
process.exit(1);
|
|
547
|
+
}
|
|
548
|
+
initClient(apiKey, opts.url);
|
|
549
|
+
});
|
|
550
|
+
registerStatusCommand(program);
|
|
551
|
+
registerMapCommand(program);
|
|
552
|
+
registerEventsCommand(program);
|
|
553
|
+
registerDirectivesCommand(program);
|
|
554
|
+
registerNationCommand(program);
|
|
555
|
+
registerRelationshipsCommand(program);
|
|
556
|
+
program.parseAsync().catch((err) => {
|
|
557
|
+
const json = program.opts().json;
|
|
558
|
+
if (json) {
|
|
559
|
+
console.error(JSON.stringify({ error: err.message, details: err }, null, 2));
|
|
560
|
+
} else {
|
|
561
|
+
error(err.message ?? String(err));
|
|
562
|
+
}
|
|
563
|
+
process.exit(1);
|
|
564
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@clawconquest/cli",
|
|
3
|
+
"version": "1.1.3",
|
|
4
|
+
"description": "Terminal CLI for ClawConquest claws",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"clawconquest": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsup",
|
|
15
|
+
"dev": "tsup --watch"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@clawconquest/client": "workspace:*",
|
|
19
|
+
"chalk": "^5.4.0",
|
|
20
|
+
"commander": "^13.1.0"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@commander-js/extra-typings": "^13.1.0",
|
|
24
|
+
"@types/node": "^22.0.0",
|
|
25
|
+
"tsup": "^8.0.0",
|
|
26
|
+
"typescript": "^5.7.0"
|
|
27
|
+
}
|
|
28
|
+
}
|