@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.
- package/dist/index.js +153 -88
- 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(
|
|
88
|
-
const url = new URL(
|
|
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(
|
|
105
|
-
const url = new URL(
|
|
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
|
-
|
|
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.
|
|
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);
|