pmxtjs 2.25.2 → 2.26.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.
@@ -6,7 +6,7 @@
6
6
  * OpenAPI client, matching the Python API exactly.
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.Baozi = exports.Probable = exports.Myriad = exports.KalshiDemo = exports.Limitless = exports.Kalshi = exports.Polymarket = exports.Exchange = void 0;
9
+ exports.PolymarketUS = exports.Smarkets = exports.Metaculus = exports.Opinion = exports.Baozi = exports.Probable = exports.Myriad = exports.KalshiDemo = exports.Limitless = exports.Kalshi = exports.Polymarket = exports.Exchange = void 0;
10
10
  const index_js_1 = require("../generated/src/index.js");
11
11
  const models_js_1 = require("./models.js");
12
12
  const server_manager_js_1 = require("./server-manager.js");
@@ -1630,3 +1630,77 @@ class Baozi extends Exchange {
1630
1630
  }
1631
1631
  }
1632
1632
  exports.Baozi = Baozi;
1633
+ /**
1634
+ * Opinion exchange client.
1635
+ *
1636
+ * Polygon-based CLOB exchange. Public catalog endpoints work without
1637
+ * credentials; trading requires `apiKey` (proxy address) and `privateKey`.
1638
+ *
1639
+ * @example
1640
+ * ```typescript
1641
+ * const opinion = new Opinion();
1642
+ * const events = await opinion.fetchEvents();
1643
+ * ```
1644
+ */
1645
+ class Opinion extends Exchange {
1646
+ constructor(options = {}) {
1647
+ super("opinion", options);
1648
+ }
1649
+ }
1650
+ exports.Opinion = Opinion;
1651
+ /**
1652
+ * Metaculus exchange client.
1653
+ *
1654
+ * Forecasting platform. Public read-only access works without credentials;
1655
+ * authenticated calls accept a bearer token via `apiKey`.
1656
+ *
1657
+ * @example
1658
+ * ```typescript
1659
+ * const metaculus = new Metaculus();
1660
+ * const events = await metaculus.fetchEvents();
1661
+ * ```
1662
+ */
1663
+ class Metaculus extends Exchange {
1664
+ constructor(options = {}) {
1665
+ super("metaculus", options);
1666
+ }
1667
+ }
1668
+ exports.Metaculus = Metaculus;
1669
+ /**
1670
+ * Smarkets exchange client.
1671
+ *
1672
+ * UK-based betting exchange. Public catalog endpoints work without
1673
+ * credentials; trading requires Smarkets account email (`apiKey`) and
1674
+ * password (`privateKey`).
1675
+ *
1676
+ * @example
1677
+ * ```typescript
1678
+ * const smarkets = new Smarkets();
1679
+ * const events = await smarkets.fetchEvents();
1680
+ * ```
1681
+ */
1682
+ class Smarkets extends Exchange {
1683
+ constructor(options = {}) {
1684
+ super("smarkets", options);
1685
+ }
1686
+ }
1687
+ exports.Smarkets = Smarkets;
1688
+ /**
1689
+ * Polymarket US exchange client.
1690
+ *
1691
+ * US-regulated Polymarket venue. Public catalog endpoints work without
1692
+ * credentials; trading requires `apiKey` (keyId) and `privateKey`
1693
+ * (secretKey) issued by Polymarket US.
1694
+ *
1695
+ * @example
1696
+ * ```typescript
1697
+ * const polyUs = new PolymarketUS();
1698
+ * const events = await polyUs.fetchEvents();
1699
+ * ```
1700
+ */
1701
+ class PolymarketUS extends Exchange {
1702
+ constructor(options = {}) {
1703
+ super("polymarket_us", options);
1704
+ }
1705
+ }
1706
+ exports.PolymarketUS = PolymarketUS;
@@ -15,6 +15,7 @@ export declare class ServerManager {
15
15
  private api;
16
16
  private lockPath;
17
17
  private static readonly DEFAULT_PORT;
18
+ private static ensurePromise;
18
19
  constructor(options?: ServerManagerOptions);
19
20
  /**
20
21
  * Read server information from lock file.
@@ -43,8 +44,13 @@ export declare class ServerManager {
43
44
  private waitForServer;
44
45
  /**
45
46
  * Ensure the server is running, starting it if necessary.
47
+ *
48
+ * Concurrent calls across all ServerManager instances in the process
49
+ * are coalesced onto a single in-flight promise. See the comment on
50
+ * `ServerManager.ensurePromise` for why this matters.
46
51
  */
47
52
  ensureServerRunning(): Promise<void>;
53
+ private doEnsureServerRunning;
48
54
  private isVersionMismatch;
49
55
  /**
50
56
  * Stop the currently running server.
@@ -54,5 +60,36 @@ export declare class ServerManager {
54
60
  * Restart the server.
55
61
  */
56
62
  restart(): Promise<void>;
63
+ /**
64
+ * Start the server if it is not already running.
65
+ *
66
+ * Idempotent: if the server is already running and healthy this returns
67
+ * immediately without restarting.
68
+ */
69
+ start(): Promise<void>;
70
+ /**
71
+ * Get a structured snapshot of the sidecar server state.
72
+ *
73
+ * Returns a fresh object on every call (no shared mutable state).
74
+ */
75
+ status(): Promise<{
76
+ running: boolean;
77
+ pid: number | null;
78
+ port: number | null;
79
+ version: string | null;
80
+ uptimeSeconds: number | null;
81
+ lockFile: string;
82
+ }>;
83
+ /**
84
+ * Check whether the server's /health endpoint is currently responsive.
85
+ */
86
+ health(): Promise<boolean>;
87
+ /**
88
+ * Return the last `n` lines from the sidecar server log file.
89
+ *
90
+ * The launcher writes server stdout/stderr to ~/.pmxt/server.log.
91
+ * Returns an empty array if no log file is present.
92
+ */
93
+ logs(n?: number): string[];
57
94
  private killOldServer;
58
95
  }
@@ -50,6 +50,28 @@ class ServerManager {
50
50
  api;
51
51
  lockPath;
52
52
  static DEFAULT_PORT = 3847;
53
+ // Process-wide coalescing of concurrent ensureServerRunning() calls.
54
+ //
55
+ // Each `Exchange` instance constructs its own ServerManager and each one
56
+ // kicks off ensureServerRunning() from its constructor. Without
57
+ // coalescing, N Exchange instances created in parallel all see "no
58
+ // server running", all spawn their own sidecar via pmxt-ensure-server,
59
+ // and the lock file ends up pointing at whichever spawn wrote last. Each
60
+ // Exchange instance has already captured its own basePath at
61
+ // construction time, so most of them end up talking to a sidecar whose
62
+ // access token does NOT match the token they later read from the lock
63
+ // file — every request returns 401 Unauthorized.
64
+ //
65
+ // The fix is process-wide: when a ensureServerRunning call is in
66
+ // flight, all subsequent callers await the same promise. After the
67
+ // in-flight call settles (success OR failure) the cache is cleared so
68
+ // later callers can re-check the sidecar state (e.g. if it was killed
69
+ // by the user between ticks).
70
+ //
71
+ // This is static on purpose — all ServerManager instances in the
72
+ // process share the same sidecar and the same lock file, so they must
73
+ // share the same in-flight promise.
74
+ static ensurePromise = null;
53
75
  constructor(options = {}) {
54
76
  this.baseUrl = options.baseUrl || `http://localhost:${ServerManager.DEFAULT_PORT}`;
55
77
  this.maxRetries = options.maxRetries || 30;
@@ -137,8 +159,21 @@ class ServerManager {
137
159
  }
138
160
  /**
139
161
  * Ensure the server is running, starting it if necessary.
162
+ *
163
+ * Concurrent calls across all ServerManager instances in the process
164
+ * are coalesced onto a single in-flight promise. See the comment on
165
+ * `ServerManager.ensurePromise` for why this matters.
140
166
  */
141
167
  async ensureServerRunning() {
168
+ if (ServerManager.ensurePromise) {
169
+ return ServerManager.ensurePromise;
170
+ }
171
+ ServerManager.ensurePromise = this.doEnsureServerRunning().finally(() => {
172
+ ServerManager.ensurePromise = null;
173
+ });
174
+ return ServerManager.ensurePromise;
175
+ }
176
+ async doEnsureServerRunning() {
142
177
  // Check for force restart
143
178
  if (process.env.PMXT_ALWAYS_RESTART === '1') {
144
179
  await this.killOldServer();
@@ -237,6 +272,70 @@ class ServerManager {
237
272
  await this.stop();
238
273
  await this.ensureServerRunning();
239
274
  }
275
+ /**
276
+ * Start the server if it is not already running.
277
+ *
278
+ * Idempotent: if the server is already running and healthy this returns
279
+ * immediately without restarting.
280
+ */
281
+ async start() {
282
+ await this.ensureServerRunning();
283
+ }
284
+ /**
285
+ * Get a structured snapshot of the sidecar server state.
286
+ *
287
+ * Returns a fresh object on every call (no shared mutable state).
288
+ */
289
+ async status() {
290
+ const info = this.getServerInfo();
291
+ const running = await this.isServerRunning();
292
+ let uptimeSeconds = null;
293
+ if (info && typeof info.timestamp === "number") {
294
+ const nowSeconds = Date.now() / 1000;
295
+ const tsSeconds = info.timestamp > 1e12 ? info.timestamp / 1000 : info.timestamp;
296
+ const delta = nowSeconds - tsSeconds;
297
+ if (delta >= 0)
298
+ uptimeSeconds = delta;
299
+ }
300
+ return {
301
+ running,
302
+ pid: info?.pid ?? null,
303
+ port: info?.port ?? null,
304
+ version: info?.version ?? null,
305
+ uptimeSeconds,
306
+ lockFile: this.lockPath,
307
+ };
308
+ }
309
+ /**
310
+ * Check whether the server's /health endpoint is currently responsive.
311
+ */
312
+ async health() {
313
+ return this.isServerRunning();
314
+ }
315
+ /**
316
+ * Return the last `n` lines from the sidecar server log file.
317
+ *
318
+ * The launcher writes server stdout/stderr to ~/.pmxt/server.log.
319
+ * Returns an empty array if no log file is present.
320
+ */
321
+ logs(n = 50) {
322
+ if (n <= 0)
323
+ return [];
324
+ const logPath = (0, path_1.join)((0, path_1.dirname)(this.lockPath), "server.log");
325
+ try {
326
+ if (!(0, fs_1.existsSync)(logPath))
327
+ return [];
328
+ const content = (0, fs_1.readFileSync)(logPath, "utf-8");
329
+ const lines = content.split(/\r?\n/);
330
+ // split on a trailing newline produces an empty final element; drop it
331
+ if (lines.length > 0 && lines[lines.length - 1] === "")
332
+ lines.pop();
333
+ return lines.length > n ? lines.slice(lines.length - n) : lines;
334
+ }
335
+ catch {
336
+ return [];
337
+ }
338
+ }
240
339
  async killOldServer() {
241
340
  const info = this.getServerInfo();
242
341
  if (info && info.pid) {