gangtise-openapi-cli 0.41.1 → 0.43.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 (51) hide show
  1. package/README.md +45 -39
  2. package/dist/src/cli.js +50 -1161
  3. package/dist/src/commands/ai.js +156 -0
  4. package/dist/src/commands/alternative.js +56 -0
  5. package/dist/src/commands/auth.js +37 -0
  6. package/dist/src/commands/bond.js +76 -0
  7. package/dist/src/commands/fundamental.js +137 -0
  8. package/dist/src/commands/indicator.js +134 -0
  9. package/dist/src/commands/insight.js +338 -0
  10. package/dist/src/commands/quote.js +181 -0
  11. package/dist/src/commands/raw.js +49 -0
  12. package/dist/src/commands/reference.js +58 -0
  13. package/dist/src/commands/shared.js +197 -0
  14. package/dist/src/commands/tool.js +55 -0
  15. package/dist/src/commands/vault.js +203 -0
  16. package/dist/src/core/args.js +13 -9
  17. package/dist/src/core/asyncContent.js +32 -19
  18. package/dist/src/core/auth.js +3 -2
  19. package/dist/src/core/client.js +112 -15
  20. package/dist/src/core/commandBodies.js +2 -1
  21. package/dist/src/core/config.js +16 -3
  22. package/dist/src/core/download.js +9 -5
  23. package/dist/src/core/endpoints.js +106 -1
  24. package/dist/src/core/exitStatus.js +17 -0
  25. package/dist/src/core/fileParse.js +3 -20
  26. package/dist/src/core/opinionDetail.js +39 -0
  27. package/dist/src/core/output.js +55 -12
  28. package/dist/src/core/perSecurity.js +29 -11
  29. package/dist/src/core/printer.js +7 -8
  30. package/dist/src/core/quoteSharding.js +19 -15
  31. package/dist/src/core/rowSink.js +40 -7
  32. package/dist/src/core/titleCache.js +28 -2
  33. package/dist/src/core/transport.js +7 -4
  34. package/dist/src/core/updateCheck.js +51 -0
  35. package/dist/src/version.js +19 -1
  36. package/gangtise-openapi/SKILL.md +50 -49
  37. package/gangtise-openapi/references/commands/ai.md +35 -16
  38. package/gangtise-openapi/references/commands/alternative.md +2 -2
  39. package/gangtise-openapi/references/commands/bond.md +1 -1
  40. package/gangtise-openapi/references/commands/fundamental.md +5 -5
  41. package/gangtise-openapi/references/commands/indicator.md +29 -27
  42. package/gangtise-openapi/references/commands/insight.md +39 -31
  43. package/gangtise-openapi/references/commands/quote.md +19 -19
  44. package/gangtise-openapi/references/commands/reference-and-lookup.md +18 -15
  45. package/gangtise-openapi/references/commands/vault.md +10 -7
  46. package/gangtise-openapi/references/errors.md +27 -25
  47. package/gangtise-openapi/references/examples.md +38 -36
  48. package/gangtise-openapi/references/fields.md +8 -6
  49. package/gangtise-openapi/references/lookup-ids.md +10 -8
  50. package/gangtise-openapi/references/response-schema.md +7 -7
  51. package/package.json +1 -1
@@ -25,13 +25,16 @@ const ENDPOINT_DEFS = {
25
25
  path: "/application/open-insight/chief-opinion/v2/getList",
26
26
  kind: "json",
27
27
  description: "List domestic institution chief opinions (brief only; body via detail)",
28
+ billing: { per: "row", price: 1 },
28
29
  pagination: { enabled: true, maxPageSize: 50 },
30
+ rowId: "chiefOpinionId",
29
31
  },
30
32
  "insight.opinion.list-with-content": {
31
33
  method: "POST",
32
34
  path: "/application/open-insight/chief-opinion/getList",
33
35
  kind: "json",
34
36
  description: "List domestic institution chief opinions with the full body (v1 list)",
37
+ billing: { per: "row", price: 30 },
35
38
  pagination: { enabled: true, maxPageSize: 50 },
36
39
  retry: "no-replay",
37
40
  },
@@ -40,20 +43,25 @@ const ENDPOINT_DEFS = {
40
43
  path: "/application/open-insight/chief-opinion/getDetail",
41
44
  kind: "json",
42
45
  description: "Get domestic chief opinion bodies by ID (max 20 IDs per call)",
46
+ billing: { per: "row", price: 30, maxUnits: 20 },
43
47
  retry: "no-replay",
48
+ expects: "array",
44
49
  },
45
50
  "insight.summary.list": {
46
51
  method: "POST",
47
52
  path: "/application/open-insight/summary/v2/getList",
48
53
  kind: "json",
49
54
  description: "List summaries",
55
+ billing: { per: "row", price: 0.1 },
50
56
  pagination: { enabled: true, maxPageSize: 50 },
57
+ rowId: "summaryId",
51
58
  },
52
59
  "insight.summary.download": {
53
60
  method: "GET",
54
61
  path: "/application/open-insight/summary/v2/download/file",
55
62
  kind: "download",
56
63
  description: "Download summary file",
64
+ billing: { per: "document", price: 50 },
57
65
  retry: "no-replay",
58
66
  },
59
67
  "insight.pamirs-summary.list": {
@@ -62,6 +70,7 @@ const ENDPOINT_DEFS = {
62
70
  kind: "json",
63
71
  description: "List Pamirs expert summaries (requires the expert-summary database)",
64
72
  pagination: { enabled: true, maxPageSize: 50 },
73
+ rowId: "summaryId",
65
74
  },
66
75
  "insight.pamirs-summary.download": {
67
76
  method: "GET",
@@ -75,67 +84,88 @@ const ENDPOINT_DEFS = {
75
84
  path: "/application/open-insight/schedule/roadshow/getList",
76
85
  kind: "json",
77
86
  description: "List roadshows",
87
+ billing: { per: "row", price: 20 },
78
88
  pagination: { enabled: true, maxPageSize: 50 },
89
+ rowId: "id",
90
+ rowIdUnverified: true,
79
91
  },
80
92
  "insight.site-visit.list": {
81
93
  method: "POST",
82
94
  path: "/application/open-insight/schedule/site-visit/getList",
83
95
  kind: "json",
84
96
  description: "List site visits",
97
+ billing: { per: "row", price: 20 },
85
98
  pagination: { enabled: true, maxPageSize: 50 },
99
+ rowId: "id",
100
+ rowIdUnverified: true,
86
101
  },
87
102
  "insight.strategy.list": {
88
103
  method: "POST",
89
104
  path: "/application/open-insight/schedule/strategy-meeting/getList",
90
105
  kind: "json",
91
106
  description: "List strategy meetings",
107
+ billing: { per: "row", price: 20 },
92
108
  pagination: { enabled: true, maxPageSize: 50 },
109
+ rowId: "id",
110
+ rowIdUnverified: true,
93
111
  },
94
112
  "insight.forum.list": {
95
113
  method: "POST",
96
114
  path: "/application/open-insight/schedule/forum/getList",
97
115
  kind: "json",
98
116
  description: "List forums",
117
+ billing: { per: "row", price: 20 },
99
118
  pagination: { enabled: true, maxPageSize: 50 },
119
+ rowId: "id",
120
+ rowIdUnverified: true,
100
121
  },
101
122
  "insight.performance-calendar.list": {
102
123
  method: "POST",
103
124
  path: "/application/open-insight/schedule/performance-calendar/getList",
104
125
  kind: "json",
105
126
  description: "List earnings calendar events (forecast / express / announcement)",
127
+ billing: { per: "row", price: 0.1 },
106
128
  pagination: { enabled: true, maxPageSize: 50 },
129
+ rowId: "performanceReportId",
107
130
  },
108
131
  "insight.performance-calendar.download": {
109
132
  method: "GET",
110
133
  path: "/application/open-insight/schedule/performance-calendar/download/file",
111
134
  kind: "download",
112
135
  description: "Download an earnings report file (A-share 10 credits, HK/US 20)",
136
+ billing: { per: "document", price: 20 },
113
137
  },
114
138
  "insight.research.list": {
115
139
  method: "POST",
116
140
  path: "/application/open-insight/broker-report/getList",
117
141
  kind: "json",
118
142
  description: "List broker research reports",
143
+ billing: { per: "row", price: 0.1 },
119
144
  pagination: { enabled: true, maxPageSize: 50 },
145
+ rowId: "reportId",
120
146
  },
121
147
  "insight.research.download": {
122
148
  method: "GET",
123
149
  path: "/application/open-insight/broker-report/download/file",
124
150
  kind: "download",
125
151
  description: "Download broker research report",
152
+ billing: { per: "document", price: 10 },
126
153
  },
127
154
  "insight.foreign-report.list": {
128
155
  method: "POST",
129
156
  path: "/application/open-insight/foreign-report/getList",
130
157
  kind: "json",
131
158
  description: "List foreign reports",
159
+ billing: { per: "row", price: 0.1 },
132
160
  pagination: { enabled: true, maxPageSize: 50 },
161
+ rowId: "reportId",
133
162
  },
134
163
  "insight.foreign-report.download": {
135
164
  method: "GET",
136
165
  path: "/application/open-insight/foreign-report/download/file",
137
166
  kind: "download",
138
167
  description: "Download foreign report",
168
+ billing: { per: "document", price: 50 },
139
169
  retry: "no-replay",
140
170
  },
141
171
  "insight.announcement.list": {
@@ -143,52 +173,64 @@ const ENDPOINT_DEFS = {
143
173
  path: "/application/open-insight/announcement/getList",
144
174
  kind: "json",
145
175
  description: "List A-share announcements",
176
+ billing: { per: "row", price: 0.1 },
146
177
  pagination: { enabled: true, maxPageSize: 50 },
178
+ rowId: "announcementId",
147
179
  },
148
180
  "insight.announcement.download": {
149
181
  method: "GET",
150
182
  path: "/application/open-insight/announcement/download/file",
151
183
  kind: "download",
152
184
  description: "Download A-share announcement file",
185
+ billing: { per: "document", price: 10 },
153
186
  },
154
187
  "insight.announcement-hk.list": {
155
188
  method: "POST",
156
189
  path: "/application/open-insight/announcement-hk/getList",
157
190
  kind: "json",
158
191
  description: "List HK announcements",
192
+ billing: { per: "row", price: 0.1 },
159
193
  pagination: { enabled: true, maxPageSize: 50 },
194
+ rowId: "announcementId",
160
195
  },
161
196
  "insight.announcement-hk.download": {
162
197
  method: "GET",
163
198
  path: "/application/open-insight/announcement-hk/download/file",
164
199
  kind: "download",
165
200
  description: "Download HK announcement file",
201
+ billing: { per: "document", price: 20 },
166
202
  },
167
203
  "insight.announcement-us.list": {
168
204
  method: "POST",
169
205
  path: "/application/open-insight/announcement-us/getList",
170
206
  kind: "json",
171
207
  description: "List US announcements",
208
+ billing: { per: "row", price: 0.1 },
172
209
  pagination: { enabled: true, maxPageSize: 50 },
210
+ rowId: "announcementId",
173
211
  },
174
212
  "insight.announcement-us.download": {
175
213
  method: "GET",
176
214
  path: "/application/open-insight/announcement-us/download/file",
177
215
  kind: "download",
178
216
  description: "Download US announcement file",
217
+ billing: { per: "document", price: 20 },
179
218
  },
180
219
  "insight.foreign-opinion.list": {
181
220
  method: "POST",
182
221
  path: "/application/open-insight/foreign-opinion/v2/getList",
183
222
  kind: "json",
184
223
  description: "List foreign institution opinions (brief only; body via detail)",
224
+ billing: { per: "row", price: 1 },
185
225
  pagination: { enabled: true, maxPageSize: 50 },
226
+ rowId: "foreignOpinionId",
186
227
  },
187
228
  "insight.foreign-opinion.list-with-content": {
188
229
  method: "POST",
189
230
  path: "/application/open-insight/foreign-opinion/getList",
190
231
  kind: "json",
191
232
  description: "List foreign institution opinions with the full body (v1 list)",
233
+ billing: { per: "row", price: 30 },
192
234
  pagination: { enabled: true, maxPageSize: 50 },
193
235
  retry: "no-replay",
194
236
  },
@@ -197,39 +239,49 @@ const ENDPOINT_DEFS = {
197
239
  path: "/application/open-insight/foreign-opinion/getDetail",
198
240
  kind: "json",
199
241
  description: "Get foreign opinion bodies by ID (max 20 IDs per call)",
242
+ billing: { per: "row", price: 30, maxUnits: 20 },
200
243
  retry: "no-replay",
244
+ expects: "array",
201
245
  },
202
246
  "insight.independent-opinion.list": {
203
247
  method: "POST",
204
248
  path: "/application/open-insight/independent-opinion/getList",
205
249
  kind: "json",
206
250
  description: "List foreign independent analyst opinions",
251
+ billing: { per: "row", price: 5 },
207
252
  pagination: { enabled: true, maxPageSize: 50 },
253
+ rowId: "independentOpinionId",
254
+ rowIdUnverified: true,
208
255
  },
209
256
  "insight.independent-opinion.download": {
210
257
  method: "GET",
211
258
  path: "/application/open-insight/independent-opinion/download/file",
212
259
  kind: "download",
213
260
  description: "Download foreign independent opinion file",
261
+ billing: { per: "document", price: 30 },
214
262
  },
215
263
  "insight.official-account.list": {
216
264
  method: "POST",
217
265
  path: "/application/open-insight/officialAccount/getList",
218
266
  kind: "json",
219
267
  description: "List WeChat official account articles",
268
+ billing: { per: "row", price: 0.1 },
220
269
  pagination: { enabled: true, maxPageSize: 50 },
270
+ rowId: "articleId",
221
271
  },
222
272
  "insight.official-account.download": {
223
273
  method: "GET",
224
274
  path: "/application/open-insight/officialAccount/download/file",
225
275
  kind: "download",
226
276
  description: "Download WeChat official account article (txt/HTML)",
277
+ billing: { per: "document", price: 10 },
227
278
  },
228
279
  "insight.qa.list": {
229
280
  method: "POST",
230
281
  path: "/application/open-insight/Q&A-data/getList",
231
282
  kind: "json",
232
283
  description: "List investor Q&A (conference/interactive/survey) for a security",
284
+ billing: { per: "row", price: 0.1 },
233
285
  pagination: { enabled: true, maxPageSize: 500 },
234
286
  },
235
287
  "insight.report-image.list": {
@@ -243,7 +295,9 @@ const ENDPOINT_DEFS = {
243
295
  path: "/application/open-insight/summary/highlight/getList",
244
296
  kind: "json",
245
297
  description: "List meeting highlights (核心要点信息流; content is an HTML fragment)",
298
+ billing: { per: "row", price: 5 },
246
299
  pagination: { enabled: true, maxPageSize: 50, maxWindow: 10000 },
300
+ rowId: "highlightId",
247
301
  retry: "no-replay",
248
302
  expects: "list",
249
303
  },
@@ -252,6 +306,7 @@ const ENDPOINT_DEFS = {
252
306
  path: "/application/open-insight/report-image/download/file",
253
307
  kind: "download",
254
308
  description: "Download a research report image by chunkId",
309
+ billing: { per: "document", price: 0.1 },
255
310
  },
256
311
  "reference.securities-search": {
257
312
  method: "POST",
@@ -433,6 +488,7 @@ const ENDPOINT_DEFS = {
433
488
  path: "/application/open-fundamental/valuation-analysis",
434
489
  kind: "json",
435
490
  description: "Query valuation analysis",
491
+ expects: "list",
436
492
  },
437
493
  "fundamental.top-holders": {
438
494
  method: "POST",
@@ -445,12 +501,15 @@ const ENDPOINT_DEFS = {
445
501
  path: "/application/open-fundamental/earning-forecast",
446
502
  kind: "json",
447
503
  description: "Query earning forecast (consensus estimates)",
504
+ billing: { per: "row", price: 0.5, maxUnits: Number.POSITIVE_INFINITY },
505
+ retry: "no-replay",
448
506
  },
449
507
  "bond.basic-info": {
450
508
  method: "POST",
451
509
  path: "/application/open-fundamental/bond/basic-info",
452
510
  kind: "json",
453
511
  description: "Query bond static profiles (issuance, term, coupon, rating, options)",
512
+ billing: { per: "call", price: 0.4 },
454
513
  retry: "no-replay",
455
514
  expects: "list",
456
515
  },
@@ -459,6 +518,7 @@ const ENDPOINT_DEFS = {
459
518
  path: "/application/open-fundamental/bond/issuer-info",
460
519
  kind: "json",
461
520
  description: "Query bond issuer profiles (by bond code or issuer name)",
521
+ billing: { per: "call", price: 0.4 },
462
522
  retry: "no-replay",
463
523
  expects: "list",
464
524
  },
@@ -467,6 +527,7 @@ const ENDPOINT_DEFS = {
467
527
  path: "/application/open-quote/bond/daily-quote-exchange-cfets",
468
528
  kind: "json",
469
529
  description: "Query bond daily close quotes (exchange + CFETS; dirty/clean price, YTM, duration)",
530
+ billing: { per: "call", price: 0.4 },
470
531
  retry: "no-replay",
471
532
  expects: "list",
472
533
  },
@@ -475,6 +536,7 @@ const ENDPOINT_DEFS = {
475
536
  path: "/application/open-fundamental/bond/valuation-shclearing",
476
537
  kind: "json",
477
538
  description: "Query Shanghai Clearing House bond valuations (price, yield, risk measures)",
539
+ billing: { per: "call", price: 0.4 },
478
540
  retry: "no-replay",
479
541
  expects: "list",
480
542
  },
@@ -483,6 +545,7 @@ const ENDPOINT_DEFS = {
483
545
  path: "/application/open-fundamental/bond/cash-flow",
484
546
  kind: "json",
485
547
  description: "Query bond interest payment and redemption schedule",
548
+ billing: { per: "call", price: 0.4 },
486
549
  retry: "no-replay",
487
550
  expects: "list",
488
551
  },
@@ -491,6 +554,7 @@ const ENDPOINT_DEFS = {
491
554
  path: "/application/open-fundamental/bond/announcement",
492
555
  kind: "json",
493
556
  description: "Query bond announcements (pageNo/pageSize; the only paged endpoint of the family)",
557
+ billing: { per: "call", price: 0.4 },
494
558
  retry: "no-replay",
495
559
  expects: "list",
496
560
  },
@@ -499,6 +563,7 @@ const ENDPOINT_DEFS = {
499
563
  path: "/application/open-fundamental/bond/issuance-detail",
500
564
  kind: "json",
501
565
  description: "Query bond issuance and re-issuance records (bidding, pricing, cover ratios)",
566
+ billing: { per: "call", price: 0.4 },
502
567
  retry: "no-replay",
503
568
  expects: "list",
504
569
  },
@@ -507,6 +572,7 @@ const ENDPOINT_DEFS = {
507
572
  path: "/application/open-fundamental/bond/rating-overview",
508
573
  kind: "json",
509
574
  description: "Query bond / issuer / guarantor ratings side by side (max 10 bonds per call)",
575
+ billing: { per: "call", price: 0.4 },
510
576
  retry: "no-replay",
511
577
  expects: "list",
512
578
  },
@@ -515,6 +581,7 @@ const ENDPOINT_DEFS = {
515
581
  path: "/application/open-fundamental/bond/rating-change",
516
582
  kind: "json",
517
583
  description: "Query bond rating change history (max 10 bonds per call)",
584
+ billing: { per: "call", price: 0.4 },
518
585
  retry: "no-replay",
519
586
  expects: "list",
520
587
  },
@@ -523,6 +590,7 @@ const ENDPOINT_DEFS = {
523
590
  path: "/application/open-fundamental/bond/issuer-rating-change",
524
591
  kind: "json",
525
592
  description: "Query issuer rating change history (by bond code or issuer name)",
593
+ billing: { per: "call", price: 0.4 },
526
594
  retry: "no-replay",
527
595
  expects: "list",
528
596
  },
@@ -531,6 +599,7 @@ const ENDPOINT_DEFS = {
531
599
  path: "/application/open-fundamental/bond/issuance-plan",
532
600
  kind: "json",
533
601
  description: "Query the rate-bond issuance calendar over a date range",
602
+ billing: { per: "call", price: 0.4 },
534
603
  retry: "no-replay",
535
604
  expects: "list",
536
605
  },
@@ -539,6 +608,7 @@ const ENDPOINT_DEFS = {
539
608
  path: "/application/open-fundamental/bond/exercise-notice",
540
609
  kind: "json",
541
610
  description: "Query put/call exercise schedules and results for option-embedded bonds",
611
+ billing: { per: "call", price: 0.4 },
542
612
  retry: "no-replay",
543
613
  expects: "list",
544
614
  },
@@ -547,12 +617,16 @@ const ENDPOINT_DEFS = {
547
617
  path: "/application/open-ai/stock-summary/getList",
548
618
  kind: "json",
549
619
  description: "Stock highlights (refined research summary per security)",
620
+ billing: { per: "row", price: 3, maxUnits: 6000 },
621
+ retry: "no-replay",
622
+ timeoutMs: 120_000,
550
623
  },
551
624
  "ai.knowledge-batch": {
552
625
  method: "POST",
553
626
  path: "/application/open-data/ai/search/knowledge/batch",
554
627
  kind: "json",
555
628
  description: "Batch knowledge search",
629
+ billing: { per: "call", price: 10 },
556
630
  retry: "no-replay",
557
631
  },
558
632
  "ai.knowledge-resource.download": {
@@ -566,6 +640,7 @@ const ENDPOINT_DEFS = {
566
640
  path: "/application/open-ai/security-clue/getList",
567
641
  kind: "json",
568
642
  description: "List security clues",
643
+ billing: { per: "row", price: 5 },
569
644
  pagination: { enabled: true, maxPageSize: 500 },
570
645
  },
571
646
  "ai.one-pager": {
@@ -573,6 +648,7 @@ const ENDPOINT_DEFS = {
573
648
  path: "/application/open-ai/agent/one-pager",
574
649
  kind: "json",
575
650
  description: "Generate one pager",
651
+ billing: { per: "call", price: 50 },
576
652
  timeoutMs: 120_000,
577
653
  retry: "no-replay",
578
654
  },
@@ -581,6 +657,7 @@ const ENDPOINT_DEFS = {
581
657
  path: "/application/open-ai/agent/investment-logic",
582
658
  kind: "json",
583
659
  description: "Generate investment logic",
660
+ billing: { per: "call", price: 50 },
584
661
  timeoutMs: 120_000,
585
662
  retry: "no-replay",
586
663
  },
@@ -589,6 +666,7 @@ const ENDPOINT_DEFS = {
589
666
  path: "/application/open-ai/agent/peer-comparison",
590
667
  kind: "json",
591
668
  description: "Generate peer comparison",
669
+ billing: { per: "call", price: 50 },
592
670
  timeoutMs: 120_000,
593
671
  retry: "no-replay",
594
672
  },
@@ -597,6 +675,7 @@ const ENDPOINT_DEFS = {
597
675
  path: "/application/open-ai/agent/earnings-review-getid",
598
676
  kind: "json",
599
677
  description: "Get earnings review ID",
678
+ billing: { per: "call", price: 50 },
600
679
  retry: "no-replay",
601
680
  },
602
681
  "ai.earnings-review.get-content": {
@@ -610,6 +689,7 @@ const ENDPOINT_DEFS = {
610
689
  path: "/application/open-ai/agent/theme-tracking",
611
690
  kind: "json",
612
691
  description: "Get theme tracking daily report",
692
+ billing: { per: "call", price: 50 },
613
693
  timeoutMs: 120_000,
614
694
  retry: "no-replay",
615
695
  },
@@ -618,6 +698,7 @@ const ENDPOINT_DEFS = {
618
698
  path: "/application/open-ai/agent/research-outline",
619
699
  kind: "json",
620
700
  description: "Get company research outline",
701
+ billing: { per: "call", price: 50 },
621
702
  timeoutMs: 120_000,
622
703
  retry: "no-replay",
623
704
  },
@@ -626,6 +707,7 @@ const ENDPOINT_DEFS = {
626
707
  path: "/application/open-ai/hot-topic/getList",
627
708
  kind: "json",
628
709
  description: "List hot topic reports",
710
+ billing: { per: "row", price: 50 },
629
711
  pagination: { enabled: true, maxPageSize: 20 },
630
712
  retry: "no-replay",
631
713
  },
@@ -634,6 +716,7 @@ const ENDPOINT_DEFS = {
634
716
  path: "/application/open-ai/management-discuss/from-announcement",
635
717
  kind: "json",
636
718
  description: "Management discussion from financial reports (half-year/annual)",
719
+ billing: { per: "call", price: 10 },
637
720
  timeoutMs: 120_000,
638
721
  retry: "no-replay",
639
722
  },
@@ -642,6 +725,7 @@ const ENDPOINT_DEFS = {
642
725
  path: "/application/open-ai/management-discuss/from-earningsCall",
643
726
  kind: "json",
644
727
  description: "Management discussion from earnings calls",
728
+ billing: { per: "call", price: 10 },
645
729
  timeoutMs: 120_000,
646
730
  retry: "no-replay",
647
731
  },
@@ -650,6 +734,7 @@ const ENDPOINT_DEFS = {
650
734
  path: "/application/open-ai/agent/viewpoint-debate-getid",
651
735
  kind: "json",
652
736
  description: "Get viewpoint debate ID",
737
+ billing: { per: "call", price: 50 },
653
738
  retry: "no-replay",
654
739
  },
655
740
  "ai.viewpoint-debate.get-content": {
@@ -664,6 +749,7 @@ const ENDPOINT_DEFS = {
664
749
  kind: "json",
665
750
  description: "List vault drive files",
666
751
  pagination: { enabled: true, maxPageSize: 50 },
752
+ rowId: "fileId",
667
753
  },
668
754
  "vault.drive.download": {
669
755
  method: "GET",
@@ -742,6 +828,7 @@ const ENDPOINT_DEFS = {
742
828
  kind: "json",
743
829
  description: "List voice recording transcriptions",
744
830
  pagination: { enabled: true, maxPageSize: 50 },
831
+ rowId: "recordId",
745
832
  },
746
833
  "vault.record.download": {
747
834
  method: "GET",
@@ -754,13 +841,16 @@ const ENDPOINT_DEFS = {
754
841
  path: "/application/open-vault/my-conference/getList",
755
842
  kind: "json",
756
843
  description: "List my conferences",
844
+ billing: { per: "row", price: 0.1 },
757
845
  pagination: { enabled: true, maxPageSize: 50 },
846
+ rowId: "conferenceId",
758
847
  },
759
848
  "vault.my-conference.download": {
760
849
  method: "GET",
761
850
  path: "/application/open-vault/my-conference/download/file",
762
851
  kind: "download",
763
852
  description: "Download my conference resource",
853
+ billing: { per: "document", price: 50 },
764
854
  retry: "no-replay",
765
855
  },
766
856
  "vault.wechat-message.list": {
@@ -768,7 +858,8 @@ const ENDPOINT_DEFS = {
768
858
  path: "/application/open-vault/wechatgroupmsg/list",
769
859
  kind: "json",
770
860
  description: "List WeChat group messages",
771
- pagination: { enabled: true, maxPageSize: 50 },
861
+ pagination: { enabled: true, maxPageSize: 50, maxWindow: 10000 },
862
+ rowId: "msgId",
772
863
  },
773
864
  "vault.wechat-chatroom.list": {
774
865
  method: "POST",
@@ -776,6 +867,7 @@ const ENDPOINT_DEFS = {
776
867
  kind: "json",
777
868
  description: "List WeChat group chatroom IDs",
778
869
  pagination: { enabled: true, maxPageSize: 50 },
870
+ rowId: "chatroomId",
779
871
  },
780
872
  "vault.stock-pool.list": {
781
873
  method: "POST",
@@ -841,6 +933,7 @@ const ENDPOINT_DEFS = {
841
933
  path: "/application/open-alternative/concept/v2/info",
842
934
  kind: "json",
843
935
  description: "Query latest concept (theme index) profile by conceptId",
936
+ billing: { per: "call", price: 50 },
844
937
  retry: "no-replay",
845
938
  },
846
939
  "alternative.concept-info-full": {
@@ -848,6 +941,7 @@ const ENDPOINT_DEFS = {
848
941
  path: "/application/open-alternative/concept/info",
849
942
  kind: "json",
850
943
  description: "Query latest concept profile incl. catalyst events (keyEvents; v1)",
944
+ billing: { per: "call", price: 500 },
851
945
  retry: "no-replay",
852
946
  },
853
947
  "alternative.concept-securities": {
@@ -855,6 +949,7 @@ const ENDPOINT_DEFS = {
855
949
  path: "/application/open-alternative/concept/v2/securities",
856
950
  kind: "json",
857
951
  description: "Query concept (theme index) constituent securities, grouped",
952
+ billing: { per: "call", price: 50 },
858
953
  retry: "no-replay",
859
954
  },
860
955
  "alternative.concept-securities-full": {
@@ -862,6 +957,7 @@ const ENDPOINT_DEFS = {
862
957
  path: "/application/open-alternative/concept/securities",
863
958
  kind: "json",
864
959
  description: "Query concept constituents incl. isKey / inclusionReason (v1)",
960
+ billing: { per: "call", price: 500 },
865
961
  retry: "no-replay",
866
962
  },
867
963
  "indicator.search": {
@@ -897,6 +993,7 @@ const ENDPOINT_DEFS = {
897
993
  path: "/application/open-tool/file-parse/submit",
898
994
  kind: "upload",
899
995
  description: "Submit a PDF for parsing (multipart upload), returns taskId",
996
+ billing: { per: "page", price: 0.8 },
900
997
  timeoutMs: 300_000,
901
998
  retry: "no-replay",
902
999
  bigIntFields: ["taskId"],
@@ -912,10 +1009,18 @@ const ENDPOINT_DEFS = {
912
1009
  path: "/application/open-tool/web-search/search",
913
1010
  kind: "json",
914
1011
  description: "Search the public web for research (tiered sources, optional page content)",
1012
+ billing: { per: "call", price: 1 },
915
1013
  retry: "no-replay",
916
1014
  expects: "list",
917
1015
  },
918
1016
  };
1017
+ export const NO_REPLAY_ABOVE_CREDITS = 3000;
1018
+ export function worstRequestCredits(endpoint) {
1019
+ if (!endpoint.billing)
1020
+ return 0;
1021
+ const units = endpoint.pagination?.maxPageSize ?? endpoint.billing.maxUnits ?? 1;
1022
+ return units * endpoint.billing.price;
1023
+ }
919
1024
  export const ENDPOINTS = Object.fromEntries(Object.entries(ENDPOINT_DEFS).map(([key, def]) => [key, { key, ...def }]));
920
1025
  export function listEndpoints() {
921
1026
  return Object.values(ENDPOINTS).map(({ key, method, path, description }) => ({ key, method, path, description }));
@@ -0,0 +1,17 @@
1
+ export const EXIT_FAILED = 1;
2
+ export const EXIT_INCOMPLETE = 3;
3
+ export const EXIT_SUPERSEDED = 4;
4
+ export function markFailed() {
5
+ process.exitCode = EXIT_FAILED;
6
+ }
7
+ export function markIncomplete() {
8
+ if (process.exitCode !== EXIT_FAILED)
9
+ process.exitCode = EXIT_INCOMPLETE;
10
+ }
11
+ export function markSuperseded() {
12
+ if (process.exitCode !== EXIT_FAILED && process.exitCode !== EXIT_INCOMPLETE)
13
+ process.exitCode = EXIT_SUPERSEDED;
14
+ }
15
+ export function decidedExitCode() {
16
+ return Number(process.exitCode ?? 0);
17
+ }
@@ -1,9 +1,8 @@
1
1
  import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
- import { isAsyncPending, nextPollDelayMs, POLL_MAX_ATTEMPTS } from "./asyncContent.js";
3
+ import { isAsyncPending, pollUntilDone } from "./asyncContent.js";
4
4
  import { saveDownloadResult } from "./download.js";
5
5
  import { ApiError, ValidationError } from "./errors.js";
6
- import { isTransientError } from "./transport.js";
7
6
  export const FILE_PARSE_MAX_BYTES = 100 * 1024 * 1024;
8
7
  export async function submitFileParse(client, filePath) {
9
8
  const resolved = path.resolve(filePath);
@@ -50,22 +49,6 @@ export async function fetchFileParseResult(client, taskId, output) {
50
49
  return "ok";
51
50
  }
52
51
  export async function pollFileParseResult(client, taskId, output) {
53
- for (let attempt = 1; attempt <= POLL_MAX_ATTEMPTS; attempt++) {
54
- try {
55
- if (await fetchFileParseResult(client, taskId, output) === "ok")
56
- return "ok";
57
- }
58
- catch (error) {
59
- if (!isTransientError(error))
60
- throw error;
61
- const msg = error instanceof Error ? error.message : String(error);
62
- process.stderr.write(`Attempt ${attempt}/${POLL_MAX_ATTEMPTS}: transient error (${msg.slice(0, 80)}), continuing to wait...\n`);
63
- }
64
- if (attempt < POLL_MAX_ATTEMPTS) {
65
- const delay = nextPollDelayMs(attempt);
66
- process.stderr.write(`Attempt ${attempt}/${POLL_MAX_ATTEMPTS}: parse result not ready, retrying in ${Math.round(delay / 1000)}s...\n`);
67
- await new Promise(resolve => setTimeout(resolve, delay));
68
- }
69
- }
70
- return "timeout";
52
+ const outcome = await pollUntilDone(async () => await fetchFileParseResult(client, taskId, output), "parse result not ready");
53
+ return outcome === "ok" ? "ok" : "timeout";
71
54
  }
@@ -0,0 +1,39 @@
1
+ import { ApiError, ValidationError } from "./errors.js";
2
+ export async function fetchOpinionDetails(client, endpointKey, idListField, idField, ids) {
3
+ const BATCH = 20;
4
+ const unique = [...new Set(ids)];
5
+ if (!unique.length)
6
+ throw new ValidationError("pass at least one opinion ID");
7
+ const list = [];
8
+ let unfetched = [];
9
+ let unfetchedError;
10
+ for (let i = 0; i < unique.length; i += BATCH) {
11
+ try {
12
+ const rows = await client.call(endpointKey, { [idListField]: unique.slice(i, i + BATCH) });
13
+ list.push(...rows);
14
+ }
15
+ catch (error) {
16
+ if (i === 0)
17
+ throw error;
18
+ unfetched = unique.slice(i);
19
+ const message = error instanceof Error ? error.message : String(error);
20
+ unfetchedError = { message, ...(error instanceof ApiError ? { code: error.code, traceId: error.traceId } : {}) };
21
+ process.stderr.write(`[gangtise] warning: ${endpointKey} failed on batch ${i / BATCH + 1} (${message}); ${unfetched.length} ID(s) not fetched: ${unfetched.join(", ")}. The bodies already returned are kept below — re-run for the IDs in unfetchedIds only.\n`);
22
+ break;
23
+ }
24
+ }
25
+ const returned = new Set(list.map((row) => String(row[idField])));
26
+ const missing = unique.filter((id) => !returned.has(id) && !unfetched.includes(id));
27
+ const out = { total: list.length, list };
28
+ if (missing.length > 0) {
29
+ process.stderr.write(`[gangtise] warning: no body returned for ${missing.length} ID(s): ${missing.join(", ")} — the server skips an ID it has no body for without an error (e.g. a wrong ID; a just-published opinion may also not have its body yet). Result marked partial (exit 3).\n`);
30
+ out.missingIds = missing;
31
+ }
32
+ if (unfetched.length > 0) {
33
+ out.unfetchedIds = unfetched;
34
+ out.unfetchedError = unfetchedError;
35
+ }
36
+ if (missing.length > 0 || unfetched.length > 0)
37
+ out.partial = true;
38
+ return out;
39
+ }