Basic CLI platform for Spore — provides file I/O, process execution, environment variables, and standard I/O effects.
In Spore, a platform bridges the pure, effect-tracked language with real-world side effects. basic-cli is the standard platform for command-line applications.
Your Spore App (pure, effect-checked)
↕ (effect dispatch)
basic-cli Platform API (.sp modules)
↕ (foreign fn)
Rust Host (actual I/O via std::fs, std::process, etc.)
↕
Operating System
| Module | Effects | Description |
|---|---|---|
basic_cli.stdout |
uses [Console] |
Standard output |
basic_cli.stdin |
uses [Console] |
Standard input |
basic_cli.file |
uses [FileRead] or uses [FileWrite] |
File system operations |
basic_cli.dir |
uses [FileRead] or uses [FileWrite] |
Directory listing and creation |
basic_cli.env |
uses [Env] |
Environment variables |
basic_cli.cmd |
uses [Spawn] or uses [Exit] |
Process execution and explicit exit |
The canonical project-mode structure is demonstrated in examples/hello-app/:
examples/hello-app/spore.toml:
[package]
name = "hello-app"
version = "0.1.0"
type = "application"
spore-version = ">=0.1.0"
[project]
platform = "basic-cli"
default-entry = "app"
[entries.app]
path = "main.sp"
[dependencies]
basic-cli = { path = "../.." }examples/hello-app/src/main.sp:
import basic_cli.stdout as stdout
fn main() -> () uses [Console] {
println("Hello from a project-mode Spore application!");
return
}
Run the project:
cd examples/hello-app
spore check src/main.sp
spore run src/main.spThis is a real package-backed application. The in-repo example points basic-cli at ../..; projects generated by spore new vendor the package and use vendor/basic-cli instead. The formatter currently normalizes the import to import basic_cli.stdout as stdout, while println still resolves directly in scope as an imported platform API call.
For quick experiments, you can also run pure standalone .sp files (see examples/hello.sp), but production applications that call platform APIs such as println should use the project-mode structure above.
basic-cli/
├── spore.toml # Platform manifest
├── src/
│ ├── host.sp # Legacy compatibility shim
│ ├── platform_contract.sp
│ └── basic_cli/ # Spore API modules, checked and built in CI
│ ├── stdout.sp # Standard output operations
│ ├── stdin.sp # Standard input operations
│ ├── file.sp # File read/write operations
│ ├── dir.sp # Directory operations
│ ├── env.sp # Environment variable access
│ └── cmd.sp # Process execution
├── host/ # Rust host implementation
│ ├── Cargo.toml
│ └── src/
│ └── lib.rs # Foreign function implementations
└── examples/ # Canonical examples that format/check/run in CI
The current Platform contract MVP is intentionally split across two artifacts:
spore.toml[platform]metadata names the contract module, the startup contract symbol, the adapter function, and the handled effects.src/platform_contract.spowns the startup contract itself:- a hole-backed
mainfunction carries the authoritative startup signature main_for_hostis the Platform-owned adapter that receives the application startup function
- a hole-backed
Applications targeting basic-cli must implement the same startup function name/signature in their entry module. The current compiler already resolves the package's [platform] metadata and src/platform_contract.sp to validate that startup shape. Project-mode interpreter runtime support now goes through a generic package-backed host path: imported public foreign fn declarations bind by canonical operation name (print, file_exists, exit, etc.) instead of hard-coding the basic-cli package name. Startup spec stacking and native lowering for foreign fn platform projects are still follow-up work.
src/host.sp remains as a compatibility copy of the adapter for older references; current manifest-backed projects use src/platform_contract.sp.
examples/hello-app/is the canonical project-mode example — it is formatted, checked, and run in CI.examples/hello.spis a minimal pure standalone file for quick experiments — it is formatted, checked, and run in CI.src/basic_cli/is the API surface for the platform modules themselves.src/platform_contract.spis the package-owned startup contract surface.- Only add
tests/when the repo has real Spore-side regression coverage worth running withspore test.
If you want to add a new tutorial/example, treat this as the bar:
- keep it self-contained;
- make sure it passes
spore format --check,spore check, andspore run; - only then promote it into
examples/and mention it in this README.
Native spore build is still useful for platform API modules and pure standalone files, but it is not required for package-backed platform projects that rely on foreign fn declarations until the backend supports foreign/platform lowering.
Project-mode applications call imported basic-cli platform functions directly:
import basic_cli.stdout as stdout
fn main() -> () uses [Console] {
println("Hello from basic-cli!");
return
}
In this style, the uses [Console] clause records the capability required by the direct println platform API call. The lower-level perform Effect.op(...) form belongs to effect-handler syntax; use it only when documenting or implementing explicit handlers, not for the normal basic-cli project-mode tutorial path.
Following Spore's SEP-0003 (Effect System):
- Effect-gated: Every I/O function declares its required effects via
uses [Effect] - Cost-annotated: Platform functions can carry
cost [c, a, i, p]budgets where meaningful - Error-typed: Functions declare error sets via
! ErrorType - Pure by default: The platform boundary is the only place side effects occur
🚧 Early development — API is unstable and subject to change.
The canonical example is the package-backed project-mode examples/hello-app/ application. It already validates and runs with [project].platform = "basic-cli", an in-repo path dependency, and import basic_cli.stdout.
The pure standalone file example (examples/hello.sp) stays around for quick experiments. The main remaining platform gaps are startup spec stacking and native lowering for foreign fn platform projects.
MIT — see LICENSE.