# Packages A Deft package is a directory of modules with a `package.dft` manifest. Packages are the unit of distribution: install via `pkg/install`, reference by `owner/name`, import from any module. ## Package Identity Every package has a canonical `owner/name` id (e.g. `deft/editor`). Owners are globally unique on the registry; `deft` is reserved for official packages. The id is passed as a single string to every registry operation:
[pkg/install "deft/editor"]
[pkg/info    "deft/editor"]
[pkg/uninstall "deft/editor"]
## Importing Package Modules The `owner/name` id is also the import path prefix. Packages under a `:pkgs` search root (or installed by `pkg/install`) are mounted automatically as `owner/name`, so modules import without any configuration:
import "deft/ui/components/button" as button
Pin an exact version with `@version` after the name:
import "deft/ui@0.1.0/components/button" as button
- Unpinned `owner/name/...` resolves through the `:pkgs` roots in listed order — the first root providing the package wins. For installed packages the version comes from the `latest` pointer written by `pkg/install`, else the highest installed semver. - `owner/name@latest/...` is an explicit alias for unpinned. - A pin that matches a dev checkout's manifest version resolves to the dev tree (same root-order rules); otherwise it matches an installed version directory. - A missing pin errors with the list of available versions; a bare package prefix (e.g. `ui/...`) errors with a `did you mean "owner/name/..."?` hint. Dev checkouts, installed trees, and the legacy versioned layouts are all honoured — the difference between "working on a package" and "using a package" is only which root provides it, not the import syntax. ## The Manifest (`package.dft`) The manifest declares the package's identity, version, dependencies, and entry point:
def manifest %Package{
    :owner       "alice"
    :name        "math-lib"
    :version     "1.0.0"
    :description "Personal math helpers"
    :entry       "src/main.dft"
    :deps @{
        %Dep{ :owner "deft" :name "stdext" :version "0.2.0" }
    |
    }
    :capabilities %PackageCapabilities{
        :ffi false
        :tui false
    |
    }
}
`[pkg/read-manifest "path/to/dir"]` reads and validates a manifest, returning `%Package{ ... }` or `%Err{ ... }`. ## `pkg/*` Stdlib functions | Function | Returns | Purpose | |---|---|---| | `[pkg/set-registry $url]` | nil | Set registry URL (nil resets to local) | | `[pkg/get-registry]` | url or nil | Current registry | | `[pkg/install "owner/name" ?version]` | summary map or `%Err` | Install | | `[pkg/install-bytes "owner/name" $ver $bytes]` | summary map | Install pre-fetched tarball | | `[pkg/uninstall "owner/name"]` | bool or `%Err` | Uninstall | | `[pkg/list]` | list | Packages across all search roots | | `[pkg/list-paths]` | list of strings | Search roots | | `[pkg/available]` | list | Packages in the current registry | | `[pkg/info "owner/name" ?version]` | map | Package detail | | `[pkg/fetch "owner/name" ?version]` | bytes or Future | Download tarball | | `[pkg/read-manifest $dir]` | `%Package` or `%Err` | Read manifest | | `[pkg/mount $export-path $dir]` | nil | Register an import mount | ## Install Locations Installs always target the first search root (canonical: `.deft/packages/`), producing the owner-scoped versioned layout: ``` .deft/packages/<owner>/<name>/<version>/package.dft .deft/packages/<owner>/<name>/<version>/src/... ```` `pkg/install` also writes a `latest` text pointer beside the version directories (`.deft/packages/<owner>/<name>/latest`) so unpinned `owner/name` imports have a defined target. ## Search Paths At startup every `:pkgs` root is scanned and each package found is auto-mounted as `owner/name` (plus `owner/name@version` pins and the `@latest` alias). Roots are scanned in listed order — the first root providing a package wins, so a dev tree listed before `.deft/packages/` shadows the installed copies. Explicit prelude `:mounts` entries always win over the auto-scan. Use `[pkg/list-paths]` to see the active search roots:
[pkg/list-paths]
# => @{ ".deft/packages" }
All on-disk layouts are honoured: - Flat (dev): `<root>/<name>/package.dft` - Legacy versioned: `<root>/<name>/<version>/package.dft` - Owner-scoped dev: `<root>/<owner>/<name>/package.dft` - Owner-scoped installed: `<root>/<owner>/<name>/<version>/package.dft` ## Registries Two registry transports: - `dir://` (default) — tarballs live under `virt-pkg/packages/<owner>/<name>/<version>.tar.gz`. A `latest` symlink or text file is honoured. Good for local development and airgapped environments. - `http(s)://` — tarballs fetched from `<url>/packages/<owner>/<name>/<version>.tar.gz`. Set the URL with `[pkg/set-registry $url]` or the `--url` CLI flag. The `latest` file is honoured.
pkg/set-registry "https://packages.example.com"
echo [pkg/get-registry]
# => https://packages.example.com

pkg/set-registry nil                           # reset to local virt-pkg
The active registry URL is persisted in `prelude.dft`'s `:registry` field, so subsequent `deft` invocations pick it up automatically. ## Installing a Package
def result [pkg/install "deft/editor"]
# => %{ :owner "deft" :name "editor" :version "1.5.0" :path ".deft/packages/deft/editor/1.5.0" }

def specific [pkg/install "deft/editor" "1.4.0"]
`pkg/install` is synchronous — it blocks until the fetch and extract complete. Failures return `%Err{ :message ... }`. ### Manual fetch + install If you've already fetched the tarball bytes yourself (e.g. through a proxy), use `pkg/install-bytes`:
set bytes [my-fetch "https://.../editor-1.5.0.tar.gz"]
pkg/install-bytes "deft/editor" "1.5.0" $bytes
## Mounting a Package Manually Package auto-discovery covers everything under the `:pkgs` roots. `[pkg/mount $name $dir]` is the escape hatch for directories outside them (or for pointing a name at a specific checkout):
pkg/mount "alice/mylib" "/home/alice/dev/mylib"
import "alice/mylib/helpers" as helpers
Mount names may be multi-segment (`alice/mylib`) — imports resolve the longest matching mount prefix first. ## Creating a Package 1. Lay out the directory: ``` my-package/ ├── package.dft └── src/ ├── main.dft └── helpers.dft ```` 2. Write the manifest (`package.dft`):
def manifest %Package{
    :owner       "alice"
    :name        "my-package"
    :version     "0.1.0"
    :description "An example package."
    :entry       "src/main.dft"
    :deps @{}
|
}
3. (Optional) Test it locally by adding the parent directory to search paths and importing:
pkg/mount "alice/my-package" "/path/to/my-package"
import "alice/my-package/src/main" as mp
4. Publish to your registry (tarball the directory; upload to your HTTP registry or place under `virt-pkg/packages/<owner>/<name>/`). ## Capabilities A package can declare capabilities it needs (FFI, TUI, etc.) in the manifest:
:capabilities %PackageCapabilities{
    :ffi true
    :tui false
|
}
The runtime uses these to surface compatibility warnings when a package is loaded into a runtime missing a required capability. ## App Help (`help/`) An app package can ship its own help by adding a `help/` directory: ``` my-app/ ├── package.dft ├── help/ │ ├── help.md # main page │ └── shortcuts.md # extra docs └── src/ ```` `help/help.md` is the main page (any other `.md` file in the directory becomes an extra doc). The help app's Apps tab scans every configured package root for `help/` directories and lists the apps it finds; the panel-name palette's Help action opens an app's help there. The website indexes the same directories under `/platform/apps/<name>`, and full-text Search/Apropos includes them as the `app` corpus. ## Quick Reference | Form | Purpose | |---|---| | `[pkg/install "owner/name" ?ver]` | Install | | `[pkg/uninstall "owner/name"]` | Uninstall | | `[pkg/list]` | List installed | | `[pkg/available]` | List in registry | | `[pkg/info "owner/name" ?ver]` | Show detail | | `[pkg/set-registry $url]` | Switch registry | | `[pkg/fetch "owner/name" ?ver]` | Download tarball | | `[pkg/mount $export-path $dir]` | Register a mount | | `[pkg/read-manifest $dir]` | Read a `package.dft` |