-
Notifications
You must be signed in to change notification settings - Fork 0
Development Guide
andershsueh edited this page Feb 10, 2026
·
2 revisions
Guide for developers who want to contribute to ALICE or understand its internals.
- Node.js ≥ 18.0.0
- npm ≥ 9.0.0
- Git
- TypeScript knowledge
- VS Code (with TypeScript/ESLint extensions)
- Windows Terminal (Windows) or iTerm2 (macOS)
- LM Studio for testing
git clone https://github.com/AndersHsueh/Alice.git
cd Alicenpm installThis installs:
- TypeScript compiler
- ink (React for CLI)
- Tool dependencies (ajv, glob, simple-git)
- Development tools
npm run buildOutput goes to dist/ directory.
npm run devnpm run dev, NOT npm run dev:watch when testing interactive features (keyboard input).
Alice/
├── src/ # Source code
│ ├── index.ts # Entry point
│ ├── cli/ # UI layer (ink components)
│ │ ├── app.tsx # Main application
│ │ └── components/ # React components
│ │ ├── Banner.tsx
│ │ ├── ChatArea.tsx
│ │ ├── InputBox.tsx
│ │ ├── ToolCallStatus.tsx
│ │ └── DangerousCommandConfirm.tsx
│ ├── core/ # Core logic
│ │ ├── llm.ts # LLM client
│ │ ├── providers/ # LLM provider implementations
│ │ │ ├── base.ts
│ │ │ └── openai-compatible.ts
│ │ └── session.ts # Session management
│ ├── tools/ # Tool system
│ │ ├── registry.ts # Tool registration
│ │ ├── executor.ts # Tool execution
│ │ └── builtin/ # Builtin tools
│ │ ├── readFile.ts
│ │ ├── listFiles.ts
│ │ ├── searchFiles.ts
│ │ ├── getCurrentDirectory.ts
│ │ ├── getGitInfo.ts
│ │ ├── getCurrentDateTime.ts
│ │ └── executeCommand.ts
│ ├── utils/ # Utilities
│ │ ├── config.ts # Configuration management
│ │ ├── banner.ts # Banner generation
│ │ └── logger.ts # Logging (future)
│ └── types/ # TypeScript types
│ ├── index.ts # Core types
│ └── tool.ts # Tool types
├── dist/ # Build output (gitignored)
├── etc/ # Assets
│ └── alice-banner.png # README banner
├── node_modules/ # Dependencies (gitignored)
├── package.json # Project config
├── tsconfig.json # TypeScript config
└── README.md # Project README
# 1. Make changes to src/
vim src/cli/components/MyComponent.tsx
# 2. Build
npm run build
# 3. Test
npm run dev
# 4. Commit
git add .
git commit -m "feat: add MyComponent"
# 5. Push
git push origin feature/my-featurenpm run dev:watch- Checking compilation errors
- Viewing render output
- Testing non-interactive components
For interactive testing, always use npm run dev.
┌─────────────────────────────────────┐
│ CLI Entry (index.ts) │
│ - Parse arguments │
│ - Load config │
│ - Initialize app │
└──────────────┬──────────────────────┘
│
┌──────────────▼──────────────────────┐
│ UI Layer (ink components) │
│ - App.tsx (main container) │
│ - Components (Banner, Chat, Input) │
│ - State management (React hooks) │
└──────────────┬──────────────────────┘
│
┌──────────────▼──────────────────────┐
│ Core Logic (core/) │
│ - LLMClient: Manages conversation │
│ - Providers: LLM API integration │
│ - Session: History management │
└──────────────┬──────────────────────┘
│
┌──────────────▼──────────────────────┐
│ Tool System (tools/) │
│ - ToolRegistry: Tool definitions │
│ - ToolExecutor: Execute tools │
│ - Builtin tools │
└─────────────────────────────────────┘
User Input (stdin)
│
▼
InputBox component (useInput hook)
│
▼
App.tsx handleSubmit()
│
▼
LLMClient.chatStreamWithTools()
│
├─→ OpenAICompatibleProvider.chatStreamWithTools()
│ │
│ ├─→ Send to LLM with tool definitions
│ │
│ └─→ Receive: text response OR tool_calls
│
├─→ [If tool_calls] ToolExecutor.execute()
│ │
│ └─→ Run tool, return result
│
└─→ [Loop] Until text response or max iterations
│
▼
Stream chunks to UI
│
▼
ChatArea renders response
ALICE uses ESM (not CommonJS):
// package.json
{
"type": "module"
}Important rules:
- All imports must include
.jsextension (even for.tsfiles) - No
require(), useimport - No
__dirname, useimport.meta.url
Example:
// ❌ Wrong
import { foo } from './utils';
// ✅ Correct
import { foo } from './utils.js'; // .js even for .ts files!{
"compilerOptions": {
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "bundler",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"jsx": "react",
"jsxImportSource": "react",
"outDir": "./dist",
"rootDir": "./src",
"declaration": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}// src/cli/components/MyComponent.tsx
import React from 'react';
import { Box, Text } from 'ink';
interface MyComponentProps {
message: string;
}
export const MyComponent: React.FC<MyComponentProps> = ({ message }) => {
return (
<Box flexDirection="column" padding={1}>
<Text color="cyan">{message}</Text>
</Box>
);
};import React, { useState, useEffect } from 'react';
import { useInput } from 'ink';
export const InteractiveComponent: React.FC = () => {
const [count, setCount] = useState(0);
useInput((input, key) => {
if (key.upArrow) {
setCount(c => c + 1);
}
if (key.downArrow) {
setCount(c => c - 1);
}
});
return <Text>Count: {count}</Text>;
};// src/types/tool.ts
export interface AliceTool {
name: string; // Unique identifier
label: string; // Display name
description: string; // For LLM understanding
parameters: TSchema; // JSON Schema
execute: (
toolCallId: string,
params: any,
signal: AbortSignal,
onUpdate?: (partial: ToolResult) => void
) => Promise<ToolResult>;
}// src/tools/builtin/myTool.ts
import type { AliceTool, ToolResult } from '../../types/tool.js';
import { Type, type TSchema } from '@sinclair/typebox';
export const myTool: AliceTool = {
name: 'myTool',
label: 'My Tool',
description: 'Does something useful',
parameters: Type.Object({
input: Type.String({ description: 'Input parameter' }),
flag: Type.Boolean({ description: 'Optional flag', default: false })
}),
async execute(toolCallId, params, signal, onUpdate): Promise<ToolResult> {
// Validate params (already validated by registry)
const { input, flag } = params;
try {
// Report progress
onUpdate?.({
success: false,
status: 'Processing...',
progress: 0
});
// Do work
const result = await doWork(input);
// Report progress
onUpdate?.({
success: false,
status: 'Almost done...',
progress: 50
});
// More work
await doMoreWork(result);
// Return success
return {
success: true,
data: {
result: result,
flag: flag
}
};
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : String(error)
};
}
}
};// src/cli/app.tsx
import { myTool } from '../tools/builtin/myTool.js';
// In component
useEffect(() => {
toolRegistry.register(myTool);
}, []);# Build and run
npm run build
npm run dev
# Test specific features
> You: List all files
> You: What time is it?
> You: Execute: echo "test"// src/utils/test-tools.ts
import { toolRegistry } from '../tools/registry.js';
import { myTool } from '../tools/builtin/myTool.js';
async function test() {
toolRegistry.register(myTool);
const result = await toolRegistry.execute(
'test-id',
'myTool',
{ input: 'test' },
new AbortController().signal
);
console.log('Result:', result);
}
test();Run with:
npx tsx src/utils/test-tools.tsnpm test # Not yet implementedCreate .vscode/launch.json:
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug ALICE",
"runtimeExecutable": "npm",
"runtimeArgs": ["run", "dev"],
"console": "integratedTerminal",
"skipFiles": ["<node_internals>/**"]
}
]
}Press F5 to debug.
// For debugging (remove before commit)
console.error('DEBUG:', someValue); // Use stderr to not interfere with ink# Check types
npx tsc --noEmit
# Watch mode
npx tsc --noEmit --watch- Indentation: 2 spaces
- Quotes: Single quotes for strings
- Semicolons: Yes
- Trailing commas: Yes
-
Files: camelCase (
myComponent.tsx) -
Components: PascalCase (
MyComponent) -
Functions: camelCase (
myFunction) -
Constants: UPPER_SNAKE_CASE (
MAX_RETRIES) -
Types: PascalCase (
MyType)
// Good: Explain WHY, not WHAT
// Use exponential backoff to avoid rate limiting
await sleep(retryCount * 1000);
// Bad: Obvious comment
// Add 1 to counter
counter += 1;# Fork on GitHub, then clone
git clone https://github.com/YOUR_USERNAME/Alice.git
cd Alice
git remote add upstream https://github.com/AndersHsueh/Alice.gitgit checkout -b feature/my-featureBranch naming:
-
feature/- New features -
fix/- Bug fixes -
docs/- Documentation -
refactor/- Code refactoring -
test/- Tests
- Follow code style
- Add TypeScript types
- Update documentation
- Test thoroughly
git add .
git commit -m "feat: add my feature"Commit message format:
-
feat:- New feature -
fix:- Bug fix -
docs:- Documentation -
refactor:- Code refactoring -
test:- Tests -
chore:- Maintenance
git push origin feature/my-featureThen open Pull Request on GitHub.
npm run build
npm run package # Not yet implementedWill create:
-
alice-win.exe(Windows) -
alice-macos(macOS) -
alice-linux(Linux)
npm publish # Publish to npm- TypeBox - JSON Schema
- Ajv - Validation
- simple-git - Git operations
- Architecture - Detailed architecture
- Tool System - Tool system internals
- FAQ - Common questions