Skip to content
Merged
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
5 changes: 2 additions & 3 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
target
**/target
crates/tck-ui/dist
node_modules
**/node_modules
dist
release
.git
Expand Down
6 changes: 3 additions & 3 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@

## Checklist

- [ ] `cargo clippy -p tck-core -p tck-server -- -D warnings` passes
- [ ] `trunk build --release` (in `crates/tck-ui`) passes
- [ ] `cargo build -p tck-server --release` passes
- [ ] `npm run typecheck` passes
- [ ] `npm run build` passes
- [ ] `npm run typecheck` in `tck-ai` passes (if that service changed)
- [ ] I matched the existing code style

## Notes
Expand Down
38 changes: 13 additions & 25 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,36 +16,24 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-unknown-unknown
components: clippy

- uses: Swatinem/rust-cache@v2

- name: Install Trunk
uses: jetli/trunk-action@v0.5.0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
version: latest
node-version: "22"

- name: Check workspace (native crates)
run: cargo clippy -p tck-core -p tck-server -- -D warnings
- name: Install dependencies
run: npm install

- name: Build WASM UI
working-directory: crates/tck-ui
run: trunk build --release
- name: TypeScript check
run: npm run typecheck

- name: Build native server
run: cargo build -p tck-server --release
- name: Build UI
run: npm run build

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "22"
- name: Install tck-ai dependencies
working-directory: tck-ai
run: npm install

- name: TypeScript check tck-ai
working-directory: tck-ai
run: |
npm install
npm run typecheck
run: npm run typecheck
3 changes: 0 additions & 3 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,6 @@ jobs:
# JS/TS are analyzed together as one CodeQL language.
- language: javascript-typescript
build-mode: none
# Rust only supports build-mode: none (no autobuild/manual mode).
- language: rust
build-mode: none

steps:
- name: Checkout repository
Expand Down
33 changes: 8 additions & 25 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
name: Deploy to GitHub Pages

# Publishes the WebAssembly UI to GitHub Pages on every push to main.
# The deployed site is static, so the terminal (which needs the native
# tck-server) is unavailable there; AI calls go directly to the provider.
# Publishes the static UI to GitHub Pages on every push to main.
# The deployed site has no server, so terminals are unavailable there;
# AI calls have to go directly to the provider from the browser.
on:
push:
branches: [main]
Expand Down Expand Up @@ -30,39 +30,22 @@ jobs:
with:
enablement: true

- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-unknown-unknown

- uses: Swatinem/rust-cache@v2

- name: Install Trunk
uses: jetli/trunk-action@v0.5.0
with:
version: latest

- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: "22"

- name: Build tck-ai browser bundle
working-directory: tck-ai
run: |
npm ci
npm run build:browser
cp dist/tck-ai.js ../crates/tck-ui/assets/
- name: Install dependencies
run: npm install

# Pages serves the repo at /<repo>/, so assets must be referenced there.
- name: Build WASM UI
working-directory: crates/tck-ui
run: trunk build --release --public-url /keyboard/
- name: Build UI
run: npm run build -- --base=/keyboard/

- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: crates/tck-ui/dist
path: dist

deploy:
needs: build
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,6 @@ Thumbs.db
Landing page and design system.zip
landing/
.thumbnail

# Node
node_modules/
33 changes: 17 additions & 16 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,39 +1,40 @@
# T.C.K (TalentCloud Keyboard) — production image.
# Stage 1 builds the WASM UI (Trunk) and the native server (cargo); stage 2 is a
# Stage 1 installs dependencies and builds the static UI (Vite); stage 2 is a
# slim runtime that serves the bundle and runs the terminal/AI/GitHub backend.

# ---------- build ----------
FROM rust:1-bookworm AS build
FROM node:22-bookworm-slim AS build
WORKDIR /app

# WASM target + Trunk (prebuilt binary; Trunk fetches wasm-bindgen/wasm-opt itself).
ARG TRUNK_VERSION=v0.21.14
RUN rustup target add wasm32-unknown-unknown \
&& curl -sSL "https://github.com/trunk-rs/trunk/releases/download/${TRUNK_VERSION}/trunk-x86_64-unknown-linux-gnu.tar.gz" \
| tar -xz -C /usr/local/bin
# node-pty is a native addon, so the build stage needs a toolchain.
RUN apt-get update \
&& apt-get install -y --no-install-recommends python3 make g++ \
&& rm -rf /var/lib/apt/lists/*

COPY . .
COPY package.json package-lock.json ./
RUN npm ci

# Build the WebAssembly UI, then the server binary.
RUN cd crates/tck-ui && trunk build --release
RUN cargo build -p tck-server --release
COPY . .
RUN npm run build

# ---------- runtime ----------
FROM debian:bookworm-slim
FROM node:22-bookworm-slim
# ca-certificates: outbound HTTPS to GitHub/Anthropic/providers. bash: the PTY shell.
RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates bash git curl \
&& rm -rf /var/lib/apt/lists/* \
&& useradd -m -u 10001 tck

WORKDIR /app
COPY --from=build /app/target/release/tck-server /usr/local/bin/tck-server
COPY --from=build /app/crates/tck-ui/dist /app/dist
COPY --from=build /app/node_modules /app/node_modules
COPY --from=build /app/package.json /app/package.json
COPY --from=build /app/dist /app/dist
COPY --from=build /app/src /app/src

ENV TCK_DIST=/app/dist \
TCK_ADDR=0.0.0.0:3000
PORT=3000

# Run as an unprivileged user — the terminal grants a shell, so limit its reach.
USER tck
EXPOSE 3000
CMD ["tck-server"]
CMD ["npm", "run", "start"]
67 changes: 34 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
# ⌨️ T.C.K — TalentCloud Keyboard

[![CI](https://github.com/Raghuramcoding/keyboard/actions/workflows/ci.yml/badge.svg)](https://github.com/Raghuramcoding/keyboard/actions/workflows/ci.yml)
[![Rust](https://img.shields.io/badge/built%20with-Rust-orange?logo=rust)](https://www.rust-lang.org)
[![TypeScript](https://img.shields.io/badge/built%20with-TypeScript-3178c6?logo=typescript)](https://www.typescriptlang.org)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)

**T.C.K (TalentCloud Keyboard)** is an AI-powered coding environment that runs
**in your browser** (or as a native desktop app). The UI compiles to WebAssembly
(Dioxus); a native server provides real shell terminals, and a TypeScript AI
service (`tck-ai`) uses the Vercel AI SDK for unified provider access.
**in your browser**. The UI is a TypeScript SPA built with Vite; a Node server
(Hono) provides real shell terminals over WebSocket, proxies AI requests, and
talks to GitHub. A standalone AI service (`tck-ai`) is also included for
deploying provider access separately.

## Features

Expand All @@ -17,15 +18,13 @@ service (`tck-ai`) uses the Vercel AI SDK for unified provider access.
own PTY session that keeps running even if you close the tab.
- 💬 **AI chat instances** — create standalone AI chat sessions alongside your
terminal tools. Chat histories persist across page reloads via `localStorage`.
- 🦀 **Rust core** — `tck-ui` (Dioxus/WASM frontend), `tck-server` (axum
backend), `tck-core` (shared types). AI service (`tck-ai`) uses **TypeScript +
Vercel AI SDK**.
- 🌐 **Browser or desktop** — run as a web app in any browser, or install as a
native desktop app (WebView2 on Windows, WebKit on macOS/Linux).
- 🧩 **TypeScript everywhere** — `src/ui` (Vite SPA), `src/server` (Hono + node-pty),
`src/core` (shared types). AI access uses the **Vercel AI SDK**.
- 🌐 **Runs in any browser** — served by the local Node server.
- 📱 **Responsive** — a 3-column desktop layout that collapses to a touch-friendly
tabbed layout on phones.
- 💻 **Real terminals** — each tool instance gets its own genuine shell session
via a pseudo-terminal (`portable-pty`): PowerShell on Windows, `$SHELL`
via a pseudo-terminal (`node-pty`): PowerShell on Windows, `$SHELL`
(zsh/bash) on macOS & Linux, streamed to the browser over WebSocket with
reconnection support (64 KB ring buffer per session).
- 🤖 **Multi-provider AI** — Ollama, Claude, and any OpenAI-compatible endpoint
Expand Down Expand Up @@ -71,33 +70,32 @@ service (`tck-ai`) uses the Vercel AI SDK for unified provider access.
└──────────────────────────────────────────────────────┘
```

| Crate | Target | Responsibility |
|-----------------|--------------------------|--------------------------------------|
| `tck-core` | both | Shared serde types, provider presets |
| `tck-ui` | `wasm32-unknown-unknown` | Dioxus UI compiled to WebAssembly |
| `tck-server` | native | axum server: AI proxy + PTY + static |
| `tck-desktop` | native + webview | Desktop app (embeds server + WASM) |
| Module | Target | Responsibility |
|-----------------|---------|---------------------------------------------|
| `src/core` | both | Shared types (instances, settings, AI) |
| `src/ui` | browser | Vite SPA: landing page, dashboard, chat |
| `src/server` | node | Hono server: AI proxy + PTY + static assets |
| `tck-ai` | node | Standalone AI service (Vercel AI SDK) |

## Quick start

### Prerequisites

- [Rust](https://rustup.rs) (stable)
- WASM target: `rustup target add wasm32-unknown-unknown`
- [Trunk](https://trunkrs.dev): `cargo install trunk`
- [Node.js](https://nodejs.org) 22 or newer

### Build & run

```bash
# 1. Build the WebAssembly UI
cd crates/tck-ui
trunk build --release
cd ../..
# 1. Install dependencies
npm install

# 2. Run the server (serves the UI + provides terminals/AI)
cargo run -p tck-server --release
# 2. Build the UI into dist/
npm run build

# 3. Open the app
# 3. Run the server (serves dist/ + provides terminals/AI)
npm start

# 4. Open the app
# http://127.0.0.1:3000
```

Expand All @@ -119,14 +117,17 @@ Once the server is running, open the app and you'll see the **Dashboard** view:

```bash
# Terminal 1 — the backend
cargo run -p tck-server
npm run dev:server

# Terminal 2 — the UI with hot reload (proxies /api and /ws to :3000)
cd crates/tck-ui
trunk serve
npm run dev
# open http://127.0.0.1:8080
```

> **Note:** the desktop app, Windows installer, and `scripts/build.ps1` below
> are from the earlier Rust/WASM implementation and are not buildable against
> the current TypeScript sources.

## Desktop App

T.C.K can run as a native desktop application with no browser chrome.
Expand Down Expand Up @@ -186,8 +187,8 @@ your browser. See [installer/README.md](installer/README.md) for details.

| Variable | Default | Meaning |
|----------------------------|----------------------|------------------------------------------------|
| `TCK_ADDR` | `127.0.0.1:3000` | Address the server binds to |
| `TCK_DIST` | `crates/tck-ui/dist` | Directory of the built WASM bundle |
| `PORT` | `3000` | Port the server binds to |
| `TCK_DIST` | `dist` | Directory of the built UI bundle |
| `TCK_GITHUB_CLIENT_ID` | — | GitHub OAuth App client id (enables Git) |
| `TCK_GITHUB_CLIENT_SECRET` | — | GitHub OAuth App client secret |
| `TCK_GITHUB_CALLBACK` | derived from Host | Override the OAuth callback URL (non-localhost)|
Expand Down Expand Up @@ -236,8 +237,8 @@ phone can't run the native server itself, point it at a T.C.K server on your
**same Wi-Fi**:

```bash
# Bind to all interfaces so the phone can reach you
TCK_ADDR=0.0.0.0:3000 cargo run -p tck-server --release
# The server listens on all interfaces, so the phone can reach it
npm start
```

Then on the phone open `http://<your-computer-LAN-IP>:3000` (e.g.
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ services:
build: .
restart: unless-stopped
environment:
TCK_ADDR: "0.0.0.0:3000"
PORT: "3000"
TCK_DIST: "/app/dist"
# env_file passes values to the container literally (no interpolation), so
# secrets and the bcrypt hash with '$' survive intact. Provides TCK_GITHUB_*.
Expand Down
12 changes: 12 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
--fg: #e8eaf0;
--mut: #888fa3;
--acc: #5b8def;
--acc-solid: #5b8def;
--acc-shadow: rgba(91,141,239,.4);
--font-display: 'Space Grotesk', sans-serif;
--font-body: 'Hanken Grotesk', sans-serif;
Expand All @@ -33,6 +34,17 @@
--radius-lg: 24px;
}

:root[data-theme="light"] {
--bg: #f4f5f8;
--surface: #ffffff;
--surface2: #eceef3;
--raised: #e6e9f0;
--raisedh: #dbdfe9;
--bd: #d2d7e2;
--fg: #1a1d26;
--mut: #5c6376;
}

*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; height: 100%; overflow-x: clip; }
body {
Expand Down
Loading
Loading