Skip to content

axon-pkg

πŸ“¦ axpm β€” The Axon Package Manager

Git-based dependency resolution with Minimum Version Selection (MVS). S-expression manifest format, content-addressed lock files, local caching.

🚧 Under Development β€” axpm install is the canonical dependency-installation entrypoint. Remaining work focuses on registry publishing, signing, and ergonomic polish.

Terminal window
# 1. Create a new project
axpm init my-project
# 2. Add a dependency
axpm add https://git.catalystgroup.tech/labs/axon/axon-std.git
# 3. Install (resolve + lock + fetch) β€” the canonical entrypoint
axpm install
# Or in CI for strict reproducibility:
axpm install --frozen # alias for --locked --offline

Full per-command reference (synopsis, options, exit codes, examples, env vars) lives in docs/cli.md.

CommandDescription
axpm init [name]Create a new project with a .axon-pkg manifest
axpm add <git-url> [version]Add a dependency to the manifest
axpm install [--flags]Resolve + lock + fetch dependencies (canonical entrypoint)
axpm fetchDownload dependencies to ~/.axon/cache/ (legacy alias)
axpm resolveDisplay the resolved dependency tree without fetching
axpm buildCompile the project with axonc
axpm test [dir]Run the test suite
axpm cache-infoShow cached packages in ~/.axon/cache/
axpm search <query>Search the package registry index (with multi-registry fallback)
axpm publishGenerate a JSON registry entry for the current package
axpm registry <list|add|remove|use|test|path>Manage the global registry list (mirrors, fallbacks). See docs/registry.md
axpm login <url> [--token T] [--scheme S]Store an auth token for a private registry (mode 0600). See docs/auth.md
axpm logout <url>Remove a stored registry credential
axpm outdated [--json] [--strict]Check for newer package versions
axpm upgrade [pkg...] [--flags]Bump dependencies to newer versions
axpm vendor [dir] [--verify]Copy all locked deps into a local vendor/ directory for offline builds
axpm verify [--strict] [--deps-dir D]CI gate: lockfile consistency + content-hash verification
axpm audit [--json] [--strict-deps] [--db PATH]Cross-reference .axon-lock against an advisory DB; exit 2 on P0/P1
axpm keygen [--name <id>]Generate an Ed25519 signing keypair at $HOME/.axon/keys/<id>.key (mode 0600)
axpm sign <pkg>Sign a .axpkg with your Ed25519 key, writing a .sig sidecar
axpm verify-pkg <pkg>Verify a detached Ed25519 signature against a package. See docs/signing.md
axpm policy printPretty-print the parsed (policy ...) block from axon.manifest. See docs/policy.md
axpm policy checkEnforce the declared policy against .axon-lock. Exits 1 on violations.
axpm clean-cacheRemove all cached packages
axpm --helpShow CLI help
axpm --versionShow version
Terminal window
axpm init my-project
axpm init # uses current directory name
axpm init --name mylib --version 0.1.0 --license MIT # non-interactive
axpm init --force # overwrite an existing manifest

Creates a .axon-pkg manifest, src/ and tests/ directories, and a src/main.axs starter file. Fails if .axon-pkg already exists, unless --force is given.

FlagEffect
--name <name>Package name (default: current directory name). Overrides a positional [name].
--version <ver>Package version (default: 0.1.0).
--license <id>Record a (license "<id>") field in the manifest, e.g. --license MIT.
--forceOverwrite an existing .axon-pkg instead of erroring.

Passing any of --name/--version/--license runs init non-interactively (no prompts).

Note: the manifest filename is .axon-pkg for now. The canonical-name decision is tracked in #139; the (license ...) field is written into the manifest but is not yet part of the formal schema for the same reason.

Terminal window
axpm add https://git.catalystgroup.tech/labs/axon/axon-std.git
axpm add https://git.example.com/repo.git ">=0.2.0"

Adds a dependency entry to .axon-pkg. Accepts an optional semver version constraint (e.g., ">=0.2.0").

The canonical entrypoint for dependency installation. Performs three steps in one command:

  1. Resolve dependencies from .axon-pkg
  2. Lock exact versions into .axon-lock (SHA-pinned)
  3. Fetch source code to ~/.axon/cache/ and symlink to .axon/deps/
FlagEffect
(none)Resolve + lock + fetch (default)
--lockedFail (exit 3) if .axon-lock is missing or out-of-sync with manifest
--offlineNever touch the network; refuse to install if a dep is missing from cache
--no-fetchResolve + lock only; do not clone or pull
--frozenAlias for --locked --offline (recommended for CI)

Exit codes: 0 success, 1 generic failure, 2 network error, 3 lockfile mismatch.

Terminal window
axpm resolve

Displays the resolved dependency tree using MVS (Minimum Version Selection) without touching the network or writing lock files. Use this to preview what axpm install would resolve before committing to fetching.

Example output:

axon-std: resolved to 0.2.1 (git rev abc123)
(transitive deps listed indented)
Terminal window
axpm verify # consistency + content-hash check
axpm verify --strict # also fail on missing hashes / unfetched deps
axpm verify --consistency-only # skip hash verification

A CI-friendly strict gate that fails with a stable exit code when .axon-lock is missing, out of sync with .axon-pkg, or when a fetched dependency no longer matches the content hash recorded in the lockfile.

ExitMeaning
0Consistent + hashes verified
3Lockfile inconsistent with manifest (version/URL/name drift)
4Content-hash mismatch (supply-chain warning)
5No .axon-lock present

axpm install --locked / --frozen run the same consistency check (plus hash verification) before fetching, so CI pipelines can use either axpm install --frozen (install + gate) or axpm verify (gate only).

Terminal window
axpm build

Compiles the project’s entry point (specified in .axon-pkg) using axonc, with .axon/deps/ on the library search path. Produces a binary in build/. Requires a valid .axon-pkg manifest and an entry point file.

Terminal window
axpm test # runs tests/ directory
axpm test custom-dir # runs custom-dir/ directory

Discovers all test_*.axs and *_test.axs files in the test directory, compiles each with axonc, runs them, and compares output against .expected files if present. Requires a valid .axon-pkg manifest.

A workspace groups several member packages under one root so a single axpm install / build / test operates over all of them, sharing dependencies through the global cache. The root is described by an axon.manifest file holding a (workspace ...) form:

;; axon.manifest β€” at the monorepo root
(workspace
(members ("packages/*" "tools/**"))
(deps
(dep "shared-util" (path "../shared-util"))
(dep "logging" (git "https://example.com/logging.git")
(version ">=1.0.0"))))
  • (members ...) β€” a list of glob patterns. Supported wildcards: * (any run of characters within one path segment), ** (zero or more whole path segments), and ? (one character). Matched directories that contain a .axon-pkg become members; matched directories without one are skipped. Members are discovered in deterministic lexical order.
  • (deps ...) β€” workspace-level shared dependencies, each a normal (dep "name" ...) with a path or git/version source.

A member is an ordinary package (its own .axon-pkg). It refers to a shared dependency by alias β€” the alias is the shared dep’s name β€” using the (workspace) marker instead of an inline source:

;; packages/app/.axon-pkg
(package
(name "app")
(version "0.1.0")
(entry "src/main.axs")
(dependencies
(dep "shared-util" (workspace)))) ; resolved from the root's shared deps
Terminal window
axpm install # at the workspace root: discover members, write the lock,
# fetch shared deps once (deduplicated), resolve each member
axpm build # build every member; fails if any member fails
axpm test # run every member's tests; fails if any member fails

Shared dependencies are deduplicated through the global cache: a git shared dep is cloned once into ~/.axon/cache/ and every member links to it; a path shared dep is symlinked to the one real directory. No N copies.

Install writes a deterministic axon.workspace.lock recording member paths (sorted) and shared resolved dependencies (sorted by name). It contains no timestamps, host-absolute paths, or discovery-order artifacts, so repeated runs produce byte-identical output.

Error handling. Discovery rejects: malformed globs; a matched member whose .axon-pkg is unreadable/malformed; a member that is itself a workspace (recursive cycle); and members escaping the root after canonicalization (symlinks are never followed). Resolving a member (workspace) alias that has no matching root declaration is a hard error. See docs/workspace.md for the full reference.

Terminal window
axpm cache-info

Shows all packages currently cached in ~/.axon/cache/. Lists package name, cached path, and whether each is up-to-date with the lockfile.

Terminal window
axpm search <query>

Searches the registry index for packages whose name or description matches <query>. Uses the registry at https://labs.axon-lang.dev/registry/index.json by default.

The full registry list lives in $HOME/.axon/registries.json and is managed by axpm registry. When more than one registry is configured, axpm search tries each in order β€” the first one to return a valid index body wins. To see which registry served your request, watch the β€œserved by registry:” log line.

Two override mechanisms:

Terminal window
# One-shot CI override β€” bypasses the list entirely
AXON_REGISTRY_URL="https://ci-pin.example.com/registry/index.json" axpm search <query>
# Local file-backed index (works without network)
AXON_REGISTRY_URL="file:///path/to/index.json" axpm search <query>

file:// URLs are supported for local registry indexes.

Terminal window
axpm registry list
axpm registry add https://axpm-cdn.example.com/registry/index.json
axpm registry use https://axpm-cdn.example.com/registry/index.json
axpm registry remove https://labs.axon-lang.dev/registry/index.json
axpm registry test
axpm registry path

Manage a prioritized list of registry URLs (primary + fallbacks). The list lives in $HOME/.axon/registries.json and is consulted by axpm search and any future registry-bound command.

SubcommandEffect
listShow all configured registries in priority order
add <url>Append <url> to the fallback list (rejects duplicates + non-http URLs)
remove <url>Remove <url> from the list (restores DEFAULT if it would be empty)
use <url>Make <url> the new primary; demote the current primary to the end
test [url...]Reachability check (5s timeout per URL); exits 1 if any URL is unreachable
pathPrint the absolute path of the config file

The full command reference and design rationale live in docs/registry.md.

Terminal window
# Store a token for a private registry (Bearer scheme by default)
axpm login https://reg.example --token abc123
# Read the token from stdin (keeps it out of your shell history)
echo "$MY_TOKEN" | axpm login https://reg.example
# Read the token from the environment
AXPM_TOKEN=abc123 axpm login https://reg.example
# GitHub/GitLab-style PATs use the "token" scheme
axpm login https://git.example.com --scheme token --token glpat-xxxx
# List registries with stored credentials (never prints the token)
axpm login --list
# Remove a credential
axpm logout https://reg.example

Store per-registry authentication tokens so axpm search (and future registry-bound commands) can pull from private / internal registries. When a request goes to a registry you’ve logged into, axpm attaches an Authorization: <scheme> <token> header automatically. A 401/403 from a registry prints a hint telling you to run axpm login.

Flag / argEffect
<registry-url>Registry to authenticate to (matched by scheme+host+port; path ignored)
--token TToken value. Precedence: --token β†’ $AXPM_TOKEN β†’ stdin
--scheme SAuth scheme for the header (default Bearer; use token for PATs)
--listList registries with stored credentials (no secrets printed)

Credentials are stored in $HOME/.axon/credentials.json, created mode 0600 (owner read/write only) inside the 0700 $HOME/.axon/ directory β€” the same secure-file posture as the Ed25519 signing keys. The token is matched to a registry by scheme + host + port (the URL path is ignored), so a token stored for https://reg.example/registry/index.json also authenticates https://reg.example/packages/foo.tar. See docs/auth.md for the full model.

Terminal window
axpm publish

Reads the local .axon-pkg manifest and git remote to generate a JSON registry entry suitable for submission to a registry index. The output includes package name, version, git URL, description, and the current git tag SHA.

{
"name": "my-package",
"version": "0.1.0",
"git": "https://git.example.com/my-package.git",
"description": "...",
"sha": "abc123def456"
}

Requires the project to be a git repository with an origin remote. Recommend running git tag v<version> before publishing.

Terminal window
axpm outdated
axpm outdated --json
axpm outdated --strict

Reads the .axon-lock file and checks each locked package against a registry (or mock registry for testing) to determine if newer versions are available.

FlagEffect
--jsonOutput results as a JSON array
--strictExit non-zero if any package has a newer version

Example output:

NAME CURRENT LATEST STATUS
---- ------- ----- ------
libfoo 1.0.0 2.0.0 major
libbar 0.1.0 0.3.0 minor
libbaz 3.0.0 3.1.0 patch

JSON output (--json):

[
{"name":"libfoo","current":"1.0.0","latest":"2.0.0","outdated":true},
{"name":"libbar","current":"0.1.0","latest":"0.3.0","outdated":true},
{"name":"libbaz","current":"3.0.0","latest":"3.1.0","outdated":true}
]

Status values:

  • up-to-date: Current version matches latest
  • major: New major version available (e.g., 1.x β†’ 2.x)
  • minor: New minor version available (e.g., 1.2.x β†’ 1.3.x)
  • patch: New patch version available (e.g., 1.2.0 β†’ 1.2.1)
  • unknown: Package not found in registry

Mock registry: Set AXPM_MOCK_REGISTRY environment variable to a JSON file path for testing without a real registry.

Terminal window
axpm upgrade # dry-run (show what would upgrade)
axpm upgrade --all # actually apply upgrades
axpm upgrade libfoo # upgrade specific package
axpm upgrade libfoo --to=1.2.3 # pin to specific version
axpm upgrade --interactive # prompt for each package

Reads the .axon-pkg manifest and .axon-lock lockfile, then upgrades packages to the latest versions satisfying their version constraints.

FlagEffect
(none)Dry-run: show what would be upgraded
--allActually apply upgrades to lockfile
--interactivePrompt for each package before upgrading
--to=<version>Pin upgraded package to specific version
--helpShow usage

Example output:

NAME CURRENT UPGRADE TO CONSTRAINT
---- ------- ---------- ---------
libfoo 1.0.0 1.2.0 ^1.0.0
libbar 0.1.0 0.1.0 ~0.1.0
libbaz 3.0.0 3.1.0 >=3.0.0
Dry run β€” no changes made. Use --all to apply.

Constraint behavior:

  • ^1.0.0 β†’ upgrades to latest 1.x.y (won’t accept 2.0.0)
  • ~0.1.0 β†’ upgrades to latest 0.1.x (won’t accept 0.2.0)
  • >=3.0.0 β†’ upgrades to latest available

Mock registry: Uses AXPM_MOCK_REGISTRY environment variable (same as axpm outdated).

Terminal window
axpm vendor # copy deps to ./vendor/
axpm vendor ./my-vendor # copy deps to custom directory
axpm vendor --verify # verify vendor/ matches lockfile hashes
axpm vendor --verify ./my-vendor

Bundles all locked dependencies into a local directory for offline builds and air-gapped environments (CI runners without network, corporate VPNs, IoT, build farms). Each locked dep is copied from ~/.axon/cache/ into the vendor directory, and a VENDOR.txt manifest records the package names, versions, and commit SHAs for traceability.

FlagEffect
(none)Copy all locked deps to vendor/ (or specified dir)
--verifyVerify existing vendor/ matches lockfile hashes (no copy)
--helpShow usage

Air-gapped workflow:

Terminal window
# On a machine with network:
axpm install # populate ~/.axon/cache/ + write .axon-lock
axpm vendor ./vendor # bundle deps into ./vendor/
git add vendor/ # commit the bundled deps
# On the air-gapped machine:
axpm install --offline --vendor-dir=./vendor

Example output:

axpm: vendoring 3 package(s) to vendor/
[1/3] libfoo (1.2.3)
[2/3] libbar (0.1.0)
[3/3] libbaz (3.1.0)
axpm: vendored 3 package(s) to vendor/

Verify example:

Terminal window
$ axpm vendor --verify
axpm: verifying vendor directory: vendor
axpm: all 3 package(s) verified βœ“
CodeMeaning
0Success
1Generic failure (manifest missing, unknown flag, conflict)
2Network error (fetch failed, or offline cache miss)
3Lockfile mismatch under --locked / --frozen
FeatureDescriptionStatus
axpm installCanonical resolve + lock + fetch entrypointβœ…
axpm initCreate new project with manifestβœ…
axpm fetchDownload dependencies via Git (legacy alias)βœ…
axpm resolveDisplay dependency resolution tree without fetchingβœ…
axpm buildCompile project with dependenciesβœ…
axpm testRun test suiteβœ…
axpm add <url>Add dependency to manifestβœ…
Lock filesDeterministic, content-hashedβœ…
MVS resolverMinimum Version Selectionβœ…
Local cache~/.axon/cache/βœ…
--locked / --offline / --frozen flagsCI reproducibilityβœ…
axpm searchRegistry index searchβœ…
axpm publishJSON registry entry generationβœ…
axpm cache-infoShow cached packagesβœ…
axpm clean-cacheRemove cached packagesβœ…
axpm upgradeBump dependencies to newer versionsβœ…
axpm vendorBundle deps into vendor/ for offline buildsβœ…
Clipboard integrationCross-platform text clipboard (macOS/Linux/Windows)βœ…
Code signing (Ed25519)Supply-chain verificationπŸ“‹
;; .axon-pkg β€” Project manifest
(package
(name "my-project")
(version "0.1.0")
(description "An Axon project")
(entry "src/main.axs")
(dependencies
(dep "axon-std"
(git "https://git.example.com/axon/axon-std.git")
(version ">=0.1.0")))
(dev-dependencies
(dep "axon-test"
(path "../axon-test"))))

axon-pkg supports the following version operators in the version field of .axon-pkg manifest dependencies:

OperatorExampleMeaning
**Any version
==1.2.3Exact version
!=!=1.2.3Exclude version
>>1.2.3Greater than
<<2.0.0Less than
>=>=1.2.3Greater or equal
<=<=2.0.0Less or equal
~~1.2.3Same minor: >=1.2.3, <1.3.0
~>~>1.2.3Cargo compat: same as ~
^^1.2.3Same major: >=1.2.3, <2.0.0
||1.2.3 || 2.0.0Union of constraints
  • ^ (caret): Compatible changes. ^1.2.3 matches >=1.2.3, <2.0.0. For ^0.x.y (major=0), it matches >=0.x.y, <0.(y+1).0. For ^0.0.x, it matches exactly 0.0.x.
  • ~ (tilde): Patch-level changes. ~1.2.3 matches >=1.2.3, <1.3.0.
  • ~> (Cargo compat): Same semantics as ~.
  • || (union): 1.0.0 || >=2.0.0 <3.0.0 matches if EITHER constraint is satisfied.

Prerelease versions (e.g., 1.0.0-alpha, 1.0.0-rc.1) follow SemVer 2.0 ordering:

  • 1.0.0-alpha < 1.0.0 < 1.0.1
  • A prerelease version satisfies a constraint like >=1.0.0 only if the base version satisfies it, BUT a stable release does NOT satisfy a prerelease constraint (e.g., 1.0.0 does NOT satisfy >=1.0.0-alpha).
;; .axon-pkg.lock β€” Auto-generated, do not edit
(lock
(version 1)
(packages
(pkg "axon-std"
(version "0.2.1")
(git "https://git.example.com/axon/axon-std.git")
(rev "abc123def456")
(hash "sha256:..."))))
axon-pkg/
β”œβ”€β”€ src/
β”‚ β”œβ”€β”€ main.c # CLI entry point and argument parser (all subcommands)
β”‚ β”œβ”€β”€ manifest.c # S-expression manifest parser
β”‚ β”œβ”€β”€ manifest.h # Manifest data structures
β”‚ β”œβ”€β”€ resolver.c # MVS dependency resolver
β”‚ β”œβ”€β”€ resolver.h # Resolver API
β”‚ β”œβ”€β”€ lockfile.c # Lock file generation/reading
β”‚ β”œβ”€β”€ lockfile.h # Lock file API
β”‚ β”œβ”€β”€ semver.c # Semver constraint matching
β”‚ β”œβ”€β”€ semver.h # Semver API
β”‚ β”œβ”€β”€ sha256.c # SHA-256 content hashing
β”‚ β”œβ”€β”€ sha256.h # SHA-256 API
β”‚ β”œβ”€β”€ util.c # String helpers, path manipulation
β”‚ β”œβ”€β”€ util.h
β”‚ β”œβ”€β”€ test_runner.c # Lightweight test harness used by C unit tests
β”‚ β”œβ”€β”€ test_runner.h
β”‚ └── clipboard.axs # Axon clipboard API surface (axon-pkg #14)
β”œβ”€β”€ tests/
β”‚ β”œβ”€β”€ test_manifest.c
β”‚ β”œβ”€β”€ test_path_deps.c
β”‚ β”œβ”€β”€ test_vendor.c
β”‚ β”œβ”€β”€ test_semver.c
β”‚ β”œβ”€β”€ test_semver_edge.c
β”‚ β”œβ”€β”€ test_lockfile.c
β”‚ β”œβ”€β”€ test_resolver.c
β”‚ β”œβ”€β”€ test_sha256.c
β”‚ β”œβ”€β”€ test_install.c
β”‚ β”œβ”€β”€ test_lockfile_consistency.c # axon-pkg #69 (WIP β€” allow_failure in CI)
β”‚ β”œβ”€β”€ test_add.c
β”‚ β”œβ”€β”€ test_fetcher.c # Git fetcher integration tests
β”‚ β”œβ”€β”€ bench_resolver.c
β”‚ β”œβ”€β”€ test_cli.sh # CLI smoke tests
β”‚ β”œβ”€β”€ test_clipboard.axs # Axon clipboard smoke tests (#14)
β”‚ └── test_clipboard.expected # Expected output (headless)
β”œβ”€β”€ scripts/
β”‚ └── run_tests.sh # End-to-end integration tests
β”œβ”€β”€ tools/ # Mutation testing, mock registries, clipboard
β”‚ β”œβ”€β”€ clipboard.py # Clipboard reference implementation (#14)
β”‚ └── test_clipboard.py # Clipboard Python tests (#14)
β”œβ”€β”€ Makefile # Build system
β”œβ”€β”€ .gitlab-ci.yml # CI pipeline
└── README.md # This file

Note: Git-based dependency fetching is currently inlined in src/main.c (cmd_fetch, cmd_install, etc.) rather than extracted into a dedicated fetch.c/fetch.h pair. Extracting it is tracked but not yet done.

Terminal window
# Build axpm
make
# Run tests
make test
# Install to ~/.local/bin
make install
# Clean build artifacts
make clean
Terminal window
# Human-readable benchmark output
make bench
# JSON output for CI regression gates
make bench-json

The resolver benchmarks measure MVS resolution time across synthetic dependency graphs of varying sizes (10, 50, 100, 200, 500 packages) and graph types (flat, conflict, mixed). See docs/BENCHMARKING.md for the full output format specification and guidance on setting regression thresholds.

  • C compiler (gcc or clang)
  • Git (for dependency fetching)
  • POSIX systems (macOS, Linux)
Terminal window
# Unit tests
make test
# Integration tests (requires axpm binary in ./build/axpm)
make test-integration
  • C99 standard
  • 4-space indentation
  • snake_case for functions and variables
  • UPPER_SNAKE_CASE for constants/macros
  • Comprehensive error handling with descriptive messages
PackageDescriptionStatus
axon-langCompiler & runtimeβœ… Active
axon-stdStandard libraryβœ… Active
axon-pkgPackage manager🚧 WIP
axon-debugDebug toolsπŸ“‹ Planned
axon-examplesExample programsβœ… Active
axon-webProject websiteβœ… Active

MIT