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
19 changes: 14 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
<img src="public/logo.png" width="144" height="144" alt="Terax" />
<h1>Terax</h1>

<p><strong>Open-source lightweight cross-platform AI-native terminal (ADE)</strong></p>
<p><strong>Open-source lightweight cross-platform AI-native terminal (ADE)</strong></p>

<p>
<p>
<img src="https://img.shields.io/badge/version-0.6.4-blue" alt="version" />
<img src="https://img.shields.io/badge/license-Apache--2.0-green" alt="license" />
<img src="https://img.shields.io/badge/platform-macOS%20%7C%20Linux%20%7C%20Windows-lightgrey" alt="platform" />

</p>
</p>
</div>

---
Expand All @@ -31,25 +31,30 @@ Terax is a fast, lightweight AI terminal (ADE) built on Tauri 2 + Rust and React
## Features

**Terminal**

- xterm.js + WebGL renderer, multi-tab with background streaming
- Native PTY backend via `portable-pty` (zsh, bash, pwsh, …)
- Shell integration (cwd reporting, prompt markers) via injected init scripts
- Inline search, link detection, true-color

**Editor**
- CodeMirror 6 with language support for TS/JS, Rust, Python, HTML/CSS, JSON, Markdown

- CodeMirror 6 with language support for TS/JS, Rust, Python, HTML/CSS, JSON(JSONC), Markdown
- Inline AI autocomplete and AI edit diffs
- Vim mode
- Prebuilt themes: Tokyo Night, Nord, GitHub, Atom One, Aura, Copilot, Xcode

**File Explorer**

- Catppuccin icon theme (Material Icon Theme resolver)
- Fuzzy search, keyboard navigation, inline rename, context actions

**Web Preview**

- Auto-detects local dev servers and opens them in a preview tab

**AI (BYOK)**

- Providers: OpenAI, Anthropic, Google, Groq, xAI, Cerebras, OpenAI-compatible
- Local / offline models via LM Studio
- Voice input, edit diffs, multi-agent and sub-agents
Expand All @@ -58,8 +63,9 @@ Terax is a fast, lightweight AI terminal (ADE) built on Tauri 2 + Rust and React
- Tasks, plans, search, file read/write tools with approval flow

**Quality**

- Lightweight and fast (~7 MB bundle)
- API keys stored in the OS keychain
- API keys stored in the OS keychain
- No telemetry, no account required

## Windows notes
Expand All @@ -82,18 +88,21 @@ The default shell is detected in this order: `pwsh.exe` (PowerShell 7+) → `pow
## Build from source

**Prerequisites**

- Rust (stable) — https://rustup.rs
- Node 20+ and [pnpm](https://pnpm.io)
- Platform-specific Tauri prerequisites — https://tauri.app/start/prerequisites/

**Run**

```bash
pnpm install
pnpm tauri dev # development
pnpm tauri build # production bundle
```

**Checks**

```bash
pnpm exec tsc --noEmit # frontend type-check
cd src-tauri && cargo clippy # Rust lint
Expand Down
105 changes: 105 additions & 0 deletions src/modules/editor/lib/jsonc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import type { StreamParser, StringStream } from "@codemirror/language";

type JsoncState = {
inBlockComment: boolean;
};

function readString(stream: StringStream) {
let escaped = false;
stream.next();

while (!stream.eol()) {
const ch = stream.next();
if (escaped) {
escaped = false;
} else if (ch === "\\") {
escaped = true;
} else if (ch === '"') {
break;
}
}
}

function skipInlineWhitespaceAndComments(line: string, pos: number): number {
for (;;) {
while (pos < line.length && /\s/.test(line[pos]!)) pos += 1;

if (line.startsWith("//", pos)) return line.length;

if (line.startsWith("/*", pos)) {
const end = line.indexOf("*/", pos + 2);
if (end === -1) return line.length;
pos = end + 2;
continue;
}

return pos;
}
}

function isPropertyName(stream: StringStream): boolean {
const pos = skipInlineWhitespaceAndComments(stream.string, stream.pos);
return stream.string[pos] === ":";
}

export const jsonc: StreamParser<JsoncState> = {
name: "jsonc",

startState() {
return { inBlockComment: false };
},

token(stream, state) {
if (state.inBlockComment) {
if (stream.skipTo("*/")) {
stream.pos += 2;
state.inBlockComment = false;
} else {
stream.skipToEnd();
}
return "comment";
}

if (stream.eatSpace()) return null;

if (stream.match("//")) {
stream.skipToEnd();
return "comment";
}

if (stream.match("/*")) {
if (stream.skipTo("*/")) {
stream.pos += 2;
} else {
state.inBlockComment = true;
stream.skipToEnd();
}
return "comment";
}

const ch = stream.peek();

if (ch === '"') {
readString(stream);
return isPropertyName(stream) ? "propertyName" : "string";
}

if (stream.match(/^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?/)) {
return "number";
}

if (stream.match("true") || stream.match("false")) return "bool";
if (stream.match("null")) return "keyword";

if (stream.eat(/[{}\[\]:,]/)) return "punctuation";

stream.next();
return null;
},

languageData: {
closeBrackets: { brackets: ["[", "{", '"'] },
commentTokens: { line: "//", block: { open: "/*", close: "*/" } },
indentOnInput: /^\s*[\}\]]$/,
},
};
1 change: 1 addition & 0 deletions src/modules/editor/lib/languageResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const loaders: Record<string, LanguageLoader> = {
go: () => import("@codemirror/lang-go").then((m) => m.go()),
py: () => import("@codemirror/lang-python").then((m) => m.python()),
json: () => import("@codemirror/lang-json").then((m) => m.json()),
jsonc: () => import("./jsonc").then((m) => m.jsonc),

md: () => import("@codemirror/lang-markdown").then((m) => m.markdown()),
markdown: () => import("@codemirror/lang-markdown").then((m) => m.markdown()),
Expand Down
Loading