mishmar 0.0.0-stage → 0.3.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/dist/signer.js ADDED
@@ -0,0 +1,416 @@
1
+ /**
2
+ * The signer as a child process, or one already running.
3
+ *
4
+ * `Signer` starts `mishmar signer run --local …` with the agent's config and
5
+ * talks to it over a Unix socket (or, on Windows, localhost with a token).
6
+ * The child holds the agent's key and its halves of the venue keys; this
7
+ * process holds no secret at all. An order is an intent: the child turns it
8
+ * into a proposal signed with the agent's key, sends it one-shot where it can
9
+ * build the venue's message, through the channel otherwise, and hands back
10
+ * the decision as the engine recorded it. The agent's reads pass through the
11
+ * child the same way.
12
+ *
13
+ * const s = await new Signer().start(); // ~/.mishmar/agent.json, mishmar on PATH
14
+ * let d = await s.order({ tradingAccountId: "ta_…", symbol: "BTC", side: "buy", size: "0.001", price: "79500" });
15
+ * if (d.verdict === "require_approval") d = await s.wait(d.id);
16
+ * await s.stop();
17
+ *
18
+ * The child lives and dies with this process: it gets a pipe on stdin that
19
+ * is never written to and stops when the pipe closes, so it does not outlive
20
+ * a bot that crashed or was killed. A signer run on its own instead (systemd,
21
+ * launchd, Docker: `mishmar signer run --local unix:/path`) is reached with
22
+ * `Signer.connect`, which never starts or stops it; that is the way for
23
+ * several processes of one agent, or to keep the signer connected across bot
24
+ * restarts. One signer runs per config: a second one on the same config
25
+ * stops at once and says which process holds it.
26
+ *
27
+ * The binary is found through `MISHMAR_BIN`, the PATH, or
28
+ * `~/.mishmar/bin/<version>/mishmar`; `downloadBinary` fetches a release from
29
+ * GitHub and checks it against the release's checksums.
30
+ */
31
+ import { execFile, spawn } from "node:child_process";
32
+ import { createHash, randomBytes } from "node:crypto";
33
+ import { constants as fsConstants } from "node:fs";
34
+ import { access, chmod, mkdir, mkdtemp, open, readdir, realpath, rm, writeFile } from "node:fs/promises";
35
+ import * as http from "node:http";
36
+ import * as net from "node:net";
37
+ import { arch, homedir, platform, tmpdir } from "node:os";
38
+ import * as path from "node:path";
39
+ import { EngineError, resolved } from "./client.js";
40
+ export const RELEASES = "https://github.com/mishmar-io/mishmar/releases/download";
41
+ /** The release the SDK fetches when it has to (the npm bin: `npx -y mishmar …`); bumped with each release. */
42
+ export const RELEASE = "v0.2.2";
43
+ const ENV_BINARY = "MISHMAR_BIN";
44
+ const ENV_LOCAL_TOKEN = "MISHMAR_LOCAL_TOKEN";
45
+ /** The release asset's os_arch for this machine: linux_amd64, darwin_arm64, … */
46
+ export function platformName() {
47
+ const os = { linux: "linux", darwin: "darwin", win32: "windows" }[platform()];
48
+ const cpu = { x64: "amd64", arm64: "arm64" }[arch()];
49
+ if (!os || !cpu)
50
+ throw new Error(`no mishmar release for ${platform()}/${arch()}`);
51
+ return `${os}_${cpu}`;
52
+ }
53
+ function exe(name) {
54
+ return platform() === "win32" ? name + ".exe" : name;
55
+ }
56
+ async function executable(p) {
57
+ if (!p)
58
+ return false;
59
+ try {
60
+ await access(p, fsConstants.X_OK);
61
+ return true;
62
+ }
63
+ catch {
64
+ return false;
65
+ }
66
+ }
67
+ /** Whether the file starts with `#!`: a script, never the mishmar binary itself. */
68
+ async function isScript(p) {
69
+ const f = await open(p, "r");
70
+ try {
71
+ const { bytesRead, buffer } = await f.read(Buffer.alloc(2), 0, 2, 0);
72
+ return bytesRead === 2 && buffer[0] === 0x23 && buffer[1] === 0x21;
73
+ }
74
+ finally {
75
+ await f.close();
76
+ }
77
+ }
78
+ /**
79
+ * Where the mishmar binary is: the argument, `MISHMAR_BIN`, the PATH, or a
80
+ * release `downloadBinary` fetched. Throws with what to do when there is none.
81
+ */
82
+ export async function findBinary(explicit, options = {}) {
83
+ for (const c of [explicit, process.env[ENV_BINARY]])
84
+ if (await executable(c))
85
+ return c;
86
+ const name = exe("mishmar");
87
+ for (const dir of (process.env.PATH ?? "").split(path.delimiter)) {
88
+ if (!dir)
89
+ continue;
90
+ const c = path.join(dir, name);
91
+ if (!(await executable(c)))
92
+ continue;
93
+ if (options.skipScripts && (await isScript(c)))
94
+ continue;
95
+ if (options.exclude?.length && options.exclude.includes(await realpath(c).catch(() => c)))
96
+ continue;
97
+ return c;
98
+ }
99
+ const home = path.join(homedir(), ".mishmar", "bin");
100
+ try {
101
+ for (const version of (await readdir(home)).sort().reverse()) {
102
+ const c = path.join(home, version, name);
103
+ if (await executable(c))
104
+ return c;
105
+ }
106
+ }
107
+ catch {
108
+ // no downloads yet
109
+ }
110
+ throw new Error("no mishmar binary: install it from https://github.com/mishmar-io/mishmar/releases (or call downloadBinary(version)), put it on the PATH or in MISHMAR_BIN");
111
+ }
112
+ /**
113
+ * Fetch the release `version` (a tag, v0.1.0) for this machine into `destDir`
114
+ * (default `~/.mishmar/bin/<version>`), after checking its SHA-256 against the
115
+ * release's SHA256SUMS. Returns the binary's path.
116
+ */
117
+ export async function downloadBinary(version, destDir, fetchImpl = fetch) {
118
+ const name = `mishmar_${version}_${platformName()}`;
119
+ const base = `${RELEASES}/${version}`;
120
+ const sumsRes = await fetchImpl(`${base}/SHA256SUMS`);
121
+ if (!sumsRes.ok)
122
+ throw new Error(`${base}/SHA256SUMS: HTTP ${sumsRes.status}`);
123
+ let want;
124
+ for (const line of (await sumsRes.text()).split("\n")) {
125
+ const parts = line.trim().split(/\s+/);
126
+ if (parts.length === 2 && parts[1]?.replace(/^\*/, "") === `${name}.tar.gz`)
127
+ want = parts[0]?.toLowerCase();
128
+ }
129
+ if (!want)
130
+ throw new Error(`${name}.tar.gz is not in the release's SHA256SUMS`);
131
+ const res = await fetchImpl(`${base}/${name}.tar.gz`);
132
+ if (!res.ok)
133
+ throw new Error(`${base}/${name}.tar.gz: HTTP ${res.status}`);
134
+ const archive = Buffer.from(await res.arrayBuffer());
135
+ const got = createHash("sha256").update(archive).digest("hex");
136
+ if (got !== want)
137
+ throw new Error(`${name}.tar.gz: checksum ${got} is not the release's ${want}`);
138
+ const dir = destDir ?? path.join(homedir(), ".mishmar", "bin", version);
139
+ await mkdir(dir, { recursive: true, mode: 0o700 });
140
+ const work = await mkdtemp(path.join(tmpdir(), "mishmar-"));
141
+ try {
142
+ const file = path.join(work, `${name}.tar.gz`);
143
+ await writeFile(file, archive);
144
+ await new Promise((resolve, reject) => execFile("tar", ["-xzf", file, "-C", dir, exe("mishmar")], (err) => (err ? reject(err) : resolve())));
145
+ }
146
+ finally {
147
+ await rm(work, { recursive: true, force: true });
148
+ }
149
+ const out = path.join(dir, exe("mishmar"));
150
+ await chmod(out, 0o755);
151
+ return out;
152
+ }
153
+ /**
154
+ * The agent's signer, run as a child process and spoken to over its local API.
155
+ * The child stops when this process exits, however it exits. `Signer.connect`
156
+ * speaks to a signer that runs on its own instead.
157
+ */
158
+ export class Signer {
159
+ options;
160
+ address;
161
+ token;
162
+ process;
163
+ #dir;
164
+ #stderrTail = [];
165
+ #attached = false;
166
+ #ready = false;
167
+ constructor(options = {}) {
168
+ this.options = options;
169
+ this.address = options.address;
170
+ }
171
+ /**
172
+ * Speak to a signer that already runs on its own (`mishmar signer run
173
+ * --local ADDRESS`, kept up by systemd, launchd or Docker), and wait until it
174
+ * answers. `address` is `unix:/path`, or a loopback host:port with the
175
+ * signer's token. Nothing is started, and `stop` leaves the signer running.
176
+ */
177
+ static async connect(address, options = {}) {
178
+ const s = new Signer({ address, startTimeoutMs: options.startTimeoutMs, timeoutMs: options.timeoutMs });
179
+ s.#attached = true;
180
+ if (!address.startsWith("unix:")) {
181
+ s.token = options.token ?? process.env[ENV_LOCAL_TOKEN];
182
+ if (!s.token)
183
+ throw new Error(`${address}: a signer on a TCP port needs its token: pass { token } or set ${ENV_LOCAL_TOKEN}`);
184
+ }
185
+ return s.start();
186
+ }
187
+ /** Start the child and wait until its local API answers; for a signer from `connect`, only wait until it answers. */
188
+ async start() {
189
+ if (this.#attached) {
190
+ if (!this.#ready) {
191
+ await this.#waitReady(() => undefined);
192
+ this.#ready = true;
193
+ }
194
+ return this;
195
+ }
196
+ if (this.process)
197
+ return this;
198
+ const binary = await findBinary(this.options.binary);
199
+ const env = { ...process.env };
200
+ if (!this.address) {
201
+ if (platform() === "win32") {
202
+ this.address = `127.0.0.1:${await freePort()}`;
203
+ }
204
+ else {
205
+ this.#dir = await mkdtemp(path.join(tmpdir(), "mishmar-"));
206
+ this.address = "unix:" + path.join(this.#dir, "signer.sock");
207
+ }
208
+ }
209
+ if (!this.address.startsWith("unix:")) {
210
+ this.token ??= randomBytes(32).toString("base64url");
211
+ env[ENV_LOCAL_TOKEN] = this.token;
212
+ }
213
+ // The child stops when its stdin closes: when this process exits, however
214
+ // it exits. Nothing is ever written to it.
215
+ const args = ["signer", "run", "--local", this.address, "--log-level", this.options.logLevel ?? "info", "--exit-on-stdin-close"];
216
+ if (this.options.config)
217
+ args.push("--config", this.options.config);
218
+ const child = spawn(binary, args, { env, stdio: ["pipe", "ignore", "pipe"] });
219
+ child.stdin?.on("error", () => { }); // a child that exits first may close it under us
220
+ this.process = child;
221
+ let exited;
222
+ child.on("exit", (code, signal) => {
223
+ exited = { code, signal };
224
+ });
225
+ const onLog = this.options.onLog ?? ((line) => process.stderr.write(line + "\n"));
226
+ let pending = "";
227
+ child.stderr?.on("data", (chunk) => {
228
+ pending += chunk.toString("utf8");
229
+ const lines = pending.split("\n");
230
+ pending = lines.pop() ?? "";
231
+ for (const line of lines) {
232
+ this.#stderrTail.push(line);
233
+ if (this.#stderrTail.length > 40)
234
+ this.#stderrTail.shift();
235
+ onLog(line);
236
+ }
237
+ });
238
+ await this.#waitReady(() => exited);
239
+ return this;
240
+ }
241
+ async #waitReady(exitedOf) {
242
+ const deadline = Date.now() + (this.options.startTimeoutMs ?? 15_000);
243
+ for (;;) {
244
+ const exited = exitedOf();
245
+ if (exited) {
246
+ const tail = this.#stderrTail.join("\n");
247
+ await this.#cleanup();
248
+ throw new Error(`the signer exited with status ${exited.code ?? exited.signal} before it was ready:\n${tail}`);
249
+ }
250
+ try {
251
+ await this.#request("GET", "/health", { timeoutMs: 2_000 });
252
+ return;
253
+ }
254
+ catch (err) {
255
+ if (err instanceof EngineError)
256
+ throw err;
257
+ if (Date.now() >= deadline) {
258
+ await this.stop();
259
+ throw new Error(`the signer did not answer on ${this.address} within ${this.options.startTimeoutMs ?? 15_000} ms`);
260
+ }
261
+ await new Promise((r) => setTimeout(r, 100));
262
+ }
263
+ }
264
+ }
265
+ /**
266
+ * Stop the child, gently then not: its stdin closes, which stops it, and it
267
+ * gets SIGTERM, then SIGKILL after 5 s. A signer from `connect` keeps
268
+ * running; this only forgets it was ready.
269
+ */
270
+ async stop() {
271
+ if (this.#attached) {
272
+ this.#ready = false;
273
+ return;
274
+ }
275
+ const child = this.process;
276
+ if (!child)
277
+ return;
278
+ try {
279
+ child.stdin?.destroy();
280
+ if (child.exitCode === null && child.signalCode === null) {
281
+ const gone = new Promise((resolve) => child.once("exit", () => resolve()));
282
+ child.kill("SIGTERM");
283
+ const timer = new Promise((resolve) => setTimeout(() => resolve("timeout"), 5_000));
284
+ if ((await Promise.race([gone, timer])) === "timeout") {
285
+ child.kill("SIGKILL");
286
+ await gone;
287
+ }
288
+ }
289
+ }
290
+ finally {
291
+ await this.#cleanup();
292
+ }
293
+ }
294
+ async #cleanup() {
295
+ this.process = undefined;
296
+ if (this.#dir) {
297
+ await rm(this.#dir, { recursive: true, force: true });
298
+ this.#dir = undefined;
299
+ this.address = undefined;
300
+ }
301
+ }
302
+ // Proposals
303
+ /** Send an intent (the local API's ProposeRequest) and get the decision back. */
304
+ async propose(intent) {
305
+ return (await this.#request("POST", "/propose", { body: intent }));
306
+ }
307
+ /** Propose an order. A limit order needs a price; a market order is priced at the engine's mark. */
308
+ async order(intent) {
309
+ return this.propose({ orderType: "limit", reduceOnly: false, validForSeconds: 60, channel: false, ...intent });
310
+ }
311
+ /** Cancel a resting order this agent placed, by the clientOrderId its decision carries. */
312
+ async cancel(tradingAccountId, symbol, clientOrderId, validForSeconds = 60) {
313
+ return this.propose({ tradingAccountId, symbol, kind: "cancel", targetClientOrderId: clientOrderId, validForSeconds });
314
+ }
315
+ // Reads, through the child
316
+ async health() {
317
+ return (await this.#request("GET", "/health"));
318
+ }
319
+ async decision(decisionId) {
320
+ return (await this.#request("GET", `/decisions/${encodeURIComponent(decisionId)}`));
321
+ }
322
+ async decisions(query = {}) {
323
+ return (await this.#request("GET", "/decisions", { query }));
324
+ }
325
+ async approvals(status) {
326
+ return (await this.#request("GET", "/approvals", { query: { status } }));
327
+ }
328
+ async positions() {
329
+ return (await this.#request("GET", "/positions"));
330
+ }
331
+ async pnl(query = {}) {
332
+ return (await this.#request("GET", "/pnl", { query }));
333
+ }
334
+ async tradingAccount(tradingAccountId) {
335
+ return (await this.#request("GET", `/trading-accounts/${encodeURIComponent(tradingAccountId)}`));
336
+ }
337
+ async venueRules(tradingAccountId, symbol) {
338
+ return (await this.#request("GET", `/trading-accounts/${encodeURIComponent(tradingAccountId)}/venue-rules`, { query: { symbol } }));
339
+ }
340
+ /** Poll a decision until it no longer waits for a person, or until timeoutMs. */
341
+ async wait(decisionId, { timeoutMs = 60_000, intervalMs = 1_000 } = {}) {
342
+ const deadline = Date.now() + timeoutMs;
343
+ for (;;) {
344
+ const d = await this.decision(decisionId);
345
+ if (resolved(d) || Date.now() >= deadline)
346
+ return d;
347
+ await new Promise((r) => setTimeout(r, Math.min(intervalMs, Math.max(0, deadline - Date.now()))));
348
+ }
349
+ }
350
+ // Plumbing
351
+ #request(method, p, { body, query, timeoutMs } = {}) {
352
+ const address = this.address;
353
+ if (!address)
354
+ return Promise.reject(new Error("the signer is not started"));
355
+ const params = new URLSearchParams();
356
+ for (const [k, v] of Object.entries(query ?? {}))
357
+ if (v !== undefined)
358
+ params.set(k, String(v));
359
+ const queryText = params.toString();
360
+ const data = body === undefined ? undefined : JSON.stringify(body);
361
+ const headers = { Accept: "application/json" };
362
+ if (data !== undefined)
363
+ headers["Content-Type"] = "application/json";
364
+ if (this.token)
365
+ headers["Authorization"] = "Bearer " + this.token;
366
+ const target = address.startsWith("unix:")
367
+ ? { socketPath: address.slice("unix:".length) }
368
+ : { host: address.slice(0, address.lastIndexOf(":")), port: Number(address.slice(address.lastIndexOf(":") + 1)) };
369
+ return new Promise((resolve, reject) => {
370
+ const req = http.request({ ...target, method, path: p + (queryText ? "?" + queryText : ""), headers, timeout: timeoutMs ?? this.options.timeoutMs ?? 40_000 }, (res) => {
371
+ const chunks = [];
372
+ res.on("data", (c) => chunks.push(c));
373
+ res.on("end", () => {
374
+ const text = Buffer.concat(chunks).toString("utf8");
375
+ if ((res.statusCode ?? 0) >= 400) {
376
+ let code = "http_error";
377
+ let message = res.statusMessage || text.slice(0, 200);
378
+ try {
379
+ const parsed = JSON.parse(text);
380
+ if (typeof parsed.code === "string")
381
+ code = parsed.code;
382
+ if (typeof parsed.message === "string")
383
+ message = parsed.message;
384
+ }
385
+ catch {
386
+ // not JSON: keep the text
387
+ }
388
+ reject(new EngineError(res.statusCode ?? 0, code, message));
389
+ return;
390
+ }
391
+ try {
392
+ resolve(text ? JSON.parse(text) : null);
393
+ }
394
+ catch (err) {
395
+ reject(err);
396
+ }
397
+ });
398
+ });
399
+ req.on("timeout", () => req.destroy(new Error("the signer did not answer in time")));
400
+ req.on("error", reject);
401
+ if (data !== undefined)
402
+ req.write(data);
403
+ req.end();
404
+ });
405
+ }
406
+ }
407
+ function freePort() {
408
+ return new Promise((resolve, reject) => {
409
+ const srv = net.createServer();
410
+ srv.listen(0, "127.0.0.1", () => {
411
+ const port = srv.address().port;
412
+ srv.close(() => resolve(port));
413
+ });
414
+ srv.on("error", reject);
415
+ });
416
+ }
package/dist/time.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ /** Times as the contract writes them. */
2
+ /** Format a time as the contract wants it: UTC, Z suffix, fraction trimmed. */
3
+ export declare function rfc3339(d: Date): string;
4
+ /** A Date from an RFC 3339 string that carries an offset. */
5
+ export declare function parseTime(s: string): Date;
package/dist/time.js ADDED
@@ -0,0 +1,17 @@
1
+ /** Times as the contract writes them. */
2
+ /** Format a time as the contract wants it: UTC, Z suffix, fraction trimmed. */
3
+ export function rfc3339(d) {
4
+ const iso = d.toISOString(); // 2026-10-18T10:00:00.250Z
5
+ const [whole, rest] = iso.split(".");
6
+ const fraction = (rest ?? "").replace(/Z$/, "").replace(/0+$/, "");
7
+ return fraction ? `${whole}.${fraction}Z` : `${whole}Z`;
8
+ }
9
+ /** A Date from an RFC 3339 string that carries an offset. */
10
+ export function parseTime(s) {
11
+ if (!/(Z|[+-]\d\d:\d\d)$/.test(s))
12
+ throw new Error(`time ${JSON.stringify(s)} has no offset`);
13
+ const d = new Date(s);
14
+ if (Number.isNaN(d.getTime()))
15
+ throw new Error(`not a time: ${JSON.stringify(s)}`);
16
+ return d;
17
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * `mishmar` as an npm bin: `npx -y mishmar mcp`, or any other mishmar
3
+ * command, without installing the CLI first. It finds the real binary
4
+ * (`MISHMAR_BIN`, the PATH less any wrapper such as this one, then
5
+ * `~/.mishmar/bin`), fetches the pinned release when there is none, and runs
6
+ * it with the same arguments, stdin, stdout and stderr, so stdout stays the
7
+ * MCP channel; it writes nothing there itself. It forwards SIGINT and SIGTERM
8
+ * and exits with the binary's code.
9
+ */
10
+ /**
11
+ * The binary for the wrapper at `self` (the running script's path) to run:
12
+ * found, or fetched into `~/.mishmar/bin/<release>`. `log` gets one line
13
+ * when a download starts.
14
+ */
15
+ export declare function resolveBinary(self: string, log?: (line: string) => void): Promise<string>;
16
+ /** Run the binary with `argv`; the exit code to end this process with. */
17
+ export declare function main(selfUrl: string, argv?: string[]): Promise<number>;
@@ -0,0 +1,58 @@
1
+ /**
2
+ * `mishmar` as an npm bin: `npx -y mishmar mcp`, or any other mishmar
3
+ * command, without installing the CLI first. It finds the real binary
4
+ * (`MISHMAR_BIN`, the PATH less any wrapper such as this one, then
5
+ * `~/.mishmar/bin`), fetches the pinned release when there is none, and runs
6
+ * it with the same arguments, stdin, stdout and stderr, so stdout stays the
7
+ * MCP channel; it writes nothing there itself. It forwards SIGINT and SIGTERM
8
+ * and exits with the binary's code.
9
+ */
10
+ import { spawn } from "node:child_process";
11
+ import { realpath } from "node:fs/promises";
12
+ import { constants as osConstants } from "node:os";
13
+ import { fileURLToPath } from "node:url";
14
+ import { RELEASE, downloadBinary, findBinary, platformName } from "./signer.js";
15
+ /** Set on the binary this wrapper starts; a wrapper that finds it set has been started by another wrapper, which means it resolved to itself. */
16
+ const ENV_WRAPPED = "MISHMAR_WRAPPED";
17
+ /**
18
+ * The binary for the wrapper at `self` (the running script's path) to run:
19
+ * found, or fetched into `~/.mishmar/bin/<release>`. `log` gets one line
20
+ * when a download starts.
21
+ */
22
+ export async function resolveBinary(self, log = () => { }) {
23
+ const exclude = [await realpath(self).catch(() => self)];
24
+ try {
25
+ return await findBinary(undefined, { skipScripts: true, exclude });
26
+ }
27
+ catch {
28
+ log(`mishmar: fetching release ${RELEASE} for ${platformName()} into ~/.mishmar/bin/${RELEASE}`);
29
+ return await downloadBinary(RELEASE);
30
+ }
31
+ }
32
+ /** Run the binary with `argv`; the exit code to end this process with. */
33
+ export async function main(selfUrl, argv = process.argv.slice(2)) {
34
+ const stderr = (line) => process.stderr.write(line + "\n");
35
+ if (process.env[ENV_WRAPPED]) {
36
+ stderr("mishmar: this wrapper was started by another wrapper, so the PATH holds no mishmar binary; set MISHMAR_BIN to it");
37
+ return 2;
38
+ }
39
+ let binary;
40
+ try {
41
+ binary = await resolveBinary(fileURLToPath(selfUrl), stderr);
42
+ }
43
+ catch (err) {
44
+ stderr(`mishmar: ${err instanceof Error ? err.message : String(err)}`);
45
+ return 1;
46
+ }
47
+ const child = spawn(binary, argv, { stdio: "inherit", env: { ...process.env, [ENV_WRAPPED]: "1" } });
48
+ const forward = (signal) => child.kill(signal);
49
+ process.on("SIGINT", forward);
50
+ process.on("SIGTERM", forward);
51
+ return new Promise((resolve) => {
52
+ child.on("error", (err) => {
53
+ stderr(`mishmar: ${binary}: ${err.message}`);
54
+ resolve(1);
55
+ });
56
+ child.on("exit", (code, signal) => resolve(code ?? (signal ? 128 + (osConstants.signals[signal] ?? 0) : 1)));
57
+ });
58
+ }
package/package.json CHANGED
@@ -1,6 +1,51 @@
1
1
  {
2
2
  "name": "mishmar",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.3.0",
4
+ "mcpName": "io.github.mishmar-io/mishmar",
5
+ "description": "Mishmar is a risk management layer for trading bots and AI agents: every order is checked against your limits before it reaches the exchange, large orders wait for your approval, and no key can trade alone. The TypeScript SDK: run the agent's signer, propose orders, read decisions.",
6
+ "license": "Apache-2.0",
7
+ "type": "module",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "bin": {
15
+ "mishmar": "./dist/bin.js"
16
+ },
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "engines": {
21
+ "node": ">=20"
22
+ },
23
+ "scripts": {
24
+ "build": "tsc -p tsconfig.build.json && chmod +x dist/bin.js",
25
+ "prepublishOnly": "npm run build",
26
+ "typecheck": "tsc --noEmit",
27
+ "test": "node --test \"tests/**/*.test.ts\""
28
+ },
29
+ "devDependencies": {
30
+ "@types/node": "^24.3.0",
31
+ "typescript": "^5.9.3"
32
+ },
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "git+https://github.com/mishmar-io/mishmar.git",
36
+ "directory": "typescript"
37
+ },
38
+ "homepage": "https://mishmar.io",
39
+ "keywords": [
40
+ "trading",
41
+ "trading-bot",
42
+ "ai-agents",
43
+ "risk-management",
44
+ "pre-trade-risk",
45
+ "hyperliquid",
46
+ "binance",
47
+ "mpc",
48
+ "threshold-signatures",
49
+ "mcp"
50
+ ]
51
+ }