-
Notifications
You must be signed in to change notification settings - Fork 1
Publishing Runners and Bundles
dhee-core is a content-agnostic generative engine. The community extends
it by publishing runners (node executors) and bundles (pipelines)
as npm packages that dhee-core discovers and loads — the same way ESLint
discovers eslint-plugin-* / eslint-config-*.
Two distribution channels — don't conflate them:
- Authoring skills (the how-to guides
bundle-authoring/runner-authoring) ship via the Claude Code plugin marketplace in this repo (.claude-plugin/). For humans/agents writing runners and bundles.- Runners & bundles themselves (the executable code / pipeline data) ship via npm packages following the convention below. For dhee-core to load at runtime.
This document is about (2).
| Artifact | Unscoped | Scoped |
|---|---|---|
| A package that provides runner(s) | dhee-runner-<name> |
@<scope>/dhee-runner-<name> or @<scope>/dhee-runner
|
| A package that provides bundle(s) | dhee-bundle-<name> |
@<scope>/dhee-bundle-<name> or @<scope>/dhee-bundle
|
Examples: dhee-runner-runway, dhee-runner-musicgen,
@acme/dhee-runner; dhee-bundle-anime-storybook, dhee-bundle-podcast,
@acme/dhee-bundle.
Discovery match (mirrors ESLint's eslint-plugin- matcher):
^(@[^/]+/)?dhee-(runner|bundle)(-.+)?$
A package MAY provide both (a bundle that ships the runner it needs) — name it for its primary artifact and declare both entry points (below).
-
keywordsMUST includedhee-runnerand/ordhee-bundle. Discovery matches on the name pattern AND requires the keyword, so an unrelateddhee-runner-utilshelper lib isn't auto-loaded. (ESLint trusts the name alone; we add the keyword guard deliberately.) -
peerDependencies.dhee-coredeclares the engine range, like an eslint plugin peer-depends oneslint. -
dheefield names the entry point(s). Either or both keys.
The module named by dhee.runners exports an array of
{ manifest, runner } pairs (the same shapes the built-in registry uses —
RunnerManifest from src/dag/runners/registry.ts, Runner from
src/dag/schema.ts):
export const runners: Array<{ manifest: RunnerManifest; runner: Runner }> = [
{ manifest: { tool: 'runway.gen3', version: '1.2.0', engineCompat: '>=0.1.0',
credentials: ['RUNWAY_API_KEY'], displayName: 'Runway Gen-3' },
runner: runwayGen3Runner },
];dhee-core calls registry.register(manifest, runner) for each. Tool ids
are dot-namespaced and SHOULD relate to the package
(dhee-runner-runway → runway.*). credentials[] make bundles using
the runner fail validation up front if the env vars are unset.
dhee.bundles is either:
- a directory with one subdirectory per bundle (each a normal bundle
dir:
bundle.json+prompts/+schemas/+workflows/), or - a module exporting
export const bundles: DagBundle[].
A project references such a bundle through the bundle-source scheme
(src/dag/bundleSource.ts), extending the existing built-in:<id> form:
npm:dhee-bundle-podcast # the package's sole/default bundle
npm:@acme/dhee-bundle#anime_storybook # a named bundle within a multi-bundle package
A bundle package that needs a specific runner should depend on its
dhee-runner-* package (or ship the runner itself).
On engine startup dhee-core enumerates installed dependencies whose name matches the regex above and carry the matching keyword, then:
-
runners →
registry.register(...)for each exported pair; -
bundles → added to the bundle-source registry under
npm:<pkg>.
Resolution precedence, highest wins:
- Explicit project/local override (
~/.kshana/runners/, project bundles) - Installed npm package (the declared/closest version)
- Built-in (shipped in dhee-core)
A bundle's dependencies.runners semver ranges still gate at walk start:
the resolved runner must be registered and satisfy the range, or the
bundle fails before any work runs.
A bundle declares the runners it needs in bundle.json
dependencies.runners (tool id → semver range), validated against the
registry before the walk. Those are tool ids, not packages — so there
are two complementary ways to make sure a needed runner is actually
present, and to tell the user how to get it:
-
npm-native (for bundle packages). A
dhee-bundle-*package lists the runner package in itspackage.jsondependencies/peerDependencies(e.g."dhee-runner-runway": "^1"). Thennpm i <bundle-pkg>installs the runner package too, and discovery registers it — no extra step. (Same shape as aneslint-config-*depending on aneslint-plugin-*.) -
Declared install hint (for built-in / local bundles). Add
dependencies.runnerPackagestobundle.jsonmapping a tool id to its npm package:"dependencies": { "runners": { "runway.gen3": ">=1.0.0" }, "runnerPackages": { "runway.gen3": "dhee-runner-runway@^1.2.0" } }
checkBundleRunners(bundle, registry)(src/dag/ecosystem.ts) returns every required runner that isn't registered, each with a readyinstallcommand — the declared package, or, if undeclared, adhee-runner-<namespace>convention guess. A discovery / "configure this bundle" UI surfacesnpm i <package>so the user can install the missing runner before running.
Installing a dhee-runner-* package means running its code in your
engine — treat it like any npm dependency. Runners can declare required
credentials[]; review what a third-party runner asks for before
supplying keys.
Implemented in src/dag/ecosystem.ts:
-
findEcosystemPackages()— scan node_modules (cwd up;DHEE_NODE_MODULES_DIRSoverride) for name-matching + keyword-guarded packages. -
discoverNpmRunners(reg)— import eachdhee.runnersentry and register the runners; idempotent (skips already-registered tools), best-effort (one bad package never poisons the rest). Wired intorunProjectViaBundleviaensureNpmRunnersLoaded()(once per process) so npm runners are available before the bundle's runner dependencies are validated. -
findNpmBundles()+ thenpm:<pkg>[#<id>]bundle-source scheme (bundleSource.ts) +listBundles()enumeration —dhee-bundle-*packages resolve and show up in the picker (lower precedence than user / built-in, so a same-id fork still wins). -
checkBundleRunners()— missing-runner detection withnpm iinstall hints (see above).
Alongside the pre-existing loaders: built-in runners
(src/dag/runners/index.ts), custom runners from ~/.kshana/runners/
(runner.json, discovery.ts), and project/installed bundles
(installBundle.ts).
Bundles & Runners
{ "name": "dhee-runner-runway", "version": "1.2.0", "keywords": ["dhee-runner"], // REQUIRED guard — see below "peerDependencies": { "dhee-core": ">=0.1.0" }, "dhee": { // declares what this package exports "runners": "./dist/runners.js", // module exporting runners (see below) "bundles": "./bundles" // dir OR module exporting bundles } }