The ecko.json manifest
ecko init # scaffold one
{
"module": "github.com/ecko-lang/url",
"version": "0.9.5",
"entrypoint": "main.ecko",
"description": "URL parsing, query strings, and reference resolution.",
"license": "MIT",
"capabilities": [],
"dependencies": {}
}
Fields
| field | meaning |
|---|---|
module | The package's identity, normally its forge path. This is what ecko get resolves. |
version | Semantic version. A release workflow asserts the git tag matches it. |
entrypoint | The file loaded on import. Defaults to main.ecko. |
ecko | The lowest Ecko the package works on. Checked on import. |
description | One line. It appears in generated documentation. |
author, license | Metadata. |
capabilities | What the package expects to need. Advisory - see below. |
dependencies | What it imports, and what each is granted. |
environment | Environment variables to apply before the program runs. |
Any other field is yours. The whole manifest is readable from the program as std.defaults, which is why an app manifest often carries port, db_path and similar - configuration with no parsing code.
Keys must be valid identifiers, because they are read as defaults.<key>. A key with a dash is an error rather than a value you cannot reach.
ecko is the version you need
{ "module": "github.com/me/gps", "version": "1.0.0",
"entrypoint": "main.ecko", "ecko": "0.30.0" }
Someone importing this on an older build is told so:
package 'gps' needs Ecko 0.30 or newer, and this is 0.29.0.
Upgrade with `curl -fsSL https://ecko.sh/install | sh`, or pin an
earlier version of the package.
Without it the failure surfaces wherever the missing feature happens to be used - a message about a verb that does not exist, pointing nowhere near the cause.
Compared on major and minor only. A package at 0.30.4 needs the 0.30 line, not that exact patch, so a patch release of the package never demands a patch release of Ecko.
Leave it out if you do not need one. Absent means "any", which is right for most packages, and a floor set higher than the truth only turns people away who would have been fine.
capabilities is advisory
The list documents intent. What a package actually gets is the grant its importer gives it, and nothing else. A manifest asking for net in a project that grants nothing gets nothing.
Keep it honest anyway: it is what a reader checks first, and [] is a genuine claim worth making - it says this package cannot touch the network, the filesystem or the environment.
Dependencies
{
"dependencies": {
"url": "github.com/ecko-lang/url@v0.9.5",
"mysql": {
"path": "github.com/ecko-lang/mysql-client",
"version": "v0.9.5",
"grant": ["net"]
}
}
}
The short form is a string. The long form adds grant, and is also how you choose the import name: the key is what import binds. That matters when a repository name is not a valid identifier - mysql-client cannot be an Ecko identifier, so it is aliased to mysql.
Name and directory must agree
import foo reads vendor/.../foo/ecko.json and errors if the manifest disagrees with the directory it was found in. This is why a repository named for its forge slug declares the import name its users will type.
A malformed manifest is a hard error, not a silent zero-grant. Failing loudly was a deliberate change: a typo used to produce a package that mysteriously could not do anything.
environment
{ "environment": { "ECKO_AI_PROVIDER": "ollama", "ECKO_MAX_DEPTH": "5000" } }
Applied before evaluation, and it overrides the surrounding shell - the opposite precedence to std.config. The reason is that this block exists so a project can pin the environment its code expects, which it cannot do if whatever happens to be exported wins. Machine-specific values belong in the shell, not here. Never put a secret in it.