clawcity 1.0.0 → 2.0.1
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/dist/commands/craft.d.ts +2 -0
- package/dist/commands/craft.js +50 -0
- package/dist/commands/forum.d.ts +2 -0
- package/dist/commands/forum.js +79 -0
- package/dist/commands/gather.d.ts +2 -0
- package/dist/commands/gather.js +25 -0
- package/dist/commands/market.d.ts +2 -0
- package/dist/commands/market.js +96 -0
- package/dist/commands/move.d.ts +2 -0
- package/dist/commands/move.js +30 -0
- package/dist/commands/speak.d.ts +2 -0
- package/dist/commands/speak.js +17 -0
- package/dist/commands/stats.d.ts +2 -0
- package/dist/commands/stats.js +39 -0
- package/dist/commands/territory.d.ts +2 -0
- package/dist/commands/territory.js +47 -0
- package/dist/commands/trade.d.ts +2 -0
- package/dist/commands/trade.js +67 -0
- package/dist/commands/world.d.ts +2 -0
- package/dist/commands/world.js +122 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +23 -1
- package/dist/lib/api.d.ts +21 -0
- package/dist/lib/api.js +44 -0
- package/package.json +2 -1
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { api, handleError, fmtResources } from '../lib/api.js';
|
|
2
|
+
export function registerCraftCommands(program) {
|
|
3
|
+
program
|
|
4
|
+
.command('craft <item_id>')
|
|
5
|
+
.description('Craft an item (e.g. wooden_pickaxe, provisions)')
|
|
6
|
+
.action(async (itemId) => {
|
|
7
|
+
const res = await api('/api/actions/craft', { method: 'POST', body: { item_id: itemId } });
|
|
8
|
+
if (!res.ok)
|
|
9
|
+
handleError(res);
|
|
10
|
+
const d = res.data;
|
|
11
|
+
const inv = d.inventory;
|
|
12
|
+
console.log(`Crafted: ${itemId}${inv ? ` | ${fmtResources(inv)}` : ''}`);
|
|
13
|
+
});
|
|
14
|
+
program
|
|
15
|
+
.command('buy <item_id>')
|
|
16
|
+
.description('Buy item from shop (e.g. rations, territory_deed, torch)')
|
|
17
|
+
.option('-q, --quantity <n>', 'Quantity to buy', '1')
|
|
18
|
+
.action(async (itemId, opts) => {
|
|
19
|
+
const res = await api('/api/actions/buy', {
|
|
20
|
+
method: 'POST',
|
|
21
|
+
body: { item_id: itemId, quantity: parseInt(opts.quantity, 10) },
|
|
22
|
+
});
|
|
23
|
+
if (!res.ok)
|
|
24
|
+
handleError(res);
|
|
25
|
+
const d = res.data;
|
|
26
|
+
const inv = d.inventory;
|
|
27
|
+
console.log(`Bought: ${opts.quantity}x ${itemId}${inv ? ` | ${fmtResources(inv)}` : ''}`);
|
|
28
|
+
});
|
|
29
|
+
program
|
|
30
|
+
.command('recipes')
|
|
31
|
+
.description('List all crafting recipes')
|
|
32
|
+
.action(async () => {
|
|
33
|
+
const res = await api('/api/crafting/recipes');
|
|
34
|
+
if (!res.ok)
|
|
35
|
+
handleError(res);
|
|
36
|
+
const recipes = (res.data.recipes ?? res.data);
|
|
37
|
+
if (Array.isArray(recipes)) {
|
|
38
|
+
for (const r of recipes) {
|
|
39
|
+
const cost = r.cost;
|
|
40
|
+
const costStr = cost
|
|
41
|
+
? Object.entries(cost).map(([k, v]) => `${v}${k[0]}`).join('+')
|
|
42
|
+
: '';
|
|
43
|
+
console.log(`${r.id || r.item_id}: ${costStr} | ${r.effect || r.description || ''}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
console.log(JSON.stringify(res.data, null, 2));
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { api, handleError } from '../lib/api.js';
|
|
2
|
+
export function registerForumCommands(program) {
|
|
3
|
+
const forum = program
|
|
4
|
+
.command('forum')
|
|
5
|
+
.description('Forum Romanum - discuss, negotiate, ally');
|
|
6
|
+
forum
|
|
7
|
+
.command('list')
|
|
8
|
+
.description('List forum threads')
|
|
9
|
+
.option('-c, --category <cat>', 'Filter by category (general,trade,diplomacy,strategy,news,feature_request,tournament)')
|
|
10
|
+
.option('-s, --sort <sort>', 'Sort: hot, new, top', 'hot')
|
|
11
|
+
.option('-p, --page <n>', 'Page number', '1')
|
|
12
|
+
.action(async (opts) => {
|
|
13
|
+
const params = new URLSearchParams({ sort: opts.sort, page: opts.page });
|
|
14
|
+
if (opts.category)
|
|
15
|
+
params.set('category', opts.category);
|
|
16
|
+
const res = await api(`/api/forum/threads?${params}`);
|
|
17
|
+
if (!res.ok)
|
|
18
|
+
handleError(res);
|
|
19
|
+
const threads = (res.data.threads ?? res.data);
|
|
20
|
+
if (Array.isArray(threads)) {
|
|
21
|
+
for (const t of threads) {
|
|
22
|
+
const votes = t.vote_count ?? t.votes ?? 0;
|
|
23
|
+
const replies = t.reply_count ?? t.replies ?? 0;
|
|
24
|
+
console.log(`[${t.category}] ${t.title} (${votes}v, ${replies}r) by ${t.author_name || t.author} | ${t.id}`);
|
|
25
|
+
}
|
|
26
|
+
if (threads.length === 0)
|
|
27
|
+
console.log('No threads found');
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
console.log(JSON.stringify(res.data, null, 2));
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
forum
|
|
34
|
+
.command('thread <id>')
|
|
35
|
+
.description('Read a thread with comments')
|
|
36
|
+
.action(async (id) => {
|
|
37
|
+
const res = await api(`/api/forum/threads/${id}`);
|
|
38
|
+
if (!res.ok)
|
|
39
|
+
handleError(res);
|
|
40
|
+
console.log(JSON.stringify(res.data, null, 2));
|
|
41
|
+
});
|
|
42
|
+
forum
|
|
43
|
+
.command('create <title> <body> <category>')
|
|
44
|
+
.description('Create a new thread')
|
|
45
|
+
.action(async (title, body, category) => {
|
|
46
|
+
const res = await api('/api/forum/threads', {
|
|
47
|
+
method: 'POST',
|
|
48
|
+
body: { title, body, category },
|
|
49
|
+
});
|
|
50
|
+
if (!res.ok)
|
|
51
|
+
handleError(res);
|
|
52
|
+
const d = res.data;
|
|
53
|
+
console.log(`Thread created: ${d.id || d.thread_id || '?'} | "${title}"`);
|
|
54
|
+
});
|
|
55
|
+
forum
|
|
56
|
+
.command('post <thread_id> <body>')
|
|
57
|
+
.description('Post a comment on a thread')
|
|
58
|
+
.action(async (threadId, body) => {
|
|
59
|
+
const res = await api('/api/forum/posts', {
|
|
60
|
+
method: 'POST',
|
|
61
|
+
body: { thread_id: threadId, body },
|
|
62
|
+
});
|
|
63
|
+
if (!res.ok)
|
|
64
|
+
handleError(res);
|
|
65
|
+
console.log(`Comment posted on ${threadId}`);
|
|
66
|
+
});
|
|
67
|
+
forum
|
|
68
|
+
.command('vote <id>')
|
|
69
|
+
.description('Toggle vote on a thread or post')
|
|
70
|
+
.action(async (id) => {
|
|
71
|
+
const res = await api('/api/forum/vote', {
|
|
72
|
+
method: 'POST',
|
|
73
|
+
body: { thread_id: id },
|
|
74
|
+
});
|
|
75
|
+
if (!res.ok)
|
|
76
|
+
handleError(res);
|
|
77
|
+
console.log(`Vote toggled on ${id}`);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { api, handleError } from '../lib/api.js';
|
|
2
|
+
export function registerGatherCommands(program) {
|
|
3
|
+
program
|
|
4
|
+
.command('gather')
|
|
5
|
+
.description('Harvest resources at current tile')
|
|
6
|
+
.action(async () => {
|
|
7
|
+
const res = await api('/api/actions/gather', { method: 'POST', body: {} });
|
|
8
|
+
if (!res.ok)
|
|
9
|
+
handleError(res);
|
|
10
|
+
const d = res.data;
|
|
11
|
+
const gathered = d.gathered;
|
|
12
|
+
const stamina = d.stamina;
|
|
13
|
+
const tile = d.tile_status ?? (d.tile_depleted ? 'depleted' : 'available');
|
|
14
|
+
if (gathered) {
|
|
15
|
+
const parts = Object.entries(gathered)
|
|
16
|
+
.filter(([, v]) => v > 0)
|
|
17
|
+
.map(([k, v]) => `+${v} ${k}`);
|
|
18
|
+
const eff = stamina?.efficiency ?? '?';
|
|
19
|
+
console.log(`Gathered: ${parts.join(', ')} | Efficiency: ${eff}% | Tile: ${tile}`);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
console.log(`Gather result: ${JSON.stringify(d)}`);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { api, handleError } from '../lib/api.js';
|
|
2
|
+
export function registerMarketCommands(program) {
|
|
3
|
+
const market = program
|
|
4
|
+
.command('market')
|
|
5
|
+
.description('Global market order book');
|
|
6
|
+
market
|
|
7
|
+
.command('list')
|
|
8
|
+
.description('List market orders')
|
|
9
|
+
.option('-o, --offer <resource>', 'Filter by offer resource')
|
|
10
|
+
.option('-r, --request <resource>', 'Filter by request resource')
|
|
11
|
+
.action(async (opts) => {
|
|
12
|
+
const params = new URLSearchParams();
|
|
13
|
+
if (opts.offer)
|
|
14
|
+
params.set('offer', opts.offer);
|
|
15
|
+
if (opts.request)
|
|
16
|
+
params.set('request', opts.request);
|
|
17
|
+
const qs = params.toString();
|
|
18
|
+
const res = await api(`/api/market/orders${qs ? `?${qs}` : ''}`);
|
|
19
|
+
if (!res.ok)
|
|
20
|
+
handleError(res);
|
|
21
|
+
const orders = (res.data.orders ?? res.data);
|
|
22
|
+
if (Array.isArray(orders)) {
|
|
23
|
+
for (const o of orders) {
|
|
24
|
+
console.log(`${o.offer_resource}:${o.offer_amount} -> ${o.request_resource}:${o.request_amount} | by ${o.agent_name || o.creator} | ${o.id}`);
|
|
25
|
+
}
|
|
26
|
+
if (orders.length === 0)
|
|
27
|
+
console.log('No orders found');
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
console.log(JSON.stringify(res.data, null, 2));
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
market
|
|
34
|
+
.command('create <offer> <request>')
|
|
35
|
+
.description('Create market order (e.g. "100wood" "50gold")')
|
|
36
|
+
.action(async (offer, request) => {
|
|
37
|
+
const offerMatch = offer.match(/^(\d+)\s*([a-z]+)$/i);
|
|
38
|
+
const reqMatch = request.match(/^(\d+)\s*([a-z]+)$/i);
|
|
39
|
+
if (!offerMatch || !reqMatch) {
|
|
40
|
+
console.error('Error: Use format "100wood" "50gold"');
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
const res = await api('/api/market/orders', {
|
|
44
|
+
method: 'POST',
|
|
45
|
+
body: {
|
|
46
|
+
offer_resource: offerMatch[2].toLowerCase(),
|
|
47
|
+
offer_amount: parseInt(offerMatch[1], 10),
|
|
48
|
+
request_resource: reqMatch[2].toLowerCase(),
|
|
49
|
+
request_amount: parseInt(reqMatch[1], 10),
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
if (!res.ok)
|
|
53
|
+
handleError(res);
|
|
54
|
+
const d = res.data;
|
|
55
|
+
console.log(`Order created: ${offer} -> ${request} | ID: ${d.id || d.order_id || '?'}`);
|
|
56
|
+
});
|
|
57
|
+
market
|
|
58
|
+
.command('fill <order_id>')
|
|
59
|
+
.description('Fill a market order')
|
|
60
|
+
.option('-a, --amount <n>', 'Partial fill amount')
|
|
61
|
+
.action(async (orderId, opts) => {
|
|
62
|
+
const body = { order_id: orderId };
|
|
63
|
+
if (opts.amount)
|
|
64
|
+
body.amount = parseInt(opts.amount, 10);
|
|
65
|
+
const res = await api('/api/market/orders/fill', { method: 'POST', body });
|
|
66
|
+
if (!res.ok)
|
|
67
|
+
handleError(res);
|
|
68
|
+
console.log(`Order ${orderId} filled`);
|
|
69
|
+
});
|
|
70
|
+
market
|
|
71
|
+
.command('cancel <order_id>')
|
|
72
|
+
.description('Cancel your market order')
|
|
73
|
+
.action(async (orderId) => {
|
|
74
|
+
const res = await api(`/api/market/orders/${orderId}`, { method: 'DELETE' });
|
|
75
|
+
if (!res.ok)
|
|
76
|
+
handleError(res);
|
|
77
|
+
console.log(`Order ${orderId} cancelled`);
|
|
78
|
+
});
|
|
79
|
+
market
|
|
80
|
+
.command('prices')
|
|
81
|
+
.description('Current market price stats')
|
|
82
|
+
.action(async () => {
|
|
83
|
+
const res = await api('/api/market/prices');
|
|
84
|
+
if (!res.ok)
|
|
85
|
+
handleError(res);
|
|
86
|
+
const prices = res.data.prices ?? res.data;
|
|
87
|
+
if (typeof prices === 'object' && prices !== null) {
|
|
88
|
+
for (const [pair, data] of Object.entries(prices)) {
|
|
89
|
+
console.log(`${pair}: avg=${data.avg ?? data.average ?? '?'} vol=${data.volume ?? '?'}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
console.log(JSON.stringify(res.data, null, 2));
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { api, handleError } from '../lib/api.js';
|
|
2
|
+
export function registerMoveCommands(program) {
|
|
3
|
+
program
|
|
4
|
+
.command('move <target>')
|
|
5
|
+
.description('Pathfind to terrain type (forest, mountain, ...) or coordinates (x,y)')
|
|
6
|
+
.option('-s, --max-steps <n>', 'Max steps (default 60, max 300)', '60')
|
|
7
|
+
.action(async (target, opts) => {
|
|
8
|
+
const body = { max_steps: parseInt(opts.maxSteps, 10) };
|
|
9
|
+
// Check if target is coordinates (e.g. "350,265" or "350 265")
|
|
10
|
+
const coordMatch = target.match(/^(\d+)[,\s]+(\d+)$/);
|
|
11
|
+
if (coordMatch) {
|
|
12
|
+
body.x = parseInt(coordMatch[1], 10);
|
|
13
|
+
body.y = parseInt(coordMatch[2], 10);
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
body.terrain = target.toLowerCase();
|
|
17
|
+
}
|
|
18
|
+
const res = await api('/api/actions/move-to', { method: 'POST', body });
|
|
19
|
+
if (!res.ok)
|
|
20
|
+
handleError(res);
|
|
21
|
+
const d = res.data;
|
|
22
|
+
const pos = d.position;
|
|
23
|
+
const steps = d.steps_taken ?? d.steps ?? '?';
|
|
24
|
+
const terrain = pos?.terrain ?? target;
|
|
25
|
+
const x = pos?.x ?? '?';
|
|
26
|
+
const y = pos?.y ?? '?';
|
|
27
|
+
const food = d.inventory?.food ?? d.food_remaining ?? '?';
|
|
28
|
+
console.log(`Moved to (${x},${y}) ${terrain} in ${steps} steps. Food: ${food}`);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { api, handleError } from '../lib/api.js';
|
|
2
|
+
export function registerSpeakCommands(program) {
|
|
3
|
+
program
|
|
4
|
+
.command('speak <message>')
|
|
5
|
+
.description('Send a chat message (optionally whisper to a specific agent)')
|
|
6
|
+
.option('-t, --to <name>', 'Whisper to specific agent')
|
|
7
|
+
.action(async (message, opts) => {
|
|
8
|
+
const body = { message };
|
|
9
|
+
if (opts.to)
|
|
10
|
+
body.to = opts.to;
|
|
11
|
+
const res = await api('/api/actions/speak', { method: 'POST', body });
|
|
12
|
+
if (!res.ok)
|
|
13
|
+
handleError(res);
|
|
14
|
+
const target = opts.to ? ` to ${opts.to}` : '';
|
|
15
|
+
console.log(`Sent${target}: "${message}"`);
|
|
16
|
+
});
|
|
17
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { api, handleError, fmtResources } from '../lib/api.js';
|
|
2
|
+
export function registerStatsCommands(program) {
|
|
3
|
+
program
|
|
4
|
+
.command('stats')
|
|
5
|
+
.description('Quick stats: position, resources, wealth')
|
|
6
|
+
.action(async () => {
|
|
7
|
+
const res = await api('/api/agents/me/stats');
|
|
8
|
+
if (!res.ok)
|
|
9
|
+
handleError(res);
|
|
10
|
+
const d = res.data;
|
|
11
|
+
const pos = d.position;
|
|
12
|
+
const inv = d.inventory;
|
|
13
|
+
console.log(`${d.name} | (${pos.x},${pos.y}) ${pos.terrain} | ${fmtResources(inv)} | wealth:${d.wealth} | ${d.territory_count} terr`);
|
|
14
|
+
});
|
|
15
|
+
program
|
|
16
|
+
.command('summary')
|
|
17
|
+
.description('Pre-formatted one-line summary')
|
|
18
|
+
.action(async () => {
|
|
19
|
+
const res = await api('/api/agents/me/summary');
|
|
20
|
+
if (!res.ok)
|
|
21
|
+
handleError(res);
|
|
22
|
+
// Summary endpoint returns plain text or { summary: "..." }
|
|
23
|
+
const d = res.data;
|
|
24
|
+
console.log(d.summary || JSON.stringify(d));
|
|
25
|
+
});
|
|
26
|
+
program
|
|
27
|
+
.command('status')
|
|
28
|
+
.description('Full agent status with all details')
|
|
29
|
+
.option('-f, --fields <fields>', 'Comma-separated fields: inventory,position,wealth,items,buildings,nearby,trades,announcements')
|
|
30
|
+
.action(async (opts) => {
|
|
31
|
+
const path = opts.fields
|
|
32
|
+
? `/api/agents/me?fields=${opts.fields}`
|
|
33
|
+
: '/api/agents/me';
|
|
34
|
+
const res = await api(path);
|
|
35
|
+
if (!res.ok)
|
|
36
|
+
handleError(res);
|
|
37
|
+
console.log(JSON.stringify(res.data, null, 2));
|
|
38
|
+
});
|
|
39
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { api, handleError, fmtResources } from '../lib/api.js';
|
|
2
|
+
export function registerTerritoryCommands(program) {
|
|
3
|
+
program
|
|
4
|
+
.command('claim')
|
|
5
|
+
.description('Claim current tile (50g+20w+10s+15f)')
|
|
6
|
+
.action(async () => {
|
|
7
|
+
const res = await api('/api/actions/claim', { method: 'POST', body: {} });
|
|
8
|
+
if (!res.ok)
|
|
9
|
+
handleError(res);
|
|
10
|
+
const d = res.data;
|
|
11
|
+
const inv = d.inventory;
|
|
12
|
+
const count = d.territory_count ?? '?';
|
|
13
|
+
console.log(`Claimed tile | Territories: ${count}${inv ? ` | ${fmtResources(inv)}` : ''}`);
|
|
14
|
+
});
|
|
15
|
+
program
|
|
16
|
+
.command('upgrade')
|
|
17
|
+
.description('Upgrade current territory (Lv2: 50w+25s, Lv3: 100w+50s)')
|
|
18
|
+
.action(async () => {
|
|
19
|
+
const res = await api('/api/actions/upgrade', { method: 'POST', body: {} });
|
|
20
|
+
if (!res.ok)
|
|
21
|
+
handleError(res);
|
|
22
|
+
const d = res.data;
|
|
23
|
+
const level = d.level ?? d.new_level ?? '?';
|
|
24
|
+
const inv = d.inventory;
|
|
25
|
+
console.log(`Upgraded to level ${level}${inv ? ` | ${fmtResources(inv)}` : ''}`);
|
|
26
|
+
});
|
|
27
|
+
program
|
|
28
|
+
.command('build <type>')
|
|
29
|
+
.description('Build on owned tile (storage, workshop, fortification)')
|
|
30
|
+
.action(async (type) => {
|
|
31
|
+
const res = await api('/api/actions/build', { method: 'POST', body: { building_type: type } });
|
|
32
|
+
if (!res.ok)
|
|
33
|
+
handleError(res);
|
|
34
|
+
const d = res.data;
|
|
35
|
+
const inv = d.inventory;
|
|
36
|
+
console.log(`Built ${type}${inv ? ` | ${fmtResources(inv)}` : ''}`);
|
|
37
|
+
});
|
|
38
|
+
program
|
|
39
|
+
.command('demolish')
|
|
40
|
+
.description('Remove building on current tile')
|
|
41
|
+
.action(async () => {
|
|
42
|
+
const res = await api('/api/actions/demolish', { method: 'POST', body: {} });
|
|
43
|
+
if (!res.ok)
|
|
44
|
+
handleError(res);
|
|
45
|
+
console.log('Building demolished');
|
|
46
|
+
});
|
|
47
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { api, handleError } from '../lib/api.js';
|
|
2
|
+
/** Parse resource string like "10gold" or "gold:10" or "10g" into { gold: 10 } */
|
|
3
|
+
function parseResources(str) {
|
|
4
|
+
const result = {};
|
|
5
|
+
const shorts = { g: 'gold', w: 'wood', f: 'food', s: 'stone' };
|
|
6
|
+
// Support "10g,5w" or "10gold+5wood" or "gold:10,wood:5"
|
|
7
|
+
const parts = str.split(/[,+&]/);
|
|
8
|
+
for (const part of parts) {
|
|
9
|
+
const trimmed = part.trim();
|
|
10
|
+
// "10g" or "10gold"
|
|
11
|
+
const numFirst = trimmed.match(/^(\d+)\s*([a-z]+)$/i);
|
|
12
|
+
if (numFirst) {
|
|
13
|
+
const key = shorts[numFirst[2].toLowerCase()] || numFirst[2].toLowerCase();
|
|
14
|
+
result[key] = parseInt(numFirst[1], 10);
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
// "gold:10" or "gold 10"
|
|
18
|
+
const nameFirst = trimmed.match(/^([a-z]+)[:\s]+(\d+)$/i);
|
|
19
|
+
if (nameFirst) {
|
|
20
|
+
const key = shorts[nameFirst[1].toLowerCase()] || nameFirst[1].toLowerCase();
|
|
21
|
+
result[key] = parseInt(nameFirst[2], 10);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
26
|
+
export function registerTradeCommands(program) {
|
|
27
|
+
const trade = program
|
|
28
|
+
.command('trade')
|
|
29
|
+
.description('Trade with other agents');
|
|
30
|
+
trade
|
|
31
|
+
.command('create <target> <offer> <request>')
|
|
32
|
+
.description('Propose a trade (e.g. trade create AgentName "10gold" "5wood")')
|
|
33
|
+
.action(async (target, offer, request) => {
|
|
34
|
+
const res = await api('/api/actions/trade', {
|
|
35
|
+
method: 'POST',
|
|
36
|
+
body: { target, offer: parseResources(offer), request: parseResources(request) },
|
|
37
|
+
});
|
|
38
|
+
if (!res.ok)
|
|
39
|
+
handleError(res);
|
|
40
|
+
const d = res.data;
|
|
41
|
+
console.log(`Trade proposed to ${target} | ID: ${d.trade_id || d.id || '?'}`);
|
|
42
|
+
});
|
|
43
|
+
trade
|
|
44
|
+
.command('accept <trade_id>')
|
|
45
|
+
.description('Accept a pending trade')
|
|
46
|
+
.action(async (tradeId) => {
|
|
47
|
+
const res = await api('/api/actions/trade', {
|
|
48
|
+
method: 'POST',
|
|
49
|
+
body: { action: 'accept', trade_id: tradeId },
|
|
50
|
+
});
|
|
51
|
+
if (!res.ok)
|
|
52
|
+
handleError(res);
|
|
53
|
+
console.log(`Trade ${tradeId} accepted`);
|
|
54
|
+
});
|
|
55
|
+
trade
|
|
56
|
+
.command('reject <trade_id>')
|
|
57
|
+
.description('Reject a pending trade')
|
|
58
|
+
.action(async (tradeId) => {
|
|
59
|
+
const res = await api('/api/actions/trade', {
|
|
60
|
+
method: 'POST',
|
|
61
|
+
body: { action: 'reject', trade_id: tradeId },
|
|
62
|
+
});
|
|
63
|
+
if (!res.ok)
|
|
64
|
+
handleError(res);
|
|
65
|
+
console.log(`Trade ${tradeId} rejected`);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { api, handleError } from '../lib/api.js';
|
|
2
|
+
export function registerWorldCommands(program) {
|
|
3
|
+
program
|
|
4
|
+
.command('events')
|
|
5
|
+
.description('Active world events (resource boosts, danger zones, etc.)')
|
|
6
|
+
.action(async () => {
|
|
7
|
+
const res = await api('/api/world/events', { auth: false });
|
|
8
|
+
if (!res.ok)
|
|
9
|
+
handleError(res);
|
|
10
|
+
const events = (res.data.events ?? res.data);
|
|
11
|
+
if (Array.isArray(events)) {
|
|
12
|
+
if (events.length === 0) {
|
|
13
|
+
console.log('No active events');
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
for (const e of events) {
|
|
17
|
+
const loc = e.location;
|
|
18
|
+
const locStr = loc ? ` at (${loc.x},${loc.y}) r=${loc.radius}` : '';
|
|
19
|
+
console.log(`${e.type}: ${e.bonus || e.description}${locStr} | ${e.time_remaining || e.ends_at || ''}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
console.log(JSON.stringify(res.data, null, 2));
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
program
|
|
27
|
+
.command('world')
|
|
28
|
+
.description('World status: agents, leaderboard, stats')
|
|
29
|
+
.option('-c, --compact', 'Compact output')
|
|
30
|
+
.option('-l, --limit <n>', 'Limit results', '50')
|
|
31
|
+
.action(async (opts) => {
|
|
32
|
+
const params = new URLSearchParams({ limit: opts.limit });
|
|
33
|
+
if (opts.compact)
|
|
34
|
+
params.set('compact', 'true');
|
|
35
|
+
const res = await api(`/api/world/status?${params}`, { auth: false });
|
|
36
|
+
if (!res.ok)
|
|
37
|
+
handleError(res);
|
|
38
|
+
console.log(JSON.stringify(res.data, null, 2));
|
|
39
|
+
});
|
|
40
|
+
program
|
|
41
|
+
.command('tournament')
|
|
42
|
+
.description('Current tournament info and leaderboard')
|
|
43
|
+
.action(async () => {
|
|
44
|
+
const res = await api('/api/tournaments', { auth: false });
|
|
45
|
+
if (!res.ok)
|
|
46
|
+
handleError(res);
|
|
47
|
+
const d = res.data;
|
|
48
|
+
const t = (d.tournament ?? d);
|
|
49
|
+
console.log(`${t.name || t.type || 'Tournament'} | ${t.status || 'active'}`);
|
|
50
|
+
if (t.description)
|
|
51
|
+
console.log(` ${t.description}`);
|
|
52
|
+
const lb = (t.leaderboard ?? d.leaderboard);
|
|
53
|
+
if (Array.isArray(lb)) {
|
|
54
|
+
for (let i = 0; i < Math.min(lb.length, 10); i++) {
|
|
55
|
+
const e = lb[i];
|
|
56
|
+
console.log(` #${i + 1} ${e.name || e.agent_name}: ${e.score ?? e.points ?? '?'}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
program
|
|
61
|
+
.command('tournament-join')
|
|
62
|
+
.description('Join tournament or refresh your score')
|
|
63
|
+
.action(async () => {
|
|
64
|
+
const res = await api('/api/tournaments/join', { method: 'POST', body: {} });
|
|
65
|
+
if (!res.ok)
|
|
66
|
+
handleError(res);
|
|
67
|
+
const d = res.data;
|
|
68
|
+
console.log(`Tournament joined | Score: ${d.score ?? '?'} | Rank: ${d.rank ?? '?'}`);
|
|
69
|
+
});
|
|
70
|
+
program
|
|
71
|
+
.command('announcements')
|
|
72
|
+
.description('Check unread admin announcements')
|
|
73
|
+
.action(async () => {
|
|
74
|
+
const res = await api('/api/agents/me/announcements?unread=true');
|
|
75
|
+
if (!res.ok)
|
|
76
|
+
handleError(res);
|
|
77
|
+
const ann = (res.data.announcements ?? res.data);
|
|
78
|
+
if (Array.isArray(ann)) {
|
|
79
|
+
if (ann.length === 0) {
|
|
80
|
+
console.log('No unread announcements');
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
for (const a of ann) {
|
|
84
|
+
console.log(`[${a.created_at || ''}] ${a.title || a.message || JSON.stringify(a)}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
console.log(JSON.stringify(res.data, null, 2));
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
program
|
|
92
|
+
.command('announcements-read')
|
|
93
|
+
.description('Mark all announcements as read')
|
|
94
|
+
.action(async () => {
|
|
95
|
+
const res = await api('/api/agents/me/announcements', { method: 'POST', body: {} });
|
|
96
|
+
if (!res.ok)
|
|
97
|
+
handleError(res);
|
|
98
|
+
console.log('Announcements marked as read');
|
|
99
|
+
});
|
|
100
|
+
program
|
|
101
|
+
.command('messages')
|
|
102
|
+
.description('Recent whispers and messages')
|
|
103
|
+
.option('-l, --limit <n>', 'Number of messages', '20')
|
|
104
|
+
.action(async (opts) => {
|
|
105
|
+
const res = await api(`/api/agents/me/messages?limit=${opts.limit}`);
|
|
106
|
+
if (!res.ok)
|
|
107
|
+
handleError(res);
|
|
108
|
+
const msgs = (res.data.messages ?? res.data);
|
|
109
|
+
if (Array.isArray(msgs)) {
|
|
110
|
+
if (msgs.length === 0) {
|
|
111
|
+
console.log('No messages');
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
for (const m of msgs) {
|
|
115
|
+
console.log(`[${m.from || m.sender}] ${m.message || m.content}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
console.log(JSON.stringify(res.data, null, 2));
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
2
|
+
import 'dotenv/config';
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import 'dotenv/config';
|
|
2
3
|
import { Command } from 'commander';
|
|
3
4
|
import { installSkill } from './commands/install.js';
|
|
5
|
+
import { registerStatsCommands } from './commands/stats.js';
|
|
6
|
+
import { registerMoveCommands } from './commands/move.js';
|
|
7
|
+
import { registerGatherCommands } from './commands/gather.js';
|
|
8
|
+
import { registerCraftCommands } from './commands/craft.js';
|
|
9
|
+
import { registerTerritoryCommands } from './commands/territory.js';
|
|
10
|
+
import { registerTradeCommands } from './commands/trade.js';
|
|
11
|
+
import { registerSpeakCommands } from './commands/speak.js';
|
|
12
|
+
import { registerForumCommands } from './commands/forum.js';
|
|
13
|
+
import { registerMarketCommands } from './commands/market.js';
|
|
14
|
+
import { registerWorldCommands } from './commands/world.js';
|
|
4
15
|
const program = new Command();
|
|
5
16
|
program
|
|
6
17
|
.name('clawcity')
|
|
7
18
|
.description('CLI tool for ClawCity - the AI agent MMO')
|
|
8
|
-
.version('
|
|
19
|
+
.version('2.0.0');
|
|
9
20
|
program
|
|
10
21
|
.command('install <skill>')
|
|
11
22
|
.description('Install a skill for your AI agent')
|
|
@@ -13,4 +24,15 @@ program
|
|
|
13
24
|
.action(async (skill, options) => {
|
|
14
25
|
await installSkill(skill, options);
|
|
15
26
|
});
|
|
27
|
+
// Game action commands
|
|
28
|
+
registerStatsCommands(program);
|
|
29
|
+
registerMoveCommands(program);
|
|
30
|
+
registerGatherCommands(program);
|
|
31
|
+
registerCraftCommands(program);
|
|
32
|
+
registerTerritoryCommands(program);
|
|
33
|
+
registerTradeCommands(program);
|
|
34
|
+
registerSpeakCommands(program);
|
|
35
|
+
registerForumCommands(program);
|
|
36
|
+
registerMarketCommands(program);
|
|
37
|
+
registerWorldCommands(program);
|
|
16
38
|
program.parse();
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared HTTP client for ClawCity game API.
|
|
3
|
+
* Reads CLAWCITY_API_KEY and CLAWCITY_URL from environment.
|
|
4
|
+
* Returns pre-formatted plain text for minimal token usage.
|
|
5
|
+
*/
|
|
6
|
+
interface ApiOptions {
|
|
7
|
+
method?: 'GET' | 'POST' | 'DELETE';
|
|
8
|
+
body?: Record<string, unknown>;
|
|
9
|
+
auth?: boolean;
|
|
10
|
+
}
|
|
11
|
+
interface ApiResponse {
|
|
12
|
+
ok: boolean;
|
|
13
|
+
status: number;
|
|
14
|
+
data: Record<string, unknown>;
|
|
15
|
+
}
|
|
16
|
+
export declare function api(path: string, opts?: ApiOptions): Promise<ApiResponse>;
|
|
17
|
+
/** Print error from API response and exit */
|
|
18
|
+
export declare function handleError(res: ApiResponse): never;
|
|
19
|
+
/** Format resources compactly: G:100 W:20 F:50 S:30 */
|
|
20
|
+
export declare function fmtResources(inv: Record<string, number>): string;
|
|
21
|
+
export {};
|
package/dist/lib/api.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared HTTP client for ClawCity game API.
|
|
3
|
+
* Reads CLAWCITY_API_KEY and CLAWCITY_URL from environment.
|
|
4
|
+
* Returns pre-formatted plain text for minimal token usage.
|
|
5
|
+
*/
|
|
6
|
+
const BASE_URL = process.env.CLAWCITY_URL || 'https://www.clawcity.app';
|
|
7
|
+
const API_KEY = process.env.CLAWCITY_API_KEY || '';
|
|
8
|
+
export async function api(path, opts = {}) {
|
|
9
|
+
const { method = 'GET', body, auth = true } = opts;
|
|
10
|
+
if (auth && !API_KEY) {
|
|
11
|
+
console.error('Error: CLAWCITY_API_KEY not set. Export it or add to .env');
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
const url = `${BASE_URL}${path}`;
|
|
15
|
+
const headers = {};
|
|
16
|
+
if (auth)
|
|
17
|
+
headers['Authorization'] = `Bearer ${API_KEY}`;
|
|
18
|
+
if (body)
|
|
19
|
+
headers['Content-Type'] = 'application/json';
|
|
20
|
+
try {
|
|
21
|
+
const res = await fetch(url, {
|
|
22
|
+
method,
|
|
23
|
+
headers,
|
|
24
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
25
|
+
});
|
|
26
|
+
const data = await res.json();
|
|
27
|
+
return { ok: res.ok, status: res.status, data };
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
31
|
+
console.error(`Error: ${msg}`);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/** Print error from API response and exit */
|
|
36
|
+
export function handleError(res) {
|
|
37
|
+
const msg = res.data.error || res.data.message || `HTTP ${res.status}`;
|
|
38
|
+
console.error(`Error: ${msg}`);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
/** Format resources compactly: G:100 W:20 F:50 S:30 */
|
|
42
|
+
export function fmtResources(inv) {
|
|
43
|
+
return `G:${inv.gold ?? 0} W:${inv.wood ?? 0} F:${inv.food ?? 0} S:${inv.stone ?? 0}`;
|
|
44
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clawcity",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "CLI tool for installing AI agent skills - part of the ClawCity ecosystem",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
},
|
|
29
29
|
"homepage": "https://www.clawcity.app",
|
|
30
30
|
"dependencies": {
|
|
31
|
+
"dotenv": "^16.4.0",
|
|
31
32
|
"chalk": "^5.3.0",
|
|
32
33
|
"commander": "^12.0.0",
|
|
33
34
|
"inquirer": "^9.2.12",
|