Skip to content

Development Guide

andershsueh edited this page Feb 10, 2026 · 2 revisions

Development Guide

📖 Languages: English only | 中文翻译征集中 | 日本語翻訳募集中

Guide for developers who want to contribute to ALICE or understand its internals.

Prerequisites

Required

  • Node.js ≥ 18.0.0
  • npm ≥ 9.0.0
  • Git
  • TypeScript knowledge

Recommended

  • VS Code (with TypeScript/ESLint extensions)
  • Windows Terminal (Windows) or iTerm2 (macOS)
  • LM Studio for testing

Getting Started

1. Clone Repository

git clone https://github.com/AndersHsueh/Alice.git
cd Alice

2. Install Dependencies

npm install

This installs:

  • TypeScript compiler
  • ink (React for CLI)
  • Tool dependencies (ajv, glob, simple-git)
  • Development tools

3. Build Project

npm run build

Output goes to dist/ directory.

4. Run Development Version

npm run dev

⚠️ Important: Use npm run dev, NOT npm run dev:watch when testing interactive features (keyboard input).


Project Structure

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

Development Workflow

Standard Workflow

# 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-feature

Watch Mode (Limited)

npm run dev:watch

⚠️ Limitation: Watch mode intercepts stdin, so you can't type in ALICE. Only useful for:

  • Checking compilation errors
  • Viewing render output
  • Testing non-interactive components

For interactive testing, always use npm run dev.


Architecture

Layer Overview

┌─────────────────────────────────────┐
│         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                    │
└─────────────────────────────────────┘

Data Flow

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

TypeScript Configuration

ESM Modules

ALICE uses ESM (not CommonJS):

// package.json
{
  "type": "module"
}

Important rules:

  • All imports must include .js extension (even for .ts files)
  • No require(), use import
  • No __dirname, use import.meta.url

Example:

// ❌ Wrong
import { foo } from './utils';

// ✅ Correct
import { foo } from './utils.js';  // .js even for .ts files!

tsconfig.json

{
  "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"]
}

Creating Components

React Component (ink)

// 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>
  );
};

Using Hooks

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>;
};

Creating Tools

Tool Interface

// 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>;
}

Example Tool

// 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)
      };
    }
  }
};

Registering Tool

// src/cli/app.tsx
import { myTool } from '../tools/builtin/myTool.js';

// In component
useEffect(() => {
  toolRegistry.register(myTool);
}, []);

Testing

Manual Testing

# 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"

Test Scripts

// 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.ts

Unit Tests (Future)

npm test  # Not yet implemented

Debugging

VS Code Launch Configuration

Create .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.

Console Logging

// For debugging (remove before commit)
console.error('DEBUG:', someValue);  // Use stderr to not interfere with ink

TypeScript Errors

# Check types
npx tsc --noEmit

# Watch mode
npx tsc --noEmit --watch

Code Style

Formatting

  • Indentation: 2 spaces
  • Quotes: Single quotes for strings
  • Semicolons: Yes
  • Trailing commas: Yes

Naming Conventions

  • Files: camelCase (myComponent.tsx)
  • Components: PascalCase (MyComponent)
  • Functions: camelCase (myFunction)
  • Constants: UPPER_SNAKE_CASE (MAX_RETRIES)
  • Types: PascalCase (MyType)

Comments

// 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;

Contributing

1. Fork Repository

# 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.git

2. Create Branch

git checkout -b feature/my-feature

Branch naming:

  • feature/ - New features
  • fix/ - Bug fixes
  • docs/ - Documentation
  • refactor/ - Code refactoring
  • test/ - Tests

3. Make Changes

  • Follow code style
  • Add TypeScript types
  • Update documentation
  • Test thoroughly

4. Commit

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

5. Push and PR

git push origin feature/my-feature

Then open Pull Request on GitHub.


Building for Production

Build Binary (Future)

npm run build
npm run package  # Not yet implemented

Will create:

  • alice-win.exe (Windows)
  • alice-macos (macOS)
  • alice-linux (Linux)

Distribution (Future)

npm publish  # Publish to npm

Resources

Documentation

Tools

Community


See Also

Clone this wiki locally