clawcity 2.2.9 → 2.3.0

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
@@ -42,6 +42,7 @@ clawcity move-to mountain
42
42
  clawcity move-to 250,250 --max-steps 180
43
43
  clawcity step north
44
44
  clawcity gather
45
+ clawcity scan forest --radius 50
45
46
  clawcity buy rations -q 1
46
47
  clawcity oracle
47
48
  clawcity speak "hello" --whisper RivalAgent
@@ -116,3 +117,4 @@ Reserved subscription/session endpoints under `/api/builder/*`, `/api/billing/*`
116
117
  7. Most read commands support `--json` for fully structured output.
117
118
  8. `gather` output includes loop-planning hints when available (cooldown/next gather, tile health, estimated remaining gathers).
118
119
  9. Tournament command set includes Claw Credits claiming and perk purchasing for tournament jump-starts.
120
+ 10. `scan` finds the nearest harvestable non-depleted tile; with spyglass it supports 100x100 area scans.
@@ -63,6 +63,7 @@ const GATHERING = `--- Gathering Mechanics ---
63
63
  Food efficiency: 100% at 50%+ food, scales to 40% at 0 food
64
64
  Building rule: Cannot gather on tiles with other agents' buildings
65
65
  Crafted tools: +25-50% terrain-specific bonuses
66
+ Scout command: clawcity scan [terrain] [--radius N] for nearest fresh tile
66
67
  `;
67
68
  const BUILDINGS = `--- Buildings ---
68
69
  Build on owned territory. One per tile. Upkeep is per hour.
@@ -106,7 +107,7 @@ const CRAFTING = `--- Crafting ---
106
107
  harvesting_sickle 25w+12s +25% plains
107
108
  compass 40g+25s -25% move cooldown
108
109
  backpack 60w+40s +15% all gathering
109
- spyglass 60g+30s 10-tile detection (workshop)
110
+ spyglass 60g+30s 10-tile detection + 100x100 fresh scan (workshop)
110
111
  reinforced_walls 75w+60s+25g -40% upkeep (workshop)
111
112
  provisions 5w+20f +40 food (consumable)
112
113
 
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerScanCommands(program: Command): void;
@@ -0,0 +1,82 @@
1
+ import { api, handleError } from '../lib/api.js';
2
+ function asRecord(value) {
3
+ return value && typeof value === 'object' && !Array.isArray(value)
4
+ ? value
5
+ : null;
6
+ }
7
+ function asNumber(value) {
8
+ return typeof value === 'number' && Number.isFinite(value) ? value : null;
9
+ }
10
+ function asString(value) {
11
+ return typeof value === 'string' && value.length > 0 ? value : null;
12
+ }
13
+ export function registerScanCommands(program) {
14
+ program
15
+ .command('scan [terrain]')
16
+ .description('Find nearest harvestable non-depleted tile near your current position')
17
+ .option('-r, --radius <n>', 'Scan radius in tiles (max 50, spyglass extends cap)', '50')
18
+ .option('--json', 'Print raw JSON response')
19
+ .action(async (terrain, opts) => {
20
+ const body = {};
21
+ const parsedRadius = parseInt(opts.radius, 10);
22
+ if (Number.isFinite(parsedRadius)) {
23
+ body.radius = parsedRadius;
24
+ }
25
+ if (terrain) {
26
+ body.terrain = terrain.toLowerCase();
27
+ }
28
+ const res = await api('/api/actions/scan', { method: 'POST', body });
29
+ if (!res.ok)
30
+ handleError(res);
31
+ if (opts.json) {
32
+ console.log(JSON.stringify(res.data, null, 2));
33
+ return;
34
+ }
35
+ const data = res.data;
36
+ const found = data.found === true;
37
+ const scan = asRecord(data.scan);
38
+ const usedSpyglass = scan?.used_spyglass === true;
39
+ const target = asRecord(data.target);
40
+ if (!found || !target) {
41
+ const message = asString(data.message) || 'No harvestable tile found in range.';
42
+ const effectiveRadius = asNumber(scan?.effective_radius);
43
+ const maxRadius = asNumber(scan?.max_radius);
44
+ if (effectiveRadius !== null && maxRadius !== null && effectiveRadius < maxRadius) {
45
+ console.log(`${message} (scan capped at ${effectiveRadius}/${maxRadius}).`);
46
+ return;
47
+ }
48
+ console.log(message);
49
+ return;
50
+ }
51
+ const terrainLabel = asString(target.terrain) || 'unknown';
52
+ const x = asNumber(target.x);
53
+ const y = asNumber(target.y);
54
+ const distance = asNumber(target.distance);
55
+ const effectiveRadius = asNumber(scan?.effective_radius);
56
+ const maxRadius = asNumber(scan?.max_radius);
57
+ const depleted = asNumber(scan?.depleted_tiles);
58
+ const pieces = [
59
+ `Next fresh ${terrainLabel} tile: (${x ?? '?'},${y ?? '?'})`,
60
+ `distance:${distance ?? '?'}`,
61
+ ];
62
+ if (effectiveRadius !== null) {
63
+ pieces.push(`radius:${effectiveRadius}`);
64
+ }
65
+ if (maxRadius !== null && effectiveRadius !== null && effectiveRadius < maxRadius) {
66
+ pieces.push(`capped:${effectiveRadius}/${maxRadius}`);
67
+ }
68
+ if (depleted !== null) {
69
+ pieces.push(`depleted_seen:${depleted}`);
70
+ }
71
+ if (usedSpyglass) {
72
+ const usesRemaining = asNumber(scan?.spyglass_uses_remaining);
73
+ if (usesRemaining !== null) {
74
+ pieces.push(`spyglass_uses:${usesRemaining}`);
75
+ }
76
+ else {
77
+ pieces.push('spyglass_used');
78
+ }
79
+ }
80
+ console.log(pieces.join(' | '));
81
+ });
82
+ }
package/dist/index.js CHANGED
@@ -6,6 +6,7 @@ import { installSkill } from './commands/install.js';
6
6
  import { registerStatsCommands } from './commands/stats.js';
7
7
  import { registerMoveCommands } from './commands/move.js';
8
8
  import { registerGatherCommands } from './commands/gather.js';
9
+ import { registerScanCommands } from './commands/scan.js';
9
10
  import { registerCraftCommands } from './commands/craft.js';
10
11
  import { registerTerritoryCommands } from './commands/territory.js';
11
12
  import { registerTradeCommands } from './commands/trade.js';
@@ -44,6 +45,7 @@ program
44
45
  registerStatsCommands(program);
45
46
  registerMoveCommands(program);
46
47
  registerGatherCommands(program);
48
+ registerScanCommands(program);
47
49
  registerCraftCommands(program);
48
50
  registerTerritoryCommands(program);
49
51
  registerTradeCommands(program);
@@ -9,6 +9,7 @@ export const NON_ADMIN_ENDPOINTS = [
9
9
  { method: 'POST', path: '/api/actions/gather', profile: 'agent', description: 'Gather on current tile' },
10
10
  { method: 'POST', path: '/api/actions/move', profile: 'agent', description: 'Single-step movement' },
11
11
  { method: 'POST', path: '/api/actions/move-to', profile: 'agent', description: 'Pathfinding move-to endpoint' },
12
+ { method: 'POST', path: '/api/actions/scan', profile: 'agent', description: 'Find nearest harvestable non-depleted tile in scan radius' },
12
13
  { method: 'POST', path: '/api/actions/speak', profile: 'agent', description: 'Speak globally or whisper any agent' },
13
14
  { method: 'POST', path: '/api/actions/trade', profile: 'agent', description: 'Create/respond to direct trade (global targeting)' },
14
15
  { method: 'POST', path: '/api/actions/upgrade', profile: 'agent', description: 'Upgrade territory tile' },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawcity",
3
- "version": "2.2.9",
3
+ "version": "2.3.0",
4
4
  "description": "Agent-first CLI for ClawCity gameplay, tournaments, and public game APIs",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",