Skip to content

Development Guide

Detailed guide for setting up your development environment and working with the ai.matey codebase.

Before you begin, ensure you have:

  • Node.js 18+ installed
  • npm 9+ (comes with Node.js)
  • Git for version control
  • A code editor (we recommend VS Code)
Terminal window
# Fork the repository on GitHub first, then:
git clone https://github.com/YOUR_USERNAME/ai.matey.git
cd ai.matey
Terminal window
npm install

This installs dependencies for all packages in the monorepo.

Create a .env file for API keys (optional, needed for testing):

.env
OPENAI_API_KEY=sk-...
ANTHROPIC_API_KEY=sk-ant-...
GEMINI_API_KEY=...
GROQ_API_KEY=...

Note: Never commit API keys! The .env file is gitignored.

Terminal window
npm run build

This builds all packages in dependency order using Turbo.

ai.matey uses npm workspaces for package management and Turbo for build orchestration.

{
"workspaces": [
"packages/*"
]
}

All packages are in packages/. Dependencies between packages are managed automatically.

Packages have internal dependencies:

ai.matey.core → ai.matey.types
ai.matey.frontend → ai.matey.types
ai.matey.backend → ai.matey.types
ai.matey.middleware → ai.matey.core, ai.matey.types
Terminal window
# Build all packages
npm run build
# Build specific package
npm run build --workspace=ai.matey.core
# Build in watch mode
npm run dev
Terminal window
# Run all tests
npm test
# Run tests for specific package
npm test --workspace=ai.matey.core
# Run tests in watch mode
npm test -- --watch
# Run tests with coverage
npm test -- --coverage
Terminal window
# Check all packages
npm run lint
# Auto-fix issues
npm run lint:fix
# Lint specific package
npm run lint --workspace=ai.matey.core
Terminal window
# Format all code
npm run format
# Check formatting
npm run format:check
Terminal window
# Clean build artifacts
npm run clean
# Clean and rebuild
npm run clean && npm run build
  1. Create package directory:

    Terminal window
    mkdir packages/ai.matey.newpackage
    cd packages/ai.matey.newpackage
  2. Initialize package.json:

    {
    "name": "ai.matey.newpackage",
    "version": "0.1.0",
    "main": "./dist/index.js",
    "types": "./dist/index.d.ts",
    "scripts": {
    "build": "tsc",
    "test": "jest"
    },
    "dependencies": {
    "ai.matey.types": "*"
    },
    "devDependencies": {
    "typescript": "^5.0.0",
    "jest": "^29.0.0"
    }
    }
  3. Add tsconfig.json:

    {
    "extends": "../../tsconfig.json",
    "compilerOptions": {
    "outDir": "./dist",
    "rootDir": "./src"
    },
    "include": ["src/**/*"]
    }
  4. Create source files:

    Terminal window
    mkdir src
    touch src/index.ts
Terminal window
# Add to specific package
npm install package-name --workspace=ai.matey.core
# Add to root (dev dependencies)
npm install -D package-name

Packages automatically link in the monorepo:

{
"dependencies": {
"ai.matey.core": "*" // Uses local version
}
}
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"lib": ["ES2020"],
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}

Packages extend the root config:

{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"],
"references": [
{ "path": "../ai.matey.types" }
]
}

Tests live alongside source code:

src/
├── bridge.ts
├── bridge.test.ts
├── router.ts
└── router.test.ts
bridge.test.ts
import { Bridge } from './bridge';
import { MockFrontendAdapter, MockBackendAdapter } from '../test-utils';
describe('Bridge', () => {
let bridge: Bridge;
let mockBackend: MockBackendAdapter;
beforeEach(() => {
mockBackend = new MockBackendAdapter();
bridge = new Bridge(
new MockFrontendAdapter(),
mockBackend
);
});
describe('chat()', () => {
it('should process requests correctly', async () => {
mockBackend.setResponse({
choices: [{ message: { content: 'Test response' } }]
});
const response = await bridge.chat({
model: 'test',
messages: [{ role: 'user', content: 'Test' }]
});
expect(response.choices[0].message.content).toBe('Test response');
});
it('should handle errors', async () => {
mockBackend.setError(new Error('API Error'));
await expect(
bridge.chat({
model: 'test',
messages: [{ role: 'user', content: 'Test' }]
})
).rejects.toThrow('API Error');
});
});
describe('chatStream()', () => {
it('should stream responses', async () => {
const stream = await bridge.chatStream({
model: 'test',
messages: [{ role: 'user', content: 'Test' }],
stream: true
});
const chunks = [];
for await (const chunk of stream) {
chunks.push(chunk);
}
expect(chunks.length).toBeGreaterThan(0);
});
});
});

Create mock adapters for testing:

test-utils/mock-backend.ts
export class MockBackendAdapter implements BackendAdapter {
name = 'mock';
private response: any;
private error: Error | null = null;
setResponse(response: any) {
this.response = response;
}
setError(error: Error) {
this.error = error;
}
async chat(request: IRChatCompletionRequest) {
if (this.error) throw this.error;
return this.response || { choices: [{ message: { content: 'Mock' } }] };
}
async *chatStream(request: IRChatCompletionRequest) {
if (this.error) throw this.error;
yield { choices: [{ delta: { content: 'Mock' } }] };
}
}

Create .vscode/launch.json:

{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug Tests",
"program": "${workspaceFolder}/node_modules/jest/bin/jest",
"args": ["--runInBand", "--no-coverage"],
"cwd": "${workspaceFolder}",
"console": "integratedTerminal"
},
{
"type": "node",
"request": "launch",
"name": "Debug Current File",
"program": "${file}",
"cwd": "${workspaceFolder}",
"console": "integratedTerminal"
}
]
}
  1. Use breakpoints in VS Code
  2. Add debugger statements in code
  3. Use console.log strategically
  4. Run tests in watch mode for quick feedback
{
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
},
"test": {
"dependsOn": ["build"],
"outputs": []
},
"lint": {
"outputs": []
}
}
}

Turbo caches build outputs. To clear cache:

Terminal window
npx turbo run build --force
  1. Create adapter file:

    Terminal window
    cd packages/ai.matey.backend
    touch src/adapters/newprovider.ts
  2. Implement adapter:

    export class NewProviderBackendAdapter implements BackendAdapter {
    name = 'newprovider';
    // ... implementation
    }
  3. Add tests:

    Terminal window
    touch src/adapters/newprovider.test.ts
  4. Export:

    src/index.ts
    export { NewProviderBackendAdapter } from './adapters/newprovider';
  5. Test:

    Terminal window
    npm test --workspace=ai.matey.backend
  1. Create middleware file:

    Terminal window
    cd packages/ai.matey.middleware
    touch src/newmiddleware.ts
  2. Implement:

    export function createNewMiddleware(options) {
    return {
    name: 'new-middleware',
    async execute(request, next) {
    // Implementation
    return await next(request);
    }
    };
    }
  3. Test and export

Terminal window
# Clean and rebuild
npm run clean
npm install
npm run build
Terminal window
# Run specific test
npm test -- bridge.test.ts
# Update snapshots
npm test -- -u
Terminal window
# Check types
npx tsc --noEmit
# Rebuild types
npm run build --workspace=ai.matey.types
Terminal window
# Clear npm cache
npm cache clean --force
# Reinstall
rm -rf node_modules
npm install
  1. Write tests first (TDD)
  2. Keep PRs focused - one feature/fix per PR
  3. Update docs with code changes
  4. Add examples for new features
  5. Run linter before committing
  6. Test edge cases
  7. Handle errors gracefully

Happy coding! 🚀