burnrate 0.1.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.
Files changed (39) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +507 -0
  3. package/dist/cli/format.d.ts +13 -0
  4. package/dist/cli/format.js +319 -0
  5. package/dist/cli/index.d.ts +7 -0
  6. package/dist/cli/index.js +1121 -0
  7. package/dist/cli/setup.d.ts +8 -0
  8. package/dist/cli/setup.js +143 -0
  9. package/dist/core/async-engine.d.ts +411 -0
  10. package/dist/core/async-engine.js +2274 -0
  11. package/dist/core/async-worldgen.d.ts +19 -0
  12. package/dist/core/async-worldgen.js +221 -0
  13. package/dist/core/engine.d.ts +154 -0
  14. package/dist/core/engine.js +1104 -0
  15. package/dist/core/pathfinding.d.ts +38 -0
  16. package/dist/core/pathfinding.js +146 -0
  17. package/dist/core/types.d.ts +489 -0
  18. package/dist/core/types.js +359 -0
  19. package/dist/core/worldgen.d.ts +22 -0
  20. package/dist/core/worldgen.js +292 -0
  21. package/dist/db/database.d.ts +83 -0
  22. package/dist/db/database.js +829 -0
  23. package/dist/db/turso-database.d.ts +177 -0
  24. package/dist/db/turso-database.js +1586 -0
  25. package/dist/mcp/server.d.ts +7 -0
  26. package/dist/mcp/server.js +1877 -0
  27. package/dist/server/api.d.ts +8 -0
  28. package/dist/server/api.js +1234 -0
  29. package/dist/server/async-tick-server.d.ts +5 -0
  30. package/dist/server/async-tick-server.js +63 -0
  31. package/dist/server/errors.d.ts +78 -0
  32. package/dist/server/errors.js +156 -0
  33. package/dist/server/rate-limit.d.ts +22 -0
  34. package/dist/server/rate-limit.js +134 -0
  35. package/dist/server/tick-server.d.ts +9 -0
  36. package/dist/server/tick-server.js +114 -0
  37. package/dist/server/validation.d.ts +194 -0
  38. package/dist/server/validation.js +114 -0
  39. package/package.json +65 -0
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * BURNRATE Setup CLI
4
+ * Configures Claude Code MCP settings for BURNRATE
5
+ *
6
+ * Usage: npx burnrate setup
7
+ */
8
+ export {};
@@ -0,0 +1,143 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * BURNRATE Setup CLI
4
+ * Configures Claude Code MCP settings for BURNRATE
5
+ *
6
+ * Usage: npx burnrate setup
7
+ */
8
+ import fs from 'fs';
9
+ import path from 'path';
10
+ import os from 'os';
11
+ import readline from 'readline';
12
+ const rl = readline.createInterface({
13
+ input: process.stdin,
14
+ output: process.stdout
15
+ });
16
+ function ask(question, defaultValue) {
17
+ const prompt = defaultValue ? `${question} [${defaultValue}]: ` : `${question}: `;
18
+ return new Promise((resolve) => {
19
+ rl.question(prompt, (answer) => {
20
+ resolve(answer.trim() || defaultValue || '');
21
+ });
22
+ });
23
+ }
24
+ async function main() {
25
+ console.log(`
26
+ ╔══════════════════════════════════════════════════════════════╗
27
+ ║ BURNRATE SETUP ║
28
+ ║ The front doesn't feed itself. ║
29
+ ╚══════════════════════════════════════════════════════════════╝
30
+ `);
31
+ // 1. Get API URL
32
+ const apiUrl = await ask('API server URL', 'https://api.burnrate.cc');
33
+ // 2. Get API key (optional — can join later)
34
+ const apiKey = await ask('API key (press Enter to skip — you can join in-game later)', '');
35
+ // 3. Validate connection
36
+ console.log(`\nTesting connection to ${apiUrl}...`);
37
+ try {
38
+ const response = await fetch(`${apiUrl}/health`);
39
+ const data = await response.json();
40
+ if (data.status === 'ok') {
41
+ console.log(` ✓ Server is online (tick ${data.tick})`);
42
+ }
43
+ else {
44
+ console.log(` ⚠ Server responded but may have issues: ${JSON.stringify(data)}`);
45
+ }
46
+ }
47
+ catch (error) {
48
+ console.log(` ✗ Could not reach ${apiUrl}`);
49
+ console.log(` Make sure the server is running and the URL is correct.`);
50
+ const proceed = await ask('Continue anyway? (y/n)', 'n');
51
+ if (proceed.toLowerCase() !== 'y') {
52
+ rl.close();
53
+ process.exit(1);
54
+ }
55
+ }
56
+ // 4. If we have an API key, validate it
57
+ if (apiKey) {
58
+ console.log('\nValidating API key...');
59
+ try {
60
+ const response = await fetch(`${apiUrl}/me`, {
61
+ headers: { 'X-API-Key': apiKey }
62
+ });
63
+ if (response.ok) {
64
+ const data = await response.json();
65
+ console.log(` ✓ Authenticated as ${data.name} (${data.tier} tier, ${data.reputation} rep)`);
66
+ }
67
+ else {
68
+ console.log(' ✗ Invalid API key. You can set it later in your MCP config.');
69
+ }
70
+ }
71
+ catch {
72
+ console.log(' ⚠ Could not validate key (server unreachable).');
73
+ }
74
+ }
75
+ // 5. Find MCP server path
76
+ // When installed via npm, the MCP server is in the package's dist/ folder
77
+ // When running from source, it's relative to this file
78
+ let mcpServerPath;
79
+ // Check if we're running from an npm install (dist/cli/setup.js)
80
+ const distPath = path.resolve(path.dirname(new URL(import.meta.url).pathname), '..', 'mcp', 'server.js');
81
+ if (fs.existsSync(distPath)) {
82
+ mcpServerPath = distPath;
83
+ }
84
+ else {
85
+ // Fallback: ask the user
86
+ mcpServerPath = await ask('Path to MCP server (dist/mcp/server.js)', path.join(process.cwd(), 'dist', 'mcp', 'server.js'));
87
+ }
88
+ console.log(`\nMCP server path: ${mcpServerPath}`);
89
+ // 6. Write Claude Code settings
90
+ const claudeSettingsDir = path.join(os.homedir(), '.claude');
91
+ const claudeSettingsPath = path.join(claudeSettingsDir, 'settings.json');
92
+ let settings = {};
93
+ if (fs.existsSync(claudeSettingsPath)) {
94
+ try {
95
+ settings = JSON.parse(fs.readFileSync(claudeSettingsPath, 'utf-8'));
96
+ }
97
+ catch {
98
+ console.log(' ⚠ Could not parse existing settings.json, creating new one.');
99
+ }
100
+ }
101
+ if (!settings.mcpServers) {
102
+ settings.mcpServers = {};
103
+ }
104
+ const env = {
105
+ BURNRATE_API_URL: apiUrl
106
+ };
107
+ if (apiKey) {
108
+ env.BURNRATE_API_KEY = apiKey;
109
+ }
110
+ settings.mcpServers.burnrate = {
111
+ command: 'node',
112
+ args: [mcpServerPath],
113
+ env
114
+ };
115
+ // Ensure directory exists
116
+ if (!fs.existsSync(claudeSettingsDir)) {
117
+ fs.mkdirSync(claudeSettingsDir, { recursive: true });
118
+ }
119
+ fs.writeFileSync(claudeSettingsPath, JSON.stringify(settings, null, 2));
120
+ console.log(`\n✓ Claude Code MCP settings written to ${claudeSettingsPath}`);
121
+ // 7. Summary
122
+ console.log(`
123
+ ╔══════════════════════════════════════════════════════════════╗
124
+ ║ SETUP COMPLETE ║
125
+ ╠══════════════════════════════════════════════════════════════╣
126
+ ║ ║
127
+ ║ Restart Claude Code to load the MCP server. ║
128
+ ║ ║
129
+ ║ Then ask Claude: ║
130
+ ║ "Use burnrate_join to create a character named MyName" ║
131
+ ║ ║
132
+ ║ Or if you already have an API key: ║
133
+ ║ "Use burnrate_status to see my inventory" ║
134
+ ║ ║
135
+ ╚══════════════════════════════════════════════════════════════╝
136
+ `);
137
+ rl.close();
138
+ }
139
+ main().catch((err) => {
140
+ console.error('Setup failed:', err);
141
+ rl.close();
142
+ process.exit(1);
143
+ });
@@ -0,0 +1,411 @@
1
+ /**
2
+ * BURNRATE Async Game Engine
3
+ * Multiplayer-ready engine using TursoDatabase
4
+ */
5
+ import { TursoDatabase } from '../db/turso-database.js';
6
+ import { Player, Shipment, Unit, MarketOrder, Contract, GameEvent, getZoneEfficiency, Inventory, Resource, IntelReportWithFreshness, ContractType, FactionRank, Faction, ShipmentType, LICENSE_REQUIREMENTS, SeasonScore, TUTORIAL_CONTRACTS } from './types.js';
7
+ export declare class AsyncGameEngine {
8
+ private db;
9
+ constructor(db: TursoDatabase);
10
+ processTick(): Promise<{
11
+ tick: number;
12
+ events: GameEvent[];
13
+ }>;
14
+ private processSupplyBurn;
15
+ private processShipments;
16
+ private completeShipment;
17
+ private checkInterception;
18
+ private getRaidersOnRoute;
19
+ private interceptShipment;
20
+ private processUnitMaintenance;
21
+ private processContractExpiration;
22
+ private resetDailyActions;
23
+ private processFieldRegeneration;
24
+ private processStockpileDecay;
25
+ canPlayerAct(playerId: string): Promise<{
26
+ allowed: boolean;
27
+ reason?: string;
28
+ }>;
29
+ recordPlayerAction(playerId: string): Promise<void>;
30
+ createShipmentWithPath(playerId: string, type: 'courier' | 'freight' | 'convoy', path: string[], cargo: Partial<Inventory>): Promise<{
31
+ success: boolean;
32
+ shipment?: Shipment;
33
+ error?: string;
34
+ }>;
35
+ placeOrder(playerId: string, zoneId: string, resource: Resource, side: 'buy' | 'sell', price: number, quantity: number): Promise<{
36
+ success: boolean;
37
+ order?: MarketOrder;
38
+ error?: string;
39
+ }>;
40
+ private matchOrders;
41
+ private executeTrade;
42
+ depositSU(playerId: string, zoneId: string, amount: number): Promise<{
43
+ success: boolean;
44
+ error?: string;
45
+ }>;
46
+ depositStockpile(playerId: string, zoneId: string, resource: 'medkits' | 'comms', amount: number): Promise<{
47
+ success: boolean;
48
+ error?: string;
49
+ }>;
50
+ getZoneEfficiency(zoneId: string): Promise<{
51
+ success: boolean;
52
+ efficiency?: ReturnType<typeof getZoneEfficiency>;
53
+ error?: string;
54
+ }>;
55
+ scan(playerId: string, targetType: 'zone' | 'route', targetId: string): Promise<{
56
+ success: boolean;
57
+ intel?: any;
58
+ error?: string;
59
+ }>;
60
+ produce(playerId: string, output: string, quantity: number): Promise<{
61
+ success: boolean;
62
+ produced?: number;
63
+ units?: Unit[];
64
+ error?: string;
65
+ }>;
66
+ extract(playerId: string, quantity: number): Promise<{
67
+ success: boolean;
68
+ extracted?: {
69
+ resource: string;
70
+ amount: number;
71
+ };
72
+ error?: string;
73
+ }>;
74
+ captureZone(playerId: string, zoneId: string): Promise<{
75
+ success: boolean;
76
+ error?: string;
77
+ }>;
78
+ assignEscort(playerId: string, unitId: string, shipmentId: string): Promise<{
79
+ success: boolean;
80
+ error?: string;
81
+ }>;
82
+ listUnitForSale(playerId: string, unitId: string, price: number): Promise<{
83
+ success: boolean;
84
+ error?: string;
85
+ }>;
86
+ unlistUnit(playerId: string, unitId: string): Promise<{
87
+ success: boolean;
88
+ error?: string;
89
+ }>;
90
+ hireUnit(playerId: string, unitId: string): Promise<{
91
+ success: boolean;
92
+ unit?: Unit;
93
+ error?: string;
94
+ }>;
95
+ deployRaider(playerId: string, unitId: string, routeId: string): Promise<{
96
+ success: boolean;
97
+ error?: string;
98
+ }>;
99
+ createFaction(playerId: string, name: string, tag: string): Promise<{
100
+ success: boolean;
101
+ faction?: any;
102
+ error?: string;
103
+ }>;
104
+ joinFaction(playerId: string, factionId: string): Promise<{
105
+ success: boolean;
106
+ error?: string;
107
+ }>;
108
+ leaveFaction(playerId: string): Promise<{
109
+ success: boolean;
110
+ error?: string;
111
+ }>;
112
+ getFactionIntel(playerId: string): Promise<{
113
+ success: boolean;
114
+ intel?: IntelReportWithFreshness[];
115
+ error?: string;
116
+ }>;
117
+ /**
118
+ * Promote a faction member to a higher rank
119
+ */
120
+ promoteFactionMember(playerId: string, targetPlayerId: string, newRank: FactionRank): Promise<{
121
+ success: boolean;
122
+ error?: string;
123
+ }>;
124
+ /**
125
+ * Demote a faction member to a lower rank
126
+ */
127
+ demoteFactionMember(playerId: string, targetPlayerId: string, newRank: FactionRank): Promise<{
128
+ success: boolean;
129
+ error?: string;
130
+ }>;
131
+ /**
132
+ * Kick a member from the faction
133
+ */
134
+ kickFactionMember(playerId: string, targetPlayerId: string): Promise<{
135
+ success: boolean;
136
+ error?: string;
137
+ }>;
138
+ /**
139
+ * Transfer faction leadership to another member
140
+ */
141
+ transferFactionLeadership(playerId: string, targetPlayerId: string): Promise<{
142
+ success: boolean;
143
+ error?: string;
144
+ }>;
145
+ /**
146
+ * Deposit resources to faction treasury
147
+ */
148
+ depositToTreasury(playerId: string, resources: Partial<Record<keyof Inventory, number>>): Promise<{
149
+ success: boolean;
150
+ error?: string;
151
+ }>;
152
+ /**
153
+ * Withdraw resources from faction treasury
154
+ */
155
+ withdrawFromTreasury(playerId: string, resources: Partial<Record<keyof Inventory, number>>): Promise<{
156
+ success: boolean;
157
+ error?: string;
158
+ }>;
159
+ /**
160
+ * Get faction details (for members)
161
+ */
162
+ getFactionDetails(playerId: string): Promise<{
163
+ success: boolean;
164
+ faction?: Faction;
165
+ myRank?: FactionRank;
166
+ error?: string;
167
+ }>;
168
+ /**
169
+ * Get available licenses and their requirements
170
+ */
171
+ getLicenseStatus(playerId: string): Promise<{
172
+ success: boolean;
173
+ licenses?: {
174
+ type: ShipmentType;
175
+ unlocked: boolean;
176
+ requirements: typeof LICENSE_REQUIREMENTS[ShipmentType];
177
+ canUnlock: boolean;
178
+ }[];
179
+ error?: string;
180
+ }>;
181
+ /**
182
+ * Award reputation to a player (can be positive or negative)
183
+ */
184
+ private awardReputation;
185
+ /**
186
+ * Get player reputation details
187
+ */
188
+ /**
189
+ * Get current season information
190
+ */
191
+ getSeasonStatus(): Promise<{
192
+ seasonNumber: number;
193
+ week: number;
194
+ ticksUntilWeekEnd: number;
195
+ ticksUntilSeasonEnd: number;
196
+ }>;
197
+ /**
198
+ * Get season leaderboard
199
+ */
200
+ getLeaderboard(seasonNumber?: number, entityType?: 'player' | 'faction', limit?: number): Promise<{
201
+ success: boolean;
202
+ leaderboard?: SeasonScore[];
203
+ season?: number;
204
+ error?: string;
205
+ }>;
206
+ /**
207
+ * Get a player's season score
208
+ */
209
+ getPlayerSeasonScore(playerId: string, seasonNumber?: number): Promise<{
210
+ success: boolean;
211
+ score?: SeasonScore | null;
212
+ rank?: number;
213
+ error?: string;
214
+ }>;
215
+ getReputationDetails(playerId: string): Promise<{
216
+ success: boolean;
217
+ reputation?: number;
218
+ title?: string;
219
+ nextTitle?: {
220
+ title: string;
221
+ threshold: number;
222
+ remaining: number;
223
+ } | null;
224
+ error?: string;
225
+ }>;
226
+ /**
227
+ * Unlock a license for a player
228
+ */
229
+ unlockLicense(playerId: string, licenseType: ShipmentType): Promise<{
230
+ success: boolean;
231
+ error?: string;
232
+ }>;
233
+ /**
234
+ * Get a player's personal intel with freshness decay applied
235
+ */
236
+ getPlayerIntel(playerId: string, limit?: number): Promise<{
237
+ success: boolean;
238
+ intel?: IntelReportWithFreshness[];
239
+ error?: string;
240
+ }>;
241
+ /**
242
+ * Get the most recent intel on a specific target
243
+ */
244
+ getTargetIntel(playerId: string, targetType: 'zone' | 'route', targetId: string): Promise<{
245
+ success: boolean;
246
+ intel?: IntelReportWithFreshness | null;
247
+ error?: string;
248
+ }>;
249
+ /**
250
+ * Create a new contract
251
+ */
252
+ createContract(playerId: string, type: ContractType, details: {
253
+ fromZoneId?: string;
254
+ toZoneId?: string;
255
+ resource?: Resource;
256
+ quantity?: number;
257
+ targetType?: 'zone' | 'route';
258
+ targetId?: string;
259
+ }, reward: number, deadline: number, bonus?: {
260
+ deadline: number;
261
+ credits: number;
262
+ }): Promise<{
263
+ success: boolean;
264
+ contract?: Contract;
265
+ error?: string;
266
+ }>;
267
+ /**
268
+ * Accept a contract
269
+ */
270
+ acceptContract(playerId: string, contractId: string): Promise<{
271
+ success: boolean;
272
+ error?: string;
273
+ }>;
274
+ /**
275
+ * Complete a contract (called when conditions are met)
276
+ */
277
+ completeContract(playerId: string, contractId: string): Promise<{
278
+ success: boolean;
279
+ reward?: {
280
+ credits: number;
281
+ reputation: number;
282
+ };
283
+ bonus?: boolean;
284
+ error?: string;
285
+ }>;
286
+ /**
287
+ * Cancel a contract (poster only, before acceptance)
288
+ */
289
+ cancelContract(playerId: string, contractId: string): Promise<{
290
+ success: boolean;
291
+ error?: string;
292
+ }>;
293
+ /**
294
+ * Get contracts related to a player
295
+ */
296
+ getMyContracts(playerId: string): Promise<{
297
+ success: boolean;
298
+ contracts?: Contract[];
299
+ error?: string;
300
+ }>;
301
+ /**
302
+ * Verify if a contract's conditions have been met
303
+ */
304
+ private verifyContractCompletion;
305
+ travel(playerId: string, toZoneId: string): Promise<{
306
+ success: boolean;
307
+ error?: string;
308
+ }>;
309
+ private findRoute;
310
+ private recordEvent;
311
+ getTutorialStatus(playerId: string): Promise<{
312
+ success: boolean;
313
+ step?: number;
314
+ total?: number;
315
+ currentContract?: typeof TUTORIAL_CONTRACTS[number] | null;
316
+ error?: string;
317
+ }>;
318
+ completeTutorialStep(playerId: string, step: number): Promise<{
319
+ success: boolean;
320
+ step?: number;
321
+ reward?: {
322
+ credits: number;
323
+ reputation: number;
324
+ };
325
+ error?: string;
326
+ }>;
327
+ ensureStarterFaction(): Promise<void>;
328
+ createDoctrine(playerId: string, title: string, content: string): Promise<{
329
+ success: boolean;
330
+ doctrine?: any;
331
+ error?: string;
332
+ }>;
333
+ getFactionDoctrines(playerId: string): Promise<{
334
+ success: boolean;
335
+ doctrines?: any[];
336
+ error?: string;
337
+ }>;
338
+ updateDoctrine(playerId: string, doctrineId: string, content: string): Promise<{
339
+ success: boolean;
340
+ error?: string;
341
+ }>;
342
+ deleteDoctrine(playerId: string, doctrineId: string): Promise<{
343
+ success: boolean;
344
+ error?: string;
345
+ }>;
346
+ createConditionalOrder(playerId: string, zoneId: string, resource: string, side: string, triggerPrice: number, quantity: number, condition: string): Promise<{
347
+ success: boolean;
348
+ order?: any;
349
+ error?: string;
350
+ }>;
351
+ createTimeWeightedOrder(playerId: string, zoneId: string, resource: string, side: string, price: number, totalQuantity: number, quantityPerTick: number): Promise<{
352
+ success: boolean;
353
+ order?: any;
354
+ error?: string;
355
+ }>;
356
+ processConditionalOrders(tick: number): Promise<void>;
357
+ processTimeWeightedOrders(tick: number): Promise<void>;
358
+ private processAdvancedOrders;
359
+ registerWebhook(playerId: string, url: string, events: string[]): Promise<{
360
+ success: boolean;
361
+ webhook?: any;
362
+ error?: string;
363
+ }>;
364
+ getWebhooks(playerId: string): Promise<{
365
+ success: boolean;
366
+ webhooks?: any[];
367
+ error?: string;
368
+ }>;
369
+ deleteWebhook(playerId: string, webhookId: string): Promise<{
370
+ success: boolean;
371
+ error?: string;
372
+ }>;
373
+ triggerWebhooks(eventType: string, data: any): Promise<void>;
374
+ exportPlayerData(playerId: string): Promise<{
375
+ success: boolean;
376
+ data?: {
377
+ player: Player;
378
+ units: Unit[];
379
+ shipments: Shipment[];
380
+ contracts: Contract[];
381
+ intel: IntelReportWithFreshness[];
382
+ events: GameEvent[];
383
+ };
384
+ error?: string;
385
+ }>;
386
+ executeBatch(playerId: string, operations: Array<{
387
+ action: string;
388
+ params: any;
389
+ }>): Promise<{
390
+ success: boolean;
391
+ results?: Array<{
392
+ action: string;
393
+ result: any;
394
+ }>;
395
+ error?: string;
396
+ }>;
397
+ getFactionAnalytics(playerId: string): Promise<{
398
+ success: boolean;
399
+ analytics?: {
400
+ members: any[];
401
+ zones: any[];
402
+ activity: any[];
403
+ };
404
+ error?: string;
405
+ }>;
406
+ getFactionAuditLogs(playerId: string, limit?: number): Promise<{
407
+ success: boolean;
408
+ logs?: any[];
409
+ error?: string;
410
+ }>;
411
+ }