@tbd-vote/cli 0.1.0 → 0.1.2

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 (2) hide show
  1. package/dist/index.js +153 -88
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -5,6 +5,8 @@ import { Command } from "commander";
5
5
 
6
6
  // src/commands/login.ts
7
7
  import readline from "readline";
8
+ import fs3 from "fs";
9
+ import path3 from "path";
8
10
 
9
11
  // src/lib/config.ts
10
12
  import fs from "fs";
@@ -16,6 +18,7 @@ var API_BASE_URL = "https://production-tbd-bets-api.tbd.vote";
16
18
  var WEB_URL = "https://tbd.vote";
17
19
  var API_KEY_PREFIX = "tbd_api_";
18
20
  var DEFAULT_BET_SIZE = "1.00";
21
+ var DEFAULT_MAX_BET_PER_CAMPAIGN = "20.00";
19
22
  var STRATEGY_FILENAME = "STRATEGY.md";
20
23
 
21
24
  // src/lib/config.ts
@@ -30,7 +33,8 @@ var DEFAULTS = {
30
33
  "api-key": null,
31
34
  "bet-size": DEFAULT_BET_SIZE,
32
35
  "default-status": "open",
33
- "default-limit": "20"
36
+ "default-limit": "20",
37
+ "max-bet-per-campaign": DEFAULT_MAX_BET_PER_CAMPAIGN
34
38
  };
35
39
  function getConfig() {
36
40
  try {
@@ -84,8 +88,8 @@ function getAuthHeaders() {
84
88
  function getBaseUrl() {
85
89
  return getConfigValue("api-url") || API_BASE_URL;
86
90
  }
87
- async function apiGet(path3, params) {
88
- const url = new URL(path3, getBaseUrl());
91
+ async function apiGet(path4, params) {
92
+ const url = new URL(path4, getBaseUrl());
89
93
  if (params) {
90
94
  for (const [key, value] of Object.entries(params)) {
91
95
  if (value !== void 0) {
@@ -101,8 +105,8 @@ async function apiGet(path3, params) {
101
105
  }
102
106
  return handleResponse(response);
103
107
  }
104
- async function apiPost(path3, body) {
105
- const url = new URL(path3, getBaseUrl());
108
+ async function apiPost(path4, body) {
109
+ const url = new URL(path4, getBaseUrl());
106
110
  let response;
107
111
  try {
108
112
  response = await fetch(url.toString(), {
@@ -117,7 +121,11 @@ async function apiPost(path3, body) {
117
121
  }
118
122
  async function handleResponse(response) {
119
123
  if (response.ok) {
120
- return await response.json();
124
+ const body = await response.json();
125
+ if (body.responseObject !== void 0) {
126
+ return body.responseObject;
127
+ }
128
+ return body;
121
129
  }
122
130
  if (response.status === 429) {
123
131
  const retryAfter = response.headers.get("Retry-After");
@@ -207,6 +215,102 @@ function printTable(rows, columns) {
207
215
  }
208
216
  }
209
217
 
218
+ // src/commands/strategy.ts
219
+ import fs2 from "fs";
220
+ import path2 from "path";
221
+ var DEFAULT_TEMPLATE = `# Strategy
222
+
223
+ You are an autonomous prediction market agent on tbd.vote. This file guides how you analyze campaigns and pick options.
224
+
225
+ ## Analysis Approach
226
+ - Read the campaign question carefully. Identify what specific outcome it's asking about
227
+ - Research the topic using your existing knowledge. Consider recent events, trends, and data
228
+ - Evaluate each option independently before comparing them
229
+
230
+ ## Picking a Winner
231
+ - Estimate the true probability of each option based on available information
232
+ - Compare your estimate to the market odds. Only bet when there's a gap
233
+ - Favor options where the market is underpricing a likely outcome
234
+ - If no option has a clear edge, skip the campaign entirely
235
+
236
+ ## What Makes a Good Bet
237
+ - You can articulate a specific reason the market is wrong
238
+ - The edge is based on information or reasoning, not gut feeling
239
+ - The true probability meaningfully differs from the implied odds
240
+
241
+ ## What to Avoid
242
+ - Markets you can't reason about (e.g., pure randomness)
243
+ - Questions where all options seem fairly priced
244
+ - Campaigns where you lack relevant knowledge to form a view
245
+
246
+ ## Risk Management
247
+ - The CLI enforces a max spend per campaign (default 20 USDC, adjust with \`tbd-vote config set max-bet-per-campaign <amount>\`)
248
+ - Check your balance before each betting round with \`tbd-vote balance --json\`
249
+ - Diversify across campaigns rather than concentrating on one
250
+
251
+ ## Before Each Bet
252
+ - State which option you're picking and why
253
+ - Explain what the market might be getting wrong
254
+ - Rate your confidence: low, medium, or high
255
+ `;
256
+ function getStrategyPath() {
257
+ return path2.join(getConfigDir(), STRATEGY_FILENAME);
258
+ }
259
+ function getDisplayPath() {
260
+ return `~/.tbd/${STRATEGY_FILENAME}`;
261
+ }
262
+ function registerStrategy(program2) {
263
+ const strategy = program2.command("strategy").description("View or initialize your betting strategy file").action(() => {
264
+ const jsonMode = program2.opts().json ?? false;
265
+ const filePath = getStrategyPath();
266
+ const displayPath = getDisplayPath();
267
+ if (fs2.existsSync(filePath)) {
268
+ const content = fs2.readFileSync(filePath, "utf-8");
269
+ if (jsonMode) {
270
+ printSuccess({ path: displayPath, exists: true, content }, true);
271
+ } else {
272
+ process.stdout.write(content);
273
+ }
274
+ } else {
275
+ if (jsonMode) {
276
+ printSuccess({ path: displayPath, exists: false, content: null }, true);
277
+ } else {
278
+ process.stdout.write(
279
+ `No strategy file found at ${displayPath}
280
+ Create one with: tbd-vote strategy init
281
+ `
282
+ );
283
+ }
284
+ }
285
+ });
286
+ strategy.command("init").description("Create a starter STRATEGY.md template").option("--force", "Overwrite existing file").action((opts) => {
287
+ const jsonMode = program2.opts().json ?? false;
288
+ const filePath = getStrategyPath();
289
+ const displayPath = getDisplayPath();
290
+ if (fs2.existsSync(filePath) && !opts.force) {
291
+ printError(
292
+ new Error(
293
+ `${displayPath} already exists. Use --force to overwrite.`
294
+ ),
295
+ jsonMode
296
+ );
297
+ return;
298
+ }
299
+ const configDir = getConfigDir();
300
+ if (!fs2.existsSync(configDir)) {
301
+ fs2.mkdirSync(configDir, { recursive: true });
302
+ }
303
+ const existed = fs2.existsSync(filePath);
304
+ fs2.writeFileSync(filePath, DEFAULT_TEMPLATE);
305
+ const overwrote = existed && opts.force;
306
+ const message = overwrote ? `Overwrote ${displayPath} with default template.` : `Created ${displayPath} \u2014 edit it to define your betting strategy.`;
307
+ printSuccess(
308
+ jsonMode ? { status: "ok", path: displayPath, message } : message,
309
+ jsonMode
310
+ );
311
+ });
312
+ }
313
+
210
314
  // src/commands/login.ts
211
315
  function registerLogin(program2) {
212
316
  program2.command("login").description("Authenticate with your TBD API key").option("--key <api-key>", "API key (non-interactive mode)").action(async (opts) => {
@@ -241,6 +345,7 @@ async function nonInteractiveLogin(apiKey, jsonMode) {
241
345
  return;
242
346
  }
243
347
  setConfig("api-key", apiKey);
348
+ ensureStrategyFile();
244
349
  printSuccess(
245
350
  jsonMode ? { status: "ok", message: "API key verified and saved." } : "\u2713 API key verified and saved.",
246
351
  jsonMode
@@ -292,6 +397,7 @@ async function interactiveLogin(jsonMode) {
292
397
  return;
293
398
  }
294
399
  setConfig("api-key", apiKey);
400
+ ensureStrategyFile();
295
401
  process.stdout.write(`
296
402
  \u2713 API key verified. You're ready to go!
297
403
 
@@ -299,6 +405,14 @@ async function interactiveLogin(jsonMode) {
299
405
 
300
406
  `);
301
407
  }
408
+ function ensureStrategyFile() {
409
+ const configDir = getConfigDir();
410
+ const strategyPath = path3.join(configDir, STRATEGY_FILENAME);
411
+ if (!fs3.existsSync(strategyPath)) {
412
+ fs3.mkdirSync(configDir, { recursive: true });
413
+ fs3.writeFileSync(strategyPath, DEFAULT_TEMPLATE);
414
+ }
415
+ }
302
416
 
303
417
  // src/commands/auth.ts
304
418
  function registerAuth(program2) {
@@ -352,7 +466,8 @@ var ALLOWED_KEYS = [
352
466
  "api-url",
353
467
  "bet-size",
354
468
  "default-status",
355
- "default-limit"
469
+ "default-limit",
470
+ "max-bet-per-campaign"
356
471
  ];
357
472
  function isAllowedKey(key) {
358
473
  return ALLOWED_KEYS.includes(key);
@@ -539,6 +654,17 @@ function registerBet(program2) {
539
654
  printError(new Error("Invalid bet amount."), jsonMode);
540
655
  return;
541
656
  }
657
+ const { balance } = await apiGet("/agents/balance");
658
+ if (balance < amount) {
659
+ printError(
660
+ new Error(
661
+ `Insufficient balance: ${balance.toFixed(2)} USDC available, but bet requires ${amount.toFixed(2)} USDC.
662
+ Fund your wallet at https://tbd.vote`
663
+ ),
664
+ jsonMode
665
+ );
666
+ return;
667
+ }
542
668
  const campaign = await apiGet(
543
669
  `/agents/campaigns/${campaignId}`
544
670
  );
@@ -555,9 +681,27 @@ Valid options: ${valid}`
555
681
  );
556
682
  return;
557
683
  }
684
+ const USDC_DECIMALS = 1e6;
685
+ const maxPerCampaign = parseFloat(
686
+ getConfigValue("max-bet-per-campaign") || "20.00"
687
+ );
688
+ const existingSpend = (campaign.userBets || []).reduce(
689
+ (sum, bet) => sum + bet.betAmount / USDC_DECIMALS,
690
+ 0
691
+ );
692
+ if (existingSpend + amount > maxPerCampaign) {
693
+ printError(
694
+ new Error(
695
+ `Would exceed max bet per campaign: ${existingSpend.toFixed(2)} already bet + ${amount.toFixed(2)} = ${(existingSpend + amount).toFixed(2)} USDC (max: ${maxPerCampaign.toFixed(2)} USDC).
696
+ Adjust with: tbd-vote config set max-bet-per-campaign <amount>`
697
+ ),
698
+ jsonMode
699
+ );
700
+ return;
701
+ }
558
702
  const data = await apiPost(
559
703
  "/agents/txns/place-bet",
560
- { campaign_id: Number(campaignId), option_id: numOptionId, amount }
704
+ { campaign_id: Number(campaignId), option_id: numOptionId, amount: Math.round(amount * USDC_DECIMALS) }
561
705
  );
562
706
  const result = {
563
707
  ...data,
@@ -580,88 +724,9 @@ Valid options: ${valid}`
580
724
  });
581
725
  }
582
726
 
583
- // src/commands/strategy.ts
584
- import fs2 from "fs";
585
- import path2 from "path";
586
- var DEFAULT_TEMPLATE = `# Betting Strategy
587
-
588
- ## Focus
589
- <!-- Which categories or topics should the agent prioritize? -->
590
- All categories.
591
-
592
- ## Risk Profile
593
- <!-- How aggressive should the agent bet? -->
594
- Conservative \u2014 default bet size, diversify across campaigns.
595
-
596
- ## Decision Criteria
597
- <!-- What factors should the agent weigh when picking an option? -->
598
- Favor options with clear informational edges. Avoid 50/50 coin-flip markets.
599
-
600
- ## Personality
601
- <!-- Any tone or style for the agent's reasoning? -->
602
- Analytical and data-driven. Explain reasoning before placing each bet.
603
- `;
604
- function getStrategyPath() {
605
- return path2.join(getConfigDir(), STRATEGY_FILENAME);
606
- }
607
- function getDisplayPath() {
608
- return `~/.tbd/${STRATEGY_FILENAME}`;
609
- }
610
- function registerStrategy(program2) {
611
- const strategy = program2.command("strategy").description("View or initialize your betting strategy file").action(() => {
612
- const jsonMode = program2.opts().json ?? false;
613
- const filePath = getStrategyPath();
614
- const displayPath = getDisplayPath();
615
- if (fs2.existsSync(filePath)) {
616
- const content = fs2.readFileSync(filePath, "utf-8");
617
- if (jsonMode) {
618
- printSuccess({ path: displayPath, exists: true, content }, true);
619
- } else {
620
- process.stdout.write(content);
621
- }
622
- } else {
623
- if (jsonMode) {
624
- printSuccess({ path: displayPath, exists: false, content: null }, true);
625
- } else {
626
- process.stdout.write(
627
- `No strategy file found at ${displayPath}
628
- Create one with: tbd-vote strategy init
629
- `
630
- );
631
- }
632
- }
633
- });
634
- strategy.command("init").description("Create a starter STRATEGY.md template").option("--force", "Overwrite existing file").action((opts) => {
635
- const jsonMode = program2.opts().json ?? false;
636
- const filePath = getStrategyPath();
637
- const displayPath = getDisplayPath();
638
- if (fs2.existsSync(filePath) && !opts.force) {
639
- printError(
640
- new Error(
641
- `${displayPath} already exists. Use --force to overwrite.`
642
- ),
643
- jsonMode
644
- );
645
- return;
646
- }
647
- const configDir = getConfigDir();
648
- if (!fs2.existsSync(configDir)) {
649
- fs2.mkdirSync(configDir, { recursive: true });
650
- }
651
- const existed = fs2.existsSync(filePath);
652
- fs2.writeFileSync(filePath, DEFAULT_TEMPLATE);
653
- const overwrote = existed && opts.force;
654
- const message = overwrote ? `Overwrote ${displayPath} with default template.` : `Created ${displayPath} \u2014 edit it to define your betting strategy.`;
655
- printSuccess(
656
- jsonMode ? { status: "ok", path: displayPath, message } : message,
657
- jsonMode
658
- );
659
- });
660
- }
661
-
662
727
  // src/index.ts
663
728
  var program = new Command();
664
- program.name("tbd-vote").description("CLI for AI agents to browse and bet on TBD").version("0.1.0").option("--json", "Output as JSON");
729
+ program.name("tbd-vote").description("CLI for AI agents to browse and bet on TBD").version("0.1.2").option("--json", "Output as JSON");
665
730
  registerLogin(program);
666
731
  registerAuth(program);
667
732
  registerConfig(program);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tbd-vote/cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "CLI for AI agents to browse and bet on TBD",
5
5
  "bin": {
6
6
  "tbd-vote": "./dist/index.js"