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
1 change: 1 addition & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ jobs:
cp -r crates/capsule-wit crates/capsule-core/
sed -i 's|../../../../capsule-wit|../../../capsule-wit|g' crates/capsule-core/src/wasm/compiler/mod.rs
sed -i 's|"../capsule-wit"|"./capsule-wit"|g' crates/capsule-core/src/wasm/state.rs
sed -i 's|../../../../capsule-wit|../../../capsule-wit|g' crates/capsule-core/src/wasm/utilities/wit_manager.rs

- name: Publish capsule-core
working-directory: crates/capsule-core
Expand Down
38 changes: 38 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ Configure your tasks with these parameters:
| `timeout` | Maximum execution time | `str` | unlimited | `"30s"`, `"5m"`, `"1h"` |
| `max_retries` / `maxRetries` | Number of retry attempts on failure | `int` | `0` | `3` |
| `allowed_files` / `allowedFiles` | Folders accessible in the sandbox | `list` | `[]` | `["./data", "./output"]` |
| `env_variables` / `envVariables` | Environment variables accessible in the sandbox | `list` | `[]` | `["API_KEY"]` |

### Compute Levels

Expand Down Expand Up @@ -304,6 +305,43 @@ Available methods:
- `files.list(path)` — List directory contents
- `files.exists(path)` — Check if file exists

### Environment Variables

Tasks can access environment variables to read configuration, API keys, or other runtime settings.

#### Python

Use Python's standard `os.environ` to access environment variables:
```python
from capsule import task
import os

@task(name="main", env_variables=["API_KEY"])
def main() -> dict:
api_key = os.environ.get("API_KEY")
return {"api_key": api_key}
```

#### TypeScript / JavaScript

Use the `env` API provided by the SDK:
```typescript
import { task, env } from "@capsule-run/sdk";

export const main = task({
name: "main",
envVariables: ["API_KEY"]
}, () => {
const apiKey = env.get("API_KEY");
return { apiKeySet: apiKey !== undefined, environment };
});
```

Available methods:
- `env.get(key)` — Get a specific environment variable (returns `undefined` if not found)
- `env.has(key)` — Check if an environment variable exists
- `env.getAll()` — Get all environment variables as an object


## Compatibility

Expand Down
11 changes: 9 additions & 2 deletions crates/capsule-cli/Cargo.lock

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

5 changes: 3 additions & 2 deletions crates/capsule-cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "capsule-run"
version = "0.4.1"
version = "0.4.2"
edition = "2024"
description = "Secure WASM runtime to isolate and manage AI agent tasks"
license = "Apache-2.0"
Expand All @@ -16,6 +16,7 @@ path = "src/main.rs"

[dependencies]
clap = { version = "4.5.53", features = ["derive"] }
capsule-core = { version= "0.4.1", path = "../capsule-core" }
capsule-core = { version= "0.4.2", path = "../capsule-core" }
tokio = { version = "1.48.0", features = ["rt", "rt-multi-thread", "macros"] }
serde_json = "1"
dotenvy = "0.15.7"
17 changes: 15 additions & 2 deletions crates/capsule-cli/docs/README-pypi.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ Run it:
capsule run hello.py
```

> [!TIP]
> Use `--verbose` to display real-time task execution details.

## How It Works
Expand All @@ -53,7 +52,6 @@ def analyze_data(dataset: list) -> dict:
return {"processed": len(dataset), "status": "complete"}
```

> [!NOTE]
> The runtime requires a task named `"main"` as the entry point. Python can define the main task itself, but it's recommended to set it manually.

When you run `capsule run main.py`, your code is compiled into a WebAssembly module and executed in a dedicated sandbox.
Expand Down Expand Up @@ -97,6 +95,7 @@ Every task returns a structured JSON envelope containing both the result and exe
| `timeout` | Maximum execution time | `str` | unlimited | `"30s"`, `"5m"` |
| `max_retries` | Retry attempts on failure | `int` | `0` | `3` |
| `allowed_files` | Folders accessible in the sandbox | `list` | `[]` | `["./data", "./output"]` |
| `env_variables` | Environment variables accessible in the sandbox | `list` | `[]` | `["API_KEY"]` |

### Compute Levels

Expand Down Expand Up @@ -154,6 +153,20 @@ def main() -> str:
restricted_writer()
```

### Environment Variables

Tasks can access environment variables to read configuration, API keys, or other runtime settings. Use Python's standard `os.environ` to access environment variables:

```python
from capsule import task
import os

@task(name="main", env_variables=["API_KEY"])
def main() -> dict:
api_key = os.environ.get("API_KEY")
return {"api_key": api_key}
```

## Compatibility

✅ **Supported:**
Expand Down
24 changes: 22 additions & 2 deletions crates/capsule-cli/npm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ Run it:
capsule run hello.ts
```

> [!TIP]
> Use `--verbose` to display real-time task execution details.

## How It Works
Expand Down Expand Up @@ -71,7 +70,6 @@ export const main = task({
});
```

> [!NOTE]
> The runtime requires a task named `"main"` as the entry point.

When you run `capsule run main.ts`, your code is compiled into a WebAssembly module and executed in a dedicated sandbox.
Expand Down Expand Up @@ -115,6 +113,7 @@ Every task returns a structured JSON envelope containing both the result and exe
| `timeout` | Maximum execution time | `string` or `number` | unlimited | `"30s"`, `"5m"`, `30000` (ms) |
| `maxRetries` | Retry attempts on failure | `number` | `0` | `3` |
| `allowedFiles` | Folders accessible in the sandbox | `string[]` | `[]` | `["./data", "./output"]` |
| `envVariables` | Environment variables accessible in the sandbox | `string[]` | `[]` | `["API_KEY"]` |

### Compute Levels

Expand Down Expand Up @@ -171,6 +170,27 @@ Available methods:
- `files.list(path)` — List directory contents
- `files.exists(path)` — Check if file exists

### Environment Variables

Tasks can access environment variables to read configuration, API keys, or other runtime settings. Use the `env` API provided by the SDK:

```typescript
import { task, env } from "@capsule-run/sdk";

export const main = task({
name: "main",
envVariables: ["API_KEY"]
}, () => {
const apiKey = env.get("API_KEY");
return { apiKeySet: apiKey !== undefined };
});
```

Available methods:
- `env.get(key)` — Get a specific environment variable (returns `undefined` if not found)
- `env.has(key)` — Check if an environment variable exists
- `env.getAll()` — Get all environment variables as an object

## Compatibility

✅ **Supported:**
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.4.1",
"version": "0.4.2",
"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.4.1",
"@capsule-run/cli-darwin-x64": "0.4.1",
"@capsule-run/cli-linux-x64": "0.4.1",
"@capsule-run/cli-win32-x64": "0.4.1"
"@capsule-run/cli-darwin-arm64": "0.4.2",
"@capsule-run/cli-darwin-x64": "0.4.2",
"@capsule-run/cli-linux-x64": "0.4.2",
"@capsule-run/cli-win32-x64": "0.4.2"
}
}
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.4.1"
version = "0.4.2"
description = "Secure WASM runtime to isolate and manage AI agent tasks"
readme = "docs/README-pypi.md"
license = {text = "Apache-2.0"}
Expand Down
29 changes: 23 additions & 6 deletions crates/capsule-cli/src/commands/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,20 @@ fn extract_main_execution_policy(
Some(task_config.to_execution_policy(capsule_toml))
}

fn load_env_variables(project_root: &Path) -> Result<(), RunError> {
let env_files = vec![".env", ".env.local", ".env.development", ".env.production"];

for file_name in env_files {
let file_path = project_root.join(file_name);

if file_path.exists() {
dotenvy::from_path(&file_path).map_err(|e| RunError::IoError(e.to_string()))?;
}
}

Ok(())
}

pub async fn execute(
file_path: Option<&Path>,
args: Vec<String>,
Expand All @@ -135,6 +149,15 @@ pub async fn execute(
reporter.finish_progress(Some("Environment ready"));

reporter.start_progress("Initializing runtime");

let project_root = file_path
.canonicalize()
.ok()
.and_then(|p| p.parent().map(|p| p.to_path_buf()))
.unwrap_or_else(|| std::env::current_dir().unwrap_or_default());

load_env_variables(&project_root)?;

let runtime_config = RuntimeConfig {
cache_dir: compile_result.cache_dir,
verbose,
Expand All @@ -146,12 +169,6 @@ pub async fn execute(

let runtime = Runtime::new(runtime_config, manifest.capsule_toml)?;

let project_root = file_path
.canonicalize()
.ok()
.and_then(|p| p.parent().map(|p| p.to_path_buf()))
.unwrap_or_else(|| std::env::current_dir().unwrap_or_default());

let create_instance_command = CreateInstance::new(execution_policy.clone(), args.clone())
.wasm_path(compile_result.wasm_path)
.project_root(project_root);
Expand Down
2 changes: 1 addition & 1 deletion crates/capsule-core/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-core/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "capsule-core"
version = "0.4.1"
version = "0.4.2"
edition = "2024"
description = "Core library for Capsule - WASM runtime for AI agent isolation"
license = "Apache-2.0"
Expand Down
1 change: 1 addition & 0 deletions crates/capsule-core/src/config/manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ pub struct DefaultPolicy {
pub default_timeout: Option<String>,
pub default_max_retries: Option<u64>,
pub default_allowed_files: Option<Vec<String>>,
pub default_env_variables: Option<Vec<String>>,
}

#[derive(Debug, Default, Deserialize)]
Expand Down
7 changes: 7 additions & 0 deletions crates/capsule-core/src/wasm/commands/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,17 @@ impl RuntimeCommand for CreateInstance {

capsule::host::api::add_to_linker(&mut linker, |state: &mut State| state)?;

let envs = std::env::vars()
.collect::<Vec<_>>()
.into_iter()
.filter(|(key, _)| self.policy.env_variables.contains(key))
.collect::<Vec<_>>();

let mut wasi_builder = WasiCtxBuilder::new();
wasi_builder
.inherit_stdout()
.inherit_stderr()
.envs(&envs)
.args(&self.args);

for path_spec in &self.policy.allowed_files {
Expand Down
7 changes: 4 additions & 3 deletions crates/capsule-core/src/wasm/compiler/javascript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,11 @@ impl JavascriptWasmCompiler {
import * as hostApi from 'capsule:host/api';
import * as fsTypes from 'wasi:filesystem/types@0.2.0';
import * as fsPreopens from 'wasi:filesystem/preopens@0.2.0';
import * as environment from 'wasi:cli/environment@0.2.0';
globalThis['capsule:host/api'] = hostApi;
globalThis['wasi:filesystem/types'] = fsTypes;
globalThis['wasi:filesystem/preopens'] = fsPreopens;
globalThis['wasi:cli/environment'] = environment;
import '{}';
import {{ exports }} from '{}/dist/app.js';
export const taskRunner = exports;
Expand All @@ -165,6 +167,7 @@ export const taskRunner = exports;
.arg("--platform=neutral")
.arg("--external:capsule:host/api")
.arg("--external:wasi:filesystem/*")
.arg("--external:wasi:cli/*")
.arg(format!("--outfile={}", bundled_path_normalized.display()))
.current_dir(&sdk_path_normalized)
.stdout(Stdio::piped())
Expand Down Expand Up @@ -293,9 +296,7 @@ export const taskRunner = exports;
if !output.status.success() {
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
eprintln!("TypeScript compilation failed!");
eprintln!("stdout: {}", stdout);
eprintln!("stderr: {}", stderr);

return Err(JavascriptWasmCompilerError::CompileFailed(format!(
"TypeScript compilation failed: {}{}",
stderr.trim(),
Expand Down
Loading