Contributing to dario
September 7, 2026 · View on GitHub
PRs welcome. The codebase is ~12,650 lines across 27 TypeScript files and stays dependency-free at runtime.
Setup
git clone https://github.com/askalf/dario
cd dario
npm install
npm run dev # runs with tsx, no build needed
Structure
| File | Purpose |
|---|---|
src/proxy.ts | HTTP proxy server, request dispatch, rate governor, billing tag, account-pool routing (the one credential path since v5.0 — pool-as-primitive), OpenAI-compat backend routing, SSE streaming forwarder |
src/cc-template.ts | CC request template engine, forward tool mapping (translateArgs), reverse tool mapping (translateBack), reverseMapResponse for non-streaming responses, createStreamingReverseMapper for SSE streaming tool_use blocks, framework/orchestration scrubbing |
src/cc-template-data.json | CC request template data (25 tools, 25KB system prompt) |
src/cc-oauth-detect.ts | Auto-detect OAuth config from the installed Claude Code binary (v3.4.3+), anchored on BASE_API_URL:"https://api.anthropic.com" |
src/oauth.ts | dario login token storage, refresh, credential detection, macOS keychain fallback (v3.7.0+); since v5.0 the login credential is back-filled into the pool under the reserved login alias |
src/accounts.ts | Per-account credential storage at ~/.dario/accounts/<alias>.json (v3.5.0+) — the backing store for the account pool, the one credential model since v5.0 |
src/pool.ts | Account pool, headroom-aware selection, failover-target selection, request queueing (v3.5.0+); always constructed since v5.0 — a plain dario login is a pool of one |
src/analytics.ts | Rolling request history, per-account / per-model stats, burn-rate, exhaustion predictions (v3.5.0+) |
src/openai-backend.ts | OpenAI-compat backend credential storage and request forwarder (v3.6.0+) |
src/cli.ts | CLI entry point, command routing (login, proxy, accounts, backend, status, refresh, logout), Bun auto-relaunch |
src/index.ts | Library exports |
test/issue-29-tool-translation.mjs | In-process regression test for the tool-use reverse translation layer (28 assertions, no OAuth or live proxy required) |
test/compat.mjs | Live-proxy end-to-end compat suite (tool use, streaming, OpenAI compat). Requires a running dario proxy and authenticated Claude credentials. |
test/e2e.mjs | Live-proxy end-to-end smoke suite |
test/stealth-test.mjs | Live-proxy stealth suite (billing classification, thinking stripping, field scrubbing) |
test/oauth-detector.mjs | End-to-end test for the OAuth detector against a real CC binary |
Before submitting
npm run build— must compile clean.npm test— in-process regression test for the tool-use reverse translation layer (no OAuth or upstream calls required; runs anywhere).npm audit --production --audit-level=high— no high-severity vulnerabilities.- For changes that touch
proxy.ts,cc-template.ts, or streaming behavior: test manually against a live proxy withdario proxy --verboseand then runnode test/compat.mjs(requires valid credentials). The live suites aren't wired intonpm testbecause they require credentials and consume real subscription usage. - No new runtime dependencies — dario's zero-runtime-deps posture is load-bearing for its audit story.
- Keep it simple — this project's value is that it's small enough to audit.
Security issues
Do not open a public issue. Email security@askalf.org instead. See SECURITY.md.
Review policy
Every PR goes through at least one review round. The bar for merge is:
- Functional. Tests pass locally and on CI (the full
npm testsuite, not just the change's new tests). - Necessary. The change solves a stated problem (linked issue, user report, or review-feedback entry). "While I was in there" changes get split into a separate PR.
- Non-breaking by default. Any change that removes or alters a
@stableAPI per STABILITY.md requires a deprecation cycle — no breaking changes slip through on patch or minor releases. - Test coverage for behavior changes. New flags, new code paths, new exit conditions all get at least one assertion in
test/. Pure refactors are exempt. - Zero new runtime deps. This is non-negotiable — dario's audit story depends on it. Dev-only deps (TypeScript, tsx,
@types/node) are fine.
PRs that don't meet the bar get comments explaining why, not a silent block. If the bar seems arbitrary in a specific case, argue it in the PR — every bar item has been negotiated before.
Linking an issue you didn't file
Write Addresses #N, not Fixes #N. A closing keyword ends the thread the moment the PR merges, and the reporter — who may still be mid-question — reads the close as the answer rather than as a keyword firing. That is what happened on #1244: it closed under the reporter while he was asking follow-ups, and had to be reopened and explained. The person who filed an issue is the one who closes it, once the fix reads right to them.
Two things enforce it. scripts/check-issue-close-keywords.mjs fails CI on a PR whose title, body, or any commit message on the branch would auto-close somebody else's issue — a squash merge carries the branch's commit messages as the commit body, and GitHub scans a commit message raw, backticks and all, so a keyword quoted in code is still a keyword (#1255 closed #1244 that way on its own first merge). Quoting one on purpose? Keep the word and the number apart — Fixes … #1244 — or drop the #. It and .github/workflows/keyword-close-notice.yml comments on one that got closed anyway, naming what closed it and inviting the reporter to reopen. The close stands — reopening every keyword-closed report would also reopen the ones that really are fixed. What was missing on #1244 was the explanation, not the open state. Your own issues and the drift watchers' bot-filed alerts are exempt — Fixes #N those freely.
Release cadence
For the actual release mechanics — the inline auto-release chain in cc-drift-auto-release.yml (build → smoke → GHCR push → GitHub release with attested artifacts → tokenless npm publish via OIDC trusted publishing), the pre-merge checklist, and the post-publish smoke against the installed npm bin added after dario#143 (that's npm's bin stub, unrelated to the dario shim transport removed in v5.0) — see RELEASING.md.
See STABILITY.md for the full policy. Summary:
- Patch (
5.0.x) — bug fixes, review-feedback, drift patches. Often multiple per day during active cycles. - Minor (
5.1.0) — new flags, new exports, new endpoints. Ships when accumulated new surface justifies it. - Major (
5.0.0) — removes@deprecatedAPIs, changes@stablebehavior. v5.0 made the account pool the one credential model (a plaindario loginis a pool of one) and deleted the deprecated shim transport. Every major carries a migration guide — see MIGRATION.md.
New features default to @experimental for at least one minor before being promoted to @stable. The promotion is a CHANGELOG entry, not a code change — the JSDoc tag moves and the CHANGELOG.md notes the graduation.
Semver commitments
@stableAPI: never breaks without a major bump + a minor-long deprecation cycle@experimentalAPI: can change in any minor, but breaks are called out in CHANGELOG@deprecatedAPI: removed at the next major, stays working with a one-shot warning until then- Internal (unmarked) APIs: free to change without notice — don't depend on them
If your PR touches something at the @stable boundary in a way that requires a shape change, flag it in the PR title: [STABILITY] and propose whether it's a deprecation cycle or a new-minor addition that leaves the old surface intact.