@tokenomist-ai/tokenomist-cli 0.1.1 → 0.2.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/LICENSE +1 -1
- package/README.md +28 -14
- package/dist/chunk-DCGA3ANT.js +84 -0
- package/dist/chunk-DCGA3ANT.js.map +1 -0
- package/dist/index.js +454 -60
- package/dist/index.js.map +1 -1
- package/dist/update-check.js +3 -80
- package/dist/update-check.js.map +1 -1
- package/package.json +2 -2
- package/skills/tokenomist-allocation-breakdown/SKILL.md +2 -1
- package/skills/tokenomist-burn-buyback/SKILL.md +6 -2
- package/skills/tokenomist-emission-report/SKILL.md +4 -0
- package/skills/tokenomist-fundraising-lookup/SKILL.md +4 -0
- package/skills/tokenomist-point-in-time/SKILL.md +95 -0
- package/skills/tokenomist-unlock-analysis/SKILL.md +4 -0
package/dist/index.js
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
package_default
|
|
4
|
+
} from "./chunk-DCGA3ANT.js";
|
|
2
5
|
|
|
3
6
|
// src/index.ts
|
|
4
|
-
import { Command as
|
|
7
|
+
import { Command as Command12 } from "commander";
|
|
5
8
|
|
|
6
9
|
// src/commands/token.ts
|
|
7
10
|
import { Command } from "commander";
|
|
8
11
|
|
|
9
12
|
// src/api/errors.ts
|
|
10
13
|
function classifyStatus(status) {
|
|
14
|
+
if (status === 400) return "BAD_REQUEST" /* BAD_REQUEST */;
|
|
11
15
|
if (status === 401) return "UNAUTHORIZED" /* UNAUTHORIZED */;
|
|
12
16
|
if (status === 403) return "FORBIDDEN" /* FORBIDDEN */;
|
|
13
17
|
if (status === 404) return "NOT_FOUND" /* NOT_FOUND */;
|
|
18
|
+
if (status === 422) return "VALIDATION_ERROR" /* VALIDATION_ERROR */;
|
|
14
19
|
if (status === 429) return "RATE_LIMITED" /* RATE_LIMITED */;
|
|
15
20
|
if (status >= 500 && status < 600) return "SERVER_ERROR" /* SERVER_ERROR */;
|
|
16
21
|
return "UNKNOWN" /* UNKNOWN */;
|
|
@@ -30,6 +35,14 @@ var ApiError = class _ApiError extends Error {
|
|
|
30
35
|
this.name = "ApiError";
|
|
31
36
|
}
|
|
32
37
|
retriesExhausted = false;
|
|
38
|
+
/** How many attempts were made before giving up (set with retriesExhausted). */
|
|
39
|
+
attempts;
|
|
40
|
+
/** Point-in-time routes: machine-readable cause, e.g. NO_VERSION_AT_AS_OF. */
|
|
41
|
+
errorCode;
|
|
42
|
+
/** Point-in-time routes: the next/first `asOf` that has a version (on NO_VERSION_AT_AS_OF, or inside the plan's range on PLAN_NOT_ELIGIBLE). */
|
|
43
|
+
earliestValidDate;
|
|
44
|
+
/** 422 validation errors: one entry per failing field. */
|
|
45
|
+
fieldErrors;
|
|
33
46
|
get retryable() {
|
|
34
47
|
return this.code === "RATE_LIMITED" /* RATE_LIMITED */ || this.code === "SERVER_ERROR" /* SERVER_ERROR */ || this.code === "NETWORK_ERROR" /* NETWORK_ERROR */;
|
|
35
48
|
}
|
|
@@ -37,30 +50,56 @@ var ApiError = class _ApiError extends Error {
|
|
|
37
50
|
const b = body && typeof body === "object" ? body : {};
|
|
38
51
|
const errorMessage = typeof b.errorMessage === "string" ? b.errorMessage : void 0;
|
|
39
52
|
const errorDetail = typeof b.errorDetail === "string" ? b.errorDetail : void 0;
|
|
53
|
+
let err;
|
|
40
54
|
if (status === 200 && b.status === false) {
|
|
41
55
|
const bodyStatus = typeof b.statusCode === "number" ? b.statusCode : 0;
|
|
42
|
-
const
|
|
43
|
-
|
|
56
|
+
const code = bodyStatus > 0 ? classifyStatus(bodyStatus) : "UNKNOWN" /* UNKNOWN */;
|
|
57
|
+
err = new _ApiError(
|
|
44
58
|
errorMessage || "API request failed",
|
|
45
|
-
|
|
59
|
+
code,
|
|
46
60
|
errorDetail,
|
|
47
61
|
bodyStatus || void 0
|
|
48
62
|
);
|
|
63
|
+
} else {
|
|
64
|
+
err = new _ApiError(
|
|
65
|
+
errorMessage || `HTTP ${status}`,
|
|
66
|
+
classifyStatus(status),
|
|
67
|
+
errorDetail,
|
|
68
|
+
status,
|
|
69
|
+
parseRetryAfter(retryAfterHeader)
|
|
70
|
+
);
|
|
49
71
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
72
|
+
if (typeof b.errorCode === "string" && b.errorCode) err.errorCode = b.errorCode;
|
|
73
|
+
if (typeof b.earliestValidDate === "string" && b.earliestValidDate) {
|
|
74
|
+
err.earliestValidDate = b.earliestValidDate;
|
|
75
|
+
}
|
|
76
|
+
if (Array.isArray(b.data)) {
|
|
77
|
+
const fields = b.data.filter(
|
|
78
|
+
(f) => !!f && typeof f === "object" && typeof f.message === "string"
|
|
79
|
+
);
|
|
80
|
+
if (fields.length > 0) err.fieldErrors = fields;
|
|
81
|
+
}
|
|
82
|
+
return err;
|
|
58
83
|
}
|
|
59
84
|
static fromNetworkError(error) {
|
|
60
85
|
const message = error instanceof Error ? error.message : String(error);
|
|
61
86
|
return new _ApiError(message, "NETWORK_ERROR" /* NETWORK_ERROR */);
|
|
62
87
|
}
|
|
63
88
|
};
|
|
89
|
+
function errorHeadline(err) {
|
|
90
|
+
const code = err.errorCode ? `${err.code} (${err.errorCode})` : err.code;
|
|
91
|
+
return `${code}: ${err.message}`;
|
|
92
|
+
}
|
|
93
|
+
function formatApiError(err) {
|
|
94
|
+
const gaveUp = err.retriesExhausted ? ` (gave up after ${err.attempts ?? "repeated"} attempts)` : "";
|
|
95
|
+
const lines = [`${errorHeadline(err)}${gaveUp}`];
|
|
96
|
+
if (err.detail && err.detail !== err.message) lines.push(`Detail: ${err.detail}`);
|
|
97
|
+
for (const f of err.fieldErrors ?? []) {
|
|
98
|
+
lines.push(` - ${f.key}: ${f.message}`);
|
|
99
|
+
}
|
|
100
|
+
if (err.earliestValidDate) lines.push(`Earliest valid date: ${err.earliestValidDate}`);
|
|
101
|
+
return lines;
|
|
102
|
+
}
|
|
64
103
|
|
|
65
104
|
// src/api/retry.ts
|
|
66
105
|
var DEFAULTS = {
|
|
@@ -91,13 +130,14 @@ async function withRetry(fn, options) {
|
|
|
91
130
|
const calculated = baseDelay * Math.pow(2, attempt - 1) + jitter;
|
|
92
131
|
const delay = err.retryAfter ? Math.min(err.retryAfter * 1e3, maxDelay) : Math.min(calculated, maxDelay);
|
|
93
132
|
process.stderr.write(
|
|
94
|
-
`${err
|
|
133
|
+
`${errorHeadline(err)}. Retrying in ${Math.round(delay / 1e3)}s... (attempt ${attempt + 1}/${maxAttempts})
|
|
95
134
|
`
|
|
96
135
|
);
|
|
97
136
|
await sleep(delay);
|
|
98
137
|
}
|
|
99
138
|
}
|
|
100
139
|
lastError.retriesExhausted = true;
|
|
140
|
+
lastError.attempts = maxAttempts;
|
|
101
141
|
throw lastError;
|
|
102
142
|
}
|
|
103
143
|
|
|
@@ -207,20 +247,25 @@ var TokenomistClient = class {
|
|
|
207
247
|
this.cache = new ResponseCache();
|
|
208
248
|
}
|
|
209
249
|
cache;
|
|
250
|
+
/** GET a V5 route and return its `data` only. */
|
|
210
251
|
async get(path, params) {
|
|
252
|
+
return (await this.getWithMeta(path, params)).data;
|
|
253
|
+
}
|
|
254
|
+
/** GET a V5 route and return `data` together with the envelope's `metadata`. */
|
|
255
|
+
async getWithMeta(path, params) {
|
|
211
256
|
const url = this.buildUrl(path, params);
|
|
212
257
|
const urlStr = url.toString();
|
|
213
258
|
const cacheKey = this.cache.buildKey(urlStr, this.apiKey);
|
|
214
259
|
const useCache = this.options?.noCache !== true;
|
|
215
260
|
if (useCache) {
|
|
216
261
|
const cached = this.cache.get(cacheKey);
|
|
217
|
-
if (cached) return cached;
|
|
262
|
+
if (cached && typeof cached === "object" && "metadata" in cached) return cached;
|
|
218
263
|
}
|
|
219
|
-
const
|
|
264
|
+
const result = await withRetry(() => this.fetchFromApi(url));
|
|
220
265
|
if (useCache) {
|
|
221
|
-
this.cache.set(cacheKey, urlStr,
|
|
266
|
+
this.cache.set(cacheKey, urlStr, result);
|
|
222
267
|
}
|
|
223
|
-
return
|
|
268
|
+
return result;
|
|
224
269
|
}
|
|
225
270
|
buildUrl(path, params) {
|
|
226
271
|
const url = new URL(path, this.baseUrl);
|
|
@@ -231,6 +276,7 @@ var TokenomistClient = class {
|
|
|
231
276
|
}
|
|
232
277
|
}
|
|
233
278
|
}
|
|
279
|
+
url.searchParams.set("showError", "true");
|
|
234
280
|
return url;
|
|
235
281
|
}
|
|
236
282
|
async fetchFromApi(url) {
|
|
@@ -258,7 +304,9 @@ var TokenomistClient = class {
|
|
|
258
304
|
if (!body.status) {
|
|
259
305
|
throw ApiError.fromResponse(200, body);
|
|
260
306
|
}
|
|
261
|
-
|
|
307
|
+
const metadata = body.metadata ?? {};
|
|
308
|
+
const data = body.data ?? (metadata.page !== void 0 ? [] : null);
|
|
309
|
+
return { data, metadata };
|
|
262
310
|
}
|
|
263
311
|
};
|
|
264
312
|
|
|
@@ -430,21 +478,61 @@ import { Command as Command2 } from "commander";
|
|
|
430
478
|
|
|
431
479
|
// src/api/unlock.ts
|
|
432
480
|
async function listUnlockEvents(client, params) {
|
|
433
|
-
return client.
|
|
481
|
+
return client.getWithMeta(
|
|
482
|
+
"/v5/unlock/events/list",
|
|
483
|
+
params
|
|
484
|
+
);
|
|
434
485
|
}
|
|
435
486
|
async function listUpcomingUnlockEvents(client, params) {
|
|
436
|
-
return client.
|
|
487
|
+
return client.getWithMeta(
|
|
437
488
|
"/v5/unlock/events/upcoming",
|
|
438
489
|
params
|
|
439
490
|
);
|
|
440
491
|
}
|
|
441
492
|
async function getUnlockEvents(client, tokenId, params) {
|
|
442
|
-
return client.
|
|
493
|
+
return client.getWithMeta(
|
|
443
494
|
`/v5/unlock/events/${encodeURIComponent(tokenId)}`,
|
|
444
495
|
params
|
|
445
496
|
);
|
|
446
497
|
}
|
|
447
498
|
|
|
499
|
+
// src/output/pagination.ts
|
|
500
|
+
function paginationOf(metadata) {
|
|
501
|
+
if (metadata.page === void 0 || metadata.totalPages === void 0) return void 0;
|
|
502
|
+
return {
|
|
503
|
+
page: metadata.page,
|
|
504
|
+
totalPages: metadata.totalPages,
|
|
505
|
+
pageSize: metadata.pageSize ?? 0,
|
|
506
|
+
// Upstream omits `total` when it is 0.
|
|
507
|
+
total: metadata.total ?? 0
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
function formatPaginationHint(p) {
|
|
511
|
+
const parts = [`Page ${p.page}/${p.totalPages}`, `${p.total.toLocaleString()} total`];
|
|
512
|
+
if (p.page < p.totalPages) {
|
|
513
|
+
parts.push(`next: --page ${p.page + 1} (or raise --page-size)`);
|
|
514
|
+
}
|
|
515
|
+
return parts.join(" \xB7 ");
|
|
516
|
+
}
|
|
517
|
+
function printPaginationHint(metadata, stream = process.stderr) {
|
|
518
|
+
const p = paginationOf(metadata);
|
|
519
|
+
if (!p) return;
|
|
520
|
+
stream.write(`${formatPaginationHint(p)}
|
|
521
|
+
`);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// src/commands/validate.ts
|
|
525
|
+
function validateDateRange(start, end, names = ["--start", "--end"]) {
|
|
526
|
+
if (start && end && start > end) {
|
|
527
|
+
throw new Error(`${names[0]} ${start} is after ${names[1]} ${end}`);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
function validateVersionSelector(asOf, versionId) {
|
|
531
|
+
if (asOf && versionId) {
|
|
532
|
+
throw new Error("Use either --as-of or --version-id, not both");
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
448
536
|
// src/commands/unlock.ts
|
|
449
537
|
var listColumns = [
|
|
450
538
|
{ key: "tokenName", header: "Token" },
|
|
@@ -491,7 +579,11 @@ addListFilterOptions(
|
|
|
491
579
|
const client = new TokenomistClient(config.apiKey, config.baseUrl, {
|
|
492
580
|
noCache: config.noCache
|
|
493
581
|
});
|
|
494
|
-
|
|
582
|
+
validateDateRange(opts.minUnlockDate, opts.maxUnlockDate, [
|
|
583
|
+
"--min-unlock-date",
|
|
584
|
+
"--max-unlock-date"
|
|
585
|
+
]);
|
|
586
|
+
const { data, metadata } = await listUnlockEvents(client, {
|
|
495
587
|
minMarketCap: opts.minMarketCap,
|
|
496
588
|
minReleasedPercentage: opts.minReleasedPercentage,
|
|
497
589
|
minUnlockDate: opts.minUnlockDate,
|
|
@@ -507,6 +599,7 @@ addListFilterOptions(
|
|
|
507
599
|
eventCount: d.events.length
|
|
508
600
|
}));
|
|
509
601
|
output(rows, listColumns, globalOpts.output || "table");
|
|
602
|
+
printPaginationHint(metadata);
|
|
510
603
|
});
|
|
511
604
|
addUpcomingFilterOptions(
|
|
512
605
|
unlockCommand.command("upcoming").description("List next upcoming unlock event per token")
|
|
@@ -516,7 +609,8 @@ addUpcomingFilterOptions(
|
|
|
516
609
|
const client = new TokenomistClient(config.apiKey, config.baseUrl, {
|
|
517
610
|
noCache: config.noCache
|
|
518
611
|
});
|
|
519
|
-
|
|
612
|
+
validateDateRange(opts.start, opts.end);
|
|
613
|
+
const { data, metadata } = await listUpcomingUnlockEvents(client, {
|
|
520
614
|
start: opts.start,
|
|
521
615
|
end: opts.end,
|
|
522
616
|
standardAllocation: opts.standardAllocation,
|
|
@@ -538,6 +632,7 @@ addUpcomingFilterOptions(
|
|
|
538
632
|
upcomingColumns,
|
|
539
633
|
globalOpts.output || "table"
|
|
540
634
|
);
|
|
635
|
+
printPaginationHint(metadata);
|
|
541
636
|
});
|
|
542
637
|
addPaginationOptions(
|
|
543
638
|
unlockCommand.command("events <tokenId>").description("Get unlock events for a specific token").option("--start <date>", "Start date (YYYY-MM-DD)").option("--end <date>", "End date (YYYY-MM-DD)").option("--standard-allocation <names>", "Filter by allocation (comma-separated)")
|
|
@@ -547,7 +642,8 @@ addPaginationOptions(
|
|
|
547
642
|
const client = new TokenomistClient(config.apiKey, config.baseUrl, {
|
|
548
643
|
noCache: config.noCache
|
|
549
644
|
});
|
|
550
|
-
|
|
645
|
+
validateDateRange(opts.start, opts.end);
|
|
646
|
+
const { data, metadata } = await getUnlockEvents(client, tokenId, {
|
|
551
647
|
start: opts.start,
|
|
552
648
|
end: opts.end,
|
|
553
649
|
standardAllocation: opts.standardAllocation,
|
|
@@ -561,6 +657,7 @@ addPaginationOptions(
|
|
|
561
657
|
valueToMarketCap: d.cliffUnlocks.valueToMarketCap
|
|
562
658
|
}));
|
|
563
659
|
output(rows, eventsColumns, globalOpts.output || "table");
|
|
660
|
+
printPaginationHint(metadata);
|
|
564
661
|
});
|
|
565
662
|
|
|
566
663
|
// src/commands/investor.ts
|
|
@@ -568,7 +665,7 @@ import { Command as Command3 } from "commander";
|
|
|
568
665
|
|
|
569
666
|
// src/api/investor.ts
|
|
570
667
|
async function listInvestors(client, params) {
|
|
571
|
-
return client.
|
|
668
|
+
return client.getWithMeta(
|
|
572
669
|
"/v5/fundraising/investors/list",
|
|
573
670
|
params
|
|
574
671
|
);
|
|
@@ -605,7 +702,11 @@ investorCommand.command("list").description("List all investors with their fundi
|
|
|
605
702
|
const client = new TokenomistClient(config.apiKey, config.baseUrl, {
|
|
606
703
|
noCache: config.noCache
|
|
607
704
|
});
|
|
608
|
-
|
|
705
|
+
validateDateRange(opts.startLatestFundingDate, opts.endLatestFundingDate, [
|
|
706
|
+
"--start-latest-funding-date",
|
|
707
|
+
"--end-latest-funding-date"
|
|
708
|
+
]);
|
|
709
|
+
const { data, metadata } = await listInvestors(client, {
|
|
609
710
|
startLatestFundingDate: opts.startLatestFundingDate,
|
|
610
711
|
endLatestFundingDate: opts.endLatestFundingDate,
|
|
611
712
|
page: opts.page,
|
|
@@ -613,9 +714,11 @@ investorCommand.command("list").description("List all investors with their fundi
|
|
|
613
714
|
});
|
|
614
715
|
const rows = data.map((d) => ({
|
|
615
716
|
...d,
|
|
616
|
-
|
|
717
|
+
// The list omits fundingRounds in production; null renders as "—", not a misleading 0.
|
|
718
|
+
roundCount: d.fundingRounds ? d.fundingRounds.length : null
|
|
617
719
|
}));
|
|
618
720
|
output(rows, listColumns2, globalOpts.output || "table");
|
|
721
|
+
printPaginationHint(metadata);
|
|
619
722
|
});
|
|
620
723
|
investorCommand.command("detail <investorId>").description("Get investor detail with funding rounds").option("--is-lead-investor", "Filter by lead investor status").option("--funding-date-min <date>", "Min funding date (YYYY-MM-DD)").option("--funding-date-max <date>", "Max funding date (YYYY-MM-DD)").option("--raised-amount-min <value>", "Min raised amount").option("--raised-amount-max <value>", "Max raised amount").action(async (investorId, opts, cmd) => {
|
|
621
724
|
const globalOpts = cmd.optsWithGlobals();
|
|
@@ -695,13 +798,15 @@ import { Command as Command5 } from "commander";
|
|
|
695
798
|
|
|
696
799
|
// src/api/burn.ts
|
|
697
800
|
async function listBurns(client, params) {
|
|
698
|
-
return client.
|
|
801
|
+
return client.getWithMeta("/v5/burn/list", { ...params });
|
|
699
802
|
}
|
|
700
803
|
async function getBurnDetail(client, tokenId, params) {
|
|
701
|
-
|
|
804
|
+
const data = await client.get(
|
|
702
805
|
`/v5/burn/${encodeURIComponent(tokenId)}`,
|
|
703
806
|
params
|
|
704
807
|
);
|
|
808
|
+
if (!data.burns?.length && data.totalBurnValue == null) data.totalBurnValue = 0;
|
|
809
|
+
return data;
|
|
705
810
|
}
|
|
706
811
|
|
|
707
812
|
// src/commands/burn.ts
|
|
@@ -709,13 +814,17 @@ var listColumns3 = [
|
|
|
709
814
|
{ key: "tokenId", header: "ID" },
|
|
710
815
|
{ key: "tokenName", header: "Name" },
|
|
711
816
|
{ key: "tokenSymbol", header: "Symbol" },
|
|
712
|
-
{ key: "totalBurnAmount", header: "Total Burned", align: "right" }
|
|
817
|
+
{ key: "totalBurnAmount", header: "Total Burned", align: "right" },
|
|
818
|
+
{ key: "totalBurnValue", header: "Total Value (USD)", align: "right" }
|
|
713
819
|
];
|
|
714
820
|
var detailColumns2 = [
|
|
715
821
|
{ key: "burnEventLabel", header: "Label" },
|
|
716
822
|
{ key: "burnType", header: "Type" },
|
|
717
823
|
{ key: "burnDate", header: "Date" },
|
|
718
|
-
{ key: "amount", header: "Amount", align: "right" }
|
|
824
|
+
{ key: "amount", header: "Amount", align: "right" },
|
|
825
|
+
// Added in 0.2.0 — appended so 0.1.x CSV columns keep their positions.
|
|
826
|
+
{ key: "pricePerToken", header: "Price", align: "right" },
|
|
827
|
+
{ key: "value", header: "Value (USD)", align: "right" }
|
|
719
828
|
];
|
|
720
829
|
var burnCommand = new Command5("burn").description("Token burn commands");
|
|
721
830
|
burnCommand.command("list").description("List all tokens with burn events").option("--token-id <ids>", "Filter by token IDs (comma-separated)").option("--page <number>", "Page number").option("--page-size <number>", "Items per page").action(async (opts, cmd) => {
|
|
@@ -724,12 +833,13 @@ burnCommand.command("list").description("List all tokens with burn events").opti
|
|
|
724
833
|
const client = new TokenomistClient(config.apiKey, config.baseUrl, {
|
|
725
834
|
noCache: config.noCache
|
|
726
835
|
});
|
|
727
|
-
const data = await listBurns(client, {
|
|
836
|
+
const { data, metadata } = await listBurns(client, {
|
|
728
837
|
tokenId: opts.tokenId,
|
|
729
838
|
page: opts.page,
|
|
730
839
|
pageSize: opts.pageSize
|
|
731
840
|
});
|
|
732
841
|
output(data, listColumns3, globalOpts.output || "table");
|
|
842
|
+
printPaginationHint(metadata);
|
|
733
843
|
});
|
|
734
844
|
burnCommand.command("detail <tokenId>").description("Get burn events for a specific token").option("--start <date>", "Start date (YYYY-MM-DD)").option("--end <date>", "End date (YYYY-MM-DD)").action(async (tokenId, opts, cmd) => {
|
|
735
845
|
const globalOpts = cmd.optsWithGlobals();
|
|
@@ -737,6 +847,7 @@ burnCommand.command("detail <tokenId>").description("Get burn events for a speci
|
|
|
737
847
|
const client = new TokenomistClient(config.apiKey, config.baseUrl, {
|
|
738
848
|
noCache: config.noCache
|
|
739
849
|
});
|
|
850
|
+
validateDateRange(opts.start, opts.end);
|
|
740
851
|
const data = await getBurnDetail(client, tokenId, {
|
|
741
852
|
start: opts.start,
|
|
742
853
|
end: opts.end
|
|
@@ -747,6 +858,9 @@ burnCommand.command("detail <tokenId>").description("Get burn events for a speci
|
|
|
747
858
|
} else {
|
|
748
859
|
console.log(`Token: ${data.tokenName} (${data.tokenSymbol})`);
|
|
749
860
|
console.log(`Total Burned: ${data.totalBurnAmount.toLocaleString()}`);
|
|
861
|
+
if (outputMode === "table") {
|
|
862
|
+
console.log(`Total Value (USD): ${data.totalBurnValue?.toLocaleString() ?? "\u2014"}`);
|
|
863
|
+
}
|
|
750
864
|
console.log();
|
|
751
865
|
output(data.burns, detailColumns2, outputMode);
|
|
752
866
|
}
|
|
@@ -757,13 +871,15 @@ import { Command as Command6 } from "commander";
|
|
|
757
871
|
|
|
758
872
|
// src/api/buyback.ts
|
|
759
873
|
async function listBuybacks(client, params) {
|
|
760
|
-
return client.
|
|
874
|
+
return client.getWithMeta("/v5/buyback/list", { ...params });
|
|
761
875
|
}
|
|
762
876
|
async function getBuybackDetail(client, tokenId, params) {
|
|
763
|
-
|
|
877
|
+
const data = await client.get(
|
|
764
878
|
`/v5/buyback/${encodeURIComponent(tokenId)}`,
|
|
765
879
|
params
|
|
766
880
|
);
|
|
881
|
+
if (!data.buybacks?.length && data.totalBuybackValue == null) data.totalBuybackValue = 0;
|
|
882
|
+
return data;
|
|
767
883
|
}
|
|
768
884
|
|
|
769
885
|
// src/commands/buyback.ts
|
|
@@ -771,7 +887,8 @@ var listColumns4 = [
|
|
|
771
887
|
{ key: "tokenId", header: "ID" },
|
|
772
888
|
{ key: "tokenName", header: "Name" },
|
|
773
889
|
{ key: "tokenSymbol", header: "Symbol" },
|
|
774
|
-
{ key: "totalBuybackAmount", header: "Total Buyback", align: "right" }
|
|
890
|
+
{ key: "totalBuybackAmount", header: "Total Buyback", align: "right" },
|
|
891
|
+
{ key: "totalBuybackValue", header: "Total Value (USD)", align: "right" }
|
|
775
892
|
];
|
|
776
893
|
var detailColumns3 = [
|
|
777
894
|
{ key: "buybackEventLabel", header: "Label" },
|
|
@@ -780,7 +897,9 @@ var detailColumns3 = [
|
|
|
780
897
|
{ key: "tokenAmount", header: "Amount", align: "right" },
|
|
781
898
|
{ key: "value", header: "Value", align: "right" },
|
|
782
899
|
{ key: "spentAmount", header: "Spent", align: "right" },
|
|
783
|
-
{ key: "spentUnit", header: "Unit" }
|
|
900
|
+
{ key: "spentUnit", header: "Unit" },
|
|
901
|
+
// Added in 0.2.0 — appended so 0.1.x CSV columns keep their positions.
|
|
902
|
+
{ key: "pricePerToken", header: "Price", align: "right" }
|
|
784
903
|
];
|
|
785
904
|
var buybackCommand = new Command6("buyback").description("Token buyback commands");
|
|
786
905
|
buybackCommand.command("list").description("List all tokens with buyback events").option("--token-id <ids>", "Filter by token IDs (comma-separated)").option("--page <number>", "Page number").option("--page-size <number>", "Items per page").action(async (opts, cmd) => {
|
|
@@ -789,12 +908,13 @@ buybackCommand.command("list").description("List all tokens with buyback events"
|
|
|
789
908
|
const client = new TokenomistClient(config.apiKey, config.baseUrl, {
|
|
790
909
|
noCache: config.noCache
|
|
791
910
|
});
|
|
792
|
-
const data = await listBuybacks(client, {
|
|
911
|
+
const { data, metadata } = await listBuybacks(client, {
|
|
793
912
|
tokenId: opts.tokenId,
|
|
794
913
|
page: opts.page,
|
|
795
914
|
pageSize: opts.pageSize
|
|
796
915
|
});
|
|
797
916
|
output(data, listColumns4, globalOpts.output || "table");
|
|
917
|
+
printPaginationHint(metadata);
|
|
798
918
|
});
|
|
799
919
|
buybackCommand.command("detail <tokenId>").description("Get buyback events for a specific token").option("--start <date>", "Start date (YYYY-MM-DD)").option("--end <date>", "End date (YYYY-MM-DD)").action(async (tokenId, opts, cmd) => {
|
|
800
920
|
const globalOpts = cmd.optsWithGlobals();
|
|
@@ -802,6 +922,7 @@ buybackCommand.command("detail <tokenId>").description("Get buyback events for a
|
|
|
802
922
|
const client = new TokenomistClient(config.apiKey, config.baseUrl, {
|
|
803
923
|
noCache: config.noCache
|
|
804
924
|
});
|
|
925
|
+
validateDateRange(opts.start, opts.end);
|
|
805
926
|
const data = await getBuybackDetail(client, tokenId, {
|
|
806
927
|
start: opts.start,
|
|
807
928
|
end: opts.end
|
|
@@ -812,6 +933,9 @@ buybackCommand.command("detail <tokenId>").description("Get buyback events for a
|
|
|
812
933
|
} else {
|
|
813
934
|
console.log(`Token: ${data.tokenName} (${data.tokenSymbol})`);
|
|
814
935
|
console.log(`Total Buyback: ${data.totalBuybackAmount.toLocaleString()}`);
|
|
936
|
+
if (outputMode === "table") {
|
|
937
|
+
console.log(`Total Value (USD): ${data.totalBuybackValue?.toLocaleString() ?? "\u2014"}`);
|
|
938
|
+
}
|
|
815
939
|
console.log();
|
|
816
940
|
output(data.buybacks, detailColumns3, outputMode);
|
|
817
941
|
}
|
|
@@ -822,13 +946,13 @@ import { Command as Command7 } from "commander";
|
|
|
822
946
|
|
|
823
947
|
// src/api/emission.ts
|
|
824
948
|
async function getDailyEmission(client, tokenId, params) {
|
|
825
|
-
return client.
|
|
949
|
+
return client.getWithMeta(
|
|
826
950
|
`/v5/daily-emission/${encodeURIComponent(tokenId)}`,
|
|
827
951
|
params
|
|
828
952
|
);
|
|
829
953
|
}
|
|
830
954
|
async function getWeeklyEmission(client, tokenId, params) {
|
|
831
|
-
return client.
|
|
955
|
+
return client.getWithMeta(
|
|
832
956
|
`/v5/emission/${encodeURIComponent(tokenId)}`,
|
|
833
957
|
params
|
|
834
958
|
);
|
|
@@ -852,7 +976,8 @@ emissionCommand.command("daily <tokenId>").description("Get daily emission break
|
|
|
852
976
|
const client = new TokenomistClient(config.apiKey, config.baseUrl, {
|
|
853
977
|
noCache: config.noCache
|
|
854
978
|
});
|
|
855
|
-
|
|
979
|
+
validateDateRange(opts.start, opts.end);
|
|
980
|
+
const { data, metadata } = await getDailyEmission(client, tokenId, {
|
|
856
981
|
start: opts.start,
|
|
857
982
|
end: opts.end,
|
|
858
983
|
standardAllocation: opts.standardAllocation,
|
|
@@ -860,6 +985,7 @@ emissionCommand.command("daily <tokenId>").description("Get daily emission break
|
|
|
860
985
|
pageSize: opts.pageSize
|
|
861
986
|
});
|
|
862
987
|
output(data, columns3, globalOpts.output || "table");
|
|
988
|
+
printPaginationHint(metadata);
|
|
863
989
|
});
|
|
864
990
|
emissionCommand.command("weekly <tokenId>").description("Get weekly emission breakdown for a token").option("--start <date>", "Start date (YYYY-MM-DD)").option("--end <date>", "End date (YYYY-MM-DD)").option("--standard-allocation <names>", "Filter by allocation (comma-separated)").option("--page <number>", "Page number").option("--page-size <number>", "Items per page").action(async (tokenId, opts, cmd) => {
|
|
865
991
|
const globalOpts = cmd.optsWithGlobals();
|
|
@@ -867,7 +993,8 @@ emissionCommand.command("weekly <tokenId>").description("Get weekly emission bre
|
|
|
867
993
|
const client = new TokenomistClient(config.apiKey, config.baseUrl, {
|
|
868
994
|
noCache: config.noCache
|
|
869
995
|
});
|
|
870
|
-
|
|
996
|
+
validateDateRange(opts.start, opts.end);
|
|
997
|
+
const { data, metadata } = await getWeeklyEmission(client, tokenId, {
|
|
871
998
|
start: opts.start,
|
|
872
999
|
end: opts.end,
|
|
873
1000
|
standardAllocation: opts.standardAllocation,
|
|
@@ -875,6 +1002,7 @@ emissionCommand.command("weekly <tokenId>").description("Get weekly emission bre
|
|
|
875
1002
|
pageSize: opts.pageSize
|
|
876
1003
|
});
|
|
877
1004
|
output(data, columns3, globalOpts.output || "table");
|
|
1005
|
+
printPaginationHint(metadata);
|
|
878
1006
|
});
|
|
879
1007
|
|
|
880
1008
|
// src/commands/allocation.ts
|
|
@@ -982,8 +1110,12 @@ import { Command as Command10 } from "commander";
|
|
|
982
1110
|
// src/schema.json
|
|
983
1111
|
var schema_default = {
|
|
984
1112
|
name: "tok",
|
|
985
|
-
version: "0.
|
|
1113
|
+
version: "0.2.0",
|
|
986
1114
|
description: "CLI tool for the Tokenomist API",
|
|
1115
|
+
notes: {
|
|
1116
|
+
pagination: "Paginated commands (unlock list/upcoming/events, emission daily/weekly, investor list, burn list, buyback list, pit versions/daily-emission/unlock-events) return one page (default 20 rows). The page position is printed to stderr as 'Page <page>/<totalPages> \xB7 <total> total'; stdout stays the data only. A page past the last returns the last page again. pit commands with --output json include it as a 'pagination' object.",
|
|
1117
|
+
pointInTime: "pit commands answer from one tokenomics version: --as-of <instant> (YYYY-MM-DD or RFC 3339) or --version-id <id> from 'pit versions', never both; neither means the current version. Their --output json is {asOf, version, data, pagination?}; pit versions returns {data, pagination}. They need a plan with point-in-time access (API, ELITE or TEAM); others get FORBIDDEN (PLAN_NOT_ELIGIBLE or Forbidden)."
|
|
1118
|
+
},
|
|
987
1119
|
globalOptions: {
|
|
988
1120
|
"api-key": { flags: "-k, --api-key <key>", description: "API key (overrides env/config)" },
|
|
989
1121
|
output: { flags: "-o, --output <format>", description: "Output format: table, json, csv", default: "table" },
|
|
@@ -1143,6 +1275,54 @@ var schema_default = {
|
|
|
1143
1275
|
arguments: { tokenId: { required: true, description: "Token ID" } },
|
|
1144
1276
|
example: "tok allocation detail solana --output json"
|
|
1145
1277
|
},
|
|
1278
|
+
"pit versions <tokenId>": {
|
|
1279
|
+
description: "List a token's tokenomics versions on its project timeline (point-in-time)",
|
|
1280
|
+
arguments: { tokenId: { required: true, description: "Token ID (e.g. arbitrum)" } },
|
|
1281
|
+
options: {
|
|
1282
|
+
start: { flags: "--start <date>", description: "Keep versions valid from this day (YYYY-MM-DD)" },
|
|
1283
|
+
end: { flags: "--end <date>", description: "Keep versions valid from up to this day (YYYY-MM-DD)" },
|
|
1284
|
+
page: { flags: "--page <number>", description: "Page number" },
|
|
1285
|
+
"page-size": { flags: "--page-size <number>", description: "Items per page" }
|
|
1286
|
+
},
|
|
1287
|
+
example: "tok pit versions arbitrum --output json"
|
|
1288
|
+
},
|
|
1289
|
+
"pit allocation <tokenId>": {
|
|
1290
|
+
description: "Get a token's allocations as published at a version (point-in-time, no USD fields)",
|
|
1291
|
+
arguments: { tokenId: { required: true, description: "Token ID" } },
|
|
1292
|
+
options: {
|
|
1293
|
+
"as-of": { flags: "--as-of <instant>", description: "Answer as known at this instant (YYYY-MM-DD or RFC 3339)" },
|
|
1294
|
+
"version-id": { flags: "--version-id <id>", description: "Answer from this version (from pit versions)" }
|
|
1295
|
+
},
|
|
1296
|
+
example: "tok pit allocation arbitrum --as-of 2024-01-01 --output json"
|
|
1297
|
+
},
|
|
1298
|
+
"pit daily-emission <tokenId>": {
|
|
1299
|
+
description: "Get a token's daily emission (cliff + linear) as published at a version (point-in-time, no USD fields)",
|
|
1300
|
+
arguments: { tokenId: { required: true, description: "Token ID" } },
|
|
1301
|
+
options: {
|
|
1302
|
+
"as-of": { flags: "--as-of <instant>", description: "Answer as known at this instant (YYYY-MM-DD or RFC 3339)" },
|
|
1303
|
+
"version-id": { flags: "--version-id <id>", description: "Answer from this version (from pit versions)" },
|
|
1304
|
+
start: { flags: "--start <date>", description: "Start date (YYYY-MM-DD)" },
|
|
1305
|
+
end: { flags: "--end <date>", description: "End date (YYYY-MM-DD)" },
|
|
1306
|
+
"standard-allocation": { flags: "--standard-allocation <names>", description: "Filter by allocation (comma-separated: community, founderTeam, privateInvestors, publicInvestors, others, reserve)" },
|
|
1307
|
+
page: { flags: "--page <number>", description: "Page number" },
|
|
1308
|
+
"page-size": { flags: "--page-size <number>", description: "Items per page" }
|
|
1309
|
+
},
|
|
1310
|
+
example: "tok pit daily-emission arbitrum --as-of 2024-01-01 --start 2024-03-01 --end 2024-03-31"
|
|
1311
|
+
},
|
|
1312
|
+
"pit unlock-events <tokenId>": {
|
|
1313
|
+
description: "Get a token's cliff unlock events as published at a version (point-in-time, no USD fields)",
|
|
1314
|
+
arguments: { tokenId: { required: true, description: "Token ID" } },
|
|
1315
|
+
options: {
|
|
1316
|
+
"as-of": { flags: "--as-of <instant>", description: "Answer as known at this instant (YYYY-MM-DD or RFC 3339)" },
|
|
1317
|
+
"version-id": { flags: "--version-id <id>", description: "Answer from this version (from pit versions)" },
|
|
1318
|
+
start: { flags: "--start <date>", description: "Start date (YYYY-MM-DD)" },
|
|
1319
|
+
end: { flags: "--end <date>", description: "End date (YYYY-MM-DD)" },
|
|
1320
|
+
"standard-allocation": { flags: "--standard-allocation <names>", description: "Filter by allocation (comma-separated: community, founderTeam, privateInvestors, publicInvestors, others, reserve)" },
|
|
1321
|
+
page: { flags: "--page <number>", description: "Page number" },
|
|
1322
|
+
"page-size": { flags: "--page-size <number>", description: "Items per page" }
|
|
1323
|
+
},
|
|
1324
|
+
example: "tok pit unlock-events arbitrum --version-id arbitrum-p-6502a0ab2e1d22b626158232 --output json"
|
|
1325
|
+
},
|
|
1146
1326
|
"auth login": {
|
|
1147
1327
|
description: "Store API key",
|
|
1148
1328
|
options: {
|
|
@@ -1174,6 +1354,211 @@ var schemaCommand = new Command10("schema").description("Print full command sche
|
|
|
1174
1354
|
console.log(JSON.stringify(schema_default, null, indent));
|
|
1175
1355
|
});
|
|
1176
1356
|
|
|
1357
|
+
// src/commands/pit.ts
|
|
1358
|
+
import { Command as Command11 } from "commander";
|
|
1359
|
+
|
|
1360
|
+
// src/api/point-in-time.ts
|
|
1361
|
+
var BASE = "/v5/point-in-time";
|
|
1362
|
+
async function getTokenVersions(client, tokenId, params) {
|
|
1363
|
+
return client.getWithMeta(
|
|
1364
|
+
`${BASE}/token-versions/${encodeURIComponent(tokenId)}`,
|
|
1365
|
+
{ ...params }
|
|
1366
|
+
);
|
|
1367
|
+
}
|
|
1368
|
+
async function getPointInTimeAllocation(client, tokenId, params) {
|
|
1369
|
+
return client.getWithMeta(
|
|
1370
|
+
`${BASE}/allocations/${encodeURIComponent(tokenId)}`,
|
|
1371
|
+
{ ...params }
|
|
1372
|
+
);
|
|
1373
|
+
}
|
|
1374
|
+
async function getPointInTimeDailyEmission(client, tokenId, params) {
|
|
1375
|
+
return client.getWithMeta(
|
|
1376
|
+
`${BASE}/daily-emission/${encodeURIComponent(tokenId)}`,
|
|
1377
|
+
{ ...params }
|
|
1378
|
+
);
|
|
1379
|
+
}
|
|
1380
|
+
async function getPointInTimeUnlockEvents(client, tokenId, params) {
|
|
1381
|
+
return client.getWithMeta(
|
|
1382
|
+
`${BASE}/unlock/events/${encodeURIComponent(tokenId)}`,
|
|
1383
|
+
{ ...params }
|
|
1384
|
+
);
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
// src/commands/pit.ts
|
|
1388
|
+
var versionColumns = [
|
|
1389
|
+
{ key: "historyOrder", header: "#", align: "right" },
|
|
1390
|
+
{ key: "id", header: "Version ID" },
|
|
1391
|
+
{ key: "status", header: "Status" },
|
|
1392
|
+
{ key: "validFrom", header: "Valid From" },
|
|
1393
|
+
{ key: "validUntil", header: "Valid Until" },
|
|
1394
|
+
{ key: "changeCategory", header: "Change" },
|
|
1395
|
+
{ key: "changeTitle", header: "Title" }
|
|
1396
|
+
];
|
|
1397
|
+
var allocationColumns = [
|
|
1398
|
+
{ key: "allocationName", header: "Allocation" },
|
|
1399
|
+
{ key: "allocationType", header: "Type" },
|
|
1400
|
+
{ key: "standardAllocationName", header: "Std. Allocation" },
|
|
1401
|
+
{ key: "allocationAmount", header: "Amount", align: "right" },
|
|
1402
|
+
{ key: "allocationUnlockedAmount", header: "Unlocked", align: "right" },
|
|
1403
|
+
{ key: "allocationLockedAmount", header: "Locked", align: "right" },
|
|
1404
|
+
{ key: "trackedAllocationPercentage", header: "Tracked %", align: "right" }
|
|
1405
|
+
];
|
|
1406
|
+
var emissionColumns = [
|
|
1407
|
+
{ key: "startDate", header: "Start" },
|
|
1408
|
+
{ key: "endDate", header: "End" },
|
|
1409
|
+
{ key: "tokenSymbol", header: "Symbol" },
|
|
1410
|
+
{ key: "unlockAmount", header: "Unlock Amt", align: "right" },
|
|
1411
|
+
{ key: "totalCumulativeUnlockedAmount", header: "Cumulative", align: "right" },
|
|
1412
|
+
{ key: "committedClaimAmount", header: "Committed Claim", align: "right" }
|
|
1413
|
+
];
|
|
1414
|
+
var unlockColumns = [
|
|
1415
|
+
{ key: "unlockDate", header: "Date" },
|
|
1416
|
+
{ key: "tokenSymbol", header: "Symbol" },
|
|
1417
|
+
{ key: "cliffAmount", header: "Cliff Amount", align: "right" },
|
|
1418
|
+
{ key: "allocationNames", header: "Allocations" }
|
|
1419
|
+
];
|
|
1420
|
+
var STANDARD_ALLOCATION_HELP = "Filter by allocation (comma-separated: community, founderTeam, privateInvestors, publicInvestors, others, reserve)";
|
|
1421
|
+
function addSelectorOptions(cmd) {
|
|
1422
|
+
return cmd.option(
|
|
1423
|
+
"--as-of <instant>",
|
|
1424
|
+
"Answer as known at this instant (YYYY-MM-DD or RFC 3339, e.g. 2024-07-01T12:00:00Z)"
|
|
1425
|
+
).option("--version-id <id>", "Answer from this version (from `tok pit versions`)");
|
|
1426
|
+
}
|
|
1427
|
+
function addSeriesOptions(cmd) {
|
|
1428
|
+
return addSelectorOptions(cmd).option("--start <date>", "Start date (YYYY-MM-DD)").option("--end <date>", "End date (YYYY-MM-DD)").option("--standard-allocation <names>", STANDARD_ALLOCATION_HELP).option("--page <number>", "Page number").option("--page-size <number>", "Items per page");
|
|
1429
|
+
}
|
|
1430
|
+
function clientFor(cmd) {
|
|
1431
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
1432
|
+
const config = resolveConfig(globalOpts);
|
|
1433
|
+
const client = new TokenomistClient(config.apiKey, config.baseUrl, {
|
|
1434
|
+
noCache: config.noCache
|
|
1435
|
+
});
|
|
1436
|
+
return { client, mode: globalOpts.output || "table" };
|
|
1437
|
+
}
|
|
1438
|
+
function printJson(result) {
|
|
1439
|
+
const { metadata, data } = result;
|
|
1440
|
+
const pagination = paginationOf(metadata);
|
|
1441
|
+
const out = {};
|
|
1442
|
+
if (metadata.asOf !== void 0) out.asOf = metadata.asOf;
|
|
1443
|
+
if (metadata.version !== void 0) out.version = metadata.version;
|
|
1444
|
+
out.data = data;
|
|
1445
|
+
if (pagination) out.pagination = pagination;
|
|
1446
|
+
console.log(JSON.stringify(out, null, 2));
|
|
1447
|
+
}
|
|
1448
|
+
function printVersionContext(result, mode) {
|
|
1449
|
+
const { asOf, version } = result.metadata;
|
|
1450
|
+
if (asOf === void 0 && version === void 0) return;
|
|
1451
|
+
const lines = [];
|
|
1452
|
+
if (asOf) lines.push(`As of: ${asOf}`);
|
|
1453
|
+
if (version) {
|
|
1454
|
+
const id = typeof version.id === "string" ? version.id : "\u2014";
|
|
1455
|
+
const status = typeof version.status === "string" ? ` (${version.status})` : "";
|
|
1456
|
+
lines.push(`Version: ${id}${status}`);
|
|
1457
|
+
}
|
|
1458
|
+
writeContext(lines, mode);
|
|
1459
|
+
}
|
|
1460
|
+
function writeContext(lines, mode) {
|
|
1461
|
+
if (lines.length === 0) return;
|
|
1462
|
+
const text = lines.join("\n") + "\n";
|
|
1463
|
+
if (mode === "csv") process.stderr.write(text);
|
|
1464
|
+
else process.stdout.write(text + "\n");
|
|
1465
|
+
}
|
|
1466
|
+
var pitCommand = new Command11("pit").alias("point-in-time").description(
|
|
1467
|
+
"Point-in-time history: a token's data as published at a past version (no USD fields)"
|
|
1468
|
+
);
|
|
1469
|
+
pitCommand.command("versions <tokenId>").description("List a token's tokenomics versions (project timeline)").option("--start <date>", "Keep versions valid from this day (YYYY-MM-DD)").option("--end <date>", "Keep versions valid from up to this day (YYYY-MM-DD)").option("--page <number>", "Page number").option("--page-size <number>", "Items per page").action(async (tokenId, opts, cmd) => {
|
|
1470
|
+
validateDateRange(opts.start, opts.end);
|
|
1471
|
+
const { client, mode } = clientFor(cmd);
|
|
1472
|
+
const result = await getTokenVersions(client, tokenId, {
|
|
1473
|
+
start: opts.start,
|
|
1474
|
+
end: opts.end,
|
|
1475
|
+
page: opts.page,
|
|
1476
|
+
pageSize: opts.pageSize
|
|
1477
|
+
});
|
|
1478
|
+
if (mode === "json") return printJson(result);
|
|
1479
|
+
const rows = result.data.map((v) => ({
|
|
1480
|
+
...v,
|
|
1481
|
+
changeCategory: v.change?.category,
|
|
1482
|
+
changeTitle: v.change?.title
|
|
1483
|
+
}));
|
|
1484
|
+
output(rows, versionColumns, mode);
|
|
1485
|
+
printPaginationHint(result.metadata);
|
|
1486
|
+
});
|
|
1487
|
+
addSelectorOptions(
|
|
1488
|
+
pitCommand.command("allocation <tokenId>").description("Get a token's allocations as published at a version")
|
|
1489
|
+
).action(async (tokenId, opts, cmd) => {
|
|
1490
|
+
validateVersionSelector(opts.asOf, opts.versionId);
|
|
1491
|
+
const { client, mode } = clientFor(cmd);
|
|
1492
|
+
const result = await getPointInTimeAllocation(client, tokenId, {
|
|
1493
|
+
asOf: opts.asOf,
|
|
1494
|
+
versionId: opts.versionId
|
|
1495
|
+
});
|
|
1496
|
+
if (mode === "json") return printJson(result);
|
|
1497
|
+
const d = result.data;
|
|
1498
|
+
printVersionContext(result, mode);
|
|
1499
|
+
writeContext(
|
|
1500
|
+
[
|
|
1501
|
+
`Token: ${d.name} (${d.symbol})`,
|
|
1502
|
+
`Max Supply: ${d.maxSupply?.toLocaleString() ?? "\u2014"}`,
|
|
1503
|
+
`Total Unlocked: ${d.totalUnlockedAmount.toLocaleString()}`,
|
|
1504
|
+
`Total Locked: ${d.totalLockedAmount.toLocaleString()}`,
|
|
1505
|
+
`Total TBD Locked: ${d.totalTBDLockedAmount.toLocaleString()}`,
|
|
1506
|
+
`Total Untracked: ${d.totalUntrackedAmount.toLocaleString()}`
|
|
1507
|
+
],
|
|
1508
|
+
mode
|
|
1509
|
+
);
|
|
1510
|
+
output(d.allocations, allocationColumns, mode);
|
|
1511
|
+
});
|
|
1512
|
+
addSeriesOptions(
|
|
1513
|
+
pitCommand.command("daily-emission <tokenId>").description("Get a token's daily emission (cliff + linear) as published at a version")
|
|
1514
|
+
).action(async (tokenId, opts, cmd) => {
|
|
1515
|
+
validateVersionSelector(opts.asOf, opts.versionId);
|
|
1516
|
+
validateDateRange(opts.start, opts.end);
|
|
1517
|
+
const { client, mode } = clientFor(cmd);
|
|
1518
|
+
const result = await getPointInTimeDailyEmission(client, tokenId, {
|
|
1519
|
+
asOf: opts.asOf,
|
|
1520
|
+
versionId: opts.versionId,
|
|
1521
|
+
start: opts.start,
|
|
1522
|
+
end: opts.end,
|
|
1523
|
+
standardAllocation: opts.standardAllocation,
|
|
1524
|
+
page: opts.page,
|
|
1525
|
+
pageSize: opts.pageSize
|
|
1526
|
+
});
|
|
1527
|
+
if (mode === "json") return printJson(result);
|
|
1528
|
+
printVersionContext(result, mode);
|
|
1529
|
+
const rows = result.data.map((d) => ({
|
|
1530
|
+
...d,
|
|
1531
|
+
committedClaimAmount: d.committedClaim?.amount ?? null
|
|
1532
|
+
}));
|
|
1533
|
+
output(rows, emissionColumns, mode);
|
|
1534
|
+
printPaginationHint(result.metadata);
|
|
1535
|
+
});
|
|
1536
|
+
addSeriesOptions(
|
|
1537
|
+
pitCommand.command("unlock-events <tokenId>").description("Get a token's cliff unlock events as published at a version")
|
|
1538
|
+
).action(async (tokenId, opts, cmd) => {
|
|
1539
|
+
validateVersionSelector(opts.asOf, opts.versionId);
|
|
1540
|
+
validateDateRange(opts.start, opts.end);
|
|
1541
|
+
const { client, mode } = clientFor(cmd);
|
|
1542
|
+
const result = await getPointInTimeUnlockEvents(client, tokenId, {
|
|
1543
|
+
asOf: opts.asOf,
|
|
1544
|
+
versionId: opts.versionId,
|
|
1545
|
+
start: opts.start,
|
|
1546
|
+
end: opts.end,
|
|
1547
|
+
standardAllocation: opts.standardAllocation,
|
|
1548
|
+
page: opts.page,
|
|
1549
|
+
pageSize: opts.pageSize
|
|
1550
|
+
});
|
|
1551
|
+
if (mode === "json") return printJson(result);
|
|
1552
|
+
printVersionContext(result, mode);
|
|
1553
|
+
const rows = result.data.map((e) => ({
|
|
1554
|
+
...e,
|
|
1555
|
+
cliffAmount: e.cliffUnlocks.cliffAmount,
|
|
1556
|
+
allocationNames: e.cliffUnlocks.allocationBreakdown.map((a) => a.allocationName)
|
|
1557
|
+
}));
|
|
1558
|
+
output(rows, unlockColumns, mode);
|
|
1559
|
+
printPaginationHint(result.metadata);
|
|
1560
|
+
});
|
|
1561
|
+
|
|
1177
1562
|
// src/banner.ts
|
|
1178
1563
|
var BANNER_COLOR_OPEN = "\x1B[38;2;197;16;82m";
|
|
1179
1564
|
var COLOR_RESET = "\x1B[0m";
|
|
@@ -1226,6 +1611,26 @@ function printWelcome(stream) {
|
|
|
1226
1611
|
`);
|
|
1227
1612
|
}
|
|
1228
1613
|
|
|
1614
|
+
// src/update-notice.ts
|
|
1615
|
+
function isNewerVersion(latest, current) {
|
|
1616
|
+
const parse = (v) => v.replace(/^v/, "").split("-")[0].split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
1617
|
+
const a = parse(latest);
|
|
1618
|
+
const b = parse(current);
|
|
1619
|
+
for (let i = 0; i < 3; i++) {
|
|
1620
|
+
if ((a[i] ?? 0) !== (b[i] ?? 0)) return (a[i] ?? 0) > (b[i] ?? 0);
|
|
1621
|
+
}
|
|
1622
|
+
return false;
|
|
1623
|
+
}
|
|
1624
|
+
function updateNotice(cache, running, now, maxAgeMs) {
|
|
1625
|
+
if (typeof cache.latest !== "string" || typeof cache.checkedAt !== "number") return null;
|
|
1626
|
+
if (now - cache.checkedAt >= maxAgeMs) return null;
|
|
1627
|
+
if (!isNewerVersion(cache.latest, running)) return null;
|
|
1628
|
+
return `
|
|
1629
|
+
Update available: ${running} \u2192 ${cache.latest} (npm i -g @tokenomist-ai/tokenomist-cli)
|
|
1630
|
+
|
|
1631
|
+
`;
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1229
1634
|
// src/index.ts
|
|
1230
1635
|
import { spawn } from "child_process";
|
|
1231
1636
|
import { readFileSync as readFileSync3 } from "fs";
|
|
@@ -1237,15 +1642,8 @@ function showUpdateNotification() {
|
|
|
1237
1642
|
if (process.env.NO_UPDATE_NOTIFIER === "1" || process.env.CI === "1") return;
|
|
1238
1643
|
try {
|
|
1239
1644
|
const raw = readFileSync3(join3(homedir3(), ".tokenomist", "update-check.json"), "utf-8");
|
|
1240
|
-
const
|
|
1241
|
-
if (
|
|
1242
|
-
process.stderr.write(
|
|
1243
|
-
`
|
|
1244
|
-
Update available: ${data.current} \u2192 ${data.latest} (npm i -g @tokenomist-ai/tokenomist-cli)
|
|
1245
|
-
|
|
1246
|
-
`
|
|
1247
|
-
);
|
|
1248
|
-
}
|
|
1645
|
+
const notice = updateNotice(JSON.parse(raw), package_default.version, Date.now(), ONE_DAY);
|
|
1646
|
+
if (notice) process.stderr.write(notice);
|
|
1249
1647
|
} catch {
|
|
1250
1648
|
}
|
|
1251
1649
|
}
|
|
@@ -1265,10 +1663,10 @@ function spawnUpdateCheck() {
|
|
|
1265
1663
|
} catch {
|
|
1266
1664
|
}
|
|
1267
1665
|
}
|
|
1268
|
-
var program = new
|
|
1666
|
+
var program = new Command12();
|
|
1269
1667
|
program.name("tok").description(
|
|
1270
|
-
"CLI tool for the Tokenomist API \u2014 query token unlocks, emissions, fundraising, burns, and
|
|
1271
|
-
).version(
|
|
1668
|
+
"CLI tool for the Tokenomist API \u2014 query token unlocks, emissions, fundraising, burns, buybacks, and point-in-time history"
|
|
1669
|
+
).version(package_default.version).option("-k, --api-key <key>", "API key (overrides env/config)").option("-o, --output <format>", "Output format: table, json, csv", "table").option("--base-url <url>", "API base URL").option("--no-cache", "Bypass response cache");
|
|
1272
1670
|
program.addCommand(tokenCommand);
|
|
1273
1671
|
program.addCommand(unlockCommand);
|
|
1274
1672
|
program.addCommand(investorCommand);
|
|
@@ -1277,6 +1675,7 @@ program.addCommand(burnCommand);
|
|
|
1277
1675
|
program.addCommand(buybackCommand);
|
|
1278
1676
|
program.addCommand(emissionCommand);
|
|
1279
1677
|
program.addCommand(allocationCommand);
|
|
1678
|
+
program.addCommand(pitCommand);
|
|
1280
1679
|
program.addCommand(authCommand);
|
|
1281
1680
|
program.addCommand(schemaCommand);
|
|
1282
1681
|
if (showWelcomeBanner()) {
|
|
@@ -1288,12 +1687,7 @@ program.parseAsync(process.argv).then(() => {
|
|
|
1288
1687
|
spawnUpdateCheck();
|
|
1289
1688
|
}).catch((err) => {
|
|
1290
1689
|
if (err instanceof ApiError) {
|
|
1291
|
-
|
|
1292
|
-
if (err.detail) console.error(`Detail: ${err.detail}`);
|
|
1293
|
-
} else {
|
|
1294
|
-
console.error(`${err.code}: ${err.message}`);
|
|
1295
|
-
if (err.detail) console.error(`Detail: ${err.detail}`);
|
|
1296
|
-
}
|
|
1690
|
+
for (const line of formatApiError(err)) console.error(line);
|
|
1297
1691
|
} else {
|
|
1298
1692
|
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
1299
1693
|
}
|