openmerit 0.4.0 → 0.4.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.
package/README.md CHANGED
@@ -38,7 +38,7 @@ openmerit scout "Extract chronic conditions as ICD-10-CM categories from health
38
38
  --dataset examples/health-declaration-codes/dataset.jsonl --providers openrouter --max-price 30 --execute
39
39
 
40
40
  # The search alone, without the agent: deterministic, for CI and other coding agents (JSON output with --json).
41
- openmerit run --langfuse-dataset sahra-enrollments --evaluator ./evaluator.ts --execute
41
+ openmerit run --langfuse-dataset sahra-enrollments --evaluator ./evaluator.mts --execute
42
42
  openmerit run --dataset items.jsonl --system-prompt "$(cat prompt.txt)" --grader json --execute
43
43
 
44
44
  # Interactive: Pi with Scout. Ask "which model should I use for …" or type /scout.
@@ -72,7 +72,7 @@ Every run writes `.openmerit/runs/<run-id>/report.md` and `report.json` in the p
72
72
  - `json`: every expected field must match; arrays are compared as sets.
73
73
  - `exact` and `contains`: for short answers.
74
74
  - `judge`: a rubric checked by Scout's model.
75
- - **Your harness** (`--evaluator module.ts`): default-export an `EvaluatorPlugin` that runs one item through your real system and returns `{ pass, output, costUsd? }`. When a model cannot run, throw an error whose `name` is `"CandidateUnavailable"`; Scout then skips it as untested. Throw any other error to stop the run. [`examples/sahra-enrollment/evaluator.ts`](examples/sahra-enrollment/evaluator.ts), which you can copy into your project, drives a deployed multi-step tool-calling agent over HTTP. It reads cost and provider errors from that agent's own Langfuse traces.
75
+ - **Your harness** (`--evaluator module.mts`, relative to the current directory): default-export an `EvaluatorPlugin` that runs one item through your real system and returns `{ pass, output, costUsd? }`. When a model cannot run, throw an error whose `name` is `"CandidateUnavailable"`; Scout then skips it as untested. Throw any other error to stop the run. [`examples/sahra-enrollment/evaluator.mts`](examples/sahra-enrollment/evaluator.mts), which you can copy into your project (change its type import to `"openmerit"`), drives a deployed multi-step tool-calling agent over HTTP. It reads cost and provider errors from that agent's own Langfuse traces.
76
76
 
77
77
  ## Limitations
78
78
 
package/dist/cli.js CHANGED
@@ -17,7 +17,7 @@ Options:
17
17
  --codebase <dir> Project Scout reads (default: current directory)
18
18
  --langfuse-dataset <name> Labelled Langfuse dataset
19
19
  --dataset <file.jsonl> Local labelled items {id?, input, expectedOutput}; pushed to Langfuse
20
- --evaluator <module.ts> Evaluator module that runs your real harness
20
+ --evaluator <module.mts> Evaluator module that runs your real harness (relative to the current directory)
21
21
  --system-prompt <text> run: production system prompt for the built-in Pi evaluator
22
22
  --grader <exact|contains|json|judge> run: how the Pi evaluator checks a reply (default json)
23
23
  --rubric <text> run: rubric for the judge grader
@@ -107,7 +107,7 @@ async function main() {
107
107
  if (command === "run") {
108
108
  const { host } = await hostFor(values);
109
109
  const evaluator = values.evaluator
110
- ? { type: "module", path: values.evaluator }
110
+ ? { type: "module", path: resolve(values.evaluator) }
111
111
  : (() => {
112
112
  if (!values["system-prompt"])
113
113
  throw new Error("run needs --evaluator <module> or --system-prompt for the built-in Pi evaluator");
@@ -118,7 +118,7 @@ async function main() {
118
118
  })();
119
119
  const params = {
120
120
  task: values.task ?? positionals.join(" ") ?? "", evaluator, filter: filterFrom(values),
121
- ...(values["langfuse-dataset"] ? { langfuseDataset: values["langfuse-dataset"] } : {}), ...(values.dataset ? { datasetPath: values.dataset } : {}),
121
+ ...(values["langfuse-dataset"] ? { langfuseDataset: values["langfuse-dataset"] } : {}), ...(values.dataset ? { datasetPath: resolve(values.dataset) } : {}),
122
122
  ...(values["sample-size"] ? { sampleSize: Number(values["sample-size"]) } : {}), ...(values.finalists ? { finalists: Number(values.finalists) } : {}),
123
123
  ...(values.repeats ? { repeats: Number(values.repeats) } : {}),
124
124
  };
@@ -157,8 +157,8 @@ async function main() {
157
157
  });
158
158
  const hints = [
159
159
  values["langfuse-dataset"] && `Use the Langfuse dataset "${values["langfuse-dataset"]}".`,
160
- values.dataset && `Use the local dataset ${values.dataset}.`,
161
- values.evaluator && `Use the evaluator module ${values.evaluator}.`,
160
+ values.dataset && `Use the local dataset ${resolve(values.dataset)}.`,
161
+ values.evaluator && `Use the evaluator module ${resolve(values.evaluator)}.`,
162
162
  (values.providers || values.tools || values["max-price"] || values.exclude) && `Candidate filter: ${JSON.stringify(filterFrom(values))}.`,
163
163
  values["sample-size"] && `sampleSize ${values["sample-size"]}.`, values.finalists && `finalists ${values.finalists}.`, values.repeats && `repeats ${values.repeats}.`,
164
164
  ].filter(Boolean).join(" ");
@@ -1,5 +1,5 @@
1
- import { type LangfuseConnection, type TransportOptions } from "./client.ts";
2
- export { connectionFromEnv, type LangfuseConnection, LangfuseError, normalizeBaseUrl, type TransportOptions } from "./client.ts";
1
+ import { type LangfuseConnection, type TransportOptions } from "./client.js";
2
+ export { connectionFromEnv, type LangfuseConnection, LangfuseError, normalizeBaseUrl, type TransportOptions } from "./client.js";
3
3
  export type ScoreValue = number | boolean | string;
4
4
  export type Metadata = Record<string, string | number | boolean>;
5
5
  export interface EvalItem {
package/dist/index.d.ts CHANGED
@@ -1,11 +1,11 @@
1
- export { rankCandidates, rank, openRouterToolSupport, type Candidate, type CandidateFilter, type RunnableModel } from "./scout/candidates.ts";
2
- export { loadEci, parseEciScores, ECI_ATTRIBUTION, type EciRow, type EciSource } from "./scout/eci.ts";
3
- export { scoutSearch, CandidateUnavailable, outward, compareFinals, meanCost, SAMPLE_SIZE, FINALISTS, type EvalItem, type Evaluate, type Outcome, type SearchOptions, type SearchResult, type Stage, type Trial } from "./scout/search.ts";
4
- export { runScout, type ScoutReport, type ScoutRunOptions, type FinalVerdict, type DatasetSource } from "./scout/pipeline.ts";
5
- export { reportMarkdown } from "./scout/report.ts";
6
- export { piEvaluator, jsonMatches, extractJson, type Grader, type ModelCaller, type PiTaskSpec } from "./scout/evaluators/pi.ts";
7
- export { loadEvaluatorPlugin, type EvaluatorPlugin } from "./scout/evaluators/module.ts";
8
- export * as scout from "./scout/service.ts";
9
- export { createScoutExtension, type ScoutExtensionOptions } from "./pi/extension.ts";
10
- export { configureHarness } from "./pi/harness.ts";
11
- export * as langfuse from "./connectors/langfuse/index.ts";
1
+ export { rankCandidates, rank, openRouterToolSupport, type Candidate, type CandidateFilter, type RunnableModel } from "./scout/candidates.js";
2
+ export { loadEci, parseEciScores, ECI_ATTRIBUTION, type EciRow, type EciSource } from "./scout/eci.js";
3
+ export { scoutSearch, CandidateUnavailable, outward, compareFinals, meanCost, SAMPLE_SIZE, FINALISTS, type EvalItem, type Evaluate, type Outcome, type SearchOptions, type SearchResult, type Stage, type Trial } from "./scout/search.js";
4
+ export { runScout, type ScoutReport, type ScoutRunOptions, type FinalVerdict, type DatasetSource } from "./scout/pipeline.js";
5
+ export { reportMarkdown } from "./scout/report.js";
6
+ export { piEvaluator, jsonMatches, extractJson, type Grader, type ModelCaller, type PiTaskSpec } from "./scout/evaluators/pi.js";
7
+ export { loadEvaluatorPlugin, type EvaluatorPlugin } from "./scout/evaluators/module.js";
8
+ export * as scout from "./scout/service.js";
9
+ export { createScoutExtension, type ScoutExtensionOptions } from "./pi/extension.js";
10
+ export { configureHarness } from "./pi/harness.js";
11
+ export * as langfuse from "./connectors/langfuse/index.js";
@@ -1,6 +1,6 @@
1
1
  import { ModelRuntime } from "@earendil-works/pi-coding-agent";
2
- import type { RunnableModel } from "../scout/candidates.ts";
3
- import type { ModelCaller } from "../scout/evaluators/pi.ts";
2
+ import type { RunnableModel } from "../scout/candidates.js";
3
+ import type { ModelCaller } from "../scout/evaluators/pi.js";
4
4
  /**
5
5
  * OpenMerit's changes to a stock Pi process, applied before Pi reads its environment:
6
6
  * - its own agent directory (~/.openmerit/agent, or $OPENMERIT_HOME/agent), so OpenMerit's
@@ -1,2 +1,2 @@
1
1
  /** Scout's workflow, added to Pi's system prompt as the `openmerit_scout` section. */
2
- export declare const SCOUT_INSTRUCTIONS = "You are OpenMerit Scout. You recommend the model a user should run for a task, from measured results on the user's own labelled examples, not from memory.\n\nWorkflow when asked which model to use:\n1. Understand the task. If a codebase is present, find where the model is called (grep for model IDs, SDK calls, \"systemPrompt\", \"model:\"), the production prompt, whether it calls tools, its output format, and existing evals: fixtures, tests with expected outputs, Langfuse datasets (scout_connections lists them), or .openmerit/datasets/*.jsonl.\n2. Find labelled data. Prefer an existing Langfuse dataset or fixture set with expected outputs. Preview it with scout_dataset; Scout needs at least 5 items. If none exists, draft at least 6 items only from what the code, fixtures or the user state, save them with scout_save_dataset, and say they are drafts the user should check. Never invent labels you cannot justify.\n3. Choose the evaluator. If the project ships an evaluator module that runs its real harness (for example .openmerit/evaluator.ts or examples/*/evaluator.ts), use evaluator type \"module\". Otherwise use type \"pi\" with the production system prompt and the strictest grader that fits: \"json\" for structured outputs, \"exact\"/\"contains\" for short answers, \"judge\" with a precise rubric only for free text.\n4. Check candidates with scout_candidates. Filter only for real constraints: providers the user can run, tools when the call uses tools, a price ceiling the user gave, data-residency or vendor exclusions the user named. Do not narrow the pool from your own preferences.\n5. Run scout_run with the task, dataset and evaluator. It binary-searches the ECI-ranked candidates (pass = every sampled item), screens neighbours, runs 5 finalists (plus cheaper, stronger challengers) on every item 3 times, records each as a Langfuse experiment, and picks the winner from Langfuse: most passes, then lower measured cost per item, then lower ECI.\n6. Answer with the recommended model ID, its passes and cost per item, the runner-up, the Langfuse experiment links, the report path, and the limitations the report lists (sample size, untested skipped models, drafted labels, residency or policy gaps). If scout_run returns plan_only, report the plan and the command to run it.\n\nRules: measured results on the user's data outrank public benchmarks. Provider, auth or billing errors are not quality failures; report them. Never print API keys or tokens.";
2
+ export declare const SCOUT_INSTRUCTIONS = "You are OpenMerit Scout. You recommend the model a user should run for a task, from measured results on the user's own labelled examples, not from memory.\n\nWorkflow when asked which model to use:\n1. Understand the task. If a codebase is present, find where the model is called (grep for model IDs, SDK calls, \"systemPrompt\", \"model:\"), the production prompt, whether it calls tools, its output format, and existing evals: fixtures, tests with expected outputs, Langfuse datasets (scout_connections lists them), or .openmerit/datasets/*.jsonl.\n2. Find labelled data. Prefer an existing Langfuse dataset or fixture set with expected outputs. Preview it with scout_dataset; Scout needs at least 5 items. If none exists, draft at least 6 items only from what the code, fixtures or the user state, save them with scout_save_dataset, and say they are drafts the user should check. Never invent labels you cannot justify.\n3. Choose the evaluator. If the project ships an evaluator module that runs its real harness (for example .openmerit/evaluator.mts, .openmerit/evaluator.ts or examples/*/evaluator.mts), use evaluator type \"module\". Otherwise use type \"pi\" with the production system prompt and the strictest grader that fits: \"json\" for structured outputs, \"exact\"/\"contains\" for short answers, \"judge\" with a precise rubric only for free text.\n4. Check candidates with scout_candidates. Filter only for real constraints: providers the user can run, tools when the call uses tools, a price ceiling the user gave, data-residency or vendor exclusions the user named. Do not narrow the pool from your own preferences.\n5. Run scout_run with the task, dataset and evaluator. It binary-searches the ECI-ranked candidates (pass = every sampled item), screens neighbours, runs 5 finalists (plus cheaper, stronger challengers) on every item 3 times, records each as a Langfuse experiment, and picks the winner from Langfuse: most passes, then lower measured cost per item, then lower ECI.\n6. Answer with the recommended model ID, its passes and cost per item, the runner-up, the Langfuse experiment links, the report path, and the limitations the report lists (sample size, untested skipped models, drafted labels, residency or policy gaps). If scout_run returns plan_only, report the plan and the command to run it.\n\nRules: measured results on the user's data outrank public benchmarks. Provider, auth or billing errors are not quality failures; report them. Never print API keys or tokens.";
@@ -4,7 +4,7 @@ export const SCOUT_INSTRUCTIONS = `You are OpenMerit Scout. You recommend the mo
4
4
  Workflow when asked which model to use:
5
5
  1. Understand the task. If a codebase is present, find where the model is called (grep for model IDs, SDK calls, "systemPrompt", "model:"), the production prompt, whether it calls tools, its output format, and existing evals: fixtures, tests with expected outputs, Langfuse datasets (scout_connections lists them), or .openmerit/datasets/*.jsonl.
6
6
  2. Find labelled data. Prefer an existing Langfuse dataset or fixture set with expected outputs. Preview it with scout_dataset; Scout needs at least 5 items. If none exists, draft at least 6 items only from what the code, fixtures or the user state, save them with scout_save_dataset, and say they are drafts the user should check. Never invent labels you cannot justify.
7
- 3. Choose the evaluator. If the project ships an evaluator module that runs its real harness (for example .openmerit/evaluator.ts or examples/*/evaluator.ts), use evaluator type "module". Otherwise use type "pi" with the production system prompt and the strictest grader that fits: "json" for structured outputs, "exact"/"contains" for short answers, "judge" with a precise rubric only for free text.
7
+ 3. Choose the evaluator. If the project ships an evaluator module that runs its real harness (for example .openmerit/evaluator.mts, .openmerit/evaluator.ts or examples/*/evaluator.mts), use evaluator type "module". Otherwise use type "pi" with the production system prompt and the strictest grader that fits: "json" for structured outputs, "exact"/"contains" for short answers, "judge" with a precise rubric only for free text.
8
8
  4. Check candidates with scout_candidates. Filter only for real constraints: providers the user can run, tools when the call uses tools, a price ceiling the user gave, data-residency or vendor exclusions the user named. Do not narrow the pool from your own preferences.
9
9
  5. Run scout_run with the task, dataset and evaluator. It binary-searches the ECI-ranked candidates (pass = every sampled item), screens neighbours, runs 5 finalists (plus cheaper, stronger challengers) on every item 3 times, records each as a Langfuse experiment, and picks the winner from Langfuse: most passes, then lower measured cost per item, then lower ECI.
10
10
  6. Answer with the recommended model ID, its passes and cost per item, the runner-up, the Langfuse experiment links, the report path, and the limitations the report lists (sample size, untested skipped models, drafted labels, residency or policy gaps). If scout_run returns plan_only, report the plan and the command to run it.
@@ -1,4 +1,4 @@
1
- import type { EciEstimate, EciRow } from "./eci.ts";
1
+ import type { EciEstimate, EciRow } from "./eci.js";
2
2
  /** A model the user's connections can run, placed on the ECI scale. */
3
3
  export interface Candidate {
4
4
  /** `provider/id` as Pi names it, such as `openrouter/deepseek/deepseek-v4-pro` or `anthropic/claude-sonnet-5-5`. */
@@ -1,4 +1,4 @@
1
- import type { Evaluate } from "../search.ts";
1
+ import type { Evaluate } from "../search.js";
2
2
  /**
3
3
  * An evaluator that runs a candidate through the user's own harness, such as a deployed agent's
4
4
  * HTTP API, and checks the result against the labelled item. A module's default export is one of these.
@@ -20,5 +20,5 @@ export interface EvaluatorPlugin {
20
20
  log: (line: string) => void;
21
21
  }): Evaluate | Promise<Evaluate>;
22
22
  }
23
- /** Imports a `.ts` or `.js` evaluator module from the user's project. Node.js 22.19 strips its types. */
23
+ /** Imports an evaluator module (`.mts`, `.ts`, `.mjs` or `.js`) from the user's project. Node.js 22.19 strips its types. */
24
24
  export declare function loadEvaluatorPlugin(path: string, cwd?: string): Promise<EvaluatorPlugin>;
@@ -8,7 +8,7 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
8
8
  };
9
9
  import { resolve } from "node:path";
10
10
  import { pathToFileURL } from "node:url";
11
- /** Imports a `.ts` or `.js` evaluator module from the user's project. Node.js 22.19 strips its types. */
11
+ /** Imports an evaluator module (`.mts`, `.ts`, `.mjs` or `.js`) from the user's project. Node.js 22.19 strips its types. */
12
12
  export async function loadEvaluatorPlugin(path, cwd = process.cwd()) {
13
13
  const loaded = await import(__rewriteRelativeImportExtension(pathToFileURL(resolve(cwd, path)).href));
14
14
  const plugin = loaded.default;
@@ -1,5 +1,5 @@
1
1
  import type { Api, AssistantMessage, Context, Model } from "@earendil-works/pi-ai";
2
- import { type Evaluate } from "../search.ts";
2
+ import { type Evaluate } from "../search.js";
3
3
  /** What the Pi evaluator needs from Pi: a `ModelRuntime`, or an extension's `ctx.modelRegistry` through `fromRegistry`. */
4
4
  export interface ModelCaller {
5
5
  getModel(provider: string, id: string): Model<Api> | undefined;
@@ -1,6 +1,6 @@
1
- import type { LangfuseConnector } from "../connectors/langfuse/index.ts";
2
- import type { Candidate } from "./candidates.ts";
3
- import { type EvalItem, type Evaluate, type SearchResult } from "./search.ts";
1
+ import type { LangfuseConnector } from "../connectors/langfuse/index.js";
2
+ import type { Candidate } from "./candidates.js";
3
+ import { type EvalItem, type Evaluate, type SearchResult } from "./search.js";
4
4
  export interface DatasetSource {
5
5
  /** An existing Langfuse dataset, such as the customer's `sahra-enrollments`. */
6
6
  langfuseDataset?: string;
@@ -1,3 +1,3 @@
1
- import type { ScoutReport } from "./pipeline.ts";
1
+ import type { ScoutReport } from "./pipeline.js";
2
2
  /** The decision record as Markdown: no keys, no raw item contents. */
3
3
  export declare function reportMarkdown(report: ScoutReport): string;
@@ -1,4 +1,4 @@
1
- import type { Candidate } from "./candidates.ts";
1
+ import type { Candidate } from "./candidates.js";
2
2
  /**
3
3
  * Scout Model Binary Search, from the ScoutBM prototype:
4
4
  * 1. Rank candidates by ECI, ascending, and draw `sampleSize` labelled items once.
@@ -1,8 +1,8 @@
1
- import { type LangfuseConnector } from "../connectors/langfuse/index.ts";
2
- import { type CandidateFilter, type RunnableModel } from "./candidates.ts";
3
- import { type ModelCaller, type PiTaskSpec } from "./evaluators/pi.ts";
4
- import { type ScoutReport } from "./pipeline.ts";
5
- import { type EvalItem } from "./search.ts";
1
+ import { type LangfuseConnector } from "../connectors/langfuse/index.js";
2
+ import { type CandidateFilter, type RunnableModel } from "./candidates.js";
3
+ import { type ModelCaller, type PiTaskSpec } from "./evaluators/pi.js";
4
+ import { type ScoutReport } from "./pipeline.js";
5
+ import { type EvalItem } from "./search.js";
6
6
  /** Everything Scout's tools need from the host: Pi's models and credentials, and where to write. */
7
7
  export interface ScoutHost {
8
8
  caller: ModelCaller;
@@ -44,7 +44,7 @@ export declare function candidates(host: ScoutHost, filter: CandidateFilter): Pr
44
44
  calibrated: number;
45
45
  unranked: number;
46
46
  };
47
- candidates: import("./candidates.ts").Candidate[];
47
+ candidates: import("./candidates.js").Candidate[];
48
48
  unranked: string[];
49
49
  dropped: {
50
50
  model: string;
@@ -102,7 +102,7 @@ export declare function estimate(candidateCount: number, datasetSize: number, sa
102
102
  note: string;
103
103
  };
104
104
  export declare function plan(host: ScoutHost, params: RunParams): Promise<{
105
- plugin: import("./evaluators/module.ts").EvaluatorPlugin | null;
105
+ plugin: import("./evaluators/module.js").EvaluatorPlugin | null;
106
106
  filter: CandidateFilter;
107
107
  ranked: {
108
108
  eci: {
@@ -123,7 +123,7 @@ export declare function plan(host: ScoutHost, params: RunParams): Promise<{
123
123
  calibrated: number;
124
124
  unranked: number;
125
125
  };
126
- candidates: import("./candidates.ts").Candidate[];
126
+ candidates: import("./candidates.js").Candidate[];
127
127
  unranked: string[];
128
128
  dropped: {
129
129
  model: string;
@@ -6,7 +6,8 @@
6
6
  // PLAYGROUND_URL (default http://localhost:8787), PLAYGROUND_TOKEN, LANGFUSE_* for measured cost.
7
7
  import { createHash, randomUUID } from "node:crypto";
8
8
  import { setTimeout as delay } from "node:timers/promises";
9
- // Copy this file into your project to adapt it; it has no runtime import from OpenMerit.
9
+ // Copy this file into your project to adapt it; it has no runtime import from OpenMerit. The .mts
10
+ // extension makes Node.js load it as an ES module whatever your package.json says.
10
11
  // In your project, import the type from the package instead: `import type { EvaluatorPlugin } from "openmerit"`.
11
12
  import type { EvaluatorPlugin } from "../../src/scout/evaluators/module.ts";
12
13
 
@@ -103,7 +104,7 @@ const plugin: EvaluatorPlugin = {
103
104
  const expected = item.expectedOutput as Expected;
104
105
  // Outlast EnrollmentFlow's own 10-minute limit and one retry, so the workflow decides the outcome.
105
106
  const signal = AbortSignal.any([AbortSignal.timeout(25 * 60_000), ...(context.signal ? [context.signal] : [])]);
106
- const call = async <T>(method: string, path: string, body?: unknown): Promise<T> => {
107
+ const call = async <T,>(method: string, path: string, body?: unknown): Promise<T> => {
107
108
  const response = await fetch(`${url}${path}`, { method, headers: { authorization: `Bearer ${token}`, "content-type": "application/json" }, body: body === undefined ? null : JSON.stringify(body), signal });
108
109
  if (!response.ok) throw new Error(`Playground ${method} ${path.split("?")[0]} HTTP ${response.status}`);
109
110
  return await response.json() as T;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openmerit",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Find the right model for your task: Scout reads your task or codebase, binary-searches benchmark-ranked models on your own labelled examples, and picks the winner from Langfuse evaluations.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -40,7 +40,7 @@
40
40
  "node": ">=22.19.0"
41
41
  },
42
42
  "scripts": {
43
- "build": "rm -rf dist && tsc -p tsconfig.build.json && chmod +x dist/cli.js",
43
+ "build": "rm -rf dist && tsc -p tsconfig.build.json && node scripts/rewrite-declaration-imports.ts && chmod +x dist/cli.js",
44
44
  "typecheck": "tsc -p tsconfig.json",
45
45
  "test": "node --test \"src/**/*.test.ts\" \"test/**/*.test.ts\"",
46
46
  "check": "npm run typecheck && npm test",