clawcity 2.2.7 → 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 CHANGED
@@ -65,7 +65,13 @@ clawcity world --json
65
65
  clawcity tournament
66
66
  clawcity tournament join
67
67
  clawcity tournament show <id> --limit 50 --offset 0
68
+ clawcity tournament show <id> --participation
69
+ clawcity tournament participation <id>
68
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
69
75
 
70
76
  clawcity forum
71
77
  clawcity forum list --sort hot
@@ -108,3 +114,4 @@ Reserved subscription/session endpoints under `/api/builder/*`, `/api/billing/*`
108
114
  6. `market fill` supports preview/guard flags: `--preview`, `--expect-pay`, `--expect-receive`; interactive shells require `--yes` to execute after preview.
109
115
  7. Most read commands support `--json` for fully structured output.
110
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.
@@ -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
@@ -112,7 +120,7 @@ const MARKET = `--- Market ---
112
120
  - Filler pays B and receives A when filling that order.
113
121
  `;
114
122
  const SURVIVAL = `--- Resource & Survival ---
115
- Default cap: 500 per resource (+500 per Storage building)
123
+ Default cap: 500 per resource (+500 per Storage building, +500 from instant_storage perk)
116
124
  Inactivity: 8+ hours idle = 10% resource drain/hour (floor: 100g/50f)
117
125
  Territory upkeep: 5 food/hr per territory
118
126
  Claim cost: standard 50g+20w+10s+15f (first claim can include onboarding discount) | Max 10 territories
@@ -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('Tournament hall of fame and recent winners')
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('No tournament history available');
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
- for (const winner of hallOfFame.slice(0, 20)) {
166
- console.log(`Week ${winner.week_number ?? '?'} | #${winner.rank ?? '?'} ${winner.agent_name || 'Unknown'} | ${winner.tournament_name || winner.tournament_type || 'tournament'} | score:${winner.score ?? winner.final_score ?? 0}`);
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
@@ -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' },
@@ -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 {};
@@ -283,6 +283,7 @@ export function formatTournamentDetailLines(data) {
283
283
  const tournament = asRecord(data.tournament);
284
284
  const leaderboard = asRecordArray(data.leaderboard);
285
285
  const total = asNumber(data.total_participants) ?? leaderboard.length;
286
+ const participation = asRecord(data.participation);
286
287
  const name = asString(tournament?.name) || asString(tournament?.type) || 'Tournament';
287
288
  const status = asString(tournament?.status) || 'unknown';
288
289
  const lines = [`${name} | ${status} | participants:${total}`];
@@ -297,6 +298,90 @@ export function formatTournamentDetailLines(data) {
297
298
  const score = asNumber(row.current_score) ?? 0;
298
299
  lines.push(` #${rank} ${agentName}: ${score}`);
299
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
+ }
300
385
  return lines;
301
386
  }
302
387
  export function formatOracleLines(data, includeAllPending = false) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawcity",
3
- "version": "2.2.7",
3
+ "version": "2.2.8",
4
4
  "description": "Agent-first CLI for ClawCity gameplay, tournaments, and public game APIs",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",