Thank you for your interest in contributing! This guide covers how to set up a development environment, run tests, and submit changes.
- Node.js ≥ 20.0.0
- npm ≥ 10
git clone https://github.com/you/auth-agent.git
cd auth-agent
npm installnpm run dev -- auth status
npm run dev -- run "Hello" --provider anthropicThis uses tsx to execute TypeScript directly, so changes to src/ are
reflected immediately.
npm run build
# Outputs: dist/ (JS + .d.ts declarations + source maps)- Keep files focused: one responsibility per file, 200–400 lines typical.
- Organise by feature (
auth/,llm/), not by type.
All data structures — especially the credential store — are treated as immutable. Functions return new objects rather than mutating existing ones.
// ✓ correct
const updated = upsertProfile(store, profileId, credential);
// ✗ wrong
store.profiles[profileId] = credential;- Never silently swallow errors.
- Throw with descriptive messages that include the actionable next step
(e.g.
"Run: auth-agent auth login --provider anthropic"). - At system boundaries (file I/O, network), catch and re-throw with context.
- Strict mode is enabled (
"strict": trueintsconfig.json). - Prefer explicit return types on exported functions.
- Avoid
any; useunknownand narrow explicitly.
The test suite uses Vitest.
npm test # run all tests once
npm run test:watch # watch mode
npm test test/codex-call.test.ts # single file- RED — Write the failing test first.
- GREEN — Write the minimal implementation to make it pass.
- REFACTOR — Clean up; verify coverage ≥ 80 %.
Global fetch — stub with vi.stubGlobal and restore in afterEach:
import { afterEach, beforeEach, vi } from "vitest";
let mockFetch: ReturnType<typeof vi.fn>;
beforeEach(() => {
mockFetch = vi.fn();
vi.stubGlobal("fetch", mockFetch);
});
afterEach(() => {
vi.unstubAllGlobals();
});External modules — use vi.mock before dynamic imports:
vi.mock("@mariozechner/pi-ai/oauth", () => ({
getOAuthApiKey: vi.fn(),
}));
const { resolveToken } = await import("../src/auth/resolve.js");Credential store isolation — set AUTH_AGENT_STORE_DIR to a tmpdir
before importing any store module:
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "auth-agent-test-"));
process.env.AUTH_AGENT_STORE_DIR = tmpDir;
const { loadStore } = await import("../src/auth/store.js");Maintain ≥ 80 % line/branch coverage on all new code.
-
Create
src/auth/<provider>.ts- Export a
login<Provider>()function and a<PROVIDER>_PROFILE_IDconstant. - Save credentials via
upsertAndSave()fromstore.ts.
- Export a
-
Register in
src/auth/resolve.ts- Add the new provider to
Providertype andPROFILE_IDSmap.
- Add the new provider to
-
Add an LLM call helper in
src/llm/<provider>-call.ts(optional)- Export
call<Provider>(options). - Export
Call<Provider>OptionsandCall<Provider>Resulttypes.
- Export
-
Wire up the CLI in
src/cli.ts- Add a branch to
auth loginandrunactions.
- Add a branch to
-
Export from
src/index.ts- Add the new types and functions to the public library API.
-
Write tests
test/<provider>-call.test.ts— unit-test pure helpers with mocked fetch.
Before opening a PR, verify:
-
npm run buildcompletes without TypeScript errors -
npm testpasses (all tests green) - New code has ≥ 80 % test coverage
- No hardcoded secrets or tokens
- Errors are handled and never silently swallowed
- New public exports are added to
src/index.tsand documented inREADME.md - Commit messages follow Conventional Commits:
feat:,fix:,refactor:,test:,docs:,chore: