M
Matvei
Guest
Developers spend a surprising amount of time dealing with project setup.
Before writing a single line of code, you often need to determine which runtime a project uses, install dependencies, navigate documentation, and remember language-specific commands. Switching between Node.js, Python, and Rust projects can feel like context-switching between entirely different ecosystems.
I wanted to explore a simple question:
What if running a project was always the same command, regardless of the language behind it?
That question led me to build Axiom, a Rust-based command-line tool designed to detect, prepare, and run projects automatically.
This article focuses on the technical decisions, architecture, challenges, and lessons learned while building the project.
Most developers have experienced something like this:
Each ecosystem has its own package manager, conventions, and startup process.
Individually, these systems work well. The friction appears when you frequently move between projects and languages.
I wanted a workflow that looked more like:
No matter what type of project was inside the directory.
I chose Rust for several reasons.
Project detection and orchestration are primarily filesystem operations. Rust's performance makes these tasks extremely fast while keeping resource usage low.
One of the goals was making installation easy.
Rust allows static binaries that can be distributed without requiring users to install additional runtimes.
Axiom spends most of its time examining project structures and executing external commands.
Rust's type system helped catch many mistakes during development before they could become runtime bugs.
At a high level, Axiom consists of four major systems:
The first challenge is determining what kind of project exists inside a directory.
Axiom scans for common indicators:
A simplified version of the logic looks like:
This allows the rest of the application to remain language-agnostic.
One of the most important design decisions was separating project detection from project execution.
Instead of writing special-case logic everywhere, Axiom uses providers.
Conceptually:
Each provider implements behavior such as:
The orchestration layer doesn't need to know whether it's talking to Node, Python, or Rust.
It simply communicates with a provider interface.
This made adding new ecosystems significantly easier than modifying a large collection of conditional statements.
As the project grew, I wanted a structured way to represent relationships between tasks.
Rather than treating setup as a linear list of commands, Axiom models operations as a dependency graph.
For example:
Representing execution this way provided a foundation for future improvements such as:
Even though the current implementation is relatively simple, designing around graphs early makes future expansion easier.
Repeated setup operations can become expensive.
To avoid unnecessary work, Axiom maintains a cache directory.
The cache stores metadata that allows the tool to determine whether certain preparation steps have already been completed.
Conceptually:
Benefits include:
Caching sounds simple in theory, but invalidation quickly becomes difficult.
Determining when cached information is still valid became one of the more challenging parts of development.
The biggest obstacle wasn't writing Rust code.
It was handling the differences between language ecosystems.
Node, Python, and Rust each have:
Creating a common abstraction without losing flexibility required multiple redesigns.
Simple demo projects worked almost immediately.
Real-world projects exposed issues.
Examples included:
Many bugs only appeared after testing against actual repositories rather than toy examples.
A common temptation during development is continuously adding features.
At several points I considered expanding the project into a full development platform.
Instead, I focused on a smaller objective:
Make running projects easier.
Maintaining that focus helped keep development manageable.
One question I wanted to answer was whether automation could meaningfully reduce setup time.
The exact results vary by project size and complexity, but testing showed that removing repetitive setup steps can significantly reduce the time required to get a project running.
The most valuable outcome wasn't raw speed.
It was reducing the number of decisions a developer has to make before they can begin working.
Building Axiom taught me several lessons.
A well-designed architecture creates room for future capabilities.
A poorly designed architecture creates technical debt.
Investing time in provider abstractions and orchestration layers paid off later.
Many designs seem perfect until tested against actual repositories.
The majority of meaningful improvements came from running Axiom against real-world projects.
The most useful developer tools often do one thing exceptionally well.
Every feature added to Axiom was evaluated against a simple question:
"Does this reduce friction for the user?"
If the answer was no, it probably didn't belong.
Several areas remain interesting for future exploration:
The architecture was intentionally designed to allow these capabilities without requiring a complete rewrite.
Axiom started as an attempt to remove repetitive setup work when switching between projects.
What began as a convenience experiment became a deeper exercise in software architecture, abstraction design, and cross-language tooling.
The project reinforced an idea that many developer tools share:
Small reductions in friction compound over time.
When developers spend less energy remembering commands and configuring environments, they can spend more time building software.
Before writing a single line of code, you often need to determine which runtime a project uses, install dependencies, navigate documentation, and remember language-specific commands. Switching between Node.js, Python, and Rust projects can feel like context-switching between entirely different ecosystems.
I wanted to explore a simple question:
What if running a project was always the same command, regardless of the language behind it?
That question led me to build Axiom, a Rust-based command-line tool designed to detect, prepare, and run projects automatically.
This article focuses on the technical decisions, architecture, challenges, and lessons learned while building the project.
The Problem
Most developers have experienced something like this:
Node.js
Code:
npm install
npm run dev
Python
Code:
pip install -r requirements.txt
python main.py
Rust
Code:
cargo run
Each ecosystem has its own package manager, conventions, and startup process.
Individually, these systems work well. The friction appears when you frequently move between projects and languages.
I wanted a workflow that looked more like:
Code:
axiom run
No matter what type of project was inside the directory.
Why Rust?
I chose Rust for several reasons.
Performance
Project detection and orchestration are primarily filesystem operations. Rust's performance makes these tasks extremely fast while keeping resource usage low.
Cross-Platform Distribution
One of the goals was making installation easy.
Rust allows static binaries that can be distributed without requiring users to install additional runtimes.
Reliability
Axiom spends most of its time examining project structures and executing external commands.
Rust's type system helped catch many mistakes during development before they could become runtime bugs.
Architecture Overview
At a high level, Axiom consists of four major systems:
- Project Detection
- Dependency Graph Construction
- Provider Abstraction
- Execution Orchestration
Detection Layer
The first challenge is determining what kind of project exists inside a directory.
Axiom scans for common indicators:
| Language | Detection Files |
|---|---|
| Node.js | package.json |
| Python | requirements.txt, pyproject.toml |
| Rust | Cargo.toml |
A simplified version of the logic looks like:
Code:
Directory
↓
Find Signature Files
↓
Determine Project Type
↓
Select Provider
This allows the rest of the application to remain language-agnostic.
Provider Abstraction
One of the most important design decisions was separating project detection from project execution.
Instead of writing special-case logic everywhere, Axiom uses providers.
Conceptually:
Code:
Provider
├── Node Provider
├── Python Provider
└── Rust Provider
Each provider implements behavior such as:
- Installing dependencies
- Preparing environments
- Running projects
- Reporting status
The orchestration layer doesn't need to know whether it's talking to Node, Python, or Rust.
It simply communicates with a provider interface.
This made adding new ecosystems significantly easier than modifying a large collection of conditional statements.
Dependency Graphs
As the project grew, I wanted a structured way to represent relationships between tasks.
Rather than treating setup as a linear list of commands, Axiom models operations as a dependency graph.
For example:
Code:
Install Runtime
↓
Install Dependencies
↓
Prepare Environment
↓
Execute Project
Representing execution this way provided a foundation for future improvements such as:
- Parallel operations
- Smarter caching
- Incremental execution
- Build optimization
Even though the current implementation is relatively simple, designing around graphs early makes future expansion easier.
The Cache System
Repeated setup operations can become expensive.
To avoid unnecessary work, Axiom maintains a cache directory.
The cache stores metadata that allows the tool to determine whether certain preparation steps have already been completed.
Conceptually:
Code:
~/.axiom/cache/
Benefits include:
- Reduced startup times
- Less repeated dependency work
- Consistent execution behavior
Caching sounds simple in theory, but invalidation quickly becomes difficult.
Determining when cached information is still valid became one of the more challenging parts of development.
Challenges
Ecosystem Differences
The biggest obstacle wasn't writing Rust code.
It was handling the differences between language ecosystems.
Node, Python, and Rust each have:
- Different conventions
- Different dependency systems
- Different startup commands
- Different assumptions
Creating a common abstraction without losing flexibility required multiple redesigns.
Edge Cases
Simple demo projects worked almost immediately.
Real-world projects exposed issues.
Examples included:
- Missing dependency files
- Unexpected directory layouts
- Alternative package managers
- Custom startup commands
Many bugs only appeared after testing against actual repositories rather than toy examples.
Scope Control
A common temptation during development is continuously adding features.
At several points I considered expanding the project into a full development platform.
Instead, I focused on a smaller objective:
Make running projects easier.
Maintaining that focus helped keep development manageable.
Benchmarking
One question I wanted to answer was whether automation could meaningfully reduce setup time.
The exact results vary by project size and complexity, but testing showed that removing repetitive setup steps can significantly reduce the time required to get a project running.
The most valuable outcome wasn't raw speed.
It was reducing the number of decisions a developer has to make before they can begin working.
What I Learned
Building Axiom taught me several lessons.
Abstractions Matter More Than Features
A well-designed architecture creates room for future capabilities.
A poorly designed architecture creates technical debt.
Investing time in provider abstractions and orchestration layers paid off later.
Real Projects Reveal Real Problems
Many designs seem perfect until tested against actual repositories.
The majority of meaningful improvements came from running Axiom against real-world projects.
Simplicity Wins
The most useful developer tools often do one thing exceptionally well.
Every feature added to Axiom was evaluated against a simple question:
"Does this reduce friction for the user?"
If the answer was no, it probably didn't belong.
Future Work
Several areas remain interesting for future exploration:
- Additional language ecosystems
- Smarter project detection
- Enhanced caching strategies
- AI-assisted project analysis
- Automated environment diagnostics
The architecture was intentionally designed to allow these capabilities without requiring a complete rewrite.
Final Thoughts
Axiom started as an attempt to remove repetitive setup work when switching between projects.
What began as a convenience experiment became a deeper exercise in software architecture, abstraction design, and cross-language tooling.
The project reinforced an idea that many developer tools share:
Small reductions in friction compound over time.
When developers spend less energy remembering commands and configuring environments, they can spend more time building software.