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
29 changes: 28 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,33 @@ Capsule controls CPU usage through WebAssembly's **fuel mechanism**, which meter
- **HIGH** grants maximum fuel for compute-intensive operations
- **CUSTOM** to specify an exact fuel value (e.g., `compute="1000000"`) for precise control over execution limits.

### Project Configuration (Optional)

You can create a `capsule.toml` file in your project root to set default options for all tasks and define workflow metadata:

```toml
# capsule.toml

[workflow]
name = "My AI Workflow"
version = "1.0.0"
entrypoint = "src/main.py" # Default file when running `capsule run`

[tasks]
default_compute = "MEDIUM"
default_ram = "256MB"
default_timeout = "30s"
default_max_retries = 2
```

With an entrypoint defined, you can simply run:

```bash
capsule run
```

Task-level options always override these defaults when specified.

### HTTP Client API

#### Python
Expand Down Expand Up @@ -237,7 +264,7 @@ export const restrictedWriter = task({
});

export const main = task({ name: "main" }, async () => {
restrictedWriter();
await restrictedWriter();
return await files.readText("./data/input.txt");
});
```
Expand Down
4 changes: 2 additions & 2 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,11 @@ This document tracks the development status of Capsule.

## v0.4.0: Configuration & Observability

**Status:** 🚧 Planned
**Status:** In progress

**Goal:** Improve developer workflow with project configuration and richer task feedback.

- [ ] **Config File:** `capsule.toml` for default task options.
- [x] **Config File:** `capsule.toml` for default task options.
- [ ] **Structured Output:** Tasks return a detailed JSON envelope with execution metadata:

```json
Expand Down
61 changes: 55 additions & 6 deletions crates/capsule-cli/Cargo.lock

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

2 changes: 1 addition & 1 deletion crates/capsule-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@ path = "src/main.rs"

[dependencies]
clap = { version = "4.5.53", features = ["derive"] }
capsule-core = { version= "0.3.2", path = "../capsule-core" }
capsule-core = { version= "0.4.0", path = "../capsule-core" }
tokio = { version = "1.48.0", features = ["rt", "rt-multi-thread", "macros"] }
serde_json = "1"
18 changes: 18 additions & 0 deletions crates/capsule-cli/docs/README-pypi.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,24 @@ When you run `capsule run main.py`, your code is compiled into a WebAssembly mod
- **HIGH**: Maximum fuel for compute-intensive operations
- **CUSTOM**: Specify exact fuel value (e.g., `compute="1000000"`)

### Project Configuration (Optional)

Create a `capsule.toml` file in your project root to set default options:

```toml
[workflow]
name = "My AI Workflow"
version = "1.0.0"
entrypoint = "src/main.py" # Run `capsule run` without specifying a file

[tasks]
default_compute = "MEDIUM"
default_ram = "256MB"
default_timeout = "30s"
```

Task-level options always override these defaults.

### HTTP Client

Standard `requests` library isn't compatible with WASM. Use Capsule's HTTP client:
Expand Down
20 changes: 19 additions & 1 deletion crates/capsule-cli/npm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,24 @@ When you run `capsule run main.ts`, your code is compiled into a WebAssembly mod
- **HIGH**: Maximum fuel for compute-intensive operations
- **CUSTOM**: Specify exact fuel value (e.g., `compute="1000000"`)

### Project Configuration (Optional)

Create a `capsule.toml` file in your project root to set default options:

```toml
[workflow]
name = "My AI Workflow"
version = "1.0.0"
entrypoint = "src/main.ts" # Run `capsule run` without specifying a file

[tasks]
default_compute = "MEDIUM"
default_ram = "256MB"
default_timeout = "30s"
```

Task-level options always override these defaults.

### File Access

The **entry point task** (main) has access to the entire project directory. Sub-tasks have **no filesystem access by default** and must declare `allowedFiles` to access specific paths.
Expand All @@ -108,7 +126,7 @@ export const restrictedWriter = task({
});

export const main = task({ name: "main" }, async () => {
restrictedWriter();
await restrictedWriter();
return await files.readText("./data/input.txt");
});
```
Expand Down
10 changes: 5 additions & 5 deletions crates/capsule-cli/npm/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@capsule-run/cli",
"version": "0.3.2",
"version": "0.4.0",
"description": "Secure WASM runtime to isolate and manage AI agent tasks",
"bin": {
"capsule": "./bin/capsule.js"
Expand Down Expand Up @@ -29,9 +29,9 @@
"node": ">=18"
},
"optionalDependencies": {
"@capsule-run/cli-darwin-arm64": "0.3.2",
"@capsule-run/cli-darwin-x64": "0.3.2",
"@capsule-run/cli-linux-x64": "0.3.2",
"@capsule-run/cli-win32-x64": "0.3.2"
"@capsule-run/cli-darwin-arm64": "0.4.0",
"@capsule-run/cli-darwin-x64": "0.4.0",
"@capsule-run/cli-linux-x64": "0.4.0",
"@capsule-run/cli-win32-x64": "0.4.0"
}
}
2 changes: 1 addition & 1 deletion crates/capsule-cli/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "maturin"

[project]
name = "capsule-run"
version = "0.3.2"
version = "0.4.0"
description = "Secure WASM runtime to isolate and manage AI agent tasks"
readme = "docs/README-pypi.md"
license = {text = "Apache-2.0"}
Expand Down
2 changes: 1 addition & 1 deletion crates/capsule-cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pub struct Cli {
#[derive(Subcommand)]
pub enum Commands {
Run {
file: String,
file: Option<String>,

#[arg(short, long)]
verbose: bool,
Expand Down
20 changes: 17 additions & 3 deletions crates/capsule-cli/src/commands/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ use std::fmt;
use std::path::{Path, PathBuf};
use std::time::Instant;

use capsule_core::config::manifest::{Manifest, ManifestError};

use capsule_core::wasm::commands::create::CreateInstance;
use capsule_core::wasm::commands::run::RunInstance;
use capsule_core::wasm::compiler::javascript::{
Expand Down Expand Up @@ -52,6 +54,12 @@ impl From<WasmRuntimeError> for RunError {
}
}

impl From<ManifestError> for RunError {
fn from(err: ManifestError) -> Self {
RunError::ExecutionFailed(err.to_string())
}
}

struct CompileResult {
wasm_path: PathBuf,
cache_dir: PathBuf,
Expand Down Expand Up @@ -88,22 +96,28 @@ fn compile_to_wasm(file_path: &Path) -> Result<CompileResult, RunError> {
}

pub async fn execute(
file_path: &Path,
file_path: Option<&Path>,
args: Vec<String>,
verbose: bool,
) -> Result<String, RunError> {
let manifest = Manifest::new()?;
let mut reporter = TaskReporter::new(true);

let file_path: PathBuf = match file_path {
Some(path) => path.to_path_buf(),
None => PathBuf::from(manifest.get_entrypoint()?),
};

reporter.start_progress("Preparing environment");
let compile_result = compile_to_wasm(file_path)?;
let compile_result = compile_to_wasm(&file_path)?;
reporter.finish_progress(Some("Environment ready"));

reporter.start_progress("Initializing runtime");
let runtime_config = RuntimeConfig {
cache_dir: compile_result.cache_dir,
verbose,
};
let runtime = Runtime::with_config(runtime_config)?;
let runtime = Runtime::new(runtime_config, manifest.capsule_toml)?;

let execution_policy = ExecutionPolicy::default()
.compute(Some(Compute::Custom(u64::MAX)))
Expand Down
3 changes: 2 additions & 1 deletion crates/capsule-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ async fn main() -> Result<(), CliError> {
verbose,
args,
} => {
run::execute(Path::new(&file), args, verbose).await?;
let file_path = file.as_deref().map(Path::new);
run::execute(file_path, args, verbose).await?;
}
}

Expand Down
Loading