TypeScript-first env tooling for repos that want one manifest-driven way to load, validate, generate, and clear environment files.
Keystone now also includes an experimental service-map layer for resolving local-process, Docker-style, and Portless-exposed service endpoints before env files are generated.
It is still marked experimental because the service-map contract has not yet been proven against a real consumer like Hive, and the public schema may still tighten as we validate real orchestration flows.
The package now builds to dist/ with tsup, so npm/file installs consume compiled library and CLI artifacts rather than raw source files.
For consumers outside TypeScript, Keystone should be treated as having two contracts:
- a TypeScript SDK exported from
@aureatus/keystone - an OpenAPI wire contract at
openapi/service-map.openapi.yamlfor service-to-service or cross-runtime consumption
The OpenAPI file is generated from Zod schemas in src/openapi/service-map-contract.ts.
defineManifest(...)for declaring env sources and generated outputskeystone initfor bootstrapping local env files from templateskeystone generatefor materializing derived.envfileskeystone doctorfor smoke-level validationkeystone scan-secretsfor checking template hygiene and public-output leakskeystone clearfor removing generated outputs- shared helpers for env parsing and free-port allocation
- experimental service map support with bundled Portless-aware public URL generation
keystone init --manifest env.manifest.ts
keystone generate --manifest env.manifest.ts
keystone doctor --manifest env.manifest.ts
keystone scan-secrets --manifest env.manifest.ts
keystone clear --manifest env.manifest.ts
keystone service-map resolve --manifest env.manifest.ts --json
keystone service-map resolve --manifest env.manifest.ts --context service-map.context.json --json
keystone service-map render --manifest env.manifest.ts --service api
keystone service-map render --manifest env.manifest.ts --service api --format json --output api-env.json
keystone service-map resolve --manifest env.manifest.ts --output service-map.json
keystone service-map resolve --manifest env.manifest.ts --output service-map.pretty.json --prettyimport { defineManifest } from "@aureatus/keystone";
export default defineManifest({
name: "example",
sourceFiles: [".env", ".env.local"],
initFiles: [
{
templatePath: ".env.example",
destinationPath: ".env",
},
],
variables: {
API_KEY: {
required: true,
secret: true,
},
},
outputs: [
{
path: ".generated/app.env",
includeKeys: ["API_URL", "PORT"],
header: ["# Generated by Keystone"],
},
],
});Keystone's next major layer is service-aware endpoint resolution for local processes, Portless-exposed HTTP services, and Docker or Compose-backed dependencies.
- design notes:
docs/service-map.md - goal: keep defaults small and documented, and make every inferred host, port, and URL easy to override
- planned use case: let orchestrators like Hive resolve per-cell endpoints and generated env in one place
The first implementation slice lives behind experimental.serviceMap in the manifest and supports:
local-process,docker-published, anddocker-networkruntimesdirect,portless, andnoneexposure modes- cross-service env bindings without encoding startup order
- bundled Portless hostname and public-URL formatting for HTTP services
The intended long-term split is:
- TypeScript repos can use the SDK directly
- non-TypeScript runtimes such as Hive's Elixir backend can consume the service-map output through the OpenAPI contract
Useful commands:
bun run fixtures:update
bun run openapi:generate
bun run openapi:checkEvery resolved service map now includes a schemaVersion field so external consumers like Hive can guard against contract changes.
Docker Compose helper:
import {
createServiceMapContextFromDockerCompose,
resolveServiceMap,
} from "@aureatus/keystone";
const context = createServiceMapContextFromDockerCompose(manifest, {
projectName: "my-stack",
services: {
api: {
serviceName: "api",
ports: [{ containerPort: 8080, publishedPort: 18080 }],
},
postgres: {
serviceName: "postgres",
networkHost: "postgres",
ports: [{ containerPort: 5432, publishedPort: 15432 }],
},
},
});
const serviceMap = await resolveServiceMap(manifest, {
repoRoot: process.cwd(),
context,
});For cross-runtime consumers that do not want to call the TypeScript API directly, Keystone also exposes a one-shot CLI resolver:
keystone service-map resolve --manifest env.manifest.ts --jsonThat command prints a JSON payload shaped like ResolvedServiceMap from the generated OpenAPI contract.
If you want a concrete env projection instead of the full resolved object, use service-map render.
Output options:
--context <path>loads a structured JSON context file for cell/runtime-specific service-map inputs--jsonprints the resolved service map to stdout--output <path>writes the resolved service map to a file--prettypretty-prints JSON when writing to a file
Render options:
--service <name>renders only one service's env projection--format env|jsonchooses env-file or JSON output; default isenv
Example output from the smoke fixture is checked in at fixtures/smoke-workspace/service-map.example.json.
An example structured context file is checked in at fixtures/smoke-workspace/service-map.context.example.json.
A context-resolved output example is checked in at fixtures/smoke-workspace/service-map.context.resolved.json.
Additional mock-repo fixtures live under fixtures/mock-repos/ and are covered by tests. They include:
fixtures/mock-repos/portless-web-apifixtures/mock-repos/docker-compose-stackfixtures/mock-repos/mixed-fullstack
bun run fixtures:update regenerates the checked-in service-map.example.json files for the smoke fixture and all mock repos.
SDK usage:
import {
renderServiceEnv,
renderServiceMapEnvFile,
resolveServiceMap,
} from "@aureatus/keystone";
const serviceMap = await resolveServiceMap(manifest, {
repoRoot: process.cwd(),
context: {
cellName: "auth-fix",
portless: { rootName: "auth-fix" },
services: {
api: { preferredPort: 5512 },
web: { preferredPort: 5513 },
},
},
});
if (!serviceMap) {
throw new Error("Service map is not enabled for this manifest.");
}
const apiEnv = renderServiceEnv(serviceMap, "api");
const serviceMapEnvFile = renderServiceMapEnvFile(serviceMap, {
header: ["# Generated by Keystone"],
});If you want to hand Keystone setup to an AI coding agent in another repo, copy and paste this prompt.
Published package prompt
Set up Keystone env management in this repository.
Goals:
- inspect the existing environment-variable workflow first
- summarize the current env flow before making changes, including source files, loaders, generators, validation, and developer entrypoints
- install `@aureatus/keystone`
- create an `env.manifest.ts` at the repo root
- preserve current developer workflows where possible, but route them through Keystone
- add scripts for `keystone init`, `keystone generate`, `keystone doctor`, `keystone scan-secrets`, and `keystone clear`
- mark secret env vars as `secret: true`
- mark client/browser/public outputs as `public: true`
- update `.gitignore` for generated env files
- update the repo README with the new env workflow
- run verification and report exactly what changed
Constraints:
- do not invent extra abstractions if the repo already has a clear env flow
- keep the migration incremental and compatible first
- prefer one canonical variable name per concept; use aliases only when truly needed
- do not expose secret vars to public outputs
What I want you to deliver:
1. a short audit summary of the existing env flow
2. dependency installed
3. `env.manifest.ts` added
4. package scripts updated
5. generated env outputs wired up
6. docs updated
7. verification commands run
Expected setup outputs:
- a concise pre-change audit covering env sources, loading points, validation, generated files, and main dev commands
- `env.manifest.ts` describing source env files, init templates, variables, and generated outputs
- repo scripts such as `env:init`, `env:generate`, `env:doctor`, `env:scan-secrets`, and `env:clear`
- generated app-specific env files for frontend/backend/mobile/runtime consumers
- a documented verification path using `keystone doctor`, `keystone scan-secrets`, and the repo's normal test/dev commands
Local file path prompt
Set up Keystone env management in this repository.
Install Keystone from this local path:
`file:/absolute/path/to/keystone`
Goals:
- inspect the existing environment-variable workflow first
- summarize the current env flow before making changes, including source files, loaders, generators, validation, and developer entrypoints
- add the Keystone package from the local file path above
- create an `env.manifest.ts` at the repo root
- preserve current developer workflows where possible, but route them through Keystone
- add scripts for `keystone init`, `keystone generate`, `keystone doctor`, `keystone scan-secrets`, and `keystone clear`
- mark secret env vars as `secret: true`
- mark client/browser/public outputs as `public: true`
- update `.gitignore` for generated env files
- update the repo README with the new env workflow
- run verification and report exactly what changed
Constraints:
- do not invent extra abstractions if the repo already has a clear env flow
- keep the migration incremental and compatible first
- prefer one canonical variable name per concept; use aliases only when truly needed
- do not expose secret vars to public outputs
What I want you to deliver:
1. a short audit summary of the existing env flow
2. dependency installed
3. `env.manifest.ts` added
4. package scripts updated
5. generated env outputs wired up
6. docs updated
7. verification commands run
Expected setup outputs:
- a concise pre-change audit covering env sources, loading points, validation, generated files, and main dev commands
- `env.manifest.ts` describing source env files, init templates, variables, and generated outputs
- repo scripts such as `env:init`, `env:generate`, `env:doctor`, `env:scan-secrets`, and `env:clear`
- generated app-specific env files for frontend/backend/mobile/runtime consumers
- a documented verification path using `keystone doctor`, `keystone scan-secrets`, and the repo's normal test/dev commands
mise install
bun install
bun run build
bun run openapi:generate
bun test
bun run checkbun run smoke executes an end-to-end smoke test against a fixture workspace under fixtures/smoke-workspace/. It installs the package into a temporary sub-repo, runs the CLI, verifies generated outputs, and then clears them.
That smoke test now also covers init and scan-secrets.
bun test runs focused unit tests for env parsing/formatting and manifest workflows like alias resolution, init, generate, clear, and secret scanning.