Language persisted

BigConfig Package Spec

A friendly implementation guide for building a repository that bc-pkg can pin, initialize, and run. The launcher contract is intentionally small: one language manifest, one root run file, installable package code, and a command surface that forwards cleanly to your BigConfig lifecycle.

bc-pkg compatible GitHub SHA pinned local path dev Clojure target Python target TypeScript target validate · build · create · describe · delete
Scope This page describes target packages launched by bc-pkg. There are Python and npm implementations of the launcher; there is no Clojure implementation of bc-pkg itself, but both launchers can run Clojure target packages.

Quick checklist

If these are true, your package is usually ready for a first launcher test:

Detectable

Exactly one root manifest exists: deps.edn, package.json, or pyproject.toml. TypeScript and Python manifests declare a package name.

Runnable

A root run file exists, forwards argv, and calls your CLI/workflow with a safe default profile or options map.

Installable

The package works from a GitHub commit SHA and from a local path. Generated code, built JS, and template resources are included.

Safe

No credentials ship in source. Destructive actions require an explicit user-controlled setting or environment override.

Package shape

A BigConfig package is just a normal language package with one extra convention: a root run file that becomes the user-facing entrypoint after initialization.

my-package/
├── package.json          # exactly one root manifest
├── run                   # copied by GitHub mode, symlinked by local mode
├── src/
│   ├── cli.ts
│   └── resources/...
└── dist/                 # built JS included for install/run
my-package/
├── pyproject.toml        # exactly one root manifest
├── run                   # copied by GitHub mode, symlinked by local mode
└── src/
    ├── my_package/
    └── resources/...
my-package/
├── deps.edn              # exactly one root manifest
├── bb.edn                # useful in the package repo; generated for consumers too
├── run                   # copied by GitHub mode, symlinked by local mode
└── src/
    ├── clj/...
    └── resources/...
Ambiguous roots fail fast The launcher treats a repo with more than one of deps.edn, package.json, and pyproject.toml as ambiguous. Keep each launcher target in its own language-specific root.

Launcher contract

what bc-pkg does

The launcher does not know your domain model. It only resolves a target, writes a small consumer project in the current directory, then forwards the remaining arguments to your copied run file.

Repository root contract

FileUsed forNotes
deps.ednClojure target detectionSelected only when no package.json or pyproject.toml exists at the root.
package.jsonTypeScript target detectionname is used as the dependency name in the generated consumer manifest.
pyproject.tomlPython target detection[project].name is used as the dependency name in the generated consumer manifest.
runUser-facing entrypointRequired. GitHub targets copy it; local targets symlink it where possible.

Language-specific manifest notes

Clojure CLJ

Declare normal Clojure deps and include resources in :paths. The launcher synthesizes the GitHub coordinate as io.github.<owner>/<repo> for GitHub targets.

Python PY

Expose [project].name, keep the package installable by uv, and include template resources in the wheel/sdist. With Hatch, force-include is one option.

TypeScript TS

Expose name, include the root run, resources, and built JavaScript in files. Local mode expects the built runtime target to exist.

// package.json (compact shape)
{
  "name": "my-package",
  "type": "module",
  "bin": { "my-package": "./dist/src/cli.js" },
  "files": ["dist/src", "src/resources", "run"]
}
# pyproject.toml (compact shape)
[project]
name = "my-package"
requires-python = ">=3.12"
dependencies = ["big-config @ git+https://github.com/..."]

[tool.hatch.build.targets.wheel]
packages = ["src/my_package"]
force-include = { "src/resources" = "src/resources" }
;; deps.edn (compact shape)
{:paths ["src/clj" "src/resources"]
 :deps {io.github.bigconfig-ai/big-config {:git/sha "..."}}}

Generated consumer files

On first run, bc-pkg creates or updates a small native project in the current directory. The metadata records the chosen target and prevents accidental switches later.

Target languageGenerated filesRuntime commandDependency form
Clojurerun, deps.edn, bb.ednbb run ...:git/url + :git/sha, or :local/root
TypeScriptrun, package.jsonnode run ...github:owner/repo#sha, or file:
Pythonrun, pyproject.tomluv run python run ...Git PEP 508 dependency, or editable [tool.uv.sources]

Local development mode

For live package development, pass a local path as the first argument. Paths starting with /, ./, ../, ~, or exactly ./.. are treated as local targets.

# Python launcher
uvx bc-pkg ../once/python package build

# npm launcher
npx bc-pkg ../once/typescript package build

# Clojure target through either launcher
uvx bc-pkg /abs/path/to/once/clojure package validate
Live but isolated Local mode symlinks run so run-file edits are picked up immediately, but it refuses to target the current directory. Run the launcher from a separate consumer directory so it cannot overwrite the package's own manifest.

Pins, updates, and re-entry

  • A GitHub branch or tag is resolved once to a full 40-character commit SHA.
  • Subsequent runs read the generated BigConfig metadata and reuse the pinned target.
  • Passing the same repo/ref again is allowed only when it resolves to the same SHA.
  • Switching repo, ref, SHA, local path, or local-vs-GitHub mode is a hard error rather than an implicit upgrade.

To intentionally move to a new package revision, use a new consumer directory or deliberately remove the generated consumer files and re-initialize with care.

Package contract

what authors provide

Root run file

The root run file is the package's handoff to the user. Treat it as both an executable launcher and a small editable config template. It should forward argv and pass a safe default profile/options map into your package CLI or workflow.

#!/usr/bin/env node
const DEFAULT_PROFILE = {
  profile: "default",
  params: { domain: "REPLACE_ME", package: "default" },
};
const { main } = await import("my-package/dist/src/cli.js");
main(process.argv.slice(2), DEFAULT_PROFILE);
#!/usr/bin/env python3
import sys
from my_package.cli import main

DEFAULT_PROFILE = {
    "profile": "default",
    "params": {"domain": "REPLACE_ME", "package": "default"},
}

if __name__ == "__main__":
    main(sys.argv[1:], DEFAULT_PROFILE)
#!/usr/bin/env bb
(require '[my.package.cli :as cli])

(def default-profile
  {:big-config.render/profile "default"
   :big-config.workflow/params {:domain "REPLACE_ME"
                                :package "default"}})

(cli/main* *command-line-args* default-profile)
Keep it user-safe The copied run file lands in a user's working directory. Use placeholders like REPLACE_ME, never real tokens, SSH keys, API passwords, or customer-specific infrastructure IDs.

Command surface

The launcher forwards arguments exactly as written. Your package decides what commands mean. A BigConfig package is easiest to understand when it exposes a small lifecycle vocabulary:

CommandPurpose
package validatePre-flight checks: profile shape, tools, credentials, reachable dependencies, image references, and other safe checks.
package buildRender artifacts into .dist/ without applying changes.
package createCreate or reconcile real resources.
package describeReport the current state after provisioning.
package deleteTear down resources, usually behind explicit safety settings.

Packages can support chaining, for example package validate create or package delete create, when their workflow engine composes named steps in order.

BigConfig lifecycle

BigConfig workflows thread an opts map/dict/object through step functions. Reserved engine keys are namespaced strings such as big-config/exit, big-config/err, big-config.workflow/steps, and big-config.workflow/params. Keep those boundary keys stable and keep shell execution behind the runner seam so package tests can run without spawning real tools.

Workflow steps

Use workflow, workflow_star / createWorkflowStar, and run_steps / runSteps to assemble lifecycle commands from smaller tool workflows.

Pure reports

Keep report builders like validation and describe pure, with side effects injected. That lets agents and CI verify behavior without provisioning resources.

Profiles, params, and environment overrides

Profile params should use kebab-case strings that match template variable names. Runtime overrides use BC_PAR_*: uppercase the key and replace hyphens or dots with underscores.

export BC_PAR_DOMAIN="example.com"
export BC_PAR_PROVIDER_BACKEND="r2"
export BC_PAR_COMPUTE_PREVENT_DESTROY="false"

Profiles can be composed from smaller provider/application maps, as once does for compute provider, SMTP, DNS, backend, deploy keys, and application definitions.

Templates and resources

Put template trees in packaged resources and make sure they ship in the installable artifact. The common convention is src/resources/<namespace-path>/tools/.... The renderer copies selected templates into .dist/.

LanguageResource packaging reminder
ClojureAdd src/resources to :paths.
PythonInclude resources in the wheel/sdist; force-include is one Hatch option.
TypeScriptInclude src/resources in files and resolve it from built code.

Many BigConfig packages use custom Selmer delimiters for file content. Once configures tag open <, tag close >, filter open {, and filter close }, so template variables are written as <{ var }>. That leaves literal {{ ... }} available for downstream tools like Ansible.

Provider selection Directory/provider selection is usually handled by the renderer transform list, for example [[provider, delimiters]], so a render step copies only the matching provider subdirectory.

Safety conventions

  • Generated output belongs in .dist/; never edit it as source.
  • Destructive operations should have an explicit guard or profile parameter.
  • Validation should be opt-in and runnable before create, but packages can let users chain validate create.
  • Keep credentials in the user's environment or private ignored files, not in package source or copied run files.

Author test matrix

Before publishing a package ref, test both the GitHub and local path flows in a disposable consumer directory.

# GitHub target through both launchers where practical
uvx bc-pkg owner/repo@ref package validate
npx bc-pkg owner/repo@ref package validate

# Re-entry should omit the target and use generated metadata
uvx bc-pkg package build
npx bc-pkg package build

# Local live-development target
uvx bc-pkg ../path/to/package package build
npx bc-pkg ../path/to/package package build
  • Delete the generated run file and re-run to confirm restoration works.
  • Try an intentionally different repo/ref/local path and confirm the launcher refuses the switch.
  • Inspect generated manifests for correct metadata and dependency form.
  • Search copied files for accidental credentials.
  • If your package promises deterministic build output, compare the generated .dist/ artifact.

Troubleshooting

Message / symptomLikely fix
has no deps.edn, package.json, or pyproject.tomlAdd exactly one supported root manifest.
ambiguous; found ... manifestsSplit language implementations into separate roots or worktrees.
has no runAdd a root run file and make it executable on POSIX systems.
Existing non-bc-pkg manifest conflictInitialize in a fresh consumer directory or migrate the manifest carefully.
Missing node, npm, or uvInstall the runtime required by the target language.
Private repo or rate-limit failureSet GITHUB_TOKEN and retry.
Clojure target cache problemsRemove the relevant bc-pkg cache directory for Babashka/JDK and retry; install git manually if auto-install is unavailable.

Future conformance checklist/test

A future conformance tool could make this checklist executable. The rough shape is a small harness that creates a temporary consumer directory, runs both launchers against GitHub and local targets, verifies generated metadata, tests re-entry and refusal semantics, restores a missing run file, and executes a harmless lifecycle command such as package validate or package build.

That harness is intentionally not part of this page. For now, this spec is the human-readable guide package authors can use while implementing and reviewing packages.