Compare commits

...

7 Commits

Author SHA1 Message Date
Yeachan-Heo
b9e00f87d5 feat(cli): add interactive terminal rendering scaffold
Add a clap-driven Rust CLI entrypoint with interactive and prompt modes, terminal markdown rendering, syntax-highlighted streamed output, and raw-mode input handling for the CLI workstream. The slice stays self-contained in rusty-claude-cli while preserving the existing manifest/bootstrap utilities and making workspace clippy verification pass again.\n\nConstraint: This parallel worktree owns only the CLI rendering and terminal UI slice\nConstraint: .omx artifacts must stay uncommitted\nRejected: Pull in a full TUI framework such as ratatui | unnecessary scope for the requested slice\nConfidence: medium\nScope-risk: moderate\nReversibility: clean\nDirective: Replace the demo response path with real runtime integration before treating this as production chat UX\nTested: cargo fmt; cargo test --workspace; cargo clippy --workspace --all-targets -- -D warnings; cargo run -p rusty-claude-cli -- --help; cargo run -p rusty-claude-cli -- --output-format text prompt 'stream me some markdown'\nNot-tested: Interactive raw-mode key handling in a real terminal/TTY session
2026-03-31 15:41:14 +00:00
Yeachan-Heo
d621f5d5d8 Establish a harness-first Rust port foundation
This creates a new rust/ workspace for the compatibility-first port effort, adds a README that explains the milestone and verification commands, links the new Rust docs from the repository root, and seeds a minimal CLI/runtime/registry/harness skeleton that can read the upstream TypeScript sources.

Constraint: Initial delivery had to stay inside rust/ except for the root README pointer
Constraint: The workspace started empty, so the first milestone needed proof-oriented scaffolding before broader porting
Rejected: Wait for tmux worker integration output | local verified implementation finished first and worker merges conflicted with local files
Rejected: Start with a feature-complete CLI rewrite | too risky without manifest and bootstrap harness scaffolding
Confidence: medium
Scope-risk: moderate
Reversibility: clean
Directive: Grow compatibility from extracted manifests and tests before claiming drop-in parity
Tested: cargo fmt --all; cargo check --workspace; cargo test --workspace; cargo run -p rusty-claude-cli -- --help; cargo run -p rusty-claude-cli -- dump-manifests; cargo run -p rusty-claude-cli -- bootstrap-plan
Not-tested: Real command execution parity; state/config fixture round-trips; bridge/MCP/plugin protocol replay
Related: /home/bellman/Workspace/rusty-claude-code/.omx/plans/prd-rust-port-claude-code-dropin-cli.md
Related: /home/bellman/Workspace/rusty-claude-code/.omx/plans/test-spec-rust-port-claude-code-dropin-cli.md
2026-03-31 15:11:08 +00:00
sigridjineth
c941e95fc7 docs: emphasize leaked source and include original tweet 2026-03-31 15:11:08 +00:00
sigridjineth
c9f7b96e7d docs: rewrite README in English 2026-03-31 15:11:08 +00:00
sigridjineth
66d9c1e420 docs: add comprehensive README with architecture analysis 2026-03-31 15:11:08 +00:00
sigridjineth
caad05016e init: add source code from src.zip 2026-03-31 15:10:58 +00:00
instructkr
2394140007 Rewriting Project Claw Code - Python port with Rust on the way 2026-03-31 08:04:29 -07:00
2025 changed files with 517590 additions and 105 deletions

1
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1 @@
github: instructkr

View File

@@ -0,0 +1,9 @@
{
"session_id": "00083e63395f4f3bb24fd6e7ed26439d",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,8 @@
{
"session_id": "053db140f7694d1abfb52c96e62fdede",
"messages": [
"review MCP tool"
],
"input_tokens": 3,
"output_tokens": 13
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "05a9bc9a33c24f80b7e6540d4306d59d",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "08dcdbf3d76b4ed7898c27c1a45dde73",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "0ca1cdd3176041a1916729d76effe10d",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "11fb4a2aa69b460ba53031ea6643969f",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "156dd69bbb3142e687d1a56cb97633da",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "1a9711e7305f42a8bc30d8ade03a221d",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "1b2fe9ba9b804b2896e33ddc7c7d91ae",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,8 @@
{
"session_id": "1d07428c6df44c859e6056ef13aa422d",
"messages": [
"review MCP tool"
],
"input_tokens": 3,
"output_tokens": 13
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "1e068ad0e9234d8b9e85735556ad436c",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "2763324a1d7e4832a12f14e7108ce552",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,8 @@
{
"session_id": "27d5203bdda3480681732c1f94291599",
"messages": [
"review MCP tool"
],
"input_tokens": 3,
"output_tokens": 13
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "28510de9575e40919ddb607d82e95cc1",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "2e574cba82bb43eda5aafe7ae364210b",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "317de978222e4e6c93f7e5430d44d130",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "338275156d1d405f8c627be621b3191e",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "3b93d8e47dff4a0d90782d19e33a81b6",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "3c60859387044f7b8111557a4b252745",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "439e9f7d2bfa41cbbfed6edf44074311",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "4c75b6e3a46b4f599e32eaf147d92c4d",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "4e4aae9acdf945839798f37e01c04f1b",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "5230052e7d2749be9cf318de519d9c42",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "575c96ef2c1f45cf9deb85b06c552bde",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "577b74a297fd4fed956a78b5debd8025",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "59d15a4390b648b9b19c6ebae1c5d2d8",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "5cb2ee43a1c547f79ffbc74cb311247c",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,8 @@
{
"session_id": "609a20c55c6d47c282ddc00d0c3b6dfb",
"messages": [
"review MCP tool"
],
"input_tokens": 3,
"output_tokens": 13
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "663f870db32944b3a0d2b863c19a5708",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "6d0759c31bd942b6b86f3969ba8bbf28",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "6dd0b48373c64449b756fd53425b4031",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "6f8b4a9d5a0e47f79bd732f61a6eb543",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "6f9f4e28691f46dab19d99d78127c94e",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,8 @@
{
"session_id": "72b9dcbd91054fd78d7cfdcf09de3c0b",
"messages": [
"review MCP tool"
],
"input_tokens": 3,
"output_tokens": 13
}

View File

@@ -0,0 +1,8 @@
{
"session_id": "76411759fe1e42e5bc5ac4279a68f700",
"messages": [
"review MCP tool"
],
"input_tokens": 3,
"output_tokens": 13
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "776aeb73811d4868ad20b6562eba6502",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,8 @@
{
"session_id": "7afd286cb9d14d62b56ba7ff86bcc08b",
"messages": [
"review MCP tool"
],
"input_tokens": 3,
"output_tokens": 13
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "7e96b6c76ef64983a4ba3be162bd7cdd",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "800a5672a570499883260b597ed174bd",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "834e4208013d4505a6957901a0ff151c",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "884dc57a7bba40d59a1bcc1dd80cb28b",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "8d517af648b840928016d4cc613f1133",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "8d84e1795c9d414190bc1a9c719bc63e",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "90d94293814443edb376ba3b68568c46",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "9155bcf5da0348dbac0c2374ef60c9e5",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,8 @@
{
"session_id": "91d1f071ab8148b28b7e29513cc1804f",
"messages": [
"review MCP tool"
],
"input_tokens": 3,
"output_tokens": 13
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "933e1cd8e24042d1968959aa2c67f3d6",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "94d871d03ed6410a81a8339ecba4f3dd",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "994767f1199c44d8b2052e758b4d4d3c",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "9bd50e453f24444d970dedddf3c85027",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "9d8eecbecd7d452697d2e8b5be415bd4",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "9f87d3fe67bb4aebbe0effd034382685",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "a0e9fbca75ab44e7b89022156f18f60a",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "a1d0491f8c92424fb2a2c3e2333f39d0",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "a277471dc4cf4d7f86ff065fd77ccb88",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "a3b47cec34694898919020369ab6af13",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "a6e9e4b6daac4f5a82e366cb12313eee",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "ad7f38d318df458bb1f0f0492fa71f02",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "b3b5eb702163461bb0f0dd43a82528d8",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "b4829661195449c1af0c37e8c87ce0a5",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "b4f0e18929d54d948ad29bd1dd43a85d",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "bd2f9891b77d4ab29b022f6d830c6234",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "cbe249de5950459da16440988e2ddc54",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,8 @@
{
"session_id": "db0aea45e8984d57898e91803bdcdc7c",
"messages": [
"review MCP tool"
],
"input_tokens": 3,
"output_tokens": 13
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "dbd0d1c8f734444a8ed02f77f3d9fc1a",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "e8fff9feb2ae4d8ca94575ed8fa5a03f",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "e9433328bba545d8ac94e2bbac488aec",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "f074c37ae3b84a959da394aec7ec3c68",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "f2efb0de41f949859b57854e58efad70",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,8 @@
{
"session_id": "f48004afd90f41d081a6129e96ad7651",
"messages": [
"review MCP tool"
],
"input_tokens": 3,
"output_tokens": 13
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "f5def6f008f64a918bb0f5b0d6ec3db6",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "f5efe1bbcb124e3191e4417becbe1f81",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

View File

@@ -0,0 +1,8 @@
{
"session_id": "f689195c4cf94eb998a461da808075a3",
"messages": [
"review MCP tool"
],
"input_tokens": 3,
"output_tokens": 13
}

View File

@@ -0,0 +1,8 @@
{
"session_id": "f77644ede86245eba5198322de05e4a4",
"messages": [
"review MCP tool"
],
"input_tokens": 3,
"output_tokens": 13
}

View File

@@ -0,0 +1,9 @@
{
"session_id": "ffbf6be4ed074ead98bcf18e7710e6a1",
"messages": [
"review MCP tool",
"review MCP tool"
],
"input_tokens": 6,
"output_tokens": 32
}

316
README.md
View File

@@ -1,123 +1,261 @@
# Claude Code Python Porting Workspace
# Claude Code — Leaked Source (2026-03-31)
> The primary `src/` tree in this repository is now dedicated to **Python porting work**. The March 31, 2026 Claude Code source exposure is part of the project's background, but the tracked repository is now centered on Python source rather than the exposed TypeScript snapshot.
> **On March 31, 2026, the full source code of Anthropic's Claude Code CLI was leaked** via a `.map` file exposed in their npm registry.
---
## Porting Status
## How It Leaked
The main source tree is now Python-first.
[Chaofan Shou (@Fried_rice)](https://x.com/Fried_rice) discovered the leak and posted it publicly:
- `src/` contains the active Python porting workspace
- `tests/` verifies the current Python workspace
- the exposed snapshot is no longer part of the tracked repository state
> **"Claude code source code has been leaked via a map file in their npm registry!"**
>
> — [@Fried_rice, March 31, 2026](https://x.com/Fried_rice/status/2038894956459290963)
The current Python workspace is not yet a complete one-to-one replacement for the original system, but the primary implementation surface is now Python.
The source map file in the published npm package contained a reference to the full, unobfuscated TypeScript source, which was downloadable as a zip archive from Anthropic's R2 storage bucket.
## Why this rewrite exists
---
I originally studied the exposed codebase to understand its harness, tool wiring, and agent workflow. After spending more time with the legal and ethical questions—and after reading the essay linked below—I did not want the exposed snapshot itself to remain the main tracked source tree.
## Overview
This repository now focuses on Python porting work instead.
Claude Code is Anthropic's official CLI tool that lets you interact with Claude directly from the terminal to perform software engineering tasks — editing files, running commands, searching codebases, managing git workflows, and more.
## Repository Layout
This repository contains the leaked `src/` directory.
```text
.
├── src/ # Python porting workspace
│ ├── __init__.py
│ ├── commands.py
│ ├── main.py
│ ├── models.py
│ ├── port_manifest.py
│ ├── query_engine.py
│ ├── task.py
│ └── tools.py
├── tests/ # Python verification
├── assets/omx/ # OmX workflow screenshots
├── 2026-03-09-is-legal-the-same-as-legitimate-ai-reimplementation-and-the-erosion-of-copyleft.md
└── README.md
- **Leaked on**: 2026-03-31
- **Language**: TypeScript
- **Runtime**: Bun
- **Terminal UI**: React + [Ink](https://github.com/vadimdemedes/ink) (React for CLI)
- **Scale**: ~1,900 files, 512,000+ lines of code
---
## Rust port foundation
A compatibility-first Rust port workspace now lives in [`rust/`](rust/). Start with [`rust/README.md`](rust/README.md) for the current milestone scope, workspace layout, and verification commands.
## Directory Structure
```
src/
├── main.tsx # Entrypoint (Commander.js-based CLI parser)
├── commands.ts # Command registry
├── tools.ts # Tool registry
├── Tool.ts # Tool type definitions
├── QueryEngine.ts # LLM query engine (core Anthropic API caller)
├── context.ts # System/user context collection
├── cost-tracker.ts # Token cost tracking
├── commands/ # Slash command implementations (~50)
├── tools/ # Agent tool implementations (~40)
├── components/ # Ink UI components (~140)
├── hooks/ # React hooks
├── services/ # External service integrations
├── screens/ # Full-screen UIs (Doctor, REPL, Resume)
├── types/ # TypeScript type definitions
├── utils/ # Utility functions
├── bridge/ # IDE integration bridge (VS Code, JetBrains)
├── coordinator/ # Multi-agent coordinator
├── plugins/ # Plugin system
├── skills/ # Skill system
├── keybindings/ # Keybinding configuration
├── vim/ # Vim mode
├── voice/ # Voice input
├── remote/ # Remote sessions
├── server/ # Server mode
├── memdir/ # Memory directory (persistent memory)
├── tasks/ # Task management
├── state/ # State management
├── migrations/ # Config migrations
├── schemas/ # Config schemas (Zod)
├── entrypoints/ # Initialization logic
├── ink/ # Ink renderer wrapper
├── buddy/ # Companion sprite (Easter egg)
├── native-ts/ # Native TypeScript utils
├── outputStyles/ # Output styling
├── query/ # Query pipeline
└── upstreamproxy/ # Proxy configuration
```
## Python Workspace Overview
---
The new Python `src/` tree currently provides:
## Core Architecture
- **`port_manifest.py`** — summarizes the current Python workspace structure
- **`models.py`** — dataclasses for subsystems, modules, and backlog state
- **`commands.py`** — Python-side command port metadata
- **`tools.py`** — Python-side tool port metadata
- **`query_engine.py`** — renders a Python porting summary from the active workspace
- **`main.py`** — a CLI entrypoint for manifest and summary output
### 1. Tool System (`src/tools/`)
## Quickstart
Every tool Claude Code can invoke is implemented as a self-contained module. Each tool defines its input schema, permission model, and execution logic.
Render the Python porting summary:
| Tool | Description |
|---|---|
| `BashTool` | Shell command execution |
| `FileReadTool` | File reading (images, PDFs, notebooks) |
| `FileWriteTool` | File creation / overwrite |
| `FileEditTool` | Partial file modification (string replacement) |
| `GlobTool` | File pattern matching search |
| `GrepTool` | ripgrep-based content search |
| `WebFetchTool` | Fetch URL content |
| `WebSearchTool` | Web search |
| `AgentTool` | Sub-agent spawning |
| `SkillTool` | Skill execution |
| `MCPTool` | MCP server tool invocation |
| `LSPTool` | Language Server Protocol integration |
| `NotebookEditTool` | Jupyter notebook editing |
| `TaskCreateTool` / `TaskUpdateTool` | Task creation and management |
| `SendMessageTool` | Inter-agent messaging |
| `TeamCreateTool` / `TeamDeleteTool` | Team agent management |
| `EnterPlanModeTool` / `ExitPlanModeTool` | Plan mode toggle |
| `EnterWorktreeTool` / `ExitWorktreeTool` | Git worktree isolation |
| `ToolSearchTool` | Deferred tool discovery |
| `CronCreateTool` | Scheduled trigger creation |
| `RemoteTriggerTool` | Remote trigger |
| `SleepTool` | Proactive mode wait |
| `SyntheticOutputTool` | Structured output generation |
```bash
python3 -m src.main summary
### 2. Command System (`src/commands/`)
User-facing slash commands invoked with `/` prefix.
| Command | Description |
|---|---|
| `/commit` | Create a git commit |
| `/review` | Code review |
| `/compact` | Context compression |
| `/mcp` | MCP server management |
| `/config` | Settings management |
| `/doctor` | Environment diagnostics |
| `/login` / `/logout` | Authentication |
| `/memory` | Persistent memory management |
| `/skills` | Skill management |
| `/tasks` | Task management |
| `/vim` | Vim mode toggle |
| `/diff` | View changes |
| `/cost` | Check usage cost |
| `/theme` | Change theme |
| `/context` | Context visualization |
| `/pr_comments` | View PR comments |
| `/resume` | Restore previous session |
| `/share` | Share session |
| `/desktop` | Desktop app handoff |
| `/mobile` | Mobile app handoff |
### 3. Service Layer (`src/services/`)
| Service | Description |
|---|---|
| `api/` | Anthropic API client, file API, bootstrap |
| `mcp/` | Model Context Protocol server connection and management |
| `oauth/` | OAuth 2.0 authentication flow |
| `lsp/` | Language Server Protocol manager |
| `analytics/` | GrowthBook-based feature flags and analytics |
| `plugins/` | Plugin loader |
| `compact/` | Conversation context compression |
| `policyLimits/` | Organization policy limits |
| `remoteManagedSettings/` | Remote managed settings |
| `extractMemories/` | Automatic memory extraction |
| `tokenEstimation.ts` | Token count estimation |
| `teamMemorySync/` | Team memory synchronization |
### 4. Bridge System (`src/bridge/`)
A bidirectional communication layer connecting IDE extensions (VS Code, JetBrains) with the Claude Code CLI.
- `bridgeMain.ts` — Bridge main loop
- `bridgeMessaging.ts` — Message protocol
- `bridgePermissionCallbacks.ts` — Permission callbacks
- `replBridge.ts` — REPL session bridge
- `jwtUtils.ts` — JWT-based authentication
- `sessionRunner.ts` — Session execution management
### 5. Permission System (`src/hooks/toolPermission/`)
Checks permissions on every tool invocation. Either prompts the user for approval/denial or automatically resolves based on the configured permission mode (`default`, `plan`, `bypassPermissions`, `auto`, etc.).
### 6. Feature Flags
Dead code elimination via Bun's `bun:bundle` feature flags:
```typescript
import { feature } from 'bun:bundle'
// Inactive code is completely stripped at build time
const voiceCommand = feature('VOICE_MODE')
? require('./commands/voice/index.js').default
: null
```
Print the current Python workspace manifest:
Notable flags: `PROACTIVE`, `KAIROS`, `BRIDGE_MODE`, `DAEMON`, `VOICE_MODE`, `AGENT_TRIGGERS`, `MONITOR_TOOL`
```bash
python3 -m src.main manifest
---
## Key Files in Detail
### `QueryEngine.ts` (~46K lines)
The core engine for LLM API calls. Handles streaming responses, tool-call loops, thinking mode, retry logic, and token counting.
### `Tool.ts` (~29K lines)
Defines base types and interfaces for all tools — input schemas, permission models, and progress state types.
### `commands.ts` (~25K lines)
Manages registration and execution of all slash commands. Uses conditional imports to load different command sets per environment.
### `main.tsx`
Commander.js-based CLI parser + React/Ink renderer initialization. At startup, parallelizes MDM settings, keychain prefetch, and GrowthBook initialization for faster boot.
---
## Tech Stack
| Category | Technology |
|---|---|
| Runtime | [Bun](https://bun.sh) |
| Language | TypeScript (strict) |
| Terminal UI | [React](https://react.dev) + [Ink](https://github.com/vadimdemedes/ink) |
| CLI Parsing | [Commander.js](https://github.com/tj/commander.js) (extra-typings) |
| Schema Validation | [Zod v4](https://zod.dev) |
| Code Search | [ripgrep](https://github.com/BurntSushi/ripgrep) (via GrepTool) |
| Protocols | [MCP SDK](https://modelcontextprotocol.io), LSP |
| API | [Anthropic SDK](https://docs.anthropic.com) |
| Telemetry | OpenTelemetry + gRPC |
| Feature Flags | GrowthBook |
| Auth | OAuth 2.0, JWT, macOS Keychain |
---
## Notable Design Patterns
### Parallel Prefetch
Startup time is optimized by prefetching MDM settings, keychain reads, and API preconnect in parallel — before heavy module evaluation begins.
```typescript
// main.tsx — fired as side-effects before other imports
startMdmRawRead()
startKeychainPrefetch()
```
List the current Python modules:
### Lazy Loading
```bash
python3 -m src.main subsystems --limit 16
```
Heavy modules (OpenTelemetry ~400KB, gRPC ~700KB) are deferred via dynamic `import()` until actually needed.
Run verification:
### Agent Swarms
```bash
python3 -m unittest discover -s tests -v
```
Sub-agents are spawned via `AgentTool`, with `coordinator/` handling multi-agent orchestration. `TeamCreateTool` enables team-level parallel work.
Run the parity audit against the local ignored archive (when present):
### Skill System
```bash
python3 -m src.main parity-audit
```
Reusable workflows defined in `skills/` and executed through `SkillTool`. Users can add custom skills.
Inspect mirrored command/tool inventories:
### Plugin Architecture
```bash
python3 -m src.main commands --limit 10
python3 -m src.main tools --limit 10
```
Built-in and third-party plugins are loaded through the `plugins/` subsystem.
## Current Parity Checkpoint
---
The port now mirrors the archived root-entry file surface, top-level subsystem names, and command/tool inventories much more closely than before. However, it is **not yet** a full runtime-equivalent replacement for the original TypeScript system; the Python tree still contains fewer executable runtime slices than the archived source.
## Disclaimer
## Related Essay
- [*Is legal the same as legitimate: AI reimplementation and the erosion of copyleft*](https://writings.hongminhee.org/2026/03/legal-vs-legitimate/)
The essay is dated **March 9, 2026**, so it should be read as companion analysis that predates the **March 31, 2026** source exposure that motivated this rewrite direction.
## Built with `oh-my-codex`
The restructuring and documentation work on this repository was AI-assisted and orchestrated with Yeachan Heo's [oh-my-codex (OmX)](https://github.com/Yeachan-Heo/oh-my-codex), layered on top of Codex.
- **`$team` mode:** used for coordinated parallel review and architectural feedback
- **`$ralph` mode:** used for persistent execution, verification, and completion discipline
- **Codex-driven workflow:** used to turn the main `src/` tree into a Python-first porting workspace
### OmX workflow screenshots
![OmX workflow screenshot 1](assets/omx/omx-readme-review-1.png)
*Ralph/team orchestration view while the README and essay context were being reviewed in terminal panes.*
![OmX workflow screenshot 2](assets/omx/omx-readme-review-2.png)
*Split-pane review and verification flow during the final README wording pass.*
## Ownership / Affiliation Disclaimer
- This repository does **not** claim ownership of the original Claude Code source material.
- This repository is **not affiliated with, endorsed by, or maintained by Anthropic**.
This repository archives source code that was leaked from Anthropic's npm registry on **2026-03-31**. All original source code is the property of [Anthropic](https://www.anthropic.com).

BIN
assets/clawd-hero.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 KiB

BIN
assets/instructkr.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

BIN
assets/star-history.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 KiB

BIN
assets/tweet-screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 812 KiB

BIN
assets/wsj-feature.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 873 KiB

2
rust/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
target/
.omx/

883
rust/Cargo.lock generated Normal file
View File

@@ -0,0 +1,883 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "adler2"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]]
name = "aho-corasick"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
dependencies = [
"memchr",
]
[[package]]
name = "anstream"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
[[package]]
name = "anstyle-parse"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
dependencies = [
"windows-sys",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
dependencies = [
"anstyle",
"once_cell_polyfill",
"windows-sys",
]
[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bincode"
version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
dependencies = [
"serde",
]
[[package]]
name = "bit-set"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
dependencies = [
"bit-vec",
]
[[package]]
name = "bit-vec"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
[[package]]
name = "bitflags"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "clap"
version = "4.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
[[package]]
name = "colorchoice"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
[[package]]
name = "commands"
version = "0.1.0"
[[package]]
name = "compat-harness"
version = "0.1.0"
dependencies = [
"commands",
"runtime",
"tools",
]
[[package]]
name = "convert_case"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "crc32fast"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
dependencies = [
"cfg-if",
]
[[package]]
name = "crossterm"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b"
dependencies = [
"bitflags",
"crossterm_winapi",
"derive_more",
"document-features",
"mio",
"parking_lot",
"rustix",
"signal-hook",
"signal-hook-mio",
"winapi",
]
[[package]]
name = "crossterm_winapi"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
dependencies = [
"winapi",
]
[[package]]
name = "deranged"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
dependencies = [
"powerfmt",
]
[[package]]
name = "derive_more"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134"
dependencies = [
"derive_more-impl",
]
[[package]]
name = "derive_more-impl"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb"
dependencies = [
"convert_case",
"proc-macro2",
"quote",
"rustc_version",
"syn",
]
[[package]]
name = "document-features"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61"
dependencies = [
"litrs",
]
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "errno"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys",
]
[[package]]
name = "fancy-regex"
version = "0.16.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "998b056554fbe42e03ae0e152895cd1a7e1002aec800fdc6635d20270260c46f"
dependencies = [
"bit-set",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "flate2"
version = "1.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "getopts"
version = "0.2.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfe4fbac503b8d1f88e6676011885f34b7174f46e59956bba534ba83abded4df"
dependencies = [
"unicode-width",
]
[[package]]
name = "hashbrown"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "indexmap"
version = "2.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
dependencies = [
"equivalent",
"hashbrown",
]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
[[package]]
name = "itoa"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "libc"
version = "0.2.183"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
[[package]]
name = "linked-hash-map"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]]
name = "linux-raw-sys"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
[[package]]
name = "litrs"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092"
[[package]]
name = "lock_api"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
dependencies = [
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "memchr"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
[[package]]
name = "miniz_oxide"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
dependencies = [
"adler2",
"simd-adler32",
]
[[package]]
name = "mio"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1"
dependencies = [
"libc",
"log",
"wasi",
"windows-sys",
]
[[package]]
name = "num-conv"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967"
[[package]]
name = "once_cell"
version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]]
name = "once_cell_polyfill"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
[[package]]
name = "parking_lot"
version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-link",
]
[[package]]
name = "plist"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07"
dependencies = [
"base64",
"indexmap",
"quick-xml",
"serde",
"time",
]
[[package]]
name = "powerfmt"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "pulldown-cmark"
version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c3a14896dfa883796f1cb410461aef38810ea05f2b2c33c5aded3649095fdad"
dependencies = [
"bitflags",
"getopts",
"memchr",
"pulldown-cmark-escape",
"unicase",
]
[[package]]
name = "pulldown-cmark-escape"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae"
[[package]]
name = "quick-xml"
version = "0.38.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c"
dependencies = [
"memchr",
]
[[package]]
name = "quote"
version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_syscall"
version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [
"bitflags",
]
[[package]]
name = "regex-automata"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
[[package]]
name = "runtime"
version = "0.1.0"
[[package]]
name = "rustc_version"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
dependencies = [
"semver",
]
[[package]]
name = "rustix"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
"windows-sys",
]
[[package]]
name = "rusty-claude-cli"
version = "0.1.0"
dependencies = [
"clap",
"compat-harness",
"crossterm",
"pulldown-cmark",
"runtime",
"syntect",
]
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "semver"
version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
[[package]]
name = "serde"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.149"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
dependencies = [
"itoa",
"memchr",
"serde",
"serde_core",
"zmij",
]
[[package]]
name = "signal-hook"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2"
dependencies = [
"libc",
"signal-hook-registry",
]
[[package]]
name = "signal-hook-mio"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc"
dependencies = [
"libc",
"mio",
"signal-hook",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
dependencies = [
"errno",
"libc",
]
[[package]]
name = "simd-adler32"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
[[package]]
name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "2.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syntect"
version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "656b45c05d95a5704399aeef6bd0ddec7b2b3531b7c9e900abbf7c4d2190c925"
dependencies = [
"bincode",
"fancy-regex",
"flate2",
"fnv",
"once_cell",
"plist",
"regex-syntax",
"serde",
"serde_derive",
"serde_json",
"thiserror",
"walkdir",
"yaml-rust",
]
[[package]]
name = "thiserror"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "time"
version = "0.3.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c"
dependencies = [
"deranged",
"itoa",
"num-conv",
"powerfmt",
"serde_core",
"time-core",
"time-macros",
]
[[package]]
name = "time-core"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca"
[[package]]
name = "time-macros"
version = "0.2.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215"
dependencies = [
"num-conv",
"time-core",
]
[[package]]
name = "tools"
version = "0.1.0"
[[package]]
name = "unicase"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "unicode-segmentation"
version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c"
[[package]]
name = "unicode-width"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "walkdir"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
dependencies = [
"same-file",
"winapi-util",
]
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]
[[package]]
name = "yaml-rust"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
dependencies = [
"linked-hash-map",
]
[[package]]
name = "zmij"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"

19
rust/Cargo.toml Normal file
View File

@@ -0,0 +1,19 @@
[workspace]
members = ["crates/*"]
resolver = "2"
[workspace.package]
version = "0.1.0"
edition = "2021"
license = "MIT"
publish = false
[workspace.lints.rust]
unsafe_code = "forbid"
[workspace.lints.clippy]
all = { level = "warn", priority = -1 }
pedantic = { level = "warn", priority = -1 }
module_name_repetitions = "allow"
missing_panics_doc = "allow"
missing_errors_doc = "allow"

54
rust/README.md Normal file
View File

@@ -0,0 +1,54 @@
# Rust port foundation
This directory contains the first compatibility-first Rust foundation for a drop-in Claude Code CLI replacement.
## Current milestone
This initial milestone focuses on **harness-first scaffolding**, not full feature parity:
- a Cargo workspace aligned to major upstream seams
- a placeholder CLI crate (`rusty-claude-cli`)
- runtime, command, and tool registry skeleton crates
- a `compat-harness` crate that reads the upstream TypeScript sources in `../src/`
- tests that prove upstream manifests/bootstrap hints can be extracted from the leaked TypeScript codebase
## Workspace layout
```text
rust/
├── Cargo.toml
├── README.md
├── crates/
│ ├── rusty-claude-cli/
│ ├── runtime/
│ ├── commands/
│ ├── tools/
│ └── compat-harness/
└── tests/
```
## How to use
From this directory:
```bash
cargo fmt --all
cargo check --workspace
cargo test --workspace
cargo run -p rusty-claude-cli -- --help
cargo run -p rusty-claude-cli -- dump-manifests
cargo run -p rusty-claude-cli -- bootstrap-plan
```
## Design notes
The shape follows the PRD's harness-first recommendation:
1. Extract observable upstream command/tool/bootstrap facts first.
2. Keep Rust module boundaries recognizable.
3. Grow runtime compatibility behind proof artifacts.
4. Document explicit gaps instead of implying drop-in parity too early.
## Relationship to the root README
The repository root README explains the leaked TypeScript codebase. This document tracks the Rust replacement effort that lives in `rust/`.

View File

@@ -0,0 +1,9 @@
[package]
name = "commands"
version.workspace = true
edition.workspace = true
license.workspace = true
publish.workspace = true
[lints]
workspace = true

View File

@@ -0,0 +1,29 @@
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CommandManifestEntry {
pub name: String,
pub source: CommandSource,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CommandSource {
Builtin,
InternalOnly,
FeatureGated,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct CommandRegistry {
entries: Vec<CommandManifestEntry>,
}
impl CommandRegistry {
#[must_use]
pub fn new(entries: Vec<CommandManifestEntry>) -> Self {
Self { entries }
}
#[must_use]
pub fn entries(&self) -> &[CommandManifestEntry] {
&self.entries
}
}

View File

@@ -0,0 +1,14 @@
[package]
name = "compat-harness"
version.workspace = true
edition.workspace = true
license.workspace = true
publish.workspace = true
[dependencies]
commands = { path = "../commands" }
tools = { path = "../tools" }
runtime = { path = "../runtime" }
[lints]
workspace = true

View File

@@ -0,0 +1,308 @@
use std::fs;
use std::path::{Path, PathBuf};
use commands::{CommandManifestEntry, CommandRegistry, CommandSource};
use runtime::{BootstrapPhase, BootstrapPlan};
use tools::{ToolManifestEntry, ToolRegistry, ToolSource};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UpstreamPaths {
repo_root: PathBuf,
}
impl UpstreamPaths {
#[must_use]
pub fn from_repo_root(repo_root: impl Into<PathBuf>) -> Self {
Self {
repo_root: repo_root.into(),
}
}
#[must_use]
pub fn from_workspace_dir(workspace_dir: impl AsRef<Path>) -> Self {
let workspace_dir = workspace_dir
.as_ref()
.canonicalize()
.unwrap_or_else(|_| workspace_dir.as_ref().to_path_buf());
let repo_root = workspace_dir
.parent()
.map_or_else(|| PathBuf::from(".."), Path::to_path_buf);
Self { repo_root }
}
#[must_use]
pub fn commands_path(&self) -> PathBuf {
self.repo_root.join("src/commands.ts")
}
#[must_use]
pub fn tools_path(&self) -> PathBuf {
self.repo_root.join("src/tools.ts")
}
#[must_use]
pub fn cli_path(&self) -> PathBuf {
self.repo_root.join("src/entrypoints/cli.tsx")
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExtractedManifest {
pub commands: CommandRegistry,
pub tools: ToolRegistry,
pub bootstrap: BootstrapPlan,
}
pub fn extract_manifest(paths: &UpstreamPaths) -> std::io::Result<ExtractedManifest> {
let commands_source = fs::read_to_string(paths.commands_path())?;
let tools_source = fs::read_to_string(paths.tools_path())?;
let cli_source = fs::read_to_string(paths.cli_path())?;
Ok(ExtractedManifest {
commands: extract_commands(&commands_source),
tools: extract_tools(&tools_source),
bootstrap: extract_bootstrap_plan(&cli_source),
})
}
#[must_use]
pub fn extract_commands(source: &str) -> CommandRegistry {
let mut entries = Vec::new();
let mut in_internal_block = false;
for raw_line in source.lines() {
let line = raw_line.trim();
if line.starts_with("export const INTERNAL_ONLY_COMMANDS = [") {
in_internal_block = true;
continue;
}
if in_internal_block {
if line.starts_with(']') {
in_internal_block = false;
continue;
}
if let Some(name) = first_identifier(line) {
entries.push(CommandManifestEntry {
name,
source: CommandSource::InternalOnly,
});
}
continue;
}
if line.starts_with("import ") {
for imported in imported_symbols(line) {
entries.push(CommandManifestEntry {
name: imported,
source: CommandSource::Builtin,
});
}
}
if line.contains("feature('") && line.contains("./commands/") {
if let Some(name) = first_assignment_identifier(line) {
entries.push(CommandManifestEntry {
name,
source: CommandSource::FeatureGated,
});
}
}
}
dedupe_commands(entries)
}
#[must_use]
pub fn extract_tools(source: &str) -> ToolRegistry {
let mut entries = Vec::new();
for raw_line in source.lines() {
let line = raw_line.trim();
if line.starts_with("import ") && line.contains("./tools/") {
for imported in imported_symbols(line) {
if imported.ends_with("Tool") {
entries.push(ToolManifestEntry {
name: imported,
source: ToolSource::Base,
});
}
}
}
if line.contains("feature('") && line.contains("Tool") {
if let Some(name) = first_assignment_identifier(line) {
if name.ends_with("Tool") || name.ends_with("Tools") {
entries.push(ToolManifestEntry {
name,
source: ToolSource::Conditional,
});
}
}
}
}
dedupe_tools(entries)
}
#[must_use]
pub fn extract_bootstrap_plan(source: &str) -> BootstrapPlan {
let mut phases = vec![BootstrapPhase::CliEntry];
if source.contains("--version") {
phases.push(BootstrapPhase::FastPathVersion);
}
if source.contains("startupProfiler") {
phases.push(BootstrapPhase::StartupProfiler);
}
if source.contains("--dump-system-prompt") {
phases.push(BootstrapPhase::SystemPromptFastPath);
}
if source.contains("--claude-in-chrome-mcp") {
phases.push(BootstrapPhase::ChromeMcpFastPath);
}
if source.contains("--daemon-worker") {
phases.push(BootstrapPhase::DaemonWorkerFastPath);
}
if source.contains("remote-control") {
phases.push(BootstrapPhase::BridgeFastPath);
}
if source.contains("args[0] === 'daemon'") {
phases.push(BootstrapPhase::DaemonFastPath);
}
if source.contains("args[0] === 'ps'") || source.contains("args.includes('--bg')") {
phases.push(BootstrapPhase::BackgroundSessionFastPath);
}
if source.contains("args[0] === 'new' || args[0] === 'list' || args[0] === 'reply'") {
phases.push(BootstrapPhase::TemplateFastPath);
}
if source.contains("environment-runner") {
phases.push(BootstrapPhase::EnvironmentRunnerFastPath);
}
phases.push(BootstrapPhase::MainRuntime);
BootstrapPlan::from_phases(phases)
}
fn imported_symbols(line: &str) -> Vec<String> {
let Some(after_import) = line.strip_prefix("import ") else {
return Vec::new();
};
let before_from = after_import
.split(" from ")
.next()
.unwrap_or_default()
.trim();
if before_from.starts_with('{') {
return before_from
.trim_matches(|c| c == '{' || c == '}')
.split(',')
.filter_map(|part| {
let trimmed = part.trim();
if trimmed.is_empty() {
return None;
}
Some(trimmed.split_whitespace().next()?.to_string())
})
.collect();
}
let first = before_from.split(',').next().unwrap_or_default().trim();
if first.is_empty() {
Vec::new()
} else {
vec![first.to_string()]
}
}
fn first_assignment_identifier(line: &str) -> Option<String> {
let trimmed = line.trim_start();
let candidate = trimmed.split('=').next()?.trim();
first_identifier(candidate)
}
fn first_identifier(line: &str) -> Option<String> {
let mut out = String::new();
for ch in line.chars() {
if ch.is_ascii_alphanumeric() || ch == '_' || ch == '-' {
out.push(ch);
} else if !out.is_empty() {
break;
}
}
(!out.is_empty()).then_some(out)
}
fn dedupe_commands(entries: Vec<CommandManifestEntry>) -> CommandRegistry {
let mut deduped = Vec::new();
for entry in entries {
let exists = deduped.iter().any(|seen: &CommandManifestEntry| {
seen.name == entry.name && seen.source == entry.source
});
if !exists {
deduped.push(entry);
}
}
CommandRegistry::new(deduped)
}
fn dedupe_tools(entries: Vec<ToolManifestEntry>) -> ToolRegistry {
let mut deduped = Vec::new();
for entry in entries {
let exists = deduped
.iter()
.any(|seen: &ToolManifestEntry| seen.name == entry.name && seen.source == entry.source);
if !exists {
deduped.push(entry);
}
}
ToolRegistry::new(deduped)
}
#[cfg(test)]
mod tests {
use super::*;
fn fixture_paths() -> UpstreamPaths {
let workspace_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("../..");
UpstreamPaths::from_workspace_dir(workspace_dir)
}
#[test]
fn extracts_non_empty_manifests_from_upstream_repo() {
let manifest = extract_manifest(&fixture_paths()).expect("manifest should load");
assert!(!manifest.commands.entries().is_empty());
assert!(!manifest.tools.entries().is_empty());
assert!(!manifest.bootstrap.phases().is_empty());
}
#[test]
fn detects_known_upstream_command_symbols() {
let commands = extract_commands(
&fs::read_to_string(fixture_paths().commands_path()).expect("commands.ts"),
);
let names: Vec<_> = commands
.entries()
.iter()
.map(|entry| entry.name.as_str())
.collect();
assert!(names.contains(&"addDir"));
assert!(names.contains(&"review"));
assert!(!names.contains(&"INTERNAL_ONLY_COMMANDS"));
}
#[test]
fn detects_known_upstream_tool_symbols() {
let tools =
extract_tools(&fs::read_to_string(fixture_paths().tools_path()).expect("tools.ts"));
let names: Vec<_> = tools
.entries()
.iter()
.map(|entry| entry.name.as_str())
.collect();
assert!(names.contains(&"AgentTool"));
assert!(names.contains(&"BashTool"));
}
}

View File

@@ -0,0 +1,9 @@
[package]
name = "runtime"
version.workspace = true
edition.workspace = true
license.workspace = true
publish.workspace = true
[lints]
workspace = true

View File

@@ -0,0 +1,56 @@
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BootstrapPhase {
CliEntry,
FastPathVersion,
StartupProfiler,
SystemPromptFastPath,
ChromeMcpFastPath,
DaemonWorkerFastPath,
BridgeFastPath,
DaemonFastPath,
BackgroundSessionFastPath,
TemplateFastPath,
EnvironmentRunnerFastPath,
MainRuntime,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BootstrapPlan {
phases: Vec<BootstrapPhase>,
}
impl BootstrapPlan {
#[must_use]
pub fn claude_code_default() -> Self {
Self::from_phases(vec![
BootstrapPhase::CliEntry,
BootstrapPhase::FastPathVersion,
BootstrapPhase::StartupProfiler,
BootstrapPhase::SystemPromptFastPath,
BootstrapPhase::ChromeMcpFastPath,
BootstrapPhase::DaemonWorkerFastPath,
BootstrapPhase::BridgeFastPath,
BootstrapPhase::DaemonFastPath,
BootstrapPhase::BackgroundSessionFastPath,
BootstrapPhase::TemplateFastPath,
BootstrapPhase::EnvironmentRunnerFastPath,
BootstrapPhase::MainRuntime,
])
}
#[must_use]
pub fn from_phases(phases: Vec<BootstrapPhase>) -> Self {
let mut deduped = Vec::new();
for phase in phases {
if !deduped.contains(&phase) {
deduped.push(phase);
}
}
Self { phases: deduped }
}
#[must_use]
pub fn phases(&self) -> &[BootstrapPhase] {
&self.phases
}
}

View File

@@ -0,0 +1,17 @@
[package]
name = "rusty-claude-cli"
version.workspace = true
edition.workspace = true
license.workspace = true
publish.workspace = true
[dependencies]
clap = { version = "4.5.38", features = ["derive"] }
compat-harness = { path = "../compat-harness" }
crossterm = "0.29.0"
pulldown-cmark = "0.13.0"
runtime = { path = "../runtime" }
syntect = { version = "5.2.0", default-features = false, features = ["default-fancy"] }
[lints]
workspace = true

View File

@@ -0,0 +1,246 @@
use std::io::{self, Write};
use std::path::PathBuf;
use std::thread;
use std::time::Duration;
use crate::args::{OutputFormat, PermissionMode};
use crate::input::LineEditor;
use crate::render::{Spinner, TerminalRenderer};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SessionConfig {
pub model: String,
pub permission_mode: PermissionMode,
pub config: Option<PathBuf>,
pub output_format: OutputFormat,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SessionState {
pub turns: usize,
pub compacted_messages: usize,
pub last_model: String,
}
impl SessionState {
#[must_use]
pub fn new(model: impl Into<String>) -> Self {
Self {
turns: 0,
compacted_messages: 0,
last_model: model.into(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CommandResult {
Continue,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SlashCommand {
Help,
Status,
Compact,
Unknown(String),
}
impl SlashCommand {
#[must_use]
pub fn parse(input: &str) -> Option<Self> {
let trimmed = input.trim();
if !trimmed.starts_with('/') {
return None;
}
let command = trimmed
.trim_start_matches('/')
.split_whitespace()
.next()
.unwrap_or_default();
Some(match command {
"help" => Self::Help,
"status" => Self::Status,
"compact" => Self::Compact,
other => Self::Unknown(other.to_string()),
})
}
}
pub struct CliApp {
config: SessionConfig,
renderer: TerminalRenderer,
state: SessionState,
}
impl CliApp {
#[must_use]
pub fn new(config: SessionConfig) -> Self {
let state = SessionState::new(config.model.clone());
Self {
config,
renderer: TerminalRenderer::new(),
state,
}
}
pub fn run_repl(&mut self) -> io::Result<()> {
let editor = LineEditor::new(" ");
println!("Rusty Claude CLI interactive mode");
println!("Type /help for commands. Shift+Enter or Ctrl+J inserts a newline.");
while let Some(input) = editor.read_line()? {
if input.trim().is_empty() {
continue;
}
self.handle_submission(&input, &mut io::stdout())?;
}
Ok(())
}
pub fn run_prompt(&mut self, prompt: &str, out: &mut impl Write) -> io::Result<()> {
self.render_response(prompt, out)
}
pub fn handle_submission(
&mut self,
input: &str,
out: &mut impl Write,
) -> io::Result<CommandResult> {
if let Some(command) = SlashCommand::parse(input) {
return self.handle_command(command, out);
}
self.state.turns += 1;
self.render_response(input, out)?;
Ok(CommandResult::Continue)
}
fn handle_command(
&mut self,
command: SlashCommand,
out: &mut impl Write,
) -> io::Result<CommandResult> {
match command {
SlashCommand::Help => {
writeln!(
out,
"Available commands:\n /help Show command help\n /status Show current session status\n /compact Compact local session history"
)?;
}
SlashCommand::Status => {
writeln!(
out,
"status: turns={} model={} permission-mode={:?} output-format={:?} config={}",
self.state.turns,
self.state.last_model,
self.config.permission_mode,
self.config.output_format,
self.config
.config
.as_ref()
.map_or_else(|| String::from("<none>"), |path| path.display().to_string())
)?;
}
SlashCommand::Compact => {
self.state.compacted_messages += self.state.turns;
self.state.turns = 0;
writeln!(
out,
"Compacted session history into a local summary ({} messages total compacted).",
self.state.compacted_messages
)?;
}
SlashCommand::Unknown(name) => {
writeln!(out, "Unknown slash command: /{name}")?;
}
}
Ok(CommandResult::Continue)
}
fn render_response(&mut self, input: &str, out: &mut impl Write) -> io::Result<()> {
let mut spinner = Spinner::new();
for _ in 0..3 {
spinner.tick("Generating response", out)?;
thread::sleep(Duration::from_millis(24));
}
spinner.finish("Streaming response", out)?;
let response = demo_response(input, &self.config);
match self.config.output_format {
OutputFormat::Text => self.renderer.stream_markdown(&response, out)?,
OutputFormat::Json => writeln!(out, "{{\"message\":{response:?}}}")?,
OutputFormat::Ndjson => {
writeln!(out, "{{\"type\":\"message\",\"text\":{response:?}}}")?;
}
}
Ok(())
}
}
#[must_use]
pub fn demo_response(input: &str, config: &SessionConfig) -> String {
format!(
"## Assistant\n\nModel: `{}` \nPermission mode: `{}`\n\nYou said:\n\n> {}\n\n```rust\nfn main() {{\n println!(\"streaming from rusty-claude-cli\");\n}}\n```",
config.model,
permission_mode_label(config.permission_mode),
input.trim()
)
}
#[must_use]
pub fn permission_mode_label(mode: PermissionMode) -> &'static str {
match mode {
PermissionMode::ReadOnly => "read-only",
PermissionMode::WorkspaceWrite => "workspace-write",
PermissionMode::DangerFullAccess => "danger-full-access",
}
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use crate::args::{OutputFormat, PermissionMode};
use super::{CliApp, CommandResult, SessionConfig, SlashCommand};
#[test]
fn parses_required_slash_commands() {
assert_eq!(SlashCommand::parse("/help"), Some(SlashCommand::Help));
assert_eq!(SlashCommand::parse(" /status "), Some(SlashCommand::Status));
assert_eq!(
SlashCommand::parse("/compact now"),
Some(SlashCommand::Compact)
);
}
#[test]
fn status_and_compact_commands_update_state() {
let config = SessionConfig {
model: "claude".into(),
permission_mode: PermissionMode::WorkspaceWrite,
config: Some(PathBuf::from("settings.toml")),
output_format: OutputFormat::Text,
};
let mut app = CliApp::new(config);
let mut out = Vec::new();
let result = app
.handle_submission("hello", &mut out)
.expect("submission succeeds");
assert_eq!(result, CommandResult::Continue);
app.handle_submission("/status", &mut out)
.expect("status succeeds");
app.handle_submission("/compact", &mut out)
.expect("compact succeeds");
let output = String::from_utf8_lossy(&out);
assert!(output.contains("status: turns=1"));
assert!(output.contains("Compacted session history"));
}
}

View File

@@ -0,0 +1,89 @@
use std::path::PathBuf;
use clap::{Parser, Subcommand, ValueEnum};
#[derive(Debug, Clone, Parser, PartialEq, Eq)]
#[command(
name = "rusty-claude-cli",
version,
about = "Rust Claude CLI prototype"
)]
pub struct Cli {
#[arg(long, default_value = "claude-3-7-sonnet")]
pub model: String,
#[arg(long, value_enum, default_value_t = PermissionMode::WorkspaceWrite)]
pub permission_mode: PermissionMode,
#[arg(long)]
pub config: Option<PathBuf>,
#[arg(long, value_enum, default_value_t = OutputFormat::Text)]
pub output_format: OutputFormat,
#[command(subcommand)]
pub command: Option<Command>,
}
#[derive(Debug, Clone, Subcommand, PartialEq, Eq)]
pub enum Command {
/// Read upstream TS sources and print extracted counts
DumpManifests,
/// Print the current bootstrap phase skeleton
BootstrapPlan,
/// Run a non-interactive prompt and exit
Prompt { prompt: Vec<String> },
}
#[derive(Debug, Clone, Copy, ValueEnum, PartialEq, Eq)]
pub enum PermissionMode {
ReadOnly,
WorkspaceWrite,
DangerFullAccess,
}
#[derive(Debug, Clone, Copy, ValueEnum, PartialEq, Eq)]
pub enum OutputFormat {
Text,
Json,
Ndjson,
}
#[cfg(test)]
mod tests {
use clap::Parser;
use super::{Cli, Command, OutputFormat, PermissionMode};
#[test]
fn parses_requested_flags() {
let cli = Cli::parse_from([
"rusty-claude-cli",
"--model",
"claude-3-5-haiku",
"--permission-mode",
"read-only",
"--config",
"/tmp/config.toml",
"--output-format",
"ndjson",
"prompt",
"hello",
"world",
]);
assert_eq!(cli.model, "claude-3-5-haiku");
assert_eq!(cli.permission_mode, PermissionMode::ReadOnly);
assert_eq!(
cli.config.as_deref(),
Some(std::path::Path::new("/tmp/config.toml"))
);
assert_eq!(cli.output_format, OutputFormat::Ndjson);
assert_eq!(
cli.command,
Some(Command::Prompt {
prompt: vec!["hello".into(), "world".into()]
})
);
}
}

View File

@@ -0,0 +1,248 @@
use std::io::{self, Write};
use crossterm::cursor::MoveToColumn;
use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyModifiers};
use crossterm::queue;
use crossterm::style::Print;
use crossterm::terminal::{disable_raw_mode, enable_raw_mode, Clear, ClearType};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InputBuffer {
buffer: String,
cursor: usize,
}
impl InputBuffer {
#[must_use]
pub fn new() -> Self {
Self {
buffer: String::new(),
cursor: 0,
}
}
pub fn insert(&mut self, ch: char) {
self.buffer.insert(self.cursor, ch);
self.cursor += ch.len_utf8();
}
pub fn insert_newline(&mut self) {
self.insert('\n');
}
pub fn backspace(&mut self) {
if self.cursor == 0 {
return;
}
let previous = self.buffer[..self.cursor]
.char_indices()
.last()
.map_or(0, |(idx, _)| idx);
self.buffer.drain(previous..self.cursor);
self.cursor = previous;
}
pub fn move_left(&mut self) {
if self.cursor == 0 {
return;
}
self.cursor = self.buffer[..self.cursor]
.char_indices()
.last()
.map_or(0, |(idx, _)| idx);
}
pub fn move_right(&mut self) {
if self.cursor >= self.buffer.len() {
return;
}
if let Some(next) = self.buffer[self.cursor..].chars().next() {
self.cursor += next.len_utf8();
}
}
pub fn move_home(&mut self) {
self.cursor = 0;
}
pub fn move_end(&mut self) {
self.cursor = self.buffer.len();
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.buffer
}
#[cfg(test)]
#[must_use]
pub fn cursor(&self) -> usize {
self.cursor
}
pub fn clear(&mut self) {
self.buffer.clear();
self.cursor = 0;
}
}
pub struct LineEditor {
prompt: String,
}
impl LineEditor {
#[must_use]
pub fn new(prompt: impl Into<String>) -> Self {
Self {
prompt: prompt.into(),
}
}
pub fn read_line(&self) -> io::Result<Option<String>> {
enable_raw_mode()?;
let mut stdout = io::stdout();
let mut input = InputBuffer::new();
self.redraw(&mut stdout, &input)?;
loop {
let event = event::read()?;
if let Event::Key(key) = event {
match Self::handle_key(key, &mut input) {
EditorAction::Continue => self.redraw(&mut stdout, &input)?,
EditorAction::Submit => {
disable_raw_mode()?;
writeln!(stdout)?;
return Ok(Some(input.as_str().to_owned()));
}
EditorAction::Cancel => {
disable_raw_mode()?;
writeln!(stdout)?;
return Ok(None);
}
}
}
}
}
fn handle_key(key: KeyEvent, input: &mut InputBuffer) -> EditorAction {
match key {
KeyEvent {
code: KeyCode::Char('c'),
modifiers,
..
} if modifiers.contains(KeyModifiers::CONTROL) => EditorAction::Cancel,
KeyEvent {
code: KeyCode::Char('j'),
modifiers,
..
} if modifiers.contains(KeyModifiers::CONTROL) => {
input.insert_newline();
EditorAction::Continue
}
KeyEvent {
code: KeyCode::Enter,
modifiers,
..
} if modifiers.contains(KeyModifiers::SHIFT) => {
input.insert_newline();
EditorAction::Continue
}
KeyEvent {
code: KeyCode::Enter,
..
} => EditorAction::Submit,
KeyEvent {
code: KeyCode::Backspace,
..
} => {
input.backspace();
EditorAction::Continue
}
KeyEvent {
code: KeyCode::Left,
..
} => {
input.move_left();
EditorAction::Continue
}
KeyEvent {
code: KeyCode::Right,
..
} => {
input.move_right();
EditorAction::Continue
}
KeyEvent {
code: KeyCode::Home,
..
} => {
input.move_home();
EditorAction::Continue
}
KeyEvent {
code: KeyCode::End, ..
} => {
input.move_end();
EditorAction::Continue
}
KeyEvent {
code: KeyCode::Esc, ..
} => {
input.clear();
EditorAction::Cancel
}
KeyEvent {
code: KeyCode::Char(ch),
modifiers,
..
} if modifiers.is_empty() || modifiers == KeyModifiers::SHIFT => {
input.insert(ch);
EditorAction::Continue
}
_ => EditorAction::Continue,
}
}
fn redraw(&self, out: &mut impl Write, input: &InputBuffer) -> io::Result<()> {
let display = input.as_str().replace('\n', "\\n\n> ");
queue!(
out,
MoveToColumn(0),
Clear(ClearType::CurrentLine),
Print(&self.prompt),
Print(display),
)?;
out.flush()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum EditorAction {
Continue,
Submit,
Cancel,
}
#[cfg(test)]
mod tests {
use super::InputBuffer;
#[test]
fn supports_basic_line_editing() {
let mut input = InputBuffer::new();
input.insert('h');
input.insert('i');
input.move_end();
input.insert_newline();
input.insert('x');
assert_eq!(input.as_str(), "hi\nx");
assert_eq!(input.cursor(), 4);
input.move_left();
input.backspace();
assert_eq!(input.as_str(), "hix");
assert_eq!(input.cursor(), 2);
}
}

View File

@@ -0,0 +1,63 @@
mod app;
mod args;
mod input;
mod render;
use std::path::PathBuf;
use app::{CliApp, SessionConfig};
use args::{Cli, Command};
use clap::Parser;
use compat_harness::{extract_manifest, UpstreamPaths};
use runtime::BootstrapPlan;
fn main() {
let cli = Cli::parse();
let result = match &cli.command {
Some(Command::DumpManifests) => dump_manifests(),
Some(Command::BootstrapPlan) => {
print_bootstrap_plan();
Ok(())
}
Some(Command::Prompt { prompt }) => {
let joined = prompt.join(" ");
let mut app = CliApp::new(build_session_config(&cli));
app.run_prompt(&joined, &mut std::io::stdout())
}
None => {
let mut app = CliApp::new(build_session_config(&cli));
app.run_repl()
}
};
if let Err(error) = result {
eprintln!("{error}");
std::process::exit(1);
}
}
fn build_session_config(cli: &Cli) -> SessionConfig {
SessionConfig {
model: cli.model.clone(),
permission_mode: cli.permission_mode,
config: cli.config.clone(),
output_format: cli.output_format,
}
}
fn dump_manifests() -> std::io::Result<()> {
let workspace_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../..");
let paths = UpstreamPaths::from_workspace_dir(&workspace_dir);
let manifest = extract_manifest(&paths)?;
println!("commands: {}", manifest.commands.entries().len());
println!("tools: {}", manifest.tools.entries().len());
println!("bootstrap phases: {}", manifest.bootstrap.phases().len());
Ok(())
}
fn print_bootstrap_plan() {
for phase in BootstrapPlan::claude_code_default().phases() {
println!("- {phase:?}");
}
}

View File

@@ -0,0 +1,244 @@
use std::fmt::Write as FmtWrite;
use std::io::{self, Write};
use std::thread;
use std::time::Duration;
use crossterm::cursor::{MoveToColumn, RestorePosition, SavePosition};
use crossterm::style::{Color, Print, ResetColor, SetForegroundColor, Stylize};
use crossterm::terminal::{Clear, ClearType};
use crossterm::{execute, queue};
use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag, TagEnd};
use syntect::easy::HighlightLines;
use syntect::highlighting::{Theme, ThemeSet};
use syntect::parsing::SyntaxSet;
use syntect::util::{as_24_bit_terminal_escaped, LinesWithEndings};
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct Spinner {
frame_index: usize,
}
impl Spinner {
const FRAMES: [&str; 10] = ["", "", "", "", "", "", "", "", "", ""];
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn tick(&mut self, label: &str, out: &mut impl Write) -> io::Result<()> {
let frame = Self::FRAMES[self.frame_index % Self::FRAMES.len()];
self.frame_index += 1;
queue!(
out,
SavePosition,
MoveToColumn(0),
Clear(ClearType::CurrentLine),
SetForegroundColor(Color::Blue),
Print(format!("{frame} {label}")),
ResetColor,
RestorePosition
)?;
out.flush()
}
pub fn finish(&mut self, label: &str, out: &mut impl Write) -> io::Result<()> {
self.frame_index = 0;
execute!(
out,
MoveToColumn(0),
Clear(ClearType::CurrentLine),
SetForegroundColor(Color::Green),
Print(format!("{label}\n")),
ResetColor
)?;
out.flush()
}
}
#[derive(Debug)]
pub struct TerminalRenderer {
syntax_set: SyntaxSet,
theme: Theme,
}
impl Default for TerminalRenderer {
fn default() -> Self {
let syntax_set = SyntaxSet::load_defaults_newlines();
let theme = ThemeSet::load_defaults()
.themes
.remove("base16-ocean.dark")
.unwrap_or_default();
Self { syntax_set, theme }
}
}
impl TerminalRenderer {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn render_markdown(&self, markdown: &str) -> String {
let mut output = String::new();
let parser = Parser::new_ext(markdown, Options::all());
let mut in_code_block = false;
let mut code_language = String::new();
let mut code_buffer = String::new();
let mut list_depth = 0usize;
for event in parser {
match event {
Event::Start(Tag::Heading { level, .. }) => {
output.push('\n');
let prefix = match level as u8 {
1 => "# ",
2 => "## ",
3 => "### ",
_ => "#### ",
};
output.push_str(prefix);
}
Event::End(TagEnd::Heading(..) | TagEnd::Paragraph) => {
output.push_str("\n\n");
}
Event::Start(Tag::BlockQuote(..)) => output.push_str(""),
Event::End(TagEnd::BlockQuote(..) | TagEnd::Item)
| Event::SoftBreak
| Event::HardBreak => output.push('\n'),
Event::Start(Tag::List(_)) => list_depth += 1,
Event::End(TagEnd::List(..)) => {
list_depth = list_depth.saturating_sub(1);
output.push('\n');
}
Event::Start(Tag::Item) => {
output.push_str(&" ".repeat(list_depth.saturating_sub(1)));
output.push_str("");
}
Event::Start(Tag::CodeBlock(kind)) => {
in_code_block = true;
code_language = match kind {
CodeBlockKind::Indented => String::from("text"),
CodeBlockKind::Fenced(lang) => lang.to_string(),
};
code_buffer.clear();
}
Event::End(TagEnd::CodeBlock) => {
output.push_str(&self.highlight_code(&code_buffer, &code_language));
output.push('\n');
in_code_block = false;
code_language.clear();
code_buffer.clear();
}
Event::Code(code) => {
let _ = write!(output, "{}", format!("`{code}`").with(Color::Yellow));
}
Event::Rule => output.push_str("---\n"),
Event::Text(text) => {
if in_code_block {
code_buffer.push_str(&text);
} else {
output.push_str(&text);
}
}
Event::Html(html) | Event::InlineHtml(html) => output.push_str(&html),
Event::FootnoteReference(reference) => {
let _ = write!(output, "[{reference}]");
}
Event::TaskListMarker(done) => {
output.push_str(if done { "[x] " } else { "[ ] " });
}
Event::InlineMath(math) | Event::DisplayMath(math) => output.push_str(&math),
Event::Start(Tag::Link { dest_url, .. }) => {
let _ = write!(output, "[{dest_url}]");
}
Event::Start(Tag::Image { dest_url, .. }) => {
let _ = write!(output, "[image:{dest_url}]");
}
Event::Start(
Tag::Paragraph
| Tag::Emphasis
| Tag::Strong
| Tag::Table(..)
| Tag::TableHead
| Tag::TableRow
| Tag::TableCell
| Tag::MetadataBlock(..)
| _,
)
| Event::End(
TagEnd::Emphasis
| TagEnd::Strong
| TagEnd::Link
| TagEnd::Image
| TagEnd::Table
| TagEnd::TableHead
| TagEnd::TableRow
| TagEnd::TableCell
| TagEnd::MetadataBlock(..)
| _,
) => {}
}
}
output.trim_end().to_string()
}
#[must_use]
pub fn highlight_code(&self, code: &str, language: &str) -> String {
let syntax = self
.syntax_set
.find_syntax_by_token(language)
.unwrap_or_else(|| self.syntax_set.find_syntax_plain_text());
let mut syntax_highlighter = HighlightLines::new(syntax, &self.theme);
let mut colored_output = String::new();
for line in LinesWithEndings::from(code) {
match syntax_highlighter.highlight_line(line, &self.syntax_set) {
Ok(ranges) => {
colored_output.push_str(&as_24_bit_terminal_escaped(&ranges[..], false));
}
Err(_) => colored_output.push_str(line),
}
}
colored_output
}
pub fn stream_markdown(&self, markdown: &str, out: &mut impl Write) -> io::Result<()> {
let rendered_markdown = self.render_markdown(markdown);
for chunk in rendered_markdown.split_inclusive(char::is_whitespace) {
write!(out, "{chunk}")?;
out.flush()?;
thread::sleep(Duration::from_millis(8));
}
writeln!(out)
}
}
#[cfg(test)]
mod tests {
use super::{Spinner, TerminalRenderer};
#[test]
fn renders_basic_markdown_features() {
let terminal_renderer = TerminalRenderer::new();
let markdown_output = terminal_renderer.render_markdown("# Heading\n\n- item\n\n`code`");
assert!(markdown_output.contains("# Heading"));
assert!(markdown_output.contains("• item"));
assert!(markdown_output.contains("code"));
}
#[test]
fn spinner_advances_frames() {
let mut spinner = Spinner::new();
let mut out = Vec::new();
spinner.tick("Working", &mut out).expect("tick succeeds");
spinner.tick("Working", &mut out).expect("tick succeeds");
let output = String::from_utf8_lossy(&out);
assert!(output.contains("Working"));
}
}

View File

@@ -0,0 +1,9 @@
[package]
name = "tools"
version.workspace = true
edition.workspace = true
license.workspace = true
publish.workspace = true
[lints]
workspace = true

View File

@@ -0,0 +1,28 @@
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ToolManifestEntry {
pub name: String,
pub source: ToolSource,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ToolSource {
Base,
Conditional,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ToolRegistry {
entries: Vec<ToolManifestEntry>,
}
impl ToolRegistry {
#[must_use]
pub fn new(entries: Vec<ToolManifestEntry>) -> Self {
Self { entries }
}
#[must_use]
pub fn entries(&self) -> &[ToolManifestEntry] {
&self.entries
}
}

Some files were not shown because too many files have changed in this diff Show More