@askalf/dario 6.9.0 → 6.9.2
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 +1 -0
- package/dist/pacing.d.ts +29 -0
- package/dist/pacing.js +22 -0
- package/dist/proxy.d.ts +15 -0
- package/dist/proxy.js +63 -22
- package/docs/wire-fidelity.md +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -473,6 +473,7 @@ The split isn't live, but it was announced once on short notice and could return
|
|
|
473
473
|
| Credentials | Your own subscription tokens, never logged, redacted from errors, `0600` on disk in `0700` dirs |
|
|
474
474
|
| Network | Binds `127.0.0.1` by default; upstream only to configured backends over HTTPS; hardcoded SSRF allow-list; refuses a non-loopback bind without `DARIO_API_KEY` |
|
|
475
475
|
| Telemetry | **None.** No analytics, no tracking, nothing phones home |
|
|
476
|
+
| Overhead | Measured in the open on every PR: [`scripts/bench-overhead.mjs`](./scripts/bench-overhead.mjs) runs a real proxy against an instant upstream beside a bare http server serving the same bytes. On loopback dario adds no measurable p50 wall time over that floor; the CPU per request is the number to watch, and the per-request [timing split](./docs/analytics.md#the-timing-split) shows it live |
|
|
476
477
|
| This README | CI fails if the line count above drifts from `src/` or a link or anchor here stops resolving ([`check-readme-line-count.mjs`](./scripts/check-readme-line-count.mjs), [`check-readme-links.mjs`](./scripts/check-readme-links.mjs)); the TUI screenshots are rendered from the real TUI and the diagrams are briefed art, not screenshots ([how](./scripts/readme/README.md)) |
|
|
477
478
|
|
|
478
479
|
```bash
|
package/dist/pacing.d.ts
CHANGED
|
@@ -141,3 +141,32 @@ export declare function resolveSessionStartConfig(explicit?: {
|
|
|
141
141
|
jitterMs?: number;
|
|
142
142
|
stealth?: boolean;
|
|
143
143
|
}, env?: NodeJS.ProcessEnv): SessionStartConfig;
|
|
144
|
+
/**
|
|
145
|
+
* The governor's clocks, one set per seat (v6.9.1).
|
|
146
|
+
*
|
|
147
|
+
* The floors above model ONE account's cadence — that is what the provider
|
|
148
|
+
* observes. Through v6.9.0 the proxy kept a single `lastRequestTime` for the
|
|
149
|
+
* whole process, so a pool paced every seat against every other: a three-seat
|
|
150
|
+
* pool could put at most one request per floor on the wire, and a request to
|
|
151
|
+
* an idle seat waited for a stranger's request on a busy one (dario#1244's
|
|
152
|
+
* family — the pool running behind one seat's limits). Keying the clocks by
|
|
153
|
+
* seat keeps each account's observed rhythm exactly as before while the pool
|
|
154
|
+
* as a whole moves at pool speed. API-key mode has one seat and one clock.
|
|
155
|
+
*/
|
|
156
|
+
export interface SeatPacingState {
|
|
157
|
+
/** When the seat's last request STARTED (feeds the inter-request floor). */
|
|
158
|
+
lastRequestTime: number;
|
|
159
|
+
/** When the seat's last 2xx response COMPLETED (feeds think-time). */
|
|
160
|
+
lastResponseTime: number;
|
|
161
|
+
/** Output tokens of that response (feeds think-time's per-token term). */
|
|
162
|
+
lastResponseTokens: number;
|
|
163
|
+
}
|
|
164
|
+
export declare class PacingRegistry {
|
|
165
|
+
private readonly seats;
|
|
166
|
+
/** The seat's clocks, created at zero on first sight (a fresh seat is never paced). */
|
|
167
|
+
seat(key: string): SeatPacingState;
|
|
168
|
+
/** Stamp a completed 2xx response on the seat, for the next request's think-time. */
|
|
169
|
+
noteResponse(key: string, at: number, outputTokens: number): void;
|
|
170
|
+
/** Seats the registry has seen, for tests and status. */
|
|
171
|
+
keys(): string[];
|
|
172
|
+
}
|
package/dist/pacing.js
CHANGED
|
@@ -140,3 +140,25 @@ export function resolveSessionStartConfig(explicit = {}, env = process.env) {
|
|
|
140
140
|
const jitter = pickNonNegativeInt(explicit.jitterMs, env.DARIO_SESSION_START_JITTER_MS) ?? (stealth ? 3000 : 0);
|
|
141
141
|
return { minMs: min, jitterMs: jitter };
|
|
142
142
|
}
|
|
143
|
+
export class PacingRegistry {
|
|
144
|
+
seats = new Map();
|
|
145
|
+
/** The seat's clocks, created at zero on first sight (a fresh seat is never paced). */
|
|
146
|
+
seat(key) {
|
|
147
|
+
let s = this.seats.get(key);
|
|
148
|
+
if (!s) {
|
|
149
|
+
s = { lastRequestTime: 0, lastResponseTime: 0, lastResponseTokens: 0 };
|
|
150
|
+
this.seats.set(key, s);
|
|
151
|
+
}
|
|
152
|
+
return s;
|
|
153
|
+
}
|
|
154
|
+
/** Stamp a completed 2xx response on the seat, for the next request's think-time. */
|
|
155
|
+
noteResponse(key, at, outputTokens) {
|
|
156
|
+
const s = this.seat(key);
|
|
157
|
+
s.lastResponseTime = at;
|
|
158
|
+
s.lastResponseTokens = outputTokens;
|
|
159
|
+
}
|
|
160
|
+
/** Seats the registry has seen, for tests and status. */
|
|
161
|
+
keys() {
|
|
162
|
+
return [...this.seats.keys()];
|
|
163
|
+
}
|
|
164
|
+
}
|
package/dist/proxy.d.ts
CHANGED
|
@@ -636,6 +636,21 @@ export interface ProxyLogEntry {
|
|
|
636
636
|
* null (logFile not configured). Errors are swallowed — log writes
|
|
637
637
|
* must never break the request path.
|
|
638
638
|
*/
|
|
639
|
+
/**
|
|
640
|
+
* The `data:` line of one SSE frame, without allocating a per-line array
|
|
641
|
+
* (the frame is `event: x\ndata: {...}\n\n`; the data line is the one that
|
|
642
|
+
* starts with the field name, at the start of the frame or after a newline).
|
|
643
|
+
* Null when the frame has no data line (a comment, a bare event).
|
|
644
|
+
*/
|
|
645
|
+
export declare function sseDataLine(frame: string): string | null;
|
|
646
|
+
/**
|
|
647
|
+
* Whether the analytics tap needs to parse this frame at all: only the
|
|
648
|
+
* message_start usage, the message_delta usage and thinking deltas feed a
|
|
649
|
+
* number it keeps. A text or tool-input delta — most of any stream — is
|
|
650
|
+
* skipped before JSON.parse. Substring tests on the data line; a frame that
|
|
651
|
+
* happens to contain these words inside a text delta merely costs a parse.
|
|
652
|
+
*/
|
|
653
|
+
export declare function analyticsFrameOfInterest(dataLine: string): boolean;
|
|
639
654
|
export declare function writeLogLine(stream: WriteStream | null, entry: ProxyLogEntry): void;
|
|
640
655
|
export declare function sanitizeError(err: unknown): string;
|
|
641
656
|
/**
|
package/dist/proxy.js
CHANGED
|
@@ -990,6 +990,31 @@ export function requiresClaudeLogin(poolSize, adminEnabled, hasUpstreamApiKey, n
|
|
|
990
990
|
* null (logFile not configured). Errors are swallowed — log writes
|
|
991
991
|
* must never break the request path.
|
|
992
992
|
*/
|
|
993
|
+
/**
|
|
994
|
+
* The `data:` line of one SSE frame, without allocating a per-line array
|
|
995
|
+
* (the frame is `event: x\ndata: {...}\n\n`; the data line is the one that
|
|
996
|
+
* starts with the field name, at the start of the frame or after a newline).
|
|
997
|
+
* Null when the frame has no data line (a comment, a bare event).
|
|
998
|
+
*/
|
|
999
|
+
export function sseDataLine(frame) {
|
|
1000
|
+
let at = frame.startsWith('data: ') ? 0 : frame.indexOf('\ndata: ');
|
|
1001
|
+
if (at < 0)
|
|
1002
|
+
return null;
|
|
1003
|
+
if (at > 0)
|
|
1004
|
+
at += 1;
|
|
1005
|
+
const end = frame.indexOf('\n', at);
|
|
1006
|
+
return end < 0 ? frame.slice(at) : frame.slice(at, end);
|
|
1007
|
+
}
|
|
1008
|
+
/**
|
|
1009
|
+
* Whether the analytics tap needs to parse this frame at all: only the
|
|
1010
|
+
* message_start usage, the message_delta usage and thinking deltas feed a
|
|
1011
|
+
* number it keeps. A text or tool-input delta — most of any stream — is
|
|
1012
|
+
* skipped before JSON.parse. Substring tests on the data line; a frame that
|
|
1013
|
+
* happens to contain these words inside a text delta merely costs a parse.
|
|
1014
|
+
*/
|
|
1015
|
+
export function analyticsFrameOfInterest(dataLine) {
|
|
1016
|
+
return dataLine.includes('"message_start"') || dataLine.includes('"message_delta"') || dataLine.includes('thinking_delta');
|
|
1017
|
+
}
|
|
993
1018
|
export function writeLogLine(stream, entry) {
|
|
994
1019
|
if (!stream)
|
|
995
1020
|
return;
|
|
@@ -1896,15 +1921,13 @@ export async function startProxy(opts = {}) {
|
|
|
1896
1921
|
// 500ms floor keeps the default behavior identical to v3.23; `--pace-min`
|
|
1897
1922
|
// and `--pace-jitter` let callers tune the distribution. Pure calc lives
|
|
1898
1923
|
// in src/pacing.ts so the edge cases are unit-tested without timers.
|
|
1899
|
-
const { computePacingDelay, resolvePacingConfig, computeThinkTimeDelay, resolveThinkTimeConfig, computeSessionStartDelay, resolveSessionStartConfig, } = await import('./pacing.js');
|
|
1900
|
-
|
|
1901
|
-
//
|
|
1902
|
-
// how many output tokens
|
|
1903
|
-
//
|
|
1904
|
-
//
|
|
1905
|
-
|
|
1906
|
-
let lastResponseTime = 0;
|
|
1907
|
-
let lastResponseTokens = 0;
|
|
1924
|
+
const { computePacingDelay, resolvePacingConfig, computeThinkTimeDelay, resolveThinkTimeConfig, computeSessionStartDelay, resolveSessionStartConfig, PacingRegistry, } = await import('./pacing.js');
|
|
1925
|
+
// The governor's clocks, PER SEAT (v6.9.1, src/pacing.ts PacingRegistry):
|
|
1926
|
+
// when a seat's last request started (the inter-request floor) and when its
|
|
1927
|
+
// last 2xx response completed with how many output tokens (think-time).
|
|
1928
|
+
// One clock for the whole proxy used to pace every seat against every
|
|
1929
|
+
// other, so a pool moved at one seat's speed. API-key mode is one seat.
|
|
1930
|
+
const pacingClocks = new PacingRegistry();
|
|
1908
1931
|
// --stealth toggles the behavioral-stealth preset across all three
|
|
1909
1932
|
// pacing layers (pace, think-time, session-start). When on, each
|
|
1910
1933
|
// resolver's zero-default flips to its stealth preset; explicit flags
|
|
@@ -1932,7 +1955,7 @@ export async function startProxy(opts = {}) {
|
|
|
1932
1955
|
if (verbose) {
|
|
1933
1956
|
if (stealth)
|
|
1934
1957
|
console.log('[dario] stealth: behavioral-stealth preset active (pace+think+session-start defaults non-zero)');
|
|
1935
|
-
console.log(`[dario] pacing: min=${pacingCfg.minGapMs}ms jitter=${pacingCfg.jitterMs}ms`);
|
|
1958
|
+
console.log(`[dario] pacing: min=${pacingCfg.minGapMs}ms jitter=${pacingCfg.jitterMs}ms (per seat)`);
|
|
1936
1959
|
if (thinkTimeEnabled) {
|
|
1937
1960
|
console.log(`[dario] think-time: base=${thinkTimeCfg.baseMs}ms perToken=${thinkTimeCfg.perTokenMs}ms jitter=${thinkTimeCfg.jitterMs}ms max=${thinkTimeCfg.maxMs}ms`);
|
|
1938
1961
|
}
|
|
@@ -4079,7 +4102,14 @@ export async function startProxy(opts = {}) {
|
|
|
4079
4102
|
// hand the client an OpenAI-shaped response for a Messages request. This
|
|
4080
4103
|
// route still has no reverse translation; the codex route above does,
|
|
4081
4104
|
// which is why it takes both shapes and this one does not.
|
|
4082
|
-
|
|
4105
|
+
// Only the drained-pool branch below reads this, and computing it means
|
|
4106
|
+
// parsing the whole client body again — on a 200 KB Claude Code turn
|
|
4107
|
+
// that was ~2% of dario's own CPU on every request that never took the
|
|
4108
|
+
// branch (scripts/bench-overhead.mjs profile, v6.9.2). Resolved only
|
|
4109
|
+
// when the branch can be taken.
|
|
4110
|
+
const fallbackModel = (!upstreamApiKey && !poolAccount && openaiBackend && isOpenAI)
|
|
4111
|
+
? (selectPoolFallbackForBody(body)[0] ?? null)
|
|
4112
|
+
: null;
|
|
4083
4113
|
if (!upstreamApiKey && !poolAccount && fallbackModel && openaiBackend && isOpenAI) {
|
|
4084
4114
|
const fallbackBody = buildPoolFallbackBody(body, fallbackModel);
|
|
4085
4115
|
if (!fallbackBody) {
|
|
@@ -4506,12 +4536,17 @@ export async function startProxy(opts = {}) {
|
|
|
4506
4536
|
// Opt-in via --session-start-* flags.
|
|
4507
4537
|
// We take the max because each layer enforces an independent floor
|
|
4508
4538
|
// — waiting longer satisfies all of them, so we never need to sum.
|
|
4539
|
+
// The clocks are the selected seat's. A mid-flight failover to a peer
|
|
4540
|
+
// (below) does not re-pace on the peer: the retry is the rare path, and
|
|
4541
|
+
// holding it for a floor would stack a second wait on a request that
|
|
4542
|
+
// has already been refused once.
|
|
4543
|
+
const seatClock = pacingClocks.seat(poolAccount?.alias ?? ACCOUNT_KEY_APIKEY);
|
|
4509
4544
|
const nowForPacing = Date.now();
|
|
4510
|
-
const pacingDelay = computePacingDelay(nowForPacing, lastRequestTime, pacingCfg);
|
|
4545
|
+
const pacingDelay = computePacingDelay(nowForPacing, seatClock.lastRequestTime, pacingCfg);
|
|
4511
4546
|
const thinkDelay = thinkTimeEnabled
|
|
4512
|
-
? computeThinkTimeDelay(nowForPacing, lastResponseTime, lastResponseTokens, thinkTimeCfg)
|
|
4547
|
+
? computeThinkTimeDelay(nowForPacing, seatClock.lastResponseTime, seatClock.lastResponseTokens, thinkTimeCfg)
|
|
4513
4548
|
: 0;
|
|
4514
|
-
const sessionStartDelay = (sessionStartEnabled && lastResponseTime === 0 && lastRequestTime === 0)
|
|
4549
|
+
const sessionStartDelay = (sessionStartEnabled && seatClock.lastResponseTime === 0 && seatClock.lastRequestTime === 0)
|
|
4515
4550
|
? computeSessionStartDelay(sessionStartCfg)
|
|
4516
4551
|
: 0;
|
|
4517
4552
|
const totalDelay = Math.max(pacingDelay, thinkDelay, sessionStartDelay);
|
|
@@ -4519,7 +4554,7 @@ export async function startProxy(opts = {}) {
|
|
|
4519
4554
|
pacingMs += totalDelay;
|
|
4520
4555
|
await new Promise(r => setTimeout(r, totalDelay));
|
|
4521
4556
|
}
|
|
4522
|
-
lastRequestTime = Date.now();
|
|
4557
|
+
seatClock.lastRequestTime = Date.now();
|
|
4523
4558
|
// Session ID: resolved through the rotation registry keyed by the selected
|
|
4524
4559
|
// account (src/session-rotation.ts), applying the configured idle / jitter
|
|
4525
4560
|
// / max-age / per-client policy. The template-build path resolves it
|
|
@@ -5284,14 +5319,22 @@ export async function startProxy(opts = {}) {
|
|
|
5284
5319
|
upstreamDoneAt = Date.now();
|
|
5285
5320
|
break;
|
|
5286
5321
|
}
|
|
5287
|
-
// Parse SSE events for analytics regardless of routing branch
|
|
5322
|
+
// Parse SSE events for analytics regardless of routing branch.
|
|
5323
|
+
// Only three frame kinds carry a number this tap reads — the
|
|
5324
|
+
// message_start usage, the message_delta usage, and thinking
|
|
5325
|
+
// deltas (for the ~4-chars-per-token estimate). Every other frame
|
|
5326
|
+
// is a text or tool delta, i.e. most of a stream, and parsing
|
|
5327
|
+
// them was the single largest cost in dario's own streaming path
|
|
5328
|
+
// (scripts/bench-overhead.mjs profile, v6.9.2). A substring test
|
|
5329
|
+
// on the data line decides before JSON.parse; the parse itself
|
|
5330
|
+
// is unchanged for the frames that pass.
|
|
5288
5331
|
if (analyticsDecoder && value) {
|
|
5289
5332
|
analyticsBuffer += analyticsDecoder.decode(value, { stream: true });
|
|
5290
5333
|
const parts = analyticsBuffer.split('\n\n');
|
|
5291
5334
|
analyticsBuffer = parts.pop() ?? '';
|
|
5292
5335
|
for (const part of parts) {
|
|
5293
|
-
const dataLine = part
|
|
5294
|
-
if (!dataLine)
|
|
5336
|
+
const dataLine = sseDataLine(part);
|
|
5337
|
+
if (!dataLine || !analyticsFrameOfInterest(dataLine))
|
|
5295
5338
|
continue;
|
|
5296
5339
|
try {
|
|
5297
5340
|
const e = JSON.parse(dataLine.slice(6));
|
|
@@ -5401,8 +5444,7 @@ export async function startProxy(opts = {}) {
|
|
|
5401
5444
|
// would read, and using their (often zero) output_tokens would
|
|
5402
5445
|
// pin think time to baseMs+jitter on the next request needlessly.
|
|
5403
5446
|
if (upstream.status >= 200 && upstream.status < 300) {
|
|
5404
|
-
|
|
5405
|
-
lastResponseTokens = streamOutputTokens;
|
|
5447
|
+
pacingClocks.noteResponse(poolAccount?.alias ?? ACCOUNT_KEY_APIKEY, Date.now(), streamOutputTokens);
|
|
5406
5448
|
}
|
|
5407
5449
|
{
|
|
5408
5450
|
const rl = poolAccount?.rateLimit ?? parseRateLimits(upstream.headers);
|
|
@@ -5472,8 +5514,7 @@ export async function startProxy(opts = {}) {
|
|
|
5472
5514
|
// tokens when the body wasn't JSON or had no usage block — base +
|
|
5473
5515
|
// jitter still apply but the per-token component is 0.
|
|
5474
5516
|
if (upstream.status >= 200 && upstream.status < 300) {
|
|
5475
|
-
|
|
5476
|
-
lastResponseTokens = bufferedUsage?.outputTokens ?? 0;
|
|
5517
|
+
pacingClocks.noteResponse(poolAccount?.alias ?? ACCOUNT_KEY_APIKEY, Date.now(), bufferedUsage?.outputTokens ?? 0);
|
|
5477
5518
|
}
|
|
5478
5519
|
if (bufferedUsage) {
|
|
5479
5520
|
try {
|
package/docs/wire-fidelity.md
CHANGED
|
@@ -6,7 +6,7 @@ Between v3.22 and v3.28, dario's Claude backend closed six axes along which a pr
|
|
|
6
6
|
|---|---|---|---|
|
|
7
7
|
| **Request body key order** | v3.22 | Top-level JSON key order of the outbound `/v1/messages` body is captured from CC's wire serialization and replayed byte-for-byte. Schema bumped v2 → v3; stale caches quarantined. | Automatic once a live capture exists. The baked fallback carries a v2.1.112 snapshot. |
|
|
8
8
|
| **Runtime / TLS ClientHello** | v3.23 | Classifies the runtime as `bun-match` / `bun-ja3-unverified` / `bun-bypassed` / `node-only` and surfaces the class + hint in `dario doctor`. Bun yields the BoringSSL ClientHello CC presents; Node yields OpenSSL's (distinct JA3). Being on Bun is necessary but not sufficient — only Bun ≥ v1.3.14 is measured to reproduce CC's JA3, so an older Bun is flagged `bun-ja3-unverified` rather than green (#813). | `--strict-tls` (or `DARIO_STRICT_TLS=1`) refuses to start proxy mode unless `bun-match`. `DARIO_QUIET_TLS=1` silences the startup banner in known-fine environments. |
|
|
9
|
-
| **Inter-request timing** | v3.24 | Replaces the hardcoded 500 ms floor with a configurable floor + uniform jitter. A fixed 500 ms minimum-inter-arrival is an observable edge at scale; jitter dissolves the edge. | `--pace-min=MS`, `--pace-jitter=MS`, or `DARIO_PACE_MIN_MS` / `DARIO_PACE_JITTER_MS`. Legacy `DARIO_MIN_INTERVAL_MS` still honored. |
|
|
9
|
+
| **Inter-request timing** | v3.24 | Replaces the hardcoded 500 ms floor with a configurable floor + uniform jitter. A fixed 500 ms minimum-inter-arrival is an observable edge at scale; jitter dissolves the edge. Since 6.9.1 the floor (and think-time / session-start) is kept **per seat**: the cadence the provider sees per account is unchanged, and a pool no longer paces its seats against each other. | `--pace-min=MS`, `--pace-jitter=MS`, or `DARIO_PACE_MIN_MS` / `DARIO_PACE_JITTER_MS`. Legacy `DARIO_MIN_INTERVAL_MS` still honored. |
|
|
10
10
|
| **Stream-consumption shape** | v3.25 | When a downstream client disconnects mid-stream, CC keeps reading SSE to EOF. Dario now offers the same: drain upstream to completion even when the consumer has left. Default off — don't silently burn tokens. | `--drain-on-close` / `DARIO_DRAIN_ON_CLOSE=1`. Bounded by the existing 5-minute upstream timeout. |
|
|
11
11
|
| **Session-ID lifecycle** | v3.28 | Generalizes the v3.19 hardcoded 15-minute idle rotation into a tunable `SessionRegistry` with jitter, max-age, and per-client bucketing. Fixes a v3.27 body/header rotation race as a side effect. | `--session-idle-rotate=MS` (default 900000), `--session-rotate-jitter=MS`, `--session-max-age=MS`, `--session-per-client`. Env mirrors `DARIO_SESSION_*`. Defaults are bit-identical to v3.27. |
|
|
12
12
|
| **MCP / sub-agent reach** | v3.26 + v3.27 | Not a wire axis — a *surface* axis. CC-aware tools can now address dario directly (sub-agent from inside CC, MCP server for any MCP client), so operators don't have to switch terminals to introspect the proxy. Read-only by design. | `dario subagent install` / `dario mcp`. See [`mcp-server.md`](./mcp-server.md) and [`sub-agent.md`](./sub-agent.md). |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@askalf/dario",
|
|
3
|
-
"version": "6.9.
|
|
3
|
+
"version": "6.9.2",
|
|
4
4
|
"description": "Use your Claude and ChatGPT subscriptions in Cursor, Cline, Aider, Claude Code and the Agent SDK — at subscription pricing, not per-token API bills. One local Anthropic + OpenAI-compatible endpoint: either plan answers either wire shape, with automatic failover when one hits its limit.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -42,7 +42,8 @@
|
|
|
42
42
|
"fix:pkg": "node -e \"const fs=require('fs');fs.writeFileSync('package.json',JSON.stringify(JSON.parse(fs.readFileSync('package.json','utf-8')),null,2)+'\\n')\"",
|
|
43
43
|
"audit:tui": "node tools/tui-audit/audit.mjs",
|
|
44
44
|
"readme:assets": "node scripts/readme/terminal.mjs && node scripts/readme/tui.mjs",
|
|
45
|
-
"check:readme": "node scripts/check-readme-line-count.mjs && node scripts/check-readme-links.mjs"
|
|
45
|
+
"check:readme": "node scripts/check-readme-line-count.mjs && node scripts/check-readme-links.mjs",
|
|
46
|
+
"bench": "node scripts/bench-overhead.mjs"
|
|
46
47
|
},
|
|
47
48
|
"keywords": [
|
|
48
49
|
"llm",
|