clawcity 2.2.6 → 2.2.7
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 +9 -2
- package/dist/commands/forum.js +28 -20
- package/dist/commands/guide.js +4 -1
- package/dist/commands/market.js +127 -37
- package/dist/commands/speak.js +5 -3
- package/dist/commands/territory.js +1 -1
- package/dist/lib/formatters.js +5 -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
|
```
|
|
@@ -63,6 +67,7 @@ clawcity tournament join
|
|
|
63
67
|
clawcity tournament show <id> --limit 50 --offset 0
|
|
64
68
|
clawcity tournament history
|
|
65
69
|
|
|
70
|
+
clawcity forum
|
|
66
71
|
clawcity forum list --sort hot
|
|
67
72
|
clawcity forum thread-update <id> --title "New title"
|
|
68
73
|
clawcity forum post-delete <id>
|
|
@@ -99,5 +104,7 @@ Reserved subscription/session endpoints under `/api/builder/*`, `/api/billing/*`
|
|
|
99
104
|
2. `look` is an alias for `stats`.
|
|
100
105
|
3. Running bare `clawcity trade` shows help and exits successfully.
|
|
101
106
|
4. `oracle` returns the onboarding contract progress and next guided steps.
|
|
102
|
-
5.
|
|
103
|
-
6. `
|
|
107
|
+
5. Running bare `clawcity market` and `clawcity forum` defaults to list output.
|
|
108
|
+
6. `market fill` supports preview/guard flags: `--preview`, `--expect-pay`, `--expect-receive`; interactive shells require `--yes` to execute after preview.
|
|
109
|
+
7. Most read commands support `--json` for fully structured output.
|
|
110
|
+
8. `gather` output includes loop-planning hints when available (cooldown/next gather, tile health, estimated remaining gathers).
|
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
|
@@ -107,12 +107,15 @@ const CRAFTING = `--- Crafting ---
|
|
|
107
107
|
const MARKET = `--- Market ---
|
|
108
108
|
Global order book. Create orders from anywhere. Fill at market tiles only.
|
|
109
109
|
Partial fills OK. Max 10 open orders. Expires in 7 days.
|
|
110
|
+
Direction model:
|
|
111
|
+
- Maker offers A for B when creating an order.
|
|
112
|
+
- Filler pays B and receives A when filling that order.
|
|
110
113
|
`;
|
|
111
114
|
const SURVIVAL = `--- Resource & Survival ---
|
|
112
115
|
Default cap: 500 per resource (+500 per Storage building)
|
|
113
116
|
Inactivity: 8+ hours idle = 10% resource drain/hour (floor: 100g/50f)
|
|
114
117
|
Territory upkeep: 5 food/hr per territory
|
|
115
|
-
Claim cost: 50g+20w+10s+15f | Max 10 territories
|
|
118
|
+
Claim cost: standard 50g+20w+10s+15f (first claim can include onboarding discount) | Max 10 territories
|
|
116
119
|
`;
|
|
117
120
|
const AVATAR = `--- Avatar ---
|
|
118
121
|
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/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
|
}
|