ENV · ARGS · READ_FILE
Pull host values into the transpilation¶
Capy stays a transpiler — but the values that flow into the accumulated context can come from outside the source: environment variables, positional CLI args, and sibling files. Four inner-DSL primitives expose them, gated by a sandboxed default so the playground stays safe.
The four primitives¶
| Primitive | Returns | Backed by (CLI) |
|---|---|---|
(env "NAME") |
OS env var, or "" if unset |
os.Getenv |
(arg N) |
Nth positional CLI arg (0-indexed) | os.Args (post script-path) |
(arg_count) |
how many positional args were supplied | len(os.Args[2:]) |
(args) |
full positional args list | os.Args[2:] |
(read_file "PATH") |
file contents (errors abort) | os.ReadFile |
(os) |
host OS identifier ("linux", "darwin", "windows", …) | runtime.GOOS |
(arch) |
host arch ("amd64", "arm64", …) | runtime.GOARCH |
(cwd) |
current working directory | os.Getwd |
(home_dir) |
user's home directory | os.UserHomeDir |
Use them as expressions in any function body:
function service
arg literal "service"
arg capture name string
set context.service name
set context.environment (env "ENV")
set context.version (arg 0)
set context.api_keys (read_file "keys.txt")
end
The Host abstraction¶
Capy doesn't call os.Getenv directly from the evaluator. Instead the
inner evaluator holds a domain.Host interface:
type Host interface {
Env(name string) string
Arg(i int) string
ArgCount() int
Args() []string
ReadFile(path string) (string, error)
}
Three implementations ship in the engine:
infra.OSHost— realos.Getenv/os.Args/os.ReadFile. The CLI installs this socapy run lib.capy script.capy a b cworks.ReadFileresolves relative paths against the script's directory.domain.NoOpHost— every method returns the zero value (or an error forReadFile). The default for embedded Go callers and the only host the wasm playground uses. Sandboxed by design.- anything else you implement. Test mocks, in-memory file maps, feature-flag stores, secret-manager backends — anything that satisfies the four methods.
Opting into real host access from Go¶
capy.NewLibrary defaults to NoOpHost. If you're embedding Capy in a
trusted environment and want libraries to see your env/files, opt in:
import (
"os"
"github.com/olivierdevelops/capy"
"github.com/olivierdevelops/capy/infra"
)
lib, _ := capy.NewLibraryFromFile("lib.capy")
lib.SetHost(infra.OSHost{
UserArgs: os.Args[1:],
BaseDir: ".",
})
out, _ := lib.Run(scriptSrc)
The opt-in is explicit on purpose: when you compose Capy with
user-supplied libraries, leaving the default NoOpHost means the
library cannot exfiltrate your environment.
CLI¶
The CLI installs OSHost automatically. Positional args after the
script path are visible to (arg N):
ENV=production DATABASE_URL=postgres://... \
capy run lib.capy script.capy v2.3.1 us-west-2 eu-west
# ↑ arg 0 ↑ arg 1 ↑ arg 2
read_file paths resolve relative to the script's directory, so:
reads dirname(scriptPath)/api-keys.txt.
Playground¶
The wasm playground exposes the same primitives but they're
permanently bound to NoOpHost. A sample library that calls
(env "FOO") will see "". This is by design — the playground runs
arbitrary user-supplied libraries and a malicious one shouldn't be able
to exfiltrate your env, list your local files, or read your
~/.aws/credentials.
The host-capabilities sample includes a "graceful degradation" note
in its description so newcomers understand why the playground output
differs from the CLI run.
Worked example¶
The full sample lives at
samples/host-capabilities/.
A 5-line source produces a complete Kubernetes Deployment manifest
that incorporates env vars, CLI args, and the contents of a sibling
secrets file at transpile time.
When NOT to use this¶
These primitives let a library reach into the host. That's powerful but also a tight coupling — your library now depends on something external being set up correctly. Lean toward solving the same problem with declarative input in the source first:
over
…with the library silently reading ENV and arg 0. Use the host
primitives when the values genuinely belong to the deployment context
(secrets, environment, infrastructure metadata) rather than the
"shape" of what's being generated.