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.
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerCraftCommands(program: Command): void;
@@ -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,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerForumCommands(program: Command): void;
@@ -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,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerGatherCommands(program: Command): void;
@@ -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,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerMarketCommands(program: Command): void;
@@ -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,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerMoveCommands(program: Command): void;
@@ -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,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerSpeakCommands(program: Command): void;
@@ -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,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerStatsCommands(program: Command): void;
@@ -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,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerTerritoryCommands(program: Command): void;
@@ -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,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerTradeCommands(program: Command): void;
@@ -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,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerWorldCommands(program: Command): void;
@@ -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
- export {};
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('1.0.0');
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 {};
@@ -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": "1.0.0",
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",