Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,19 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Project Overview

This is a JupyterLab extension package that provides default chat commands for Jupyter AI. It consists of:

- A Python server extension (`jupyter_ai_chat_commands`)
- A TypeScript frontend extension (`@jupyter-ai/chat-commands`)

The extension currently provides two chat commands:

- `@file:<path>`: Add a file as an attachment to a message
- `/refresh-personas`: Reload local personas defined in `.jupyter/personas`

## Development Setup

Initial setup requires micromamba/conda and Node.js 22:

```bash
micromamba install uv jupyterlab nodejs=22
jlpm
Expand All @@ -26,51 +29,60 @@ The `dev:install` script handles the complete development setup including buildi
## Common Commands

### Building

- `jlpm build` - Build both TypeScript and labextension for development
- `jlpm build:prod` - Production build with optimization
- `jlpm build:lib` - Build TypeScript source with source maps
- `jlpm build:labextension` - Build the JupyterLab extension

### Development Workflow

- `jlpm watch` - Watch mode for development (runs both TypeScript and labextension watch)
- `jupyter lab` - Start JupyterLab (run in separate terminal alongside watch)

### Linting and Testing

- `jlpm lint` - Run all linters (stylelint, prettier, eslint)
- `jlpm lint:check` - Check without fixing
- `jlpm test` - Run Jest tests with coverage
- `pytest -vv -r ap --cov jupyter_ai_chat_commands` - Run Python server tests

### Extension Management

- `jlpm dev:uninstall` - Remove development extension
- `jupyter server extension list` - Check server extension status
- `jupyter labextension list` - Check frontend extension status

## Architecture

### Frontend (TypeScript)

- Entry point: `src/index.ts` - exports main plugin and chat command plugins
- Chat commands: `src/chat-command-plugins/` contains individual command implementations
- Uses JupyterLab 4.x plugin system and `@jupyter/chat` for chat integration

### Backend (Python)

- Server extension: `jupyter_ai_chat_commands/extension_app.py`
- Request handlers: `jupyter_ai_chat_commands/handlers.py`
- Depends on `jupyterlab_chat`, `jupyter_ai_router`, and `jupyter_ai_persona_manager`

### Key Dependencies

- Frontend: `@jupyter/chat`, `@jupyterlab/application`, Material-UI icons
- Backend: `jupyter_server`, `jupyterlab_chat`, `jupyter_ai_router`

## Code Style

- TypeScript: ESLint with TypeScript rules, Prettier formatting, single quotes
- Interface naming: Must start with 'I' and use PascalCase
- CSS: Stylelint with standard config
- Python: No specific linter configured (follows standard Python conventions)

## Testing

- Frontend: Jest with coverage reporting
- Backend: Pytest with pytest-asyncio for server testing
- Integration: Playwright tests in `ui-tests/` (currently skipped)

Note: Integration tests are currently disabled - see recent commit "skip integration tests for now".
Note: Integration tests are currently disabled - see recent commit "skip integration tests for now".
16 changes: 3 additions & 13 deletions jupyter_ai_chat_commands/extension_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,21 +40,11 @@ async def initialize_async(self):
router.observe_chat_init(lambda room_id, ychat: self.on_chat_init(router, room_id))

def on_chat_init(self, router: MessageRouter, room_id: str):
router.observe_slash_cmd_msg(room_id, self.on_slash_command)
router.observe_slash_cmd_msg(room_id, "refresh-personas", self.on_slash_command)
self.log.info("Attached router observer.")

def on_slash_command(self, room_id: str, message: Message):
first_word = get_first_word(message.body)
assert first_word and first_word.startswith("/")

command_id = first_word[1:]
if command_id == "refresh-personas":
self.event_loop.create_task(self.handle_refresh_personas(room_id))
return True

# If command is unrecognized, log an error
self.log.warning(f"Unrecognized slash command: '/{command_id}'")
return False
def on_slash_command(self, room_id: str, command: str, message: Message):
self.event_loop.create_task(self.handle_refresh_personas(room_id))

async def handle_refresh_personas(self, room_id: str):
self.log.info(f"Received /refresh-personas in room '{room_id}'.")
Expand Down