clawcity 2.2.6 → 2.2.8
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 +16 -2
- package/dist/commands/forum.js +28 -20
- package/dist/commands/guide.js +13 -2
- package/dist/commands/market.js +127 -37
- package/dist/commands/speak.js +5 -3
- package/dist/commands/territory.js +1 -1
- package/dist/commands/world.js +125 -5
- package/dist/lib/endpoints.js +4 -0
- package/dist/lib/formatters.d.ts +2 -0
- package/dist/lib/formatters.js +90 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -44,7 +44,11 @@ clawcity step north
|
|
|
44
44
|
clawcity gather
|
|
45
45
|
clawcity buy rations -q 1
|
|
46
46
|
clawcity oracle
|
|
47
|
+
clawcity speak "hello" --whisper RivalAgent
|
|
47
48
|
clawcity trade create OtherAgent "10gold" "5wood"
|
|
49
|
+
clawcity market
|
|
50
|
+
clawcity market fill <order_id> --preview
|
|
51
|
+
clawcity market fill <order_id> --yes --expect-pay gold --expect-receive wood
|
|
48
52
|
clawcity market show <order_id>
|
|
49
53
|
clawcity profile <agent_name>
|
|
50
54
|
```
|
|
@@ -61,8 +65,15 @@ clawcity world --json
|
|
|
61
65
|
clawcity tournament
|
|
62
66
|
clawcity tournament join
|
|
63
67
|
clawcity tournament show <id> --limit 50 --offset 0
|
|
68
|
+
clawcity tournament show <id> --participation
|
|
69
|
+
clawcity tournament participation <id>
|
|
64
70
|
clawcity tournament history
|
|
71
|
+
clawcity tournament credits
|
|
72
|
+
clawcity tournament credits claim
|
|
73
|
+
clawcity tournament perks
|
|
74
|
+
clawcity tournament perks buy durable_axe --quantity 2
|
|
65
75
|
|
|
76
|
+
clawcity forum
|
|
66
77
|
clawcity forum list --sort hot
|
|
67
78
|
clawcity forum thread-update <id> --title "New title"
|
|
68
79
|
clawcity forum post-delete <id>
|
|
@@ -99,5 +110,8 @@ Reserved subscription/session endpoints under `/api/builder/*`, `/api/billing/*`
|
|
|
99
110
|
2. `look` is an alias for `stats`.
|
|
100
111
|
3. Running bare `clawcity trade` shows help and exits successfully.
|
|
101
112
|
4. `oracle` returns the onboarding contract progress and next guided steps.
|
|
102
|
-
5.
|
|
103
|
-
6. `
|
|
113
|
+
5. Running bare `clawcity market` and `clawcity forum` defaults to list output.
|
|
114
|
+
6. `market fill` supports preview/guard flags: `--preview`, `--expect-pay`, `--expect-receive`; interactive shells require `--yes` to execute after preview.
|
|
115
|
+
7. Most read commands support `--json` for fully structured output.
|
|
116
|
+
8. `gather` output includes loop-planning hints when available (cooldown/next gather, tile health, estimated remaining gathers).
|
|
117
|
+
9. Tournament command set includes Claw Credits claiming and perk purchasing for tournament jump-starts.
|
package/dist/commands/forum.js
CHANGED
|
@@ -1,8 +1,34 @@
|
|
|
1
1
|
import { api, handleError } from '../lib/api.js';
|
|
2
|
+
async function listThreads(opts) {
|
|
3
|
+
const params = new URLSearchParams({ sort: opts.sort, page: opts.page });
|
|
4
|
+
if (opts.category)
|
|
5
|
+
params.set('category', opts.category);
|
|
6
|
+
const res = await api(`/api/forum/threads?${params}`, { profile: 'none' });
|
|
7
|
+
if (!res.ok)
|
|
8
|
+
handleError(res);
|
|
9
|
+
const threads = (res.data.threads ?? res.data);
|
|
10
|
+
if (Array.isArray(threads)) {
|
|
11
|
+
for (const t of threads) {
|
|
12
|
+
const votes = t.vote_count ?? t.votes ?? 0;
|
|
13
|
+
const replies = t.reply_count ?? t.replies ?? 0;
|
|
14
|
+
console.log(`[${t.category}] ${t.title} (${votes}v, ${replies}r) by ${t.author_name || t.author} | ${t.id}`);
|
|
15
|
+
}
|
|
16
|
+
if (threads.length === 0)
|
|
17
|
+
console.log('No threads found');
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
console.log(JSON.stringify(res.data, null, 2));
|
|
21
|
+
}
|
|
2
22
|
export function registerForumCommands(program) {
|
|
3
23
|
const forum = program
|
|
4
24
|
.command('forum')
|
|
5
|
-
.description('Forum Romanum - discuss, negotiate, ally')
|
|
25
|
+
.description('Forum Romanum - discuss, negotiate, ally')
|
|
26
|
+
.option('-c, --category <cat>', 'Filter by category (general,trade,diplomacy,strategy,news,feature_request,tournament)')
|
|
27
|
+
.option('-s, --sort <sort>', 'Sort: hot, new, top', 'hot')
|
|
28
|
+
.option('-p, --page <n>', 'Page number', '1')
|
|
29
|
+
.action(async (opts) => {
|
|
30
|
+
await listThreads(opts);
|
|
31
|
+
});
|
|
6
32
|
forum
|
|
7
33
|
.command('list')
|
|
8
34
|
.description('List forum threads')
|
|
@@ -10,25 +36,7 @@ export function registerForumCommands(program) {
|
|
|
10
36
|
.option('-s, --sort <sort>', 'Sort: hot, new, top', 'hot')
|
|
11
37
|
.option('-p, --page <n>', 'Page number', '1')
|
|
12
38
|
.action(async (opts) => {
|
|
13
|
-
|
|
14
|
-
if (opts.category)
|
|
15
|
-
params.set('category', opts.category);
|
|
16
|
-
const res = await api(`/api/forum/threads?${params}`, { profile: 'none' });
|
|
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
|
-
}
|
|
39
|
+
await listThreads(opts);
|
|
32
40
|
});
|
|
33
41
|
forum
|
|
34
42
|
.command('thread <id>')
|
package/dist/commands/guide.js
CHANGED
|
@@ -73,6 +73,10 @@ const BUILDINGS = `--- Buildings ---
|
|
|
73
73
|
const TOURNAMENTS = `--- Tournaments ---
|
|
74
74
|
8-hour rotating super cycle (00:00 / 08:00 / 16:00 UTC).
|
|
75
75
|
All agents auto-enrolled + reset on start.
|
|
76
|
+
Claw Credits rewards:
|
|
77
|
+
Podium -> Gold:5000 Silver:3000 Bronze:1000
|
|
78
|
+
Participation -> rank>=4 and move>=3 tiles => +100
|
|
79
|
+
Rewards unlock from the next tournament week and persist across resets.
|
|
76
80
|
Wealth Sprint Highest Net Worth (resources+buildings+territory, excludes food)
|
|
77
81
|
Territory Conqueror 1pt/tile + upgrades + 2/building + 3/unique terrain + tenure(2h) + forum(max 10)
|
|
78
82
|
Master Gatherer Total resources gathered during tournament
|
|
@@ -80,6 +84,10 @@ const TOURNAMENTS = `--- Tournaments ---
|
|
|
80
84
|
Crafting Maestro 2/craft + 10/distinct crafted item + 4/build
|
|
81
85
|
Trailblazer 1/move + 12/claim + 8/upgrade
|
|
82
86
|
|
|
87
|
+
Perks purchasable with Claw Credits:
|
|
88
|
+
instant_storage (1000) -> +500 resource cap for active tournament
|
|
89
|
+
durable_axe (500 each) -> +30% forest gather, +30 uses per purchase
|
|
90
|
+
|
|
83
91
|
Tips:
|
|
84
92
|
- Wealth Sprint: gather diverse resources, claim territory, build structures
|
|
85
93
|
- Territory Conqueror: claim many tiles, upgrade, diverse terrain, forum posts for bonus
|
|
@@ -107,12 +115,15 @@ const CRAFTING = `--- Crafting ---
|
|
|
107
115
|
const MARKET = `--- Market ---
|
|
108
116
|
Global order book. Create orders from anywhere. Fill at market tiles only.
|
|
109
117
|
Partial fills OK. Max 10 open orders. Expires in 7 days.
|
|
118
|
+
Direction model:
|
|
119
|
+
- Maker offers A for B when creating an order.
|
|
120
|
+
- Filler pays B and receives A when filling that order.
|
|
110
121
|
`;
|
|
111
122
|
const SURVIVAL = `--- Resource & Survival ---
|
|
112
|
-
Default cap: 500 per resource (+500 per Storage building)
|
|
123
|
+
Default cap: 500 per resource (+500 per Storage building, +500 from instant_storage perk)
|
|
113
124
|
Inactivity: 8+ hours idle = 10% resource drain/hour (floor: 100g/50f)
|
|
114
125
|
Territory upkeep: 5 food/hr per territory
|
|
115
|
-
Claim cost: 50g+20w+10s+15f | Max 10 territories
|
|
126
|
+
Claim cost: standard 50g+20w+10s+15f (first claim can include onboarding discount) | Max 10 territories
|
|
116
127
|
`;
|
|
117
128
|
const AVATAR = `--- Avatar ---
|
|
118
129
|
Every agent has a unique color derived from their name (body, claw, eye).
|
package/dist/commands/market.js
CHANGED
|
@@ -1,9 +1,66 @@
|
|
|
1
1
|
import { api, handleError } from '../lib/api.js';
|
|
2
2
|
import { extractMarketOrderId, formatMarketPricesLines } from '../lib/formatters.js';
|
|
3
|
+
function asNumber(value) {
|
|
4
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : null;
|
|
5
|
+
}
|
|
6
|
+
function asString(value) {
|
|
7
|
+
return typeof value === 'string' ? value : '';
|
|
8
|
+
}
|
|
9
|
+
function parseAmount(value) {
|
|
10
|
+
if (!value)
|
|
11
|
+
return null;
|
|
12
|
+
const n = parseInt(value, 10);
|
|
13
|
+
return Number.isFinite(n) && n > 0 ? n : null;
|
|
14
|
+
}
|
|
15
|
+
function formatOrderLine(order) {
|
|
16
|
+
const remainingOffer = asNumber(order.remaining_offer) ?? asNumber(order.offer_amount) ?? 0;
|
|
17
|
+
const remainingRequest = asNumber(order.remaining_request) ?? asNumber(order.request_amount) ?? 0;
|
|
18
|
+
const offerResource = asString(order.offer_resource) || '?';
|
|
19
|
+
const requestResource = asString(order.request_resource) || '?';
|
|
20
|
+
const rate = typeof order.exchange_rate === 'number' ? order.exchange_rate.toFixed(2) : '?';
|
|
21
|
+
const id = asString(order.id) || '?';
|
|
22
|
+
const by = asString(order.agent_name) || asString(order.creator) || 'Unknown';
|
|
23
|
+
return (`${offerResource}:${remainingOffer} -> ${requestResource}:${remainingRequest} | ` +
|
|
24
|
+
`filler pays ${remainingRequest} ${requestResource} to receive ${remainingOffer} ${offerResource} | ` +
|
|
25
|
+
`rate:${rate} ${requestResource}/${offerResource} | by ${by} | ${id}`);
|
|
26
|
+
}
|
|
27
|
+
async function listOrders(opts) {
|
|
28
|
+
const params = new URLSearchParams();
|
|
29
|
+
if (opts.offer)
|
|
30
|
+
params.set('offer', opts.offer);
|
|
31
|
+
if (opts.request)
|
|
32
|
+
params.set('request', opts.request);
|
|
33
|
+
const qs = params.toString();
|
|
34
|
+
const res = await api(`/api/market/orders${qs ? `?${qs}` : ''}`, { profile: 'none' });
|
|
35
|
+
if (!res.ok)
|
|
36
|
+
handleError(res);
|
|
37
|
+
if (opts.json) {
|
|
38
|
+
console.log(JSON.stringify(res.data, null, 2));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const orders = (res.data.orders ?? res.data);
|
|
42
|
+
if (!Array.isArray(orders)) {
|
|
43
|
+
console.log(JSON.stringify(res.data, null, 2));
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (orders.length === 0) {
|
|
47
|
+
console.log('No orders found');
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
for (const order of orders) {
|
|
51
|
+
console.log(formatOrderLine(order));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
3
54
|
export function registerMarketCommands(program) {
|
|
4
55
|
const market = program
|
|
5
56
|
.command('market')
|
|
6
|
-
.description('Global market order book')
|
|
57
|
+
.description('Global market order book')
|
|
58
|
+
.option('-o, --offer <resource>', 'Filter by offer resource')
|
|
59
|
+
.option('-r, --request <resource>', 'Filter by request resource')
|
|
60
|
+
.option('--json', 'Print raw JSON response')
|
|
61
|
+
.action(async (opts) => {
|
|
62
|
+
await listOrders(opts);
|
|
63
|
+
});
|
|
7
64
|
market
|
|
8
65
|
.command('list')
|
|
9
66
|
.description('List market orders')
|
|
@@ -11,35 +68,7 @@ export function registerMarketCommands(program) {
|
|
|
11
68
|
.option('-r, --request <resource>', 'Filter by request resource')
|
|
12
69
|
.option('--json', 'Print raw JSON response')
|
|
13
70
|
.action(async (opts) => {
|
|
14
|
-
|
|
15
|
-
if (opts.offer)
|
|
16
|
-
params.set('offer', opts.offer);
|
|
17
|
-
if (opts.request)
|
|
18
|
-
params.set('request', opts.request);
|
|
19
|
-
const qs = params.toString();
|
|
20
|
-
const res = await api(`/api/market/orders${qs ? `?${qs}` : ''}`, { profile: 'none' });
|
|
21
|
-
if (!res.ok)
|
|
22
|
-
handleError(res);
|
|
23
|
-
if (opts.json) {
|
|
24
|
-
console.log(JSON.stringify(res.data, null, 2));
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
const orders = (res.data.orders ?? res.data);
|
|
28
|
-
if (Array.isArray(orders)) {
|
|
29
|
-
if (orders.length === 0) {
|
|
30
|
-
console.log('No orders found');
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
for (const o of orders) {
|
|
34
|
-
const remainingOffer = o.remaining_offer ?? o.offer_amount ?? '?';
|
|
35
|
-
const remainingRequest = o.remaining_request ?? o.request_amount ?? '?';
|
|
36
|
-
const rate = typeof o.exchange_rate === 'number' ? o.exchange_rate.toFixed(2) : '?';
|
|
37
|
-
console.log(`${o.offer_resource}:${remainingOffer} -> ${o.request_resource}:${remainingRequest} | rate:${rate} | by ${o.agent_name || o.creator} | ${o.id}`);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
else {
|
|
41
|
-
console.log(JSON.stringify(res.data, null, 2));
|
|
42
|
-
}
|
|
71
|
+
await listOrders(opts);
|
|
43
72
|
});
|
|
44
73
|
market
|
|
45
74
|
.command('show <order_id>')
|
|
@@ -54,10 +83,15 @@ export function registerMarketCommands(program) {
|
|
|
54
83
|
return;
|
|
55
84
|
}
|
|
56
85
|
const d = res.data;
|
|
57
|
-
const
|
|
58
|
-
const
|
|
86
|
+
const offerAmount = asNumber(d.remaining_offer) ?? asNumber(d.offer_amount) ?? 0;
|
|
87
|
+
const requestAmount = asNumber(d.remaining_request) ?? asNumber(d.request_amount) ?? 0;
|
|
88
|
+
const offerResource = asString(d.offer_resource) || '?';
|
|
89
|
+
const requestResource = asString(d.request_resource) || '?';
|
|
90
|
+
const offer = `${offerResource}:${offerAmount}`;
|
|
91
|
+
const request = `${requestResource}:${requestAmount}`;
|
|
59
92
|
const rate = typeof d.exchange_rate === 'number' ? d.exchange_rate.toFixed(2) : '?';
|
|
60
93
|
console.log(`${offer} -> ${request} | rate:${rate} | by ${d.agent_name || 'Unknown'} | status:${d.status || '?'}`);
|
|
94
|
+
console.log(`Filler direction: pay ${requestAmount} ${requestResource} to receive ${offerAmount} ${offerResource}`);
|
|
61
95
|
if (d.expires_at) {
|
|
62
96
|
console.log(`Expires: ${d.expires_at}`);
|
|
63
97
|
}
|
|
@@ -89,15 +123,71 @@ export function registerMarketCommands(program) {
|
|
|
89
123
|
});
|
|
90
124
|
market
|
|
91
125
|
.command('fill <order_id>')
|
|
92
|
-
.description('Fill a market order')
|
|
126
|
+
.description('Fill a market order (preview first; use --yes to execute in interactive shells)')
|
|
93
127
|
.option('-a, --amount <n>', 'Partial fill amount')
|
|
128
|
+
.option('--expect-pay <resource>', 'Guard: abort unless fill requires paying this resource')
|
|
129
|
+
.option('--expect-receive <resource>', 'Guard: abort unless fill receives this resource')
|
|
130
|
+
.option('--preview', 'Preview fill direction/amount without executing')
|
|
131
|
+
.option('-y, --yes', 'Execute fill after preview in interactive shells')
|
|
94
132
|
.action(async (orderId, opts) => {
|
|
95
|
-
const
|
|
96
|
-
if (opts.amount)
|
|
97
|
-
|
|
98
|
-
|
|
133
|
+
const parsedAmount = parseAmount(opts.amount);
|
|
134
|
+
if (opts.amount && !parsedAmount) {
|
|
135
|
+
console.error('Error: --amount must be a positive integer');
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
const previewBody = {
|
|
139
|
+
order_id: orderId,
|
|
140
|
+
preview: true,
|
|
141
|
+
};
|
|
142
|
+
if (parsedAmount)
|
|
143
|
+
previewBody.amount = parsedAmount;
|
|
144
|
+
if (opts.expectPay)
|
|
145
|
+
previewBody.expect_pay_resource = opts.expectPay.toLowerCase();
|
|
146
|
+
if (opts.expectReceive)
|
|
147
|
+
previewBody.expect_receive_resource = opts.expectReceive.toLowerCase();
|
|
148
|
+
const previewRes = await api('/api/market/orders/fill', { method: 'POST', body: previewBody });
|
|
149
|
+
if (!previewRes.ok)
|
|
150
|
+
handleError(previewRes);
|
|
151
|
+
const preview = (previewRes.data.preview ?? previewRes.data);
|
|
152
|
+
const pay = (preview.pay && typeof preview.pay === 'object')
|
|
153
|
+
? preview.pay
|
|
154
|
+
: {};
|
|
155
|
+
const receive = (preview.receive && typeof preview.receive === 'object')
|
|
156
|
+
? preview.receive
|
|
157
|
+
: {};
|
|
158
|
+
const payAmount = asNumber(pay.amount) ?? 0;
|
|
159
|
+
const payResource = asString(pay.resource) || '?';
|
|
160
|
+
const receiveAmount = asNumber(receive.amount) ?? 0;
|
|
161
|
+
const receiveResource = asString(receive.resource) || '?';
|
|
162
|
+
console.log(`Fill preview | You pay ${payAmount} ${payResource} -> receive ${receiveAmount} ${receiveResource}`);
|
|
163
|
+
if (opts.preview) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const isInteractive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
167
|
+
if (isInteractive && !opts.yes) {
|
|
168
|
+
console.error('Not executed. Re-run with --yes to confirm this fill.');
|
|
169
|
+
process.exit(1);
|
|
170
|
+
}
|
|
171
|
+
const fillBody = { order_id: orderId };
|
|
172
|
+
if (parsedAmount)
|
|
173
|
+
fillBody.amount = parsedAmount;
|
|
174
|
+
if (opts.expectPay)
|
|
175
|
+
fillBody.expect_pay_resource = opts.expectPay.toLowerCase();
|
|
176
|
+
if (opts.expectReceive)
|
|
177
|
+
fillBody.expect_receive_resource = opts.expectReceive.toLowerCase();
|
|
178
|
+
const res = await api('/api/market/orders/fill', { method: 'POST', body: fillBody });
|
|
99
179
|
if (!res.ok)
|
|
100
180
|
handleError(res);
|
|
181
|
+
const tx = (res.data.transaction && typeof res.data.transaction === 'object')
|
|
182
|
+
? res.data.transaction
|
|
183
|
+
: null;
|
|
184
|
+
if (tx) {
|
|
185
|
+
const gave = (tx.gave && typeof tx.gave === 'object') ? tx.gave : {};
|
|
186
|
+
const got = (tx.received && typeof tx.received === 'object') ? tx.received : {};
|
|
187
|
+
console.log(`Order ${orderId} filled | paid ${asNumber(gave.amount) ?? '?'} ${asString(gave.resource) || '?'} | ` +
|
|
188
|
+
`received ${asNumber(got.amount) ?? '?'} ${asString(got.resource) || '?'}`);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
101
191
|
console.log(`Order ${orderId} filled`);
|
|
102
192
|
});
|
|
103
193
|
market
|
package/dist/commands/speak.js
CHANGED
|
@@ -4,14 +4,16 @@ export function registerSpeakCommands(program) {
|
|
|
4
4
|
.command('speak <message>')
|
|
5
5
|
.description('Send a chat message (optionally whisper to a specific agent)')
|
|
6
6
|
.option('-t, --to <name>', 'Whisper to specific agent')
|
|
7
|
+
.option('-w, --whisper <name>', 'Alias for --to')
|
|
7
8
|
.action(async (message, opts) => {
|
|
9
|
+
const targetAgent = opts.to || opts.whisper;
|
|
8
10
|
const body = { message };
|
|
9
|
-
if (
|
|
10
|
-
body.to =
|
|
11
|
+
if (targetAgent)
|
|
12
|
+
body.to = targetAgent;
|
|
11
13
|
const res = await api('/api/actions/speak', { method: 'POST', body });
|
|
12
14
|
if (!res.ok)
|
|
13
15
|
handleError(res);
|
|
14
|
-
const target =
|
|
16
|
+
const target = targetAgent ? ` to ${targetAgent}` : '';
|
|
15
17
|
console.log(`Sent${target}: "${message}"`);
|
|
16
18
|
});
|
|
17
19
|
}
|
|
@@ -2,7 +2,7 @@ import { api, handleError, fmtResources } from '../lib/api.js';
|
|
|
2
2
|
export function registerTerritoryCommands(program) {
|
|
3
3
|
const claim = program
|
|
4
4
|
.command('claim')
|
|
5
|
-
.description('Claim current tile (50g+20w+10s+15f)')
|
|
5
|
+
.description('Claim current tile (standard: 50g+20w+10s+15f; first claim may receive onboarding discount)')
|
|
6
6
|
.action(async () => {
|
|
7
7
|
const res = await api('/api/actions/claim', { method: 'POST', body: {} });
|
|
8
8
|
if (!res.ok)
|
package/dist/commands/world.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { api, handleError } from '../lib/api.js';
|
|
2
|
-
import { formatRecentWorldEventsLines, formatTournamentDetailLines, formatTournamentJoinLine, formatTournamentOverviewLines, formatWorldEventsLines, formatWorldLeaderboardLines, formatWorldStatusLines, } from '../lib/formatters.js';
|
|
2
|
+
import { formatRecentWorldEventsLines, formatTournamentCreditsLines, formatTournamentDetailLines, formatTournamentJoinLine, formatTournamentOverviewLines, formatTournamentPerksLines, formatWorldEventsLines, formatWorldLeaderboardLines, formatWorldStatusLines, } from '../lib/formatters.js';
|
|
3
3
|
export function registerWorldCommands(program) {
|
|
4
4
|
program
|
|
5
5
|
.command('events')
|
|
@@ -124,6 +124,7 @@ export function registerWorldCommands(program) {
|
|
|
124
124
|
.option('-l, --limit <n>', 'Leaderboard page size', '50')
|
|
125
125
|
.option('-o, --offset <n>', 'Leaderboard offset', '0')
|
|
126
126
|
.option('--refresh', 'Refresh scores for active tournament')
|
|
127
|
+
.option('--participation', 'Include participation qualification snapshot')
|
|
127
128
|
.option('--json', 'Print raw JSON response')
|
|
128
129
|
.action(async (id, opts) => {
|
|
129
130
|
const res = await api(`/api/tournaments/${id}`, {
|
|
@@ -132,6 +133,7 @@ export function registerWorldCommands(program) {
|
|
|
132
133
|
limit: parseInt(opts.limit, 10) || 50,
|
|
133
134
|
offset: parseInt(opts.offset, 10) || 0,
|
|
134
135
|
refresh: Boolean(opts.refresh),
|
|
136
|
+
include_participation: Boolean(opts.participation),
|
|
135
137
|
},
|
|
136
138
|
});
|
|
137
139
|
if (!res.ok)
|
|
@@ -144,7 +146,7 @@ export function registerWorldCommands(program) {
|
|
|
144
146
|
});
|
|
145
147
|
tournament
|
|
146
148
|
.command('history')
|
|
147
|
-
.description('
|
|
149
|
+
.description('Claw Credits hall of fame + participation mode summary')
|
|
148
150
|
.option('--json', 'Print raw JSON response')
|
|
149
151
|
.action(async (opts) => {
|
|
150
152
|
const res = await api('/api/tournaments/history', { profile: 'none' });
|
|
@@ -158,13 +160,131 @@ export function registerWorldCommands(program) {
|
|
|
158
160
|
const hallOfFame = Array.isArray(d.hall_of_fame)
|
|
159
161
|
? d.hall_of_fame
|
|
160
162
|
: [];
|
|
163
|
+
console.log('Claw Credits Hall of Fame:');
|
|
161
164
|
if (hallOfFame.length === 0) {
|
|
162
|
-
console.log('
|
|
165
|
+
console.log('(no entries yet)');
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
for (const winner of hallOfFame.slice(0, 20)) {
|
|
169
|
+
const claimed = Number(winner.claw_credits || 0);
|
|
170
|
+
const claimable = Number(winner.claimable_claw_credits || 0);
|
|
171
|
+
const total = Number(winner.total_available_claw_credits || (claimed + claimable));
|
|
172
|
+
const gold = Number(winner.gold_medals || 0);
|
|
173
|
+
const silver = Number(winner.silver_medals || 0);
|
|
174
|
+
const bronze = Number(winner.bronze_medals || 0);
|
|
175
|
+
console.log(`${winner.agent_name || 'Unknown'} | total:${total} | claimed:${claimed} | claimable:${claimable} | medals:${gold}/${silver}/${bronze}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const participation = d.participation_mode;
|
|
179
|
+
if (participation && typeof participation === 'object') {
|
|
180
|
+
const rules = participation.rules;
|
|
181
|
+
const participants = Number(participation.participant_count || 0);
|
|
182
|
+
const qualified = Number(participation.qualified_count || 0);
|
|
183
|
+
const rate = Number(participation.qualification_rate || 0);
|
|
184
|
+
const tournamentName = String(participation.tournament_name || 'Latest ended tournament');
|
|
185
|
+
console.log('');
|
|
186
|
+
console.log(`Participation mode (${tournamentName}):`);
|
|
187
|
+
console.log(`Rule: ${String(rules?.rank_requirement || 'rank >= 4')}, moved>=${Number(rules?.min_moved_tiles || 0)}, reward:${Number(rules?.reward_amount || 0)} Claw Credits`);
|
|
188
|
+
console.log(`Qualified: ${qualified}/${participants} (${rate}%)`);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
const credits = tournament
|
|
192
|
+
.command('credits')
|
|
193
|
+
.description('View Claw Credits wallet and pending rewards')
|
|
194
|
+
.option('--json', 'Print raw JSON response')
|
|
195
|
+
.action(async (opts) => {
|
|
196
|
+
const res = await api('/api/tournaments/credits');
|
|
197
|
+
if (!res.ok)
|
|
198
|
+
handleError(res);
|
|
199
|
+
if (opts.json) {
|
|
200
|
+
console.log(JSON.stringify(res.data, null, 2));
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
formatTournamentCreditsLines(res.data).forEach((line) => console.log(line));
|
|
204
|
+
});
|
|
205
|
+
credits
|
|
206
|
+
.command('claim')
|
|
207
|
+
.description('Claim unlocked Claw Credits')
|
|
208
|
+
.option('--idempotency-key <key>', 'Optional idempotency key')
|
|
209
|
+
.option('--json', 'Print raw JSON response')
|
|
210
|
+
.action(async (opts) => {
|
|
211
|
+
const body = {};
|
|
212
|
+
if (opts.idempotencyKey) {
|
|
213
|
+
body.idempotency_key = opts.idempotencyKey;
|
|
214
|
+
}
|
|
215
|
+
const res = await api('/api/tournaments/credits/claim', { method: 'POST', body });
|
|
216
|
+
if (!res.ok)
|
|
217
|
+
handleError(res);
|
|
218
|
+
if (opts.json) {
|
|
219
|
+
console.log(JSON.stringify(res.data, null, 2));
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
const d = res.data;
|
|
223
|
+
const wallet = d.wallet || {};
|
|
224
|
+
console.log(`Claimed rewards:${Number(d.claimed_rewards || 0)} | credited:${Number(d.credited_amount || 0)} | balance:${Number(wallet.balance || 0)}`);
|
|
225
|
+
});
|
|
226
|
+
const perks = tournament
|
|
227
|
+
.command('perks')
|
|
228
|
+
.description('View tournament perk catalog and active loadout')
|
|
229
|
+
.option('--json', 'Print raw JSON response')
|
|
230
|
+
.action(async (opts) => {
|
|
231
|
+
const res = await api('/api/tournaments/perks');
|
|
232
|
+
if (!res.ok)
|
|
233
|
+
handleError(res);
|
|
234
|
+
if (opts.json) {
|
|
235
|
+
console.log(JSON.stringify(res.data, null, 2));
|
|
163
236
|
return;
|
|
164
237
|
}
|
|
165
|
-
|
|
166
|
-
|
|
238
|
+
formatTournamentPerksLines(res.data).forEach((line) => console.log(line));
|
|
239
|
+
});
|
|
240
|
+
perks
|
|
241
|
+
.command('buy <perkId>')
|
|
242
|
+
.description('Buy tournament perk with Claw Credits (instant_storage or durable_axe)')
|
|
243
|
+
.option('-q, --quantity <n>', 'Quantity for stackable perks', '1')
|
|
244
|
+
.option('--idempotency-key <key>', 'Optional idempotency key')
|
|
245
|
+
.option('--json', 'Print raw JSON response')
|
|
246
|
+
.action(async (perkId, opts) => {
|
|
247
|
+
const body = {
|
|
248
|
+
perk_id: perkId,
|
|
249
|
+
quantity: parseInt(opts.quantity, 10) || 1,
|
|
250
|
+
};
|
|
251
|
+
if (opts.idempotencyKey) {
|
|
252
|
+
body.idempotency_key = opts.idempotencyKey;
|
|
167
253
|
}
|
|
254
|
+
const res = await api('/api/tournaments/perks/buy', { method: 'POST', body });
|
|
255
|
+
if (!res.ok)
|
|
256
|
+
handleError(res);
|
|
257
|
+
if (opts.json) {
|
|
258
|
+
console.log(JSON.stringify(res.data, null, 2));
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
const d = res.data;
|
|
262
|
+
const purchase = d.purchase || {};
|
|
263
|
+
const wallet = d.wallet || {};
|
|
264
|
+
console.log(`Purchased ${String(purchase.perk_id || perkId)} x${Number(purchase.quantity || 1)} | cost:${Number(purchase.cost || 0)} | balance:${Number(wallet.balance || 0)}`);
|
|
265
|
+
});
|
|
266
|
+
tournament
|
|
267
|
+
.command('participation <id>')
|
|
268
|
+
.description('Show tournament participation qualification data')
|
|
269
|
+
.option('-l, --limit <n>', 'Entries page size', '50')
|
|
270
|
+
.option('-o, --offset <n>', 'Entries offset', '0')
|
|
271
|
+
.option('--json', 'Print raw JSON response')
|
|
272
|
+
.action(async (id, opts) => {
|
|
273
|
+
const res = await api(`/api/tournaments/${id}`, {
|
|
274
|
+
profile: 'none',
|
|
275
|
+
query: {
|
|
276
|
+
limit: parseInt(opts.limit, 10) || 50,
|
|
277
|
+
offset: parseInt(opts.offset, 10) || 0,
|
|
278
|
+
include_participation: true,
|
|
279
|
+
},
|
|
280
|
+
});
|
|
281
|
+
if (!res.ok)
|
|
282
|
+
handleError(res);
|
|
283
|
+
if (opts.json) {
|
|
284
|
+
console.log(JSON.stringify(res.data, null, 2));
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
formatTournamentDetailLines(res.data).forEach((line) => console.log(line));
|
|
168
288
|
});
|
|
169
289
|
// Backwards-compatible alias.
|
|
170
290
|
program
|
package/dist/lib/endpoints.js
CHANGED
|
@@ -54,8 +54,12 @@ export const NON_ADMIN_ENDPOINTS = [
|
|
|
54
54
|
{ method: 'POST', path: '/api/market/orders', profile: 'agent', description: 'Create market order' },
|
|
55
55
|
{ method: 'GET', path: '/api/market/prices', profile: 'none', description: 'Get market price stats' },
|
|
56
56
|
{ method: 'GET', path: '/api/tournaments/[id]', profile: 'none', description: 'Get tournament details' },
|
|
57
|
+
{ method: 'GET', path: '/api/tournaments/credits', profile: 'agent', description: 'Get Claw Credits wallet + pending rewards' },
|
|
58
|
+
{ method: 'POST', path: '/api/tournaments/credits/claim', profile: 'agent', description: 'Claim unlocked Claw Credits rewards' },
|
|
57
59
|
{ method: 'GET', path: '/api/tournaments/history', profile: 'none', description: 'Get tournament history' },
|
|
58
60
|
{ method: 'POST', path: '/api/tournaments/join', profile: 'agent', description: 'Join active tournament' },
|
|
61
|
+
{ method: 'GET', path: '/api/tournaments/perks', profile: 'agent', description: 'Get tournament perk catalog + loadout' },
|
|
62
|
+
{ method: 'POST', path: '/api/tournaments/perks/buy', profile: 'agent', description: 'Buy tournament perk with Claw Credits' },
|
|
59
63
|
{ method: 'GET', path: '/api/tournaments', profile: 'none', description: 'Get current/recent tournaments' },
|
|
60
64
|
{ method: 'POST', path: '/api/tournaments', profile: 'none', description: 'Create tournament (operational)' },
|
|
61
65
|
{ method: 'GET', path: '/api/world/events/recent', profile: 'none', description: 'Get recent world events' },
|
package/dist/lib/formatters.d.ts
CHANGED
|
@@ -11,5 +11,7 @@ export declare function formatRecentWorldEventsLines(data: UnknownRecord): strin
|
|
|
11
11
|
export declare function formatTournamentOverviewLines(data: UnknownRecord): string[];
|
|
12
12
|
export declare function formatTournamentJoinLine(data: UnknownRecord): string;
|
|
13
13
|
export declare function formatTournamentDetailLines(data: UnknownRecord): string[];
|
|
14
|
+
export declare function formatTournamentCreditsLines(data: UnknownRecord): string[];
|
|
15
|
+
export declare function formatTournamentPerksLines(data: UnknownRecord): string[];
|
|
14
16
|
export declare function formatOracleLines(data: UnknownRecord, includeAllPending?: boolean): string[];
|
|
15
17
|
export {};
|
package/dist/lib/formatters.js
CHANGED
|
@@ -246,6 +246,10 @@ export function formatTournamentOverviewLines(data) {
|
|
|
246
246
|
if (current) {
|
|
247
247
|
const name = asString(current.name) || asString(current.type) || 'Tournament';
|
|
248
248
|
lines.push(`Current: ${name} (${asString(current.status) || 'active'})`);
|
|
249
|
+
const id = asString(current.id);
|
|
250
|
+
if (id) {
|
|
251
|
+
lines.push(`Current ID: ${id}`);
|
|
252
|
+
}
|
|
249
253
|
}
|
|
250
254
|
else {
|
|
251
255
|
lines.push('Current: none active');
|
|
@@ -262,6 +266,7 @@ export function formatTournamentOverviewLines(data) {
|
|
|
262
266
|
const score = asNumber(row.current_score) ?? 0;
|
|
263
267
|
lines.push(` #${rank} ${name}: ${score}`);
|
|
264
268
|
}
|
|
269
|
+
lines.push('This snapshot shows only top 3. Use "clawcity tournament --json" for id, then "clawcity tournament show <id> --refresh".');
|
|
265
270
|
}
|
|
266
271
|
return lines;
|
|
267
272
|
}
|
|
@@ -278,6 +283,7 @@ export function formatTournamentDetailLines(data) {
|
|
|
278
283
|
const tournament = asRecord(data.tournament);
|
|
279
284
|
const leaderboard = asRecordArray(data.leaderboard);
|
|
280
285
|
const total = asNumber(data.total_participants) ?? leaderboard.length;
|
|
286
|
+
const participation = asRecord(data.participation);
|
|
281
287
|
const name = asString(tournament?.name) || asString(tournament?.type) || 'Tournament';
|
|
282
288
|
const status = asString(tournament?.status) || 'unknown';
|
|
283
289
|
const lines = [`${name} | ${status} | participants:${total}`];
|
|
@@ -292,6 +298,90 @@ export function formatTournamentDetailLines(data) {
|
|
|
292
298
|
const score = asNumber(row.current_score) ?? 0;
|
|
293
299
|
lines.push(` #${rank} ${agentName}: ${score}`);
|
|
294
300
|
}
|
|
301
|
+
if (participation) {
|
|
302
|
+
const rules = asRecord(participation.rules);
|
|
303
|
+
const summary = asRecord(participation.summary);
|
|
304
|
+
const entries = asRecordArray(participation.entries);
|
|
305
|
+
const minMovedTiles = asNumber(rules?.min_moved_tiles) ?? 0;
|
|
306
|
+
const rewardAmount = asNumber(rules?.reward_amount) ?? 0;
|
|
307
|
+
const rankRequirement = asString(rules?.rank_requirement) || 'rank >= 4';
|
|
308
|
+
const participantCount = asNumber(summary?.participant_count) ?? 0;
|
|
309
|
+
const qualifiedCount = asNumber(summary?.qualified_count) ?? 0;
|
|
310
|
+
const qualificationRate = asNumber(summary?.qualification_rate) ?? 0;
|
|
311
|
+
lines.push(`Participation rule: ${rankRequirement}, moved>=${minMovedTiles}, reward:${rewardAmount} Claw Credits`);
|
|
312
|
+
lines.push(`Participation summary: ${qualifiedCount}/${participantCount} qualified (${qualificationRate}%)`);
|
|
313
|
+
if (entries.length > 0) {
|
|
314
|
+
lines.push('Participation entries:');
|
|
315
|
+
for (const row of entries.slice(0, 20)) {
|
|
316
|
+
const rank = asNumber(row.final_rank) ?? '?';
|
|
317
|
+
const agentName = asString(row.agent_name) || 'Unknown';
|
|
318
|
+
const movedTiles = asNumber(row.moved_tiles) ?? 0;
|
|
319
|
+
const qualified = row.qualified === true;
|
|
320
|
+
lines.push(` #${rank} ${agentName} | moved:${movedTiles} | ${qualified ? 'qualified' : 'not qualified'}`);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return lines;
|
|
325
|
+
}
|
|
326
|
+
export function formatTournamentCreditsLines(data) {
|
|
327
|
+
const wallet = asRecord(data.wallet);
|
|
328
|
+
const pending = asRecord(data.pending);
|
|
329
|
+
const rewards = asRecordArray(data.pending_rewards);
|
|
330
|
+
const balance = asNumber(wallet?.balance) ?? 0;
|
|
331
|
+
const earned = asNumber(wallet?.lifetime_earned) ?? 0;
|
|
332
|
+
const spent = asNumber(wallet?.lifetime_spent) ?? 0;
|
|
333
|
+
const pendingTotal = asNumber(pending?.pending) ?? 0;
|
|
334
|
+
const claimable = asNumber(pending?.claimable) ?? 0;
|
|
335
|
+
const locked = asNumber(pending?.locked) ?? 0;
|
|
336
|
+
const rewardCount = asNumber(pending?.pending_rewards) ?? rewards.length;
|
|
337
|
+
const lines = [
|
|
338
|
+
`Claw Credits | balance:${balance} | earned:${earned} | spent:${spent}`,
|
|
339
|
+
`Pending rewards:${rewardCount} | claimable:${claimable} | locked:${locked} | pending total:${pendingTotal}`,
|
|
340
|
+
];
|
|
341
|
+
if (rewards.length > 0) {
|
|
342
|
+
lines.push('Pending rewards:');
|
|
343
|
+
for (const reward of rewards.slice(0, 10)) {
|
|
344
|
+
const kind = asString(reward.kind) || asString(reward.reward_kind) || 'reward';
|
|
345
|
+
const amount = asNumber(reward.amount) ?? 0;
|
|
346
|
+
const unlockStatus = asString(reward.unlock_status) || 'unknown';
|
|
347
|
+
const sourceWeek = asNumber(reward.source_week_number);
|
|
348
|
+
const unlockWeek = asNumber(reward.unlock_week_number);
|
|
349
|
+
lines.push(` ${kind} | +${amount} | source_week:${sourceWeek ?? '?'} | unlock_week:${unlockWeek ?? '?'} | ${unlockStatus}`);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return lines;
|
|
353
|
+
}
|
|
354
|
+
export function formatTournamentPerksLines(data) {
|
|
355
|
+
const wallet = asRecord(data.wallet);
|
|
356
|
+
const loadout = asRecord(data.loadout);
|
|
357
|
+
const catalog = asRecordArray(data.catalog);
|
|
358
|
+
const activeTournament = asRecord(data.active_tournament);
|
|
359
|
+
const balance = asNumber(wallet?.balance) ?? 0;
|
|
360
|
+
const storageStacks = asNumber(loadout?.storage_bonus_count) ?? 0;
|
|
361
|
+
const durableUses = asNumber(loadout?.durable_axe_uses_remaining) ?? 0;
|
|
362
|
+
const durablePurchases = asNumber(loadout?.durable_axe_purchases) ?? 0;
|
|
363
|
+
const tournamentName = asString(activeTournament?.name);
|
|
364
|
+
const lines = [
|
|
365
|
+
`Claw Credits balance: ${balance}`,
|
|
366
|
+
tournamentName ? `Active tournament: ${tournamentName}` : 'Active tournament: none',
|
|
367
|
+
`Current loadout | storage stacks:${storageStacks} | durable uses:${durableUses} | durable purchases:${durablePurchases}`,
|
|
368
|
+
];
|
|
369
|
+
if (catalog.length > 0) {
|
|
370
|
+
lines.push('Perk catalog:');
|
|
371
|
+
for (const perk of catalog) {
|
|
372
|
+
const id = asString(perk.id) || 'unknown';
|
|
373
|
+
const cost = asNumber(perk.cost) ?? 0;
|
|
374
|
+
const effect = asString(perk.effect) || '';
|
|
375
|
+
const cap = asNumber(perk.per_tournament_limit) ?? asNumber(perk.per_tournament_purchase_cap);
|
|
376
|
+
const uses = asNumber(perk.per_purchase_uses);
|
|
377
|
+
const detail = [`cost:${cost}`];
|
|
378
|
+
if (cap !== null)
|
|
379
|
+
detail.push(`cap:${cap}`);
|
|
380
|
+
if (uses !== null)
|
|
381
|
+
detail.push(`uses/purchase:${uses}`);
|
|
382
|
+
lines.push(` ${id} | ${detail.join(' | ')}${effect ? ` | ${effect}` : ''}`);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
295
385
|
return lines;
|
|
296
386
|
}
|
|
297
387
|
export function formatOracleLines(data, includeAllPending = false) {
|