pmxt-core 2.26.1 → 2.27.1

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.
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/kalshi/Kalshi.yaml
3
- * Generated at: 2026-04-08T07:35:03.814Z
3
+ * Generated at: 2026-04-09T08:46:23.692Z
4
4
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
5
5
  */
6
6
  export declare const kalshiApiSpec: {
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.kalshiApiSpec = void 0;
4
4
  /**
5
5
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/kalshi/Kalshi.yaml
6
- * Generated at: 2026-04-08T07:35:03.814Z
6
+ * Generated at: 2026-04-09T08:46:23.692Z
7
7
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
8
8
  */
9
9
  exports.kalshiApiSpec = {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/limitless/Limitless.yaml
3
- * Generated at: 2026-04-08T07:35:03.862Z
3
+ * Generated at: 2026-04-09T08:46:23.728Z
4
4
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
5
5
  */
6
6
  export declare const limitlessApiSpec: {
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.limitlessApiSpec = void 0;
4
4
  /**
5
5
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/limitless/Limitless.yaml
6
- * Generated at: 2026-04-08T07:35:03.862Z
6
+ * Generated at: 2026-04-09T08:46:23.728Z
7
7
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
8
8
  */
9
9
  exports.limitlessApiSpec = {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/myriad/myriad.yaml
3
- * Generated at: 2026-04-08T07:35:03.878Z
3
+ * Generated at: 2026-04-09T08:46:23.741Z
4
4
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
5
5
  */
6
6
  export declare const myriadApiSpec: {
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.myriadApiSpec = void 0;
4
4
  /**
5
5
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/myriad/myriad.yaml
6
- * Generated at: 2026-04-08T07:35:03.878Z
6
+ * Generated at: 2026-04-09T08:46:23.741Z
7
7
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
8
8
  */
9
9
  exports.myriadApiSpec = {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/opinion/opinion-openapi.yaml
3
- * Generated at: 2026-04-08T07:35:03.884Z
3
+ * Generated at: 2026-04-09T08:46:23.746Z
4
4
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
5
5
  */
6
6
  export declare const opinionApiSpec: {
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.opinionApiSpec = void 0;
4
4
  /**
5
5
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/opinion/opinion-openapi.yaml
6
- * Generated at: 2026-04-08T07:35:03.884Z
6
+ * Generated at: 2026-04-09T08:46:23.746Z
7
7
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
8
8
  */
9
9
  exports.opinionApiSpec = {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/polymarket/PolymarketClobAPI.yaml
3
- * Generated at: 2026-04-08T07:35:03.820Z
3
+ * Generated at: 2026-04-09T08:46:23.698Z
4
4
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
5
5
  */
6
6
  export declare const polymarketClobSpec: {
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.polymarketClobSpec = void 0;
4
4
  /**
5
5
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/polymarket/PolymarketClobAPI.yaml
6
- * Generated at: 2026-04-08T07:35:03.820Z
6
+ * Generated at: 2026-04-09T08:46:23.698Z
7
7
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
8
8
  */
9
9
  exports.polymarketClobSpec = {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/polymarket/Polymarket_Data_API.yaml
3
- * Generated at: 2026-04-08T07:35:03.838Z
3
+ * Generated at: 2026-04-09T08:46:23.711Z
4
4
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
5
5
  */
6
6
  export declare const polymarketDataSpec: {
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.polymarketDataSpec = void 0;
4
4
  /**
5
5
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/polymarket/Polymarket_Data_API.yaml
6
- * Generated at: 2026-04-08T07:35:03.838Z
6
+ * Generated at: 2026-04-09T08:46:23.711Z
7
7
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
8
8
  */
9
9
  exports.polymarketDataSpec = {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/polymarket/PolymarketGammaAPI.yaml
3
- * Generated at: 2026-04-08T07:35:03.836Z
3
+ * Generated at: 2026-04-09T08:46:23.708Z
4
4
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
5
5
  */
6
6
  export declare const polymarketGammaSpec: {
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.polymarketGammaSpec = void 0;
4
4
  /**
5
5
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/polymarket/PolymarketGammaAPI.yaml
6
- * Generated at: 2026-04-08T07:35:03.836Z
6
+ * Generated at: 2026-04-09T08:46:23.708Z
7
7
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
8
8
  */
9
9
  exports.polymarketGammaSpec = {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/probable/probable.yaml
3
- * Generated at: 2026-04-08T07:35:03.870Z
3
+ * Generated at: 2026-04-09T08:46:23.734Z
4
4
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
5
5
  */
6
6
  export declare const probableApiSpec: {
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.probableApiSpec = void 0;
4
4
  /**
5
5
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/probable/probable.yaml
6
- * Generated at: 2026-04-08T07:35:03.870Z
6
+ * Generated at: 2026-04-09T08:46:23.734Z
7
7
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
8
8
  */
9
9
  exports.probableApiSpec = {
@@ -1 +1,58 @@
1
+ import { Express } from "express";
2
+ /**
3
+ * Options accepted by {@link createApp}.
4
+ */
5
+ export interface CreateAppOptions {
6
+ /**
7
+ * Access token for the built-in `x-pmxt-access-token` auth middleware.
8
+ *
9
+ * When set, every non-`/health` request must carry a matching token.
10
+ * This is how the local sidecar protects itself from other processes
11
+ * on the same machine.
12
+ *
13
+ * When omitted (or empty string), the built-in token check is
14
+ * disabled. This is the mode hosted-pmxt uses when it mounts the core
15
+ * app under its own Bearer-auth middleware — no point double-checking.
16
+ */
17
+ accessToken?: string;
18
+ /**
19
+ * If true, skip registering `cors()` and `express.json()`.
20
+ *
21
+ * Useful when the host application has already installed its own body
22
+ * parser / CORS middleware and you just want the route handlers.
23
+ * Defaults to false.
24
+ */
25
+ skipBaseMiddleware?: boolean;
26
+ }
27
+ /**
28
+ * Build an Express app that serves the PMXT sidecar API surface without
29
+ * binding to a port.
30
+ *
31
+ * This is the mounting point for consumers like hosted-pmxt that want to
32
+ * wrap the sidecar in their own auth / quota / usage middleware and serve
33
+ * it as part of a larger Express application.
34
+ *
35
+ * The returned app registers:
36
+ * - `GET /health`
37
+ * - (optional) the built-in `x-pmxt-access-token` auth check
38
+ * - `POST /api/:exchange/:method`
39
+ * - the error handler
40
+ *
41
+ * Usage:
42
+ * ```ts
43
+ * import express from 'express';
44
+ * import { createApp as createPmxtCoreApp } from 'pmxt-core';
45
+ *
46
+ * const app = express();
47
+ * app.use(myAuthMiddleware);
48
+ * app.use('/', createPmxtCoreApp()); // no token required — we auth upstream
49
+ * app.listen(4000);
50
+ * ```
51
+ */
52
+ export declare function createApp(options?: CreateAppOptions): Express;
53
+ /**
54
+ * Start the PMXT sidecar server on the given port with the built-in
55
+ * access-token auth middleware enabled. Returns the underlying
56
+ * {@link http.Server} once it is listening.
57
+ */
1
58
  export declare function startServer(port: number, accessToken: string): Promise<import("node:http").Server<typeof import("node:http").IncomingMessage, typeof import("node:http").ServerResponse>>;
@@ -3,9 +3,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createApp = createApp;
6
7
  exports.startServer = startServer;
7
8
  const express_1 = __importDefault(require("express"));
8
9
  const cors_1 = __importDefault(require("cors"));
10
+ const fs_1 = __importDefault(require("fs"));
11
+ const path_1 = __importDefault(require("path"));
9
12
  const polymarket_1 = require("../exchanges/polymarket");
10
13
  const limitless_1 = require("../exchanges/limitless");
11
14
  const kalshi_1 = require("../exchanges/kalshi");
@@ -18,6 +21,113 @@ const metaculus_1 = require("../exchanges/metaculus");
18
21
  const smarkets_1 = require("../exchanges/smarkets");
19
22
  const polymarket_us_1 = require("../exchanges/polymarket_us");
20
23
  const errors_1 = require("../errors");
24
+ function loadMethodVerbs() {
25
+ const candidates = [
26
+ path_1.default.join(__dirname, "method-verbs.json"),
27
+ path_1.default.join(__dirname, "../../src/server/method-verbs.json"),
28
+ ];
29
+ for (const file of candidates) {
30
+ try {
31
+ if (fs_1.default.existsSync(file)) {
32
+ return JSON.parse(fs_1.default.readFileSync(file, "utf8"));
33
+ }
34
+ }
35
+ catch {
36
+ // Fall through to the next candidate.
37
+ }
38
+ }
39
+ console.warn("[pmxt-core] method-verbs.json not found — GET /api/:exchange/:method disabled. " +
40
+ "POST continues to work. Rebuild pmxt-core to regenerate.");
41
+ return {};
42
+ }
43
+ const METHOD_VERBS = loadMethodVerbs();
44
+ /**
45
+ * Coerce a query-string value into its closest native type, optionally
46
+ * honoring the arg kind declared in `method-verbs.json`.
47
+ *
48
+ * Express's built-in query parser hands us strings (or arrays of strings)
49
+ * regardless of what the method actually wants. When we know the target
50
+ * kind we respect it exactly:
51
+ * - `string` → never coerce, even if the value looks like a number.
52
+ * (Critical for venue IDs like Polymarket's all-numeric condition
53
+ * IDs, which must stay strings to keep `.trim()` etc. working.)
54
+ * - `number` → parse as float/int; non-numeric input stays a string.
55
+ * - `boolean` → accept the literal `"true"` / `"false"`, else leave as-is.
56
+ *
57
+ * When the kind is unknown (`undefined`, e.g. object-arg properties that
58
+ * aren't statically typed), fall back to the permissive heuristic:
59
+ * lift obvious numeric/boolean literals and leave everything else alone.
60
+ */
61
+ function coerceQueryValue(raw, kind) {
62
+ if (Array.isArray(raw))
63
+ return raw.map((v) => coerceQueryValue(v, kind));
64
+ if (typeof raw !== "string")
65
+ return raw;
66
+ if (kind === "string")
67
+ return raw;
68
+ if (kind === "number") {
69
+ if (/^-?\d+$/.test(raw))
70
+ return parseInt(raw, 10);
71
+ if (/^-?\d*\.\d+$/.test(raw))
72
+ return parseFloat(raw);
73
+ return raw;
74
+ }
75
+ if (kind === "boolean") {
76
+ if (raw === "true")
77
+ return true;
78
+ if (raw === "false")
79
+ return false;
80
+ return raw;
81
+ }
82
+ // Unknown kind — permissive fallback.
83
+ if (raw === "true")
84
+ return true;
85
+ if (raw === "false")
86
+ return false;
87
+ if (raw === "null")
88
+ return null;
89
+ if (/^-?\d+$/.test(raw))
90
+ return parseInt(raw, 10);
91
+ if (/^-?\d*\.\d+$/.test(raw))
92
+ return parseFloat(raw);
93
+ return raw;
94
+ }
95
+ /**
96
+ * Translate a parsed query-string object into the positional `args`
97
+ * array that `exchange[method](...args)` expects, using the per-method
98
+ * spec extracted from BaseExchange.ts at generation time.
99
+ *
100
+ * Rules:
101
+ * - Primitive args are pulled by name from the query and coerced.
102
+ * - A single object arg swallows every *remaining* query key (after
103
+ * primitive args have been consumed) as its properties.
104
+ * - Trailing `undefined`s are trimmed so optional tail params stay
105
+ * optional instead of arriving as explicit `undefined`.
106
+ */
107
+ function queryToArgs(query, spec) {
108
+ // Reserve primitive arg names so they don't leak into an object arg.
109
+ const primitiveNames = new Set(spec.filter((s) => s.kind !== "object").map((s) => s.name));
110
+ const args = [];
111
+ for (const arg of spec) {
112
+ if (arg.kind === "object") {
113
+ const obj = {};
114
+ for (const [k, v] of Object.entries(query)) {
115
+ if (primitiveNames.has(k))
116
+ continue;
117
+ obj[k] = coerceQueryValue(v);
118
+ }
119
+ args.push(Object.keys(obj).length > 0 ? obj : undefined);
120
+ }
121
+ else {
122
+ const raw = query[arg.name];
123
+ args.push(raw !== undefined ? coerceQueryValue(raw, arg.kind) : undefined);
124
+ }
125
+ }
126
+ while (args.length > 0 && args[args.length - 1] === undefined) {
127
+ args.pop();
128
+ }
129
+ return args;
130
+ }
21
131
  // Singleton instances for local usage (when no credentials provided)
22
132
  const defaultExchanges = {
23
133
  polymarket: null,
@@ -31,37 +141,65 @@ const defaultExchanges = {
31
141
  metaculus: null,
32
142
  smarkets: null,
33
143
  };
34
- async function startServer(port, accessToken) {
144
+ /**
145
+ * Build an Express app that serves the PMXT sidecar API surface without
146
+ * binding to a port.
147
+ *
148
+ * This is the mounting point for consumers like hosted-pmxt that want to
149
+ * wrap the sidecar in their own auth / quota / usage middleware and serve
150
+ * it as part of a larger Express application.
151
+ *
152
+ * The returned app registers:
153
+ * - `GET /health`
154
+ * - (optional) the built-in `x-pmxt-access-token` auth check
155
+ * - `POST /api/:exchange/:method`
156
+ * - the error handler
157
+ *
158
+ * Usage:
159
+ * ```ts
160
+ * import express from 'express';
161
+ * import { createApp as createPmxtCoreApp } from 'pmxt-core';
162
+ *
163
+ * const app = express();
164
+ * app.use(myAuthMiddleware);
165
+ * app.use('/', createPmxtCoreApp()); // no token required — we auth upstream
166
+ * app.listen(4000);
167
+ * ```
168
+ */
169
+ function createApp(options = {}) {
170
+ const { accessToken, skipBaseMiddleware = false } = options;
35
171
  const app = (0, express_1.default)();
36
- app.use((0, cors_1.default)());
37
- app.use(express_1.default.json());
172
+ if (!skipBaseMiddleware) {
173
+ app.use((0, cors_1.default)());
174
+ app.use(express_1.default.json({ limit: "2mb" }));
175
+ }
38
176
  // Health check (public)
39
177
  app.get("/health", (req, res) => {
40
178
  res.json({ status: "ok", timestamp: Date.now() });
41
179
  });
42
- // Auth Middleware
43
- app.use((req, res, next) => {
44
- const token = req.headers["x-pmxt-access-token"];
45
- if (!token || token !== accessToken) {
46
- res.status(401).json({
47
- success: false,
48
- error: "Unauthorized: Invalid or missing access token",
49
- });
50
- return;
51
- }
52
- next();
53
- });
54
- // API endpoint: POST /api/:exchange/:method
55
- // Body: { args: any[], credentials?: ExchangeCredentials }
56
- app.post("/api/:exchange/:method", async (req, res, next) => {
180
+ // Optional built-in auth. Only registered when an accessToken is
181
+ // supplied — hosted-pmxt mounts this app without a token and relies on
182
+ // its own upstream Bearer middleware.
183
+ if (accessToken) {
184
+ app.use((req, res, next) => {
185
+ const token = req.headers["x-pmxt-access-token"];
186
+ if (!token || token !== accessToken) {
187
+ res.status(401).json({
188
+ success: false,
189
+ error: "Unauthorized: Invalid or missing access token",
190
+ });
191
+ return;
192
+ }
193
+ next();
194
+ });
195
+ }
196
+ // Shared dispatch used by both GET and POST handlers below. Given the
197
+ // method name, the positional args, and optional credentials, it
198
+ // resolves the exchange instance (singleton or per-request) and
199
+ // invokes `exchange[method](...args)`.
200
+ async function dispatchMethod(req, res, next, methodName, args, credentials) {
57
201
  try {
58
202
  const exchangeName = req.params.exchange.toLowerCase();
59
- const methodName = req.params.method;
60
- const args = Array.isArray(req.body.args) ? req.body.args : [];
61
- const credentials = req.body.credentials;
62
- // 1. Get or Initialize Exchange
63
- // If credentials are provided, create a new instance for this request
64
- // Otherwise, use the singleton instance
65
203
  let exchange;
66
204
  if (credentials &&
67
205
  (credentials.privateKey ||
@@ -75,15 +213,12 @@ async function startServer(port, accessToken) {
75
213
  }
76
214
  exchange = defaultExchanges[exchangeName];
77
215
  }
78
- // Apply verbose logging if requested via header
79
216
  if (req.headers["x-pmxt-verbose"] === "true") {
80
217
  exchange.verbose = true;
81
218
  }
82
219
  else {
83
- // Reset to false for singleton instances to avoid leaking state between requests
84
220
  exchange.verbose = false;
85
221
  }
86
- // 2. Validate Method
87
222
  if (typeof exchange[methodName] !== "function") {
88
223
  res.status(404).json({
89
224
  success: false,
@@ -91,13 +226,48 @@ async function startServer(port, accessToken) {
91
226
  });
92
227
  return;
93
228
  }
94
- // 3. Execute with direct argument spreading
95
229
  const result = await exchange[methodName](...args);
96
230
  res.json({ success: true, data: result });
97
231
  }
98
232
  catch (error) {
99
233
  next(error);
100
234
  }
235
+ }
236
+ // GET /api/:exchange/:method
237
+ //
238
+ // Enabled for methods classified as idempotent reads by the OpenAPI
239
+ // generator (every method starting with `fetch` whose signature fits
240
+ // in a query string). The method name is looked up in METHOD_VERBS;
241
+ // if it isn't a GET method we return 405 so callers don't silently
242
+ // hit a stale route. POST continues to work for every method,
243
+ // including the ones exposed as GET here, so existing SDK clients
244
+ // that unconditionally POST keep functioning unchanged.
245
+ app.get("/api/:exchange/:method", async (req, res, next) => {
246
+ const methodName = req.params.method;
247
+ const meta = METHOD_VERBS[methodName];
248
+ if (!meta || meta.verb !== "get") {
249
+ res.status(405).json({
250
+ success: false,
251
+ error: `Method '${methodName}' is not available via GET. ` +
252
+ `Use POST /api/:exchange/${methodName} instead.`,
253
+ });
254
+ return;
255
+ }
256
+ const args = queryToArgs(req.query, meta.args);
257
+ // GET requests never carry credentials in the body (and query
258
+ // strings would leak them); unauthenticated reads only.
259
+ await dispatchMethod(req, res, next, methodName, args, undefined);
260
+ });
261
+ // POST /api/:exchange/:method
262
+ //
263
+ // The original RPC-shaped surface. Body: { args: any[], credentials? }.
264
+ // Accepts every method, including reads — so pre-existing clients
265
+ // that POST reads keep working forever.
266
+ app.post("/api/:exchange/:method", async (req, res, next) => {
267
+ const methodName = req.params.method;
268
+ const args = Array.isArray(req.body.args) ? req.body.args : [];
269
+ const credentials = req.body.credentials;
270
+ await dispatchMethod(req, res, next, methodName, args, credentials);
101
271
  });
102
272
  // Error handler
103
273
  app.use((error, req, res, next) => {
@@ -139,6 +309,15 @@ async function startServer(port, accessToken) {
139
309
  },
140
310
  });
141
311
  });
312
+ return app;
313
+ }
314
+ /**
315
+ * Start the PMXT sidecar server on the given port with the built-in
316
+ * access-token auth middleware enabled. Returns the underlying
317
+ * {@link http.Server} once it is listening.
318
+ */
319
+ async function startServer(port, accessToken) {
320
+ const app = createApp({ accessToken });
142
321
  return app.listen(port, "127.0.0.1");
143
322
  }
144
323
  function createExchange(name, credentials) {