|
| 1 | +# Contributing to Eggs-AI |
| 2 | + |
| 3 | +## Quick start |
| 4 | + |
| 5 | +```bash |
| 6 | +git clone https://github.com/Interested-Deving-1896/eggs-ai.git |
| 7 | +cd eggs-ai |
| 8 | +npm install |
| 9 | +npm run build |
| 10 | +npm test # 80 tests, should all pass |
| 11 | +npm run dev -- status # verify CLI works |
| 12 | +``` |
| 13 | + |
| 14 | +Node.js 18+ required. No LLM key needed for development — tests use mocks. |
| 15 | + |
| 16 | +## Project layout |
| 17 | + |
| 18 | +``` |
| 19 | +src/ |
| 20 | +├── agents/ # 6 domain agents — each takes an LLMProvider + args, returns a string |
| 21 | +├── providers/ # LLM abstraction — registry, 7 built-in providers, config loader |
| 22 | +├── knowledge/ # Static + dynamic domain knowledge (commands, issues, distro guides) |
| 23 | +├── server/ # HTTP API server (REST + SSE streaming) |
| 24 | +├── mcp/ # Model Context Protocol server (stdio JSON-RPC) |
| 25 | +├── bridge/ # eggs-gui daemon client + JSON-RPC method definitions |
| 26 | +├── sdk/ # TypeScript client SDK for the HTTP API |
| 27 | +├── tools/ # System inspection and eggs CLI wrappers |
| 28 | +└── index.ts # CLI entry point (Commander) |
| 29 | +
|
| 30 | +test/ # Vitest tests — unit, integration, E2E, MCP |
| 31 | +integrations/ # Drop-in code for eggs-gui frontends (Python, Go) |
| 32 | +examples/ # Sample configs and programmatic usage |
| 33 | +packaging/ # systemd service, .desktop file |
| 34 | +proto/ # JSON-RPC schema for eggs-gui integration |
| 35 | +``` |
| 36 | + |
| 37 | +## Development workflow |
| 38 | + |
| 39 | +### Build and test |
| 40 | + |
| 41 | +```bash |
| 42 | +npm run build # TypeScript → dist/ |
| 43 | +npm test # Run all tests once |
| 44 | +npm run test:watch # Watch mode |
| 45 | +npm run dev -- <command> # Run CLI without building (uses tsx) |
| 46 | +``` |
| 47 | + |
| 48 | +### Run the API server |
| 49 | + |
| 50 | +```bash |
| 51 | +npm run serve # Starts on http://127.0.0.1:3737 |
| 52 | +# or |
| 53 | +npm run dev -- serve --port 3737 |
| 54 | +``` |
| 55 | + |
| 56 | +### Run the MCP server |
| 57 | + |
| 58 | +```bash |
| 59 | +echo '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' | npx tsx src/mcp/server.ts |
| 60 | +``` |
| 61 | + |
| 62 | +## How things connect |
| 63 | + |
| 64 | +``` |
| 65 | +User input |
| 66 | + │ |
| 67 | + ▼ |
| 68 | +CLI (src/index.ts) ──or── API Server (src/server/api.ts) ──or── MCP Server (src/mcp/server.ts) |
| 69 | + │ │ │ |
| 70 | + ▼ ▼ ▼ |
| 71 | +Agent (src/agents/*.ts) Agent (same) Direct knowledge lookup |
| 72 | + │ │ (no LLM needed for MCP) |
| 73 | + ├── Knowledge (src/knowledge/) │ |
| 74 | + ├── System inspect (src/tools/)│ |
| 75 | + │ │ |
| 76 | + ▼ ▼ |
| 77 | +LLMProvider.chat(messages) LLMProvider.chat(messages) |
| 78 | + │ │ |
| 79 | + ▼ ▼ |
| 80 | +ProviderRegistry → Gemini / OpenAI / Anthropic / Ollama / ... |
| 81 | +``` |
| 82 | + |
| 83 | +Key distinction: **agents** need an LLM provider (they construct prompts and call `provider.chat()`). **MCP tools** return knowledge directly without an LLM — the calling AI agent (Cursor, Claude Desktop, etc.) is the LLM. |
| 84 | + |
| 85 | +## Adding a new LLM provider |
| 86 | + |
| 87 | +1. Create `src/providers/myservice.ts`: |
| 88 | + |
| 89 | +```typescript |
| 90 | +import type { LLMProvider, Message } from './base.js'; |
| 91 | + |
| 92 | +export class MyServiceProvider implements LLMProvider { |
| 93 | + name = 'myservice'; |
| 94 | + |
| 95 | + constructor(private apiKey: string, private model = 'default-model') {} |
| 96 | + |
| 97 | + async chat(messages: Message[]): Promise<string> { |
| 98 | + // Call your API, return the response text |
| 99 | + } |
| 100 | + |
| 101 | + // Optional: streaming support |
| 102 | + async chatStream?(messages: Message[], onChunk: (chunk: string) => void): Promise<string> { |
| 103 | + // Stream tokens, call onChunk for each, return full text |
| 104 | + } |
| 105 | + |
| 106 | + async isAvailable(): Promise<boolean> { |
| 107 | + // Quick check — can we reach the API? |
| 108 | + } |
| 109 | +} |
| 110 | +``` |
| 111 | + |
| 112 | +2. Register it in `src/providers/index.ts`: |
| 113 | + |
| 114 | +```typescript |
| 115 | +import { MyServiceProvider } from './myservice.js'; |
| 116 | + |
| 117 | +ProviderRegistry.register('myservice', (config) => { |
| 118 | + const key = config.apiKey || process.env.MYSERVICE_API_KEY; |
| 119 | + if (!key) throw new Error('API key required. Set MYSERVICE_API_KEY.'); |
| 120 | + return new MyServiceProvider(key, config.model); |
| 121 | +}); |
| 122 | +``` |
| 123 | + |
| 124 | +3. Add it to the auto-detection list in `autoDetectProvider()` if it has an env var. |
| 125 | + |
| 126 | +4. Add tests in `test/providers-impl.test.ts` — mock `fetch`, verify request format and headers. |
| 127 | + |
| 128 | +If the API is OpenAI-compatible (most are), you can skip step 1 and just register it with the `CustomProvider`: |
| 129 | + |
| 130 | +```typescript |
| 131 | +ProviderRegistry.register('myservice', (config) => { |
| 132 | + return new CustomProvider('myservice', 'https://api.myservice.com/v1', config.model || 'default', config.apiKey); |
| 133 | +}); |
| 134 | +``` |
| 135 | + |
| 136 | +## Adding a new agent |
| 137 | + |
| 138 | +Agents live in `src/agents/`. Each one: |
| 139 | +- Takes an `LLMProvider` and agent-specific arguments |
| 140 | +- Builds a prompt using knowledge from `src/knowledge/` |
| 141 | +- Calls `provider.chat(messages)` and returns the result |
| 142 | + |
| 143 | +1. Create `src/agents/myagent.ts`: |
| 144 | + |
| 145 | +```typescript |
| 146 | +import type { LLMProvider, Message } from '../providers/index.js'; |
| 147 | +import { SYSTEM_PROMPT } from '../knowledge/eggs-reference.js'; |
| 148 | +import { inspectSystem, formatSystemInfo } from '../tools/system-inspect.js'; |
| 149 | + |
| 150 | +export async function runMyAgent(provider: LLMProvider, userInput: string): Promise<string> { |
| 151 | + const systemInfo = inspectSystem(); |
| 152 | + |
| 153 | + const prompt = ` |
| 154 | +## System |
| 155 | +${formatSystemInfo(systemInfo)} |
| 156 | +
|
| 157 | +## Task |
| 158 | +${userInput} |
| 159 | +
|
| 160 | +Provide specific, actionable output. |
| 161 | +`; |
| 162 | + |
| 163 | + const messages: Message[] = [ |
| 164 | + { role: 'system', content: SYSTEM_PROMPT }, |
| 165 | + { role: 'user', content: prompt }, |
| 166 | + ]; |
| 167 | + |
| 168 | + return provider.chat(messages); |
| 169 | +} |
| 170 | +``` |
| 171 | + |
| 172 | +2. Wire it into the CLI in `src/index.ts` — add a new Commander command. |
| 173 | + |
| 174 | +3. Wire it into the API server in `src/server/api.ts` — add a new endpoint. |
| 175 | + |
| 176 | +4. Add tests in `test/agents.test.ts` — use a mock provider, verify the prompt contains the right context. |
| 177 | + |
| 178 | +## Adding a new MCP tool |
| 179 | + |
| 180 | +MCP tools in `src/mcp/server.ts` return knowledge directly — no LLM call. They're for other AI agents to use. |
| 181 | + |
| 182 | +1. Add the tool definition to the `TOOLS` array: |
| 183 | + |
| 184 | +```typescript |
| 185 | +{ |
| 186 | + name: 'eggs_my_tool', |
| 187 | + description: 'What this tool does (be specific — the AI agent reads this)', |
| 188 | + inputSchema: { |
| 189 | + type: 'object', |
| 190 | + properties: { |
| 191 | + param: { type: 'string', description: 'What this param is for' }, |
| 192 | + }, |
| 193 | + required: ['param'], |
| 194 | + }, |
| 195 | +}, |
| 196 | +``` |
| 197 | + |
| 198 | +2. Add a handler case in `handleTool()`: |
| 199 | + |
| 200 | +```typescript |
| 201 | +case 'eggs_my_tool': { |
| 202 | + const param = args.param as string; |
| 203 | + // Look up knowledge, inspect system, etc. |
| 204 | + return text(`Result for ${param}`); |
| 205 | +} |
| 206 | +``` |
| 207 | + |
| 208 | +3. Add tests in `test/mcp-server.test.ts` — send a JSON-RPC request, verify the response. |
| 209 | + |
| 210 | +## Adding knowledge |
| 211 | + |
| 212 | +### Static knowledge (src/knowledge/) |
| 213 | + |
| 214 | +- `eggs-reference.ts` — commands, config fields, common issues, supported distros, calamares modules, wardrobe costumes, system prompt |
| 215 | +- `distro-guides.ts` — per-distro install guides, advanced workflows, additional troubleshooting |
| 216 | + |
| 217 | +Add new entries to the existing data structures. The agents and MCP tools reference these at runtime. |
| 218 | + |
| 219 | +### Dynamic knowledge (src/knowledge/updater.ts) |
| 220 | + |
| 221 | +Fetches from GitHub API and caches locally: |
| 222 | +- Recent issues (20 most recent) |
| 223 | +- Latest release info |
| 224 | +- README excerpt |
| 225 | + |
| 226 | +Cache lives at `~/.cache/eggs-ai/` with a 24-hour TTL. Run `eggs-ai update` to refresh manually. |
| 227 | + |
| 228 | +## Testing |
| 229 | + |
| 230 | +### Test structure |
| 231 | + |
| 232 | +| File | What it tests | |
| 233 | +|------|--------------| |
| 234 | +| `providers-registry.test.ts` | Registry registration, lookup, env key resolution | |
| 235 | +| `providers-impl.test.ts` | All 6 provider implementations (mocked fetch) | |
| 236 | +| `agents.test.ts` | All 5 agents with mock providers | |
| 237 | +| `knowledge.test.ts` | Knowledge base structure validation | |
| 238 | +| `system-inspect.test.ts` | System detection on live system | |
| 239 | +| `sdk-client.test.ts` | SDK client with mocked HTTP | |
| 240 | +| `api-server.test.ts` | Integration tests against running API server | |
| 241 | +| `mcp-server.test.ts` | MCP protocol tests via subprocess | |
| 242 | +| `e2e.test.ts` | Full pipeline validation (prompt construction, history, error handling) | |
| 243 | + |
| 244 | +### Writing tests |
| 245 | + |
| 246 | +- Use `vitest` (already configured) |
| 247 | +- Mock `fetch` with `vi.stubGlobal('fetch', mockFetch)` for provider tests |
| 248 | +- Use `createMockProvider()` pattern for agent tests — captures messages sent to the LLM |
| 249 | +- MCP tests shell out to `npx tsx src/mcp/server.ts` with JSON-RPC input |
| 250 | +- API tests hit `http://127.0.0.1:3737` (requires the server to be running) |
| 251 | + |
| 252 | +### Running specific tests |
| 253 | + |
| 254 | +```bash |
| 255 | +npx vitest run test/agents.test.ts |
| 256 | +npx vitest run -t "doctor agent" # by test name |
| 257 | +``` |
| 258 | + |
| 259 | +## Code conventions |
| 260 | + |
| 261 | +- TypeScript strict mode, ES2022 target, Node16 module resolution |
| 262 | +- ESM only (`"type": "module"` in package.json) |
| 263 | +- Imports use `.js` extension (required for Node ESM) |
| 264 | +- No classes for agents — plain async functions |
| 265 | +- Classes for providers (they hold state: API keys, model names) |
| 266 | +- `ProviderRegistry` is a singleton — register once, use everywhere |
| 267 | +- Knowledge is exported as plain objects/arrays, not classes |
| 268 | + |
| 269 | +## Pull request guidelines |
| 270 | + |
| 271 | +1. Branch from `master` |
| 272 | +2. Run `npm test` — all tests must pass |
| 273 | +3. Run `npm run build` — must compile clean |
| 274 | +4. Add tests for new functionality |
| 275 | +5. Keep commits focused — one logical change per commit |
| 276 | +6. Commit message format: short summary line, blank line, details if needed |
| 277 | + |
| 278 | +## Deployment options |
| 279 | + |
| 280 | +| Method | Command | |
| 281 | +|--------|---------| |
| 282 | +| CLI | `npm run dev -- <command>` | |
| 283 | +| API server | `eggs-ai serve --port 3737` | |
| 284 | +| MCP server | `eggs-ai mcp` (stdio) | |
| 285 | +| Docker | `docker compose up -d` | |
| 286 | +| systemd | `sudo cp packaging/eggs-ai.service /etc/systemd/system/ && sudo systemctl enable --now eggs-ai` | |
| 287 | +| Install script | `sudo bash install.sh` | |
0 commit comments