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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
name: Lint & Format

on:
pull_request:
branches:
- main
push:
branches:
- main

jobs:
rust-fmt:
name: Rust Format Check
runs-on: ubuntu-latest

steps:
- name: Checkout repo
uses: actions/checkout@v4

- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt

- name: Check formatting (capsule-cli)
working-directory: crates/capsule-cli
run: cargo fmt --all -- --check

- name: Check formatting (capsule-core)
working-directory: crates/capsule-core
run: cargo fmt --all -- --check

rust-clippy:
name: Rust Clippy Check
runs-on: ubuntu-latest

steps:
- name: Checkout repo
uses: actions/checkout@v4

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

- name: Run clippy (capsule-cli)
working-directory: crates/capsule-cli
run: cargo clippy --all-targets --all-features -- -D warnings

- name: Run clippy (capsule-core)
working-directory: crates/capsule-core
run: cargo clippy --all-targets --all-features -- -D warnings
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,3 @@ capsule.egg-info
**/*.rs.bk
*.pdb
**/mutants.out*/

2 changes: 1 addition & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ This document tracks the development status of Capsule. We follow a "Release Ear
- [x] **Core:** Rust Host capable of loading Wasm Components.
- [x] **SDK (Python):** Basic `@task` decorator with JSON serialization.
- [x] **CLI:** `capsule run main.py` with JIT compilation.
- [ ] **Limits:** Basic Fuel metering for CPU protection.
- [x] **Limits:** Basic Fuel metering for CPU protection.

---

Expand Down
62 changes: 62 additions & 0 deletions crates/capsule-cli/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions crates/capsule-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ name = "capsule-cli"
version = "0.1.0"
edition = "2024"

[[bin]]
name = "capsule"
path = "src/main.rs"

[dependencies]
clap = { version = "4.5.53", features = ["derive"] }
capsule-core = { path = "../capsule-core" }
tokio = { version = "1.48.0", features = ["rt", "rt-multi-thread", "macros"] }
serde_json = "1"


indicatif = "0.17"
76 changes: 66 additions & 10 deletions crates/capsule-cli/src/commands/run.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
use std::fmt;
use std::path::Path;
use std::time::Instant;

use indicatif::{ProgressBar, ProgressStyle};

use capsule_core::wasm::commands::create::CreateInstance;
use capsule_core::wasm::commands::run::RunInstance;
use capsule_core::wasm::execution_policy::ExecutionPolicy;
use capsule_core::wasm::compiler::python::{PythonWasmCompiler, PythonWasmCompilerError};
use capsule_core::wasm::runtime::WasmRuntimeError;
use capsule_core::wasm::execution_policy::{Compute, ExecutionPolicy};
use capsule_core::wasm::runtime::Runtime;
use capsule_core::wasm::runtime::WasmRuntimeError;

pub enum RunError {
IoError(String),
CompileFailed(String),
IoError(String),
CompileFailed(String),
}

impl fmt::Display for RunError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RunError::IoError(msg) => write!(f, "{}", msg),
RunError::CompileFailed(msg) => write!(f, "{}", msg),
RunError::CompileFailed(msg) => write!(f, "🛠️ Mission aborted: {}", msg),
}
}
}
Expand All @@ -42,28 +45,81 @@ impl From<WasmRuntimeError> for RunError {

pub async fn execute(file_path: &Path, args: Vec<String>) -> Result<String, RunError> {
let compiler = PythonWasmCompiler::new(file_path)?;

let spinner = ProgressBar::new_spinner();
spinner.set_style(
ProgressStyle::default_spinner()
.tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"])
.template("{spinner:.cyan} {msg}")
.unwrap(),
);
spinner.enable_steady_tick(std::time::Duration::from_millis(80));

spinner.set_message("Preparing capsule environment");

let wasm_path = compiler.compile_wasm()?;

spinner.finish_and_clear();

let spinner = ProgressBar::new_spinner();
spinner.set_style(
ProgressStyle::default_spinner()
.tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"])
.template("{spinner:.cyan} {msg}")
.unwrap(),
);
spinner.enable_steady_tick(std::time::Duration::from_millis(80));

spinner.set_message("Launching capsule runtime");
let runtime_config = capsule_core::wasm::runtime::RuntimeConfig {
cache_dir: compiler.cache_dir.clone(),
};
let runtime = Runtime::with_config(runtime_config)?;

let execution_policy = ExecutionPolicy::default();
let create_instance_command = CreateInstance::new(execution_policy.clone(), args.clone())
.wasm_path(wasm_path);
let execution_policy = ExecutionPolicy::default().compute(Some(Compute::High));
let create_instance_command =
CreateInstance::new(execution_policy.clone(), args.clone()).wasm_path(wasm_path);

let (store, instance, task_id) = runtime.execute(create_instance_command).await?;
spinner.finish_and_clear();

println!("📡 Capsule in orbit. Systems nominal.");

let start_time = Instant::now();

let args_json = serde_json::json!({
"task_name": "main",
"args": args,
"kwargs": {}
}).to_string();
})
.to_string();

let run_instance_command = RunInstance::new(task_id, execution_policy, store, instance, args_json);
let run_instance_command =
RunInstance::new(task_id, execution_policy, store, instance, args_json);

let result = runtime.execute(run_instance_command).await?;

let elapsed = start_time.elapsed();
let time_str = format_duration(elapsed);
println!("✓ Complete ({})", time_str);

Ok(result)
}

fn format_duration(duration: std::time::Duration) -> String {
let total_secs = duration.as_secs_f64();

if total_secs < 60.0 {
format!("{:.2}s", total_secs)
} else if total_secs < 3600.0 {
let minutes = (total_secs / 60.0).floor() as u64;
let seconds = total_secs % 60.0;
format!("{}m {:.0}s", minutes, seconds)
} else {
let hours = (total_secs / 3600.0).floor() as u64;
let remaining_secs = total_secs % 3600.0;
let minutes = (remaining_secs / 60.0).floor() as u64;
let seconds = remaining_secs % 60.0;
format!("{}h {}m {:.0}s", hours, minutes, seconds)
}
}
8 changes: 3 additions & 5 deletions crates/capsule-cli/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
pub mod cli;
pub mod commands;

use clap::Parser;
use std::fmt;
use std::path::Path;
use clap::Parser;

use cli::{Cli, Commands};
use commands::{run, RunError};
use commands::{RunError, run};

#[derive(Debug)]
pub enum CliError {
RunError(String),
RunError(String),
}

impl fmt::Display for CliError {
Expand All @@ -27,8 +27,6 @@ impl From<RunError> for CliError {
}
}



#[tokio::main]
async fn main() -> Result<(), CliError> {
let cli = Cli::parse();
Expand Down
7 changes: 7 additions & 0 deletions crates/capsule-core/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 1 addition & 3 deletions crates/capsule-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ crate-type = ["rlib"]

[dependencies]
anyhow = "1"
humantime = "2"
nanoid = "0.4.0"
rusqlite = "0.37.0"
serde = { version = "1.0.228", features = ["derive"] }
Expand All @@ -18,6 +19,3 @@ wasmtime-wasi = "29.0.0"

[dev-dependencies]
rustfmt = "0.10.0"



Loading