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

fieldmeaning
moduleThe package's identity, normally its forge path. This is what ecko get resolves.
versionSemantic version. A release workflow asserts the git tag matches it.
entrypointThe file loaded on import. Defaults to main.ecko.
eckoThe lowest Ecko the package works on. Checked on import.
descriptionOne line. It appears in generated documentation.
author, licenseMetadata.
capabilitiesWhat the package expects to need. Advisory - see below.
dependenciesWhat it imports, and what each is granted.
environmentEnvironment 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. The commands that edit this file - ecko get, ecko remove - rewrite only the dependencies value and leave every other byte as you wrote it: your keys, their order, your formatting, and an explicit "capabilities": [] (which says the package is pure, so it is kept even though it is empty).

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_LIMIT_DEPTH": "5000" } }

Applied before evaluation, to any variable the shell has not already set - the same env-over-file precedence as std.config, so what the person running the program exported always wins. (Before 0.58 the block overrode the shell.) Never put a secret in it.

Ten ECKO_* variables are refused from a manifest - the ones that decide where data goes or what is trusted, like ECKO_AI_BASE_URL and ECKO_NET_ALLOW_HTTP. See defaults for the list and the reasoning.