Compile an API description — OpenAPI OR Swagger OR Postman OR saved cURL OR HTTP request files OR HAR OR an endpoint manifest — into SDKs, a CLI and an MCP server that AI agents can use safely.
tungsten is a deterministic compiler written in Rust. It reads one API description — an OpenAPI 3.0 or 3.1 document (JSON or YAML), a Swagger 2.0 document, a Postman Collection v2.1 file, a saved cURL command, an HTTP request file (.http/.rest), a HAR 1.2 capture or an endpoint manifest with JSON Schema 2020-12 models — plus two small manifests, builds one typed intermediate representation (IR), and emits TypeScript, Python, Rust, Go, Java, Kotlin, C#, F#, Dart and Ruby SDKs, a Rust command-line client, an MCP server, browser WebMCP tools and LLM-oriented docs. Documents are parsed locally; optional HTTP/Git acquisition pins the downloaded source for verified offline reuse. Every emitted client is built for an autonomous caller: failures are instructions, side effects are never duplicated, and irreversible calls need an explicit confirmation. See Supported inputs for the exact alternatives, their limits and the review each requires.
Read the documentation at pboachie.github.io/tungsten.
Why tungsten
Most SDK generators optimize for a person reading autocomplete. An agent has a finite context window, no memory between retries, and no sense of what is irreversible. tungsten changes the design goal of each stage:
-
Diagnostic error envelopes. Every failure, from argument validation to a transport error to an HTTP status, is one closed JSON shape with a category, the failing parameter, the received value, what was expected, a remediation sentence and an explicit
retryableverdict. The same 14 members in every runtime, ready to paste into a model's context. -
Safety tiers. Each operation is
read_only,mutating,destructiveorirreversible, defaulted from the HTTP method and refined inagent.yml. Tiers drive retries, MCP tool annotations and confirmation rules. Ordinary spec annotations cannot lower the method tier or remove preview from an operation with side effects: those decisions require an explicit per-operationtoolsentry inagent.yml(safetyandpreview: none, respectively). The compiler keeps the safe policy and reports TG0615/TG0616;--strictrejects these warnings. Imported operations retain their separate reviewed-policy floor. -
Previews and confirmation tokens. A destructive or irreversible call must be previewed first. The preview returns the request that would be sent and its effects, plus a confirmation token that is valid only for identical arguments. The generated CLI replaces tokens with
--yes(and--i-understandfor irreversible operations). -
Idempotency and unknown outcomes. Mutations are retried only with an idempotency key (or an identity body), always with the same key. A timeout or reset after a write is reported as
OUTCOME_UNKNOWNwith the call to verify it, not as a failure to retry blindly. - Verification. An operation can name a read to run after success and the state to expect, attached to the result as evidence.
-
MCP progressive disclosure. Past a tool-count threshold the server lists a few meta-tools (
search_tools,describe_tool,invoke,preview,list_clusters) instead of every operation. In the maintainers' measurement on the ZROtext pilot (October 2026, BPE token counts), the progressive listing of its 41 tools costs 765 tokens, against 9,473 for the 14 tools of that project's hand-written MCP server. -
Deterministic output. The same inputs produce byte-identical files. Generated files record the generator version and the digest of their inputs, and
tungsten generate --checkfails when output is stale.
Supported inputs
init --from also accepts HTTP(S) document URLs for these formats. OpenAPI
URLs work directly as compiler paths or inputs[].spec in tungsten.yml.
Other formats go through the same local importer and its required review flags:
tungsten init --from https://example.com/contracts/openapi.json --dir my-api
tungsten generate my-api --offline
tungsten check my-api --refresh-inputsThe first fetch records the source URL and SHA256 under
.tungsten-inputs/<url-sha256>/source.lock.json, beside immutable downloaded
content. Retain the complete source cache directory for offline use. Online commands verify upstream
bytes against the pin and refuse changed content. --refresh-inputs explicitly
accepts a new OpenAPI pin; converted imports are instead refreshed with
tungsten refresh-import, which previews the converted diff, operation and
output changes and requires renewed review of changed approvals before
--apply writes the conversion, pin and lock. Their source_url and
source_sha256 manifest fields preserve that provenance on every compilation.
For authentication, repeat --input-header 'Authorization: Bearer ...', or set
TUNGSTEN_INPUT_HEADERS to newline-separated Name: value headers to keep
secrets out of command history. Unscoped headers apply only to the exact URL
typed on the command line. For URLs in a manifest, scope each header to an
origin: --input-header '[https://api.example.com] Authorization: Bearer ...'.
The scheme, host and effective port must match; redirects remain refused.
Header values are excluded from pins and fetch
diagnostics. URLs must have no credentials, query or fragment. Downloads are
limited to 8 MiB and 30 seconds; redirects are refused. Remote references are
still blocked, including relative references from a remote OpenAPI document.
The cache contains original input bytes, so retain it with the same access
controls as the source document and review it before sharing.
Git inputs use git+https://host/owner/repository@ref#path in init --from,
compiler paths or OpenAPI manifest inputs. A branch or tag resolves to exactly
one commit, which is fetched shallowly into a disposable bare repository. No
checkout, hooks, submodules or repository code runs. Git supplies private-source
credentials through trusted user or system configuration. Project Git config
and inherited GIT_* overrides are ignored. --input-header
applies only to HTTP document downloads. Local repositories for offline workflows
and hermetic validation use git+file:///absolute/repository@ref#path.
tungsten init --from 'git+https://github.com/example/api@main#contracts/openapi.yaml' --dir my-api
tungsten generate my-api --offlineThe Git source lock under .tungsten-inputs/git/ records the repository URL,
requested ref, resolved commit, selected document and SHA256 of every materialized
file. Ordinary runs retain that commit; --refresh-inputs is the explicit request
to resolve a moving ref again. Converted imports retain their review provenance
and change source commits through tungsten refresh-import, which installs
exactly the reviewed commit. Discovery
recognizes all supported input formats and requires an explicit #path when
multiple documents are found. Relative references retain the compiler's local
input-root confinement. Acquisition is limited to 60 seconds, 512 files, 8 MiB per
file and 64 MiB of materialized content and fetched Git object data. Narrow
#path to bound discovery; the shallow fetch itself must still fit the object
budget. Git redirects, symlinks, submodules and unsafe paths are refused.
Retain the complete .tungsten-inputs/git/ cache for offline use, including its
locks and materialized files; offline reuse needs no Git executable. Every
materialized file is verified, and unexpected cached files or directories are
rejected. HTTP/Git pins verify source consistency, not the publisher's identity:
review and protect the cache and lock files together.
On Windows, Git's disposable bare repository uses a fresh private child of the
local temporary directory, so a deeply nested project cache does not become
Git's working directory or object-store path. Pins and verified input files
remain under the project's .tungsten-inputs/git/ directory. TMP/TEMP must
select a writable local disk directory with room for Git's pack and lock
filenames; network, unusable or overly long temporary roots fail with TG0133.
Choose a shorter local temporary path when needed. Offline pin reuse needs no
temporary repository. Git's source repository paths and external credential
helpers retain their own platform limits.
tungsten init takes one input of any of the kinds below — OpenAPI OR Swagger OR Postman OR saved cURL OR HTTP request files OR HAR, with the endpoint-manifest and manual paths alongside them — not a bundle. Acquisition may download a document or fetch a Git commit; parsing never sends or replays the API requests described by that input, and no shell, script or capture is ever executed. Non-OpenAPI inputs are converted to a normalized OpenAPI document that init saves next to the manifests (imported.openapi.json, with an import-report.json where listed; the manual path writes inferred.openapi.json and inference-report.json), so the accepted input stays separate from the normalized artifact and the typed IR the compiler builds from it.
| Input | You provide | Entry point and conversion | Limits and required review |
|---|---|---|---|
| OpenAPI 3.0 / 3.1 | JSON or YAML document |
tungsten init --from openapi.json (default when unset: openapi.json, openapi.yaml or openapi.yml in the target directory). The document is kept in place, so relative references retain meaning, and goes through the native frontend: $ref resolution, overlays |
The full frontend surface; overlays remain optional. OpenAPI 3.2 documents are read when they use only constructs 3.1 has plus the query method (sent as HTTP QUERY with its body), additionalOperations (sent with their method token as written), form-encoded in: querystring objects, style: cookie cookie parameters, $self that leaves reference resolution unchanged and response itemSchema for JSON Lines and for server-sent events with JSON or plain-text data; each other 3.2-only construct gets its own diagnostic (TG0140 to TG0156), an error unless it only adds metadata, and tungsten capabilities lists them |
| Swagger 2.0 | JSON or YAML document |
tungsten init --from swagger.json (also a default name) or any path. Bounded offline conversion to OpenAPI, saved as imported.openapi.json
|
Unsupported semantics fail explicitly instead of being guessed; conversion never resolves external references |
| Postman Collection v2.1 | JSON collection |
tungsten init --from collection.json. Structural import; omissions are listed in import-report.json
|
Scripts and requests are never executed: script-dependent behavior, non-JSON bodies and saved responses and unsupported auth helpers are omitted per item, not interpreted — this is not a Postman runtime |
| Saved cURL command | A saved command as text |
tungsten init --from command.curl --route '/pets/{id}' (auto-detected by a leading curl, a .curl extension, or --input-format curl). Parsed as data against POSIX, cmd or PowerShell quoting (--shell-dialect) |
Exactly one explicit --route path template is required; the command is parsed, never executed |
| HTTP request files |
.http or .rest file |
tungsten init --from requests.http --route '/pets/{id}' (extension-detected, or --input-format http) |
An explicit --route template per request |
| HAR 1.2 | A capture file |
tungsten init --from capture.har --har-origin https://api.example.com --har-paths paths.json. Bounded offline observations of the recorded traffic |
A multi-origin capture needs --har-origin; every captured path needs a reviewed template (--route, or a --har-paths JSON mapping) before anything is saved; --include-prefix/--exclude-prefix narrow the capture and --redact-property redacts property names. Observations are incomplete evidence about past traffic, not the live API |
| Endpoint manifest v1 | JSON manifest with declared models |
tungsten init --from endpoints.json. Loaded by the endpoint-manifest frontend |
Explicit HTTP routing metadata (base URL and endpoints) and JSON Schema 2020-12 models within the supported subset are required — not arbitrary standalone JSON-Schema-to-API inference |
| Manual endpoint | No document |
tungsten init --method get --endpoint /pets/{id}, with optional --request-example and --response-example JSON files (only their structure is persisted; the response is recorded at status 200). Writes inferred.openapi.json and inference-report.json
|
Explicit offline evidence only; not schema-free API discovery |
All conversion routes share these limits: setup inputs are bounded (8 MiB, with larger OpenAPI documents served by the explicit compiler flow below); every converted operation is marked as imported, and targets that cannot enforce imported-operation policy refuse it (TG0705 for built-in targets, TG0808 for external emitters that do not declare support) — the support matrix lists which built-in targets enforce it, non-opted-in external emitters do not, and init refuses unsupported built-in selections before saving the converted input or project manifests. Imported observations are incomplete contracts: review the inferred response types, authentication, safety tiers and origins in agent.yml and mark reviewed operations before live use.
init is the only entry point for the alternative inputs. The other commands (check, generate, mock, diff, report) take an initialized project (a directory with tungsten.yml, or the manifest), or — for the native format only — a single OpenAPI document passed directly.
Targets
The support matrix below is generated from the capability catalog (tungsten capabilities --matrix; --json gives the same declarations as data), and CI checks that it matches. A declared feature or execution environment describes source behavior, not qualification.
| Target | Aliases | Emits | Consumer toolchain | Runs on | Features | Imported-operation policy | Qualification | Artifacts |
|---|---|---|---|---|---|---|---|---|
csharp |
cs |
C# SDK | dotnet >=10.0 | dotnet |
typed_sdk, pagination_helpers
|
enforced | unknown | unknown |
dart |
Dart SDK (sealed unions, Future methods returning Result, Stream pages) | dart >=3.6 <4.0 | dart_io |
typed_sdk, pagination_helpers
|
enforced | unknown | unknown | |
docs |
llms.txt, llms-full.txt, tools.json and README | none | static | llms_txt |
enforced | unknown | unknown | |
fsharp |
fs |
F# SDK (records, discriminated unions, Task of Result) | dotnet >=10.0 | dotnet |
typed_sdk, pagination_helpers
|
enforced | unknown | unknown |
java |
Java SDK | java >=17.0; mvn installed | jvm |
typed_sdk, pagination_helpers
|
enforced | unknown | unknown | |
kotlin |
kt |
Kotlin/JVM SDK (blocking functions returning Result; opt-in coroutines) | java >=17.0; gradle installed | jvm |
typed_sdk, pagination_helpers, coroutines
|
enforced | unknown | unknown |
mcp |
MCP server over stdio or Streamable HTTP, discrete or progressive | node >=22.13 excluding 23.0.x, 23.1.x; npm installed | node |
mcp_stdio, mcp_streamable_http, mcp_embedding_search, mcp_script_sandbox
|
enforced | unknown | unknown | |
mock |
reserved; no emitter in this version | none | n/a | unknown | unknown | |||
python |
py |
Sync and async Python SDK, Pydantic v2 or dataclass models | python3 >=3.12 (python_version selects the minimum) |
cpython |
typed_sdk, pagination_helpers, pagination_checkpoints, dataclass_models, webhook_verification, typed_webhooks, streaming_downloads, telemetry_hooks
|
enforced | unknown | unknown |
ruby |
rb |
Ruby SDK | ruby >=3.3; bundle installed | ruby |
typed_sdk, pagination_helpers
|
enforced | unknown | unknown |
rust |
rs |
Async Rust SDK (serde, reqwest) and optional generated CLI | cargo >=1.88; rustc >=1.88 | native |
typed_sdk, pagination_helpers, pagination_checkpoints, generated_cli, webhook_verification, typed_webhooks, cli_login_sessions, streaming_downloads, telemetry_hooks
|
enforced | unknown | unknown |
typescript |
ts |
Typed TypeScript SDK, Result values, Zod validation | node >=22.0; npm installed | node |
typed_sdk, pagination_helpers, pagination_checkpoints, webhook_verification, typed_webhooks, streaming_downloads, telemetry_hooks
|
enforced | unknown | unknown |
webmcp |
Browser ESM package with tool manifest and registration helper | node >=22.0; npm installed | browser | webmcp_registration |
enforced | unknown | unknown |
| Feature | Domain | Opt-in option | Description |
|---|---|---|---|
typed_sdk |
target | typed models and operation methods for every compiled operation | |
pagination_helpers |
target | page iteration helpers for operations with declared pagination | |
pagination_checkpoints |
runtime | sealed, caller-owned checkpoints that resume read-only page exports after a restart | |
generated_cli |
target | cli |
a command-line client crate next to the SDK |
cli_login_sessions |
target | cli |
generated-CLI browser login, account switching and logout for OAuth2 authorizationCode schemes, with sessions in the macOS keychain, Secret Service or Windows Credential Locker, or an explicit owner-only file |
dataclass_models |
target | models |
stdlib dataclass models without a Pydantic dependency |
coroutines |
target | coroutines |
suspend functions and cold Flows next to the blocking surface |
mcp_stdio |
protocol | Model Context Protocol server over stdio | |
mcp_streamable_http |
protocol | Model Context Protocol server over Streamable HTTP | |
mcp_embedding_search |
target | search |
optional embedding index for tool search |
mcp_script_sandbox |
runtime | sandbox |
opt-in run_script sandbox |
webmcp_registration |
protocol | browser tool manifest and WebMCP registration helper | |
telemetry_hooks |
runtime | opt-in local telemetry observer with bounded, redacted operation, attempt and phase events | |
streaming_downloads |
runtime | explicit streamed response bodies with byte limits, progress, idle timeout and deadline | |
webhook_verification |
runtime | framework-free incoming webhook verification: Stripe-compatible t=,v1= HMAC-SHA256 signatures compared in constant time, secret rotation, bounded body, timestamp tolerance and an application-owned replay hook |
|
typed_webhooks |
target | typed payloads of OpenAPI webhooks and a parse helper that verifies a delivery and validates its payload before the replay claim |
|
llms_txt |
target | llms.txt, llms-full.txt and tools.json documentation |
| OpenAPI line | Support | Diagnosed constructs |
|---|---|---|
| 3.0 | supported | 0 |
| 3.1 | supported | 0 |
| 3.2 | subset | 8 |
| Input format | Versions | Route | Detection | Operations | Review flags | Acquisition |
|---|---|---|---|---|---|---|
openapi |
3.0, 3.1, 3.2 | compiler |
openapi member |
native | local, https, git | |
swagger |
2.0 | init_conversion |
swagger member |
imported | local, https, git | |
postman |
2.1 | init_conversion |
info and item members |
imported | local, https, git | |
curl |
init_conversion | a leading curl, a .curl extension or --input-format curl
|
imported | --route |
local, https, git | |
http |
init_conversion | a .http or .rest extension or --input-format http
|
imported | --route |
local, https, git | |
har |
1.2 | init_conversion |
log member |
imported | --har-paths |
local, https, git |
endpoint_manifest |
1 | init_conversion |
endpoint_manifest member |
imported | local, https, git | |
manual_endpoint |
init_inference |
--method and --endpoint, without --from
|
imported |
| IR protocol | Status | Targets | Protocol-specific extension |
|---|---|---|---|
http |
implemented | every built-in target | none; method, path, parameters and bodies are the common contract |
graphql |
reserved | none (refused, TG0706) | selection sets, fragments and subscription transport |
grpc |
reserved | none (refused, TG0706) | service and method names, streaming mode and status codes |
async_messaging |
reserved | none (refused, TG0706) | channel bindings, delivery guarantees and acknowledgment |
init selection |
Targets |
|---|---|
Default (--targets omitted) |
typescript, python, docs
|
| Wizard default |
typescript, docs
|
Local setup (--local-example, wizard) |
typescript, python, rust, mcp, webmcp, docs, and cli (Rust SDK and CLI) |
--preset app |
typescript, docs
|
--preset library |
typescript, python, rust, docs
|
--preset agent (--strict) |
typescript, mcp
|
Selecting mcp adds |
typescript |
Selecting webmcp adds |
typescript |
Source declarations only: a declared feature or environment is not qualification, and unknown evidence never becomes a compatibility claim.
Runtime installation and toolchain notes per target:
| Target | Toolchain notes | Runtime and installation |
|---|---|---|
typescript |
Node 22 |
@elysra/tungsten-runtime: matching npm package or built source |
python |
Python 3.12 by default; opt-in 3.10/3.11 | PyPI runtime for a matching published release, or matching runtimes/python source |
rust |
Rust 1.88 | Matching source paths to runtimes/rust/runtime and, for a CLI, runtimes/rust/cli-kit; see installation
|
go (external) |
Go 1.22 | External emitter (Models, resources, typed client, pagination and stream helpers). Build tungsten-emit-go from emitters/go; use runtime_path to matching runtimes/go source; see module status
|
java |
Java 17 and Maven | Use runtime_path to matching runtimes/java source |
kotlin (kt) |
Java 17 and Gradle | Use runtime_path to matching runtimes/kotlin (limits); it builds on the Java runtime in runtimes/java (java_runtime_path). The coroutine surface adds the tungsten-runtime-kotlin-coroutines artifact (runtimes/kotlin/coroutines, coroutines_runtime_path) and kotlinx-coroutines-core
|
csharp (cs) |
.NET 10 | Use runtime_path to matching runtimes/csharp, referencing Tungsten.Runtime.csproj (limits) |
fsharp (fs) |
.NET 10 | Use runtime_path to matching runtimes/fsharp (limits); it references the .NET runtime in runtimes/csharp (csharp_runtime_path) |
dart |
Dart 3.6 | Use runtime_path to matching runtimes/dart (tungsten_runtime, a dart:io package; limits) |
ruby (rb) |
Ruby 3.3 and Bundler | Use runtime_path to matching runtimes/ruby source |
mcp |
Node 22.13+; Node 23 needs 23.2+ |
@elysra/tungsten-mcp: matching npm package or built source |
webmcp |
Node 22 to build; compatible browser to run |
@elysra/tungsten-webmcp and the generated TypeScript SDK |
docs |
None beyond the compiler | No runtime dependency |
These targets are implemented in source. Starting with 0.1.3,
compiler and runtime package versions advance together with the release tag.
Generated packages default to compatible 0.1.3 runtime floors; an explicit
runtime setting remains yours to qualify. The earlier 0.1.2 release has
independently versioned non-npm runtime sources. Use the matching source checkout
when a registry artifact is unavailable: source version metadata is not evidence
of publication. Published 0.1.3 packages are available for npm, Python, Rust, Java,
C# and Ruby, and 0.1.2 packages remain available; use the explicit
released-consumer overrides below to stay on 0.1.2. Go also has a
matching-source route for stable consumers. Its v0.1.3
module has a verified Apache-2.0 archive; the older stable-commit
pseudo-version's archive notice differs, as explained in that guide.
Target runtime_path values resolve from the generated package or crate
directory; see the target's installation guidance.
Kotlin, F# and Dart are 0.1.3 targets. They require the 0.1.3 or newer compiler and matching wrapper/core runtime sources; the 0.1.2 compiler does not generate these targets.
Targets and the mock server (tungsten mock) share one IR. Generated-package and contract checks exercise each language, but source availability and passing shared scenarios do not establish complete validation, protocol or distribution parity. Unsupported features can vary by target. Review all compiler diagnostics: --strict rejects warnings, including unsupported if/then/else constraints, but informational diagnostics can still describe other unsupported features. Local checks do not establish live API acceptance or registry installation readiness.
Install
Install the compiler first. The runtime packages below
support generated output; they do not install the tungsten compiler command.
The latest published compiler release is v0.1.3,
published October 19, 2026, and includes HTTP/Git inputs and Python 3.10/3.11
compatibility. This branch can contain changes after that release; see the
Unreleased section of the changelog.
Prebuilt binaries for Linux, macOS and Windows are attached to each GitHub release. The installer checks the archive against the release's SHA256SUMS before it installs anything:
curl -fsSL https://raw.githubusercontent.com/pboachie/tungsten/main/install.sh | sh # Linux and macOS, into ~/.local/binirm https://raw.githubusercontent.com/pboachie/tungsten/main/install.ps1 | iex # WindowsPass --version v0.1.2 to install.sh (or -Version v0.1.2 to install.ps1) to pin a release, and --dir (-InstallDir) to choose the target directory.
See install.sh --help or Get-Help ./install.ps1 -Full
(PowerShell script) for environment variables and exit codes.
The installers in this source require build provenance by default. Install
GitHub CLI 2.102.0 or newer through its official
installation instructions first; older versions do not safely enforce the exact
workflow identity. The verifier must accept the archive for pboachie/tungsten,
.github/workflows/release.yml, issuer
https://token.actions.githubusercontent.com and the requested release tag.
Attestations are obtained from github.com; verification needs access to GitHub
and its trust services. Missing/unusable verifiers, unavailable or invalid
attestations, and identity mismatches refuse installation. The installer does
not bootstrap a verifier or silently choose a weaker mode.
To intentionally install using only SHA256SUMS, set
TUNGSTEN_NO_ATTESTATION=1. The installer prints a checksum-only warning and
still rejects checksum mismatches. TUNGSTEN_REQUIRE_ATTESTATION=1 remains an
explicit assertion of the default policy and conflicts with that opt-out;
setting it to 0 does not disable default verification. Both settings accept
only 0 or 1.
TUNGSTEN_REPO, TUNGSTEN_RELEASE_BASE_URL and PowerShell -BaseUrl change
download locations, not trusted signer identities. Mirrors can supply the same
official release bytes for verification. Custom builds or restricted/offline
mirrors without verifiable official provenance need the explicit checksum-only
choice; their origin is not authenticated by a matching checksum. Older tagged
installers retain their released behavior.
On Linux the musl archives (the installer default) are statically linked and
avoid a glibc dependency. Prefer them on older distributions. The published
v0.1.2 gnu archives (TUNGSTEN_LIBC=gnu) require glibc 2.34 on x86_64
and 2.39 on aarch64, as verified from their required symbol versions. Check
your architecture and glibc version before selecting a stable GNU archive:
install.sh currently checks only a 2.34 floor and does not enforce the stable
aarch64 archive's higher requirement. The 0.1.3 release targets
a common glibc 2.34 floor for both architectures; that target does not describe
the published v0.1.2 binaries.
Release archives, the Homebrew formula, the winget manifests and the ghcr.io/pboachie/tungsten container image carry build provenance attestations. To verify them with the GitHub CLI:
gh attestation verify oci://ghcr.io/pboachie/tungsten:0.1.2 --repo pboachie/tungsten
gh attestation verify tungsten.rb --repo pboachie/tungstenTo run the guided tungsten init setup right after installing, pass --init to install.sh (-Init to install.ps1). It needs a terminal, and the installer prints the PATH note first. A cancelled setup leaves the installed binary in place and exits with status 4 (5 when setup fails); run tungsten init again when ready:
curl -fsSL https://raw.githubusercontent.com/pboachie/tungsten/main/install.sh | sh -s -- --initThe irm … | iex form cannot pass -Init. Run the downloaded script as a script block instead, or save it and run .\install.ps1 -Init:
& ([scriptblock]::Create((irm https://raw.githubusercontent.com/pboachie/tungsten/main/install.ps1))) -InitOr build from source, with Rust 1.95 or newer (the repository pins 1.97.0 in rust-toolchain.toml for development):
git clone https://github.com/pboachie/tungsten
cd tungsten
git checkout v0.1.3 # latest release
cargo install --path crates/tungsten-cli # installs the `tungsten` binary
tungsten doctor # reports optional tools (node, python3, uv, formatters)Node, Python and the formatters are optional: a missing tool only disables the target or --format option that needs it.
Check for a newer release
tungsten update check asks GitHub once (5 second limit, 1 MiB response cap) for the public
release metadata of this repository and compares the latest stable release with the version
you run. It sends no project data, installs nothing and does not change project pins or
generated clients. Add --include-prerelease to consider prereleases and --json for the
machine-readable result. A newer release is recommended only when it lists the archive for
your platform and SHA256SUMS. Upgrade guidance depends on how the CLI was installed: a
pinned installer command for the release installer, cargo install for cargo, and
package-manager guidance for npm. When the installation route cannot be determined,
the command says so and prints no replacement command.
A newer release is also classified in upgrade: compatible stays on your compatibility
line (a patch release before 1.0, a minor or patch release from 1.0) and is followed by
tungsten generate --check; migration starts a new line (a minor or major release before
1.0, a major release from 1.0), is a prerelease, or has a Breaking section in its notes, and
the guidance asks you to rehearse before replacing generated clients. Release notes can only
make the classification stricter. When a newer release is available, rehearse_command holds
the tungsten upgrade rehearse --to <version> --workspace <dir> invocation to run with the new
binary in each project; tungsten upgrade apply is the separate step that applies a reviewed
rehearsal.
| Exit code | Meaning |
|---|---|
| 0 | Up to date, or this build is newer than the newest release considered |
| 5 | A newer release with your platform's archive is available |
| 6 | Could not confirm: offline, opted out, timeout, network or rate-limit failure, invalid or oversized metadata, no matching release, or a newer release without your platform's archive |
--offline (also a global flag), --no-update-check and TUNGSTEN_NO_UPDATE_CHECK=1
make no request and exit 6 with a reason in the --json result.
Update notices are off by default. Enable them with tungsten update notice --enable
(--enable prerelease, --disable, --interval-hours N), which writes the user settings
file and keeps its other keys, or with TUNGSTEN_UPDATE_NOTICE=stable (or prerelease).
tungsten update notice alone shows the effective setting, where it came from, the file
paths and the cached answer. The user settings file is $TUNGSTEN_CONFIG_DIR/settings.json
if set, else $XDG_CONFIG_HOME/tungsten/settings.json (~/.config/tungsten/settings.json),
~/Library/Application Support/tungsten/settings.json on macOS or
%APPDATA%\tungsten\settings.json on Windows:
{ "update_notice": { "channel": "stable", "interval_hours": 24 } }With notices on, tungsten update check saves its verified answer to update-check.json
in the user cache directory (TUNGSTEN_CACHE_DIR overrides it). Other commands never make
a request for notices: after an interactive command, tungsten reads that file and, at most
once per interval (TUNGSTEN_UPDATE_NOTICE_INTERVAL_HOURS, 1 to 720, default 24), prints one
line on stderr naming a newer release whose archive and SHA256SUMS the check confirmed.
Answers older than 7 days are ignored. No notice is printed with --json, --offline,
--policy, in CI, when stderr is not a terminal, or for lsp and mock.
TUNGSTEN_NO_UPDATE_CHECK=1 or TUNGSTEN_UPDATE_NOTICE=off turns notices off; an invalid
setting also turns them off. A missing, corrupt or unwritable cache never changes a command's
output or exit code.
Runtime packages
For a published compiler release and generated client, choose the runtime for the output you use. These package versions were verified on October 20, 2026; Node 22+ is required for TypeScript/MCP, Python needs 3.10+, Java needs 17+, C# needs .NET 10, and Ruby needs 3.3+. Run Python installation commands in an activated virtual environment.
For current source builds, MCP servers require Node 22.13+ (Node 23 requires 23.2+); TypeScript SDKs and WebMCP build tooling retain their Node 22 prerequisite.
Node.js support
Upstream lifecycle as of October 2026: 22 and 24 are LTS, 26 is Current, and 20 is end-of-life. Odd-numbered branches, Node 18 and older, and unreleased majors are not supported. A new major is promoted only after it passes the package build lanes; a line is retired when upstream support ends.
| Artifact | Supported lines | Declared floor | Lanes checked in public CI |
|---|---|---|---|
@elysra/tungsten-runtime and generated TypeScript SDKs (run) |
22, 24, 26 | >=22 |
22.23.4, 24.22.1, 26.12.1 |
@elysra/tungsten-mcp and generated MCP servers (run) |
22.13+, 24, 26 | ^22.13.0 || >=23.2.0 |
22.23.4, 24.22.1, 26.12.1 |
@elysra/tungsten-webmcp and generated WebMCP packages (build) |
22, 24, 26 | >=22 |
22.23.4, 24.22.1, 26.12.1 |
| Node 20 (legacy) | Not supported | — | 20.20.2, advisory only |
The WebMCP floor covers its npm build tooling only. Generated WebMCP bundles run
in a browser that provides the WebMCP API. Passing an npm build does not make a
browser compatible. The core runtime entry point uses Web APIs only; its /node
export and the MCP runtime need Node. The Node 20 canary builds the packages and
imports the SDK and WebMCP runtimes for information only. The MCP runtime cannot
load on Node 20 because it needs module.stripTypeScriptTypes (Node 22.13 or
23.2). The tungsten compiler and generated Rust CLIs never need Node.
| Output | Runtime package | Install the published runtime |
|---|---|---|
| TypeScript SDK | @elysra/tungsten-runtime |
npm install --save-exact @elysra/tungsten-runtime@0.1.3 |
| MCP server | @elysra/tungsten-mcp |
npm install --save-exact @elysra/tungsten-mcp@0.1.3 |
| Browser WebMCP | @elysra/tungsten-webmcp |
npm install --save-exact @elysra/tungsten-webmcp@0.1.3 |
| Python SDK | tungsten-runtime |
python -m pip install tungsten-runtime==0.1.3 |
| Rust SDK | tungsten-runtime |
cargo add tungsten-runtime@0.1.3 |
| Rust CLI | tungsten-cli-kit |
cargo add tungsten-cli-kit@0.1.3 |
| Java SDK | io.github.pboachie.tungsten:tungsten-runtime |
mvn dependency:get -Dartifact=io.github.pboachie.tungsten:tungsten-runtime:0.1.3 |
| C# SDK | Tungsten.Runtime |
dotnet add package Tungsten.Runtime --version 0.1.3 |
| Ruby SDK | tungsten-runtime |
gem install tungsten-runtime --version 0.1.3 |
Verify a TypeScript runtime installation with
node --input-type=module -e 'import("@elysra/tungsten-runtime").then(() => console.log("runtime ready"))',
or Python with python -c 'import tungsten_runtime; print("runtime ready")'.
Then install and build your generated package as its README describes. These
runtime imports do not generate a client or call an API.
Published 0.1.2 packages do not satisfy dependencies on 0.1.3 runtime
capabilities; use the 0.1.3 packages above, or matching runtime source as in the
quickstart below. Go supports matching stable source and the v0.1.3 module. See
the Rust installation and
Go installation options. Runtime libraries do not install
the Tungsten compiler. The runnable gallery
shows complete installation and first-use flows for each output.
Released 0.1.2 consumers
Keep the compiler and generated clients on the released route when deploying
on 0.1.2 instead of the latest release. Pin the installer to v0.1.2 as above and confirm
tungsten --version prints tungsten 0.1.2. Merge these exact runtime settings
into the targets you use in tungsten.yml; your output paths, API package names
and generated package versions remain your own:
targets:
typescript: {out: generated/typescript, runtime: "0.1.2"}
python: {out: generated/python, runtime: "==0.1.2"}
java: {out: generated/java, runtime: "0.1.2"}
csharp: {out: generated/csharp, runtime: "0.1.2"}
ruby: {out: generated/ruby, runtime: "= 0.1.2"}
mcp: {out: generated/mcp, runtime: "0.1.2", sdk_runtime: "0.1.2", sdk_path: "../typescript"}
webmcp: {out: generated/webmcp, runtime: "0.1.2", sdk_runtime: "0.1.2", sdk_path: "../typescript"}Run tungsten generate ., then follow each generated README to install and
build it. Build the TypeScript SDK before the adapters that reference it.
The 0.1.2 compiler's native runtime defaults predate the 0.1.2
packages, so keep the Java, C# and Ruby overrides explicit. Registry
consumers use those packages without a runtime source checkout.
The stable Rust source recipe
uses the unchanged v0.1.2 checkout for both Rust crates. This source route
remains valid and does not require a 0.1.3 upgrade; the 0.1.3 crates are also
available from crates.io. Newer compiler features require matching runtime source or
the 0.1.3 packages; Python 3.10/3.11 output belongs to the 0.1.3 route. Generated SDK compatibility still needs qualification against
your own API and deployment.
For Go, build the external emitter from v0.1.2 and set runtime_path to that
checkout's runtimes/go; the stable Go recipe
retains its Apache source/license context and avoids the old pseudo-version
ZIP's inherited repository-root notice.
Quickstart
Install the compiler above first. This walkthrough uses Python 3.12 or newer and uv. TypeScript targets require Node 22 or newer. Installing the compiler does not install generated SDKs or their language runtimes.
This walkthrough continues from the 0.1.3 source installation above, using the same
checkout for the compiler and runtime. From that tungsten directory, set
its absolute path and create a separate project. If you installed a release
binary, obtain its matching source archive from the release first:
export TUNGSTEN_SOURCE="$(pwd)"
mkdir ../petstore-demo
cd ../petstore-demoUse your own openapi.json, or create this complete tiny example:
{"openapi":"3.1.0","info":{"title":"Acme Petstore","version":"1"},"paths":{"/pets":{"get":{"operationId":"listPets","responses":{"200":{"description":"Pets","content":{"application/json":{"schema":{"type":"array","items":{"type":"string"}}}}}}}}}}The walkthrough uses an OpenAPI document, the native input. init --from also accepts Swagger 2.0, Postman collections, saved cURL commands, .http/.rest files, HAR captures and endpoint manifests — one of them, not a bundle — each with its own flags and review steps; see Supported inputs.
The importing guide gives
per-format commands, diagnostic families, saved-command shell dialects, HAR path
review and the supported JSON Schema subset. Imported operations stay unreviewed
until you explicitly review their safety and allowed origins in agent.yml.
init writes tungsten.yml and agent.yml with TypeScript, Python and docs
targets by default. Edit the existing targets block rather than appending a
second one. For this example, replace its Python entry with:
python: { package: acme-petstore, module: acme_petstore, out: generated/python }Review agent.yml and resolve the TODO entries for operations with side effects
before generating. The tiny example has only a read operation.
tungsten init --from openapi.json
# Edit tungsten.yml and agent.yml as described above.
tungsten check
tungsten generate
uv venv --python 3.12 .venv
# Install the language runtime and generated SDK into this environment.
# The local runtime satisfies the generated package's runtime dependency.
uv pip install --python .venv/bin/python "$TUNGSTEN_SOURCE/runtimes/python" ./generated/python
tungsten mock . --port 8099Leave the mock running. In another terminal in petstore-demo, make the first
call using the environment where you installed the SDK:
.venv/bin/python - <<'PYTHON'
from acme_petstore import AcmePetstoreClient, Ok
with AcmePetstoreClient(base_url="http://127.0.0.1:8099") as client:
result = client.pets.list()
assert isinstance(result, Ok), result
print(result.value)
PYTHONThe tiny example returns a successful list. For your own spec, the client class,
resource and method names follow its title and operation IDs. Calls return a
Result; API and transport errors carry a diagnostic envelope. When installing
a generated TypeScript SDK, build its matching source runtime first with
npm ci && npm run build in runtimes/ts, then install that directory and the
SDK's dependencies in the generated package and run npm run build. The Python
route above works without assuming runtime registry publication.
Destructive operations use the preview flow. From the TypeScript SDK generated for the ZROtext pilot:
const preview = await client.public.webhooks.disable.preview({ endpointId });
if (preview.ok && preview.value.confirmation_token) {
console.log(preview.value.effects);
await client.public.webhooks.disable({ endpointId }, { confirm: preview.value.confirmation_token });
}Redirects default to bounded same-origin reads; mutations never follow redirects
under any policy. Set defaults.redirects: none in agent.yml, or the SDK client's
redirects option to none, to follow no redirect at all. A 3xx the operation
declares (exactly or by class) is still its success response; a 3xx it does not
declare fails as UNEXPECTED_RESPONSE without a second request or retry.
Either none setting wins: client options cannot relax a manifest restriction.
Rust and C# require the runtime-owned HTTP client for this policy; omit a custom
http_client/HttpClient. Custom fetch/transport callbacks must honor the runtime's
no-follow contract. Upgrade runtimes together with generated clients; older
runtimes fail explicitly when a generated client requires this capability.
Python's ClientOptions.timeout_ms covers each native HTTP attempt through headers
and the complete ordinary body, with cancellation cleanup joined before capacity
is released. It is not an overall deadline across retries. Streaming bodies use
the idle timeout after successful headers. Custom transports must cooperate;
uninterruptible callbacks retain capacity until completion and late results are
rejected, without a hard settlement deadline. OS resolver cancellation depends on
the platform.
Migrating to the total deadline (unreleased 0.2.0): timeoutMs and its
equivalents keep their meaning. They bound one attempt, so retries, backoff,
authentication, pages and verification can together take several times as long.
To bound what a caller waits for one logical call, set the new opt-in total
deadline as well: deadlineMs (TypeScript), deadline_ms (Python), deadline
(Rust), Deadline (Go), --deadline <SECONDS> with <PREFIX>_DEADLINE or the
profile key deadline (generated Rust CLI), and toolDeadlineMs with
<API>_MCP_DEADLINE_MS (MCP server, per tool call). Other runtimes describe
their support in their README's Limits section. With both set, each attempt
gets at most the time the deadline has left, and queue admission waits no longer
than the deadline. No attempt starts after the deadline expires (code
DEADLINE_EXCEEDED). A mutation already sent at that point reports
OUTCOME_UNKNOWN and is never replayed blindly, so retry it only with the same
idempotency key. An open event stream is not cut by the deadline; its idle and
collection limits apply. Shared token refreshes and custom transports or stores
that ignore cancellation are no longer waited for, but they are not stopped.
Without a deadline, behaviour is unchanged.
For a saved collection, start with Postman import and contract review. For an API change, use the stale-output and regeneration walkthrough. Both build on the existing gallery recipes.
More examples: the runnable example gallery covers first SDKs, supported input routes, local mocks, destructive confirmation and CI/surface review workflows. Typed Python SSE shows how to declare or overlay an incomplete stream response and consume named events with sync/async cleanup. Response-contract quality shows the opt-in audit and reviewed schema overlays without inferring guaranteed types. Tungsten on six public APIs uses the public OpenAPI descriptions of Stripe, GitHub, Twilio, OpenAI, Kubernetes and Discord to show diagnostics, MCP listing sizes and generated SDK code in TypeScript, Python and Rust, reproducible with one script. The external Markdown emitter shows how to write an external emitter.
An external: target in tungsten.yml names a command that tungsten starts as a process, unsandboxed and with your privileges. It runs on every command that produces output in memory, not only on a writing generate: generate --dry-run and --check, check --ci, diff and report run it too (plain check and ir dump do not). A CI job that runs tungsten check --ci on pull requests therefore executes whatever the pull request puts in tungsten.yml. Pass --no-external or set TUNGSTEN_NO_EXTERNAL=1 to skip external targets (reported as TG0807) when the manifest is not trusted.
Hosts can explicitly select stricter external-emitter constraints with both
--policy PATH and --policy-digest blake3:<64 lowercase hex digits>. First review
a local JSON document, for example:
{"tungsten_policy":1,"external":{"deny":true,"timeout_ms":10000,"max_output_bytes":1048576}}Run tungsten policy digest PATH separately to validate and hash those exact
bytes; it prints the core BLAKE3 digest and writes no files. Execution never
creates or updates a pin. A relative policy path resolves against invocation cwd.
There is no automatic discovery, environment selection, include, interpolation,
URL fetch or replay from exported configuration. Selected symlink/reparse entries,
nonregular files and unsupported local namespaces are refused. Capture reads one
regular handle once, with a 64 KiB byte limit and nesting limit eight; strict JSON
rejects duplicate/unknown members, null, trailing data and empty constraints.
deny is a boolean; timeout is 1..=3,600,000 ms and output is 1..=2^40 bytes,
subject to platform representation. Missing constraints add no restriction.
| Command | Supplied execution policy |
|---|---|
generate (write, check, dry-run), check (including CI), report, diff, emitters
|
Capture and enforce before external run/descriptor probes |
config |
Capture and inspect, without acquiring API inputs or running tools |
policy digest |
Reject execution-policy flags; validate/hash only its named file |
doctor, init/wizard, scenario, lsp, mock, ir, overlay, explain, capabilities, schema, any unlisted command |
Reject supplied policy before reads or command effects |
--help, --version
|
Parser-owned; no policy read |
Embedding hosts select immutable CapturedPolicy documents with expected digests,
then a fresh InvocationPolicy for each invocation. CliEnv.policy intersects
the CLI selection instead of replacing it; CompileOptions.policy stays outside
editor overrides. At most 16 submitted documents and 1 MiB of captured bytes are
accepted, counting duplicates. Denials combine by OR and ceilings by minimum;
false or a weaker later document cannot undo a restriction. Config inspection
reports opaque policy[N] sources, exact digests, known pointers and all contributing
constraints, including ties, without physical paths or replayable authority.
Absent policy retains existing behavior and is reported as absent; a permissive
selection is never approval. Prepared IR does not retain a fallback context.
Long-lived hosts must explicitly recapture/revalidate pins for new invocations.
Library callers must migrate external::run and external::describe to supply
&InvocationPolicy and raw RequestedExternalLimits; a precomputed Limits
cannot substitute for context. Select InvocationPolicy::absent() explicitly
only when legacy behavior is intended. Keep missing/default requested values as
None, rather than converting them to explicit defaults that could exceed a
protected ceiling. Exhaustive CliEnv/CompileOptions literals also need their
new optional policy field (or ..Default::default()).
Explicit project timeout/output values above a protected ceiling fail (TG0810)
before input acquisition or external descriptor negotiation. Missing/null project
limits use existing defaults, then clamp to the ceiling. Descriptor probes use
their own 5 s/1 MiB defaults, also clamped. Generation requests advertise the
effective output cap. Denied execution is TG0811 and fails the requested operation;
--no-external remains an independent unconditional TG0807 skip. Invalid captures
or mismatched pins are TG0809. tungsten schema execution-policy publishes the
strict document shape.
These are per-external-operation limits, not whole-command time or heap limits. The exact pinned capture is authoritative for that invocation; canonical paths are local provenance, not replacement-safe executable identity. Restrictions do not verify parent symlinks/junctions, mounts or mapped-volume locality; a selected regular file can reside on network-backed storage, and OS file operations are not hard real-time bounded. They do not approve executables, minimize inherited environment, revoke running work, protect acquisition Git/providers, doctor tools, setup/package managers or scenario subprocesses, or isolate filesystem/network access. Policies do not establish a sandbox.
External emission and --describe own a Unix process group or Windows Job Object.
Leader exit, timeout and output overflow trigger group/job termination and leader
reaping. timeout_ms includes process launch, pipe work and cleanup; one quarter,
up to one second, is reserved for cleanup. OS calls and scheduling can exceed that
budget. Zero or unrepresentable timeouts refuse execution. Each launch reserves
four of 16 process-wide worker slots. Unfinished workers retain
their handles and slots until they finish and a later invocation joins them;
capacity exhaustion refuses before execution. TG0803 reports incomplete cleanup
and accepts no files. Investigate remaining emitter processes before retrying.
Unix descendants can escape their group or pass pipes elsewhere; these lifecycle
controls do not establish that every descendant stopped, approve an executable,
filter inherited credentials or isolate filesystem/network access. Library
embeddings must exclusively reap their external children and must not auto-reap
children or change SIGCHLD during execution.
On Linux, an unreadable /proc/self/status or an ignored/caught SIGCHLD disposition
refuses external execution before launch. That check cannot detect every signal
flag or competing reaper; the embedding ownership requirement still applies.
The same opt-out disables tungsten emitters discovery, including --describe
probes of executables on PATH. Its empty result means discovery was disabled;
it does not establish that no emitters are installed. Without the opt-out,
discovery executes those programs and requires the same trust as generation.
Browser WebMCP
Add a browser target alongside your TypeScript SDK:
targets:
typescript: { package: "@acme/petstore", out: generated/typescript }
webmcp: { package: "@acme/petstore-webmcp", out: generated/webmcp }Build the generated TypeScript SDK first, then the browser package (npm install
and npm run build in each). For source-local development, configure the SDK's
runtime_path, and the browser target's sdk_path, runtime_path
(@elysra/tungsten-webmcp) and sdk_runtime_path (@elysra/tungsten-runtime); see the guide.
Published defaults use version ranges, with sdk_runtime available to override
the browser package's direct SDK runtime range.
The generated ESM package exports a manifest and an explicit registration helper.
Your application supplies the SDK client configuration, API base URL and browser
session. Importing the package registers no tools. Registration defaults to
read-only operations; explicitly allowlist tools to expose writes or macros.
Operations that require confirmation also expose a <tool-name>__preview tool,
and execution still requires the SDK's argument-bound confirmation token.
Dispose the registration or abort its signal when the page or component leaves.
Scope registrations to the current page to keep large catalogs manageable.
For route-based loading, import a generated groups/<moduleKey> entry inside
the createWebMcpRegistrationController loader. Each group builds a scoped
descriptor/model graph and keeps read-only registration defaults. React and Vue
examples are in the browser integration guide.
Opt-in bounded streams and binary outputs are described in
tool results.
WebMCP is experimental and browser availability varies. The runtime prefers the
current document.modelContext API with async registration and abort-based
cleanup, and supports a structural adapter for the older
navigator.modelContext API. Check capability before enabling the feature.
The generated-package browser smoke passed in Chromium 154.0.8037.57 with
WebMCPTesting enabled; full cross-browser conformance remains future work.
Browser tool schemas and annotations are hints: the generated SDK validates
arguments, while your API must enforce authorization. Use a secure context,
appropriate CORS and CSRF controls, and your application's browser session;
never bundle server credentials. See the browser WebMCP guide.
Architecture
- The frontend reads OpenAPI 3.0 and 3.1, resolves references and applies overlays. The other supported inputs (Swagger 2.0, Postman, saved cURL,
.http/.rest, HAR, endpoint manifests) are converted offline to a normalized OpenAPI document first;initwrites it asimported.openapi.jsonand the frontend reads it like any OpenAPI document. - The IR is a versioned JSON document (
tungsten ir dump, schema viatungsten schema ir). - The agent compiler merges
agent.ymlinto it: safety tiers, idempotency, confirmation, remediation text, verification hooks, macros and the disclosure mode for MCP. -
Emitters (
crates/tungsten-emit-*) write each target from the IR. They are pure functions of their inputs. -
Runtimes (
runtimes/) hold the behaviour shared by generated code: validation, auth, retries, idempotency stores, the envelope, previews, pagination and middleware.
Repository layout: crates/ is the compiler (AGPL-3.0-only); runtimes/ts, runtimes/mcp, runtimes/webmcp, runtimes/python, runtimes/rust, runtimes/go, runtimes/java, runtimes/kotlin, runtimes/csharp, runtimes/fsharp, runtimes/dart and runtimes/ruby contain the libraries generated code depends on (Apache-2.0).
CLI
tungsten [--json] <command>
| Command | Purpose |
|---|---|
init |
Write project manifests from any supported input (Supported inputs): --from, --dir, --name, --targets, --force; --targets webmcp includes a linked TypeScript SDK |
workspace |
List the projects under a directory (bounded discovery, never following links) with their ids, outputs and inputs, and report outputs two projects share (TG0642); --project ID and --affected FILE select projects, --run check|generate|lock -- ARGS runs a command on each, one at a time. --project ID on check, generate, lock and the other project commands runs on one project of the workspace at PATH |
check |
Validate the manifests and specs and report diagnostics (--ci, --strict); opt into response quality with --audit-contracts or audit-only enforcement with --require-contracts; --ci runs the emitters in memory, external ones included (--no-external skips them) |
generate |
Generate every configured target (--target, --dry-run, --check, --force, --strict); --locked refuses (TG0128) unless the project matches tungsten.lock.json and acquires inputs offline |
ir dump |
Write the IR as JSON (--out, --compact) |
explain |
Explain a diagnostic code (TG0201), an operation or a type |
schema |
Print a JSON Schema: tungsten, agent, ir, cli-output, external-emitter, sdk-descriptors, tool-catalog, generation-lock, project-bundle
|
mock |
Serve a mock of the API from the IR (--port, --seed, --gate, --scenario) |
scenario validate / scenario run
|
Validate a saved scenario, or run explicit integration argv with exit and call-count assertions (Linux run) |
report |
Coverage, safety matrix, token budgets and diagnostics, as text, JSON or one static HTML file (--html); only configured targets run, --all-targets runs every known emitter |
diff |
Show regeneration changes; --semver classifies the API surface, --migration adds upgrade actions and runtime requirement review |
lsp |
Serve diagnostics, quick fixes, completion, hover, go-to-definition and symbols for tungsten.yml, agent.yml and overlays to an editor over stdio (--config) |
doctor |
Report external tools; pass a project path and --strict to fail when its required tools are missing or too old |
capabilities |
Query built-in target declarations without running tools or emitters; optionally select a target id or alias, such as capabilities py --json; --matrix prints the Markdown support matrix derived from the same declarations, and --matrix --format markdown wraps it in the markers of the README block CI checks |
lock |
Capture the project generation lock tungsten.lock.json offline: content identities of the manifest, profiles, agent manifest, inputs, references and overlays (in order), compiler and built-in emitter identities, effective target option digests and consumer toolchains. --check fails when the lock is missing, stale or corrupt, --preview writes nothing, --update accepts changed identities; --outputs also records or verifies the identity of the bytes each built-in target generates |
bundle export / bundle import
|
Export the locked input closure and tungsten.lock.json into a new plain bundle directory with a digest inventory (--preview lists it; --include-input-cache carries the verified pins of remote inputs; --include-consumer-cache DIR carries a prepared consumer dependency directory with provenance and platform notes; incomplete or credential-named content needs --allow-incomplete / --allow-sensitive), or validate a bundle completely and copy it into --dest without overwriting differing files. Neither runs emitters or grants trust |
support-bundle --preview |
Inspect the exact metadata-only support manifest without writing files; --exclude <id> removes an entry. On Linux, macOS and Windows, --output <PATH> instead publishes it to a new file in an existing, trusted directory; --inspect <FILE> checks and shows an existing bundle |
With --json, stdout carries exactly one JSON document described by tungsten schema cli-output (lsp speaks the language server protocol on stdio instead). Color is used only on a terminal and never when NO_COLOR is set.
Inspect and export support metadata
tungsten support-bundle --preview --json shows a versioned manifest containing
allowlisted tool, capability and configuration provenance, diagnostic codes and
declared prerequisites. Collection works with a broken project and does not
compile it, read API documents, probe tool versions, run extensions or contact a
network. It excludes configuration values, private paths, URLs, credentials,
source text, logs, hashes and project identifiers. Use --exclude <id> to omit
an entry.
Logs and configuration files are never collected unless you name them.
--attach-log <FILE> and --attach-config <FILE> (repeatable, at most four in
total) add user.attachment[N] entries, numbered from 0 with log files first.
Each entry has origin user_supplied and sensitivity user_supplied_text.
Such a bundle uses bundle_schema_version: 2 with privacy level
metadata_and_user_supplied; without attachments the bundle stays version 1
and unchanged. Each file must be a regular, nonsymlink file, and its path is
never recorded. Only its last 8 KiB are kept, starting at a line boundary.
Automatic redaction then replaces environment variable values, values of
credential-like keys (password, token, secret, api_key, authorization
and similar), Bearer/Basic credentials, private-key blocks, URLs, user names
in home directory paths and long token-like strings, and counts its
replacements. Redaction is not a guarantee that arbitrary text is safe to
share: review the preview, and use --exclude user.attachment[N] to drop an
attachment. An unreadable attachment fails the command before anything is
written.
On Linux, macOS and Windows, tungsten support-bundle --output <PATH> creates
a new local file.
Its bytes are exactly the same manifest serialized as compact JSON with one
trailing newline, capped at 128 KiB including metadata; entry payloads retain
their 16 KiB individual and 64 KiB combined limits. The JSON command result is
a separate receipt containing the manifest, complete artifact size, publication
state and temporary-file cleanup state. The manifest's mode: "preview" and
wrote_files: false describe inspection; the receipt records publication.
The destination directory must already exist, belong to the effective user
and have no group or other write permission. Traversed directories must be
owned by that user or root and protected from other-user replacement;
root-owned sticky ancestors such as /tmp are allowed. Symlink directories,
.. traversal and existing destination entries are refused. Publication uses
an exclusive file with mode 0600 and never replaces an existing entry. These
checks do not protect against root, another process with the same user ID,
permission changes by those actors or a filesystem that violates local file
operation semantics. Every path component is opened without following
symlinks, so macOS paths through the /tmp, /var and /etc symlinks are
refused; use the /private/... path or a directory in your home folder.
Publication needs hard-link support in the destination filesystem, which some
FAT, exFAT and network volumes lack. On Windows, every directory from the
drive root to the destination is held open without delete sharing, so none
can be renamed or replaced during export; reparse points (symlinks and
junctions), network shares, device paths, reserved device names and
alternate data streams are refused. Windows does not check the directory's
owner or ACL: export into a directory only you can write, such as one in your
user profile. Other platforms refuse export before collecting data; preview
remains available.
Collection, for preview and export alike, stops after 30 seconds with nothing written. It only reads, so an abandoned collection has no side effects; in an embedding process its worker thread may keep running until it finishes.
Library callers can cooperatively cancel an export before publication with
run_with_cancellation. Successful publication is the commit point: a later
cancellation does not remove the file. A cleanup failure after publication
returns a nonzero status while retaining published: true in the receipt.
On Linux, the tungsten binary blocks SIGINT and SIGTERM for the duration of
an export and turns them into the same cooperative cancellation: an
interrupted export stops collection or writing within about 50 ms, removes its
temporary file and exits with status 4 without publishing. A signal after
publication leaves the published file and the success status in place. On
macOS and Windows the default signal and console-control behavior still
applies. Abrupt termination (SIGKILL, a crash or power loss), interrupts on
macOS and Windows, or unconfirmed cleanup can leave one owned
.tungsten-support-*.pending entry per invocation; later commands do not sweep
existing temporary entries. File syncing before publication does not establish
a power-loss durability guarantee. There is no automatic upload or issue creation.
tungsten support-bundle --inspect <FILE> reviews an existing bundle on any
platform before you share it. It reads one regular, nonsymlink file of at most
128 KiB, writes nothing and starts no program. A bundle whose
bundle_schema_version this tungsten does not read is refused with that
number; versions 1 and 2 are read. Otherwise every object must have exactly
the keys of that schema version, and the six observed entries must be
metadata only. Version 2 may follow them with up to four entries labelled
user supplied. Recorded sizes, statuses and totals must match the payloads.
Free text in observed entries is limited to printable ASCII, and attachment
text to 12 KiB without control characters other than newline and tab. The result lists each entry with its exact payload. Errors name
only schema locations such as /entries/0/payload/os, never file content or
unexpected keys. These checks confirm the bundle's shape, not that its text is
safe to publish.
Generation prepares compatible Java, Kotlin, C#, F#, Dart and Ruby SDK structure
once per command, preserving each target's naming. Writing runs stage one
target's generated bytes at a time in an owned system temporary directory and
verify them before applying that target. --strict finishes every emitter's
diagnostic preflight before any destination writes. Temporary staging also
applies to --dry-run; destination files remain unchanged. Staging metadata is
limited to 262,144 files and 64 MiB of accounted path/digest/index metadata across
the selected targets; exceeding a limit fails with TG0701. This bounds retained
output metadata, not total compiler memory or temporary disk use. --check,
check --ci, diff and report process each target's output in memory.
Custom files remain user-owned and obsolete generated files are removed using the previous output manifest. Output writes are not a transaction across files or target directories. Cancellation or an I/O failure during application can leave earlier files written or removed; the failing file may also be partly changed. TG0701 reports completed write/removal counts. Correct the failure and run generation again. Cooperative cancellation checks run between file operations; a single emitter pass or file operation may finish first. Cleanup of owned temporary files is attempted when the command unwinds; forced process termination or a cleanup error can leave a temporary directory.
Saved mock scenarios
tungsten mock . --scenario retry.json --port 0 validates a saved scenario
against the project's callable operations, then starts a fresh loopback server
with its responses queued. The listening URL is printed in the normal report
(result.base_url with --json). Each process owns its port and mock state.
Files never run commands or expand environment variables. You can launch your
integration separately and point it at that URL, or use the explicit runner
below to manage its lifetime and assert the final result.
For an operation named openapi.listPets, this version 1 JSON file expects a
rate limit followed by one successful retry (replace the operation with one in
your project):
{
"version": 1,
"programs": [
{"operation": "openapi.listPets", "status": 429, "headers": {"Retry-After": "0"}},
{"operation": "openapi.listPets", "status": 200, "body": []}
],
"expect": {
"total_calls": 2,
"calls": [
{"operation": "openapi.listPets", "status": 429, "count": 1},
{"operation": "openapi.listPets", "status": 200, "count": 1}
]
}
}After the integration has finished and no calls are in flight, fetch
GET /__tungsten/scenario. HTTP 200 means the report was read: assert its
passed field, which is true only when the total and every listed count match
and all queued responses were consumed. calls in the report follows the
file's assertion order; omitting an assertion's status counts every call of
that operation. Status 0 counts dropped connections. Counts remain accurate
even when --max-recorded-calls evicts request payloads. Reports contain counts
only, without operation names, credentials or bodies. A snapshot can change
when another request arrives; it does not stop the server or prove provider
effects. In particular, a recorded write is not proof of an applied write.
Optional expect.effects entries assert what the mock did with an operation's
requests: applied counts fresh requests it processed to a 2xx answer, even when
that answer was then dropped (drop-after-write) or replaced (status=...;apply);
replayed counts requests answered from a stored idempotency key. Programmed
responses are neither. State at least one of the two counts. This recipe checks
that a client retries a lost write with the same Idempotency-Key, so the write
is applied once and replayed once:
{
"version": 1,
"programs": [
{"operation": "openapi.createPet", "inject": "drop-after-write"}
],
"expect": {
"total_calls": 2,
"calls": [
{"operation": "openapi.createPet", "status": 0, "count": 1},
{"operation": "openapi.createPet", "status": 201, "count": 1}
],
"effects": [
{"operation": "openapi.createPet", "applied": 1, "replayed": 1}
]
}
}A client that sends a new key on the retry still matches both call counts, but
reports applied 2 and replayed 0, so the scenario fails. The report lists
effects in file order only when the file declares any. Effect counts describe
the mock's own processing; they do not prove a real provider's behavior or that
the client received a response.
The mock-scenarios gallery recipe runs 429-then-success, interrupted-stream, malformed-response and lost-write scenarios against a small integration, and shows each deliberately broken retry failing.
Programs use the existing /__tungsten/program controls: status (200–599),
optional JSON body, string-valued headers, times, error code, and
cut_after bytes; or inject with optional times. For malformed JSON, use a
string body and an application/json header. For stream interruption, use
inject: "drop-mid-stream" or a stream string body with cut_after. For a lost
write response, use inject: "drop-after-write"; the mock's existing validation
and idempotency behavior still applies. inject: "reset" is unsupported in a
program because it acts before routing. Unknown fields and unsupported controls
are rejected before the port is bound.
Version 1 limits each file to 1 MiB, 256 programs, 256 call assertions, 256
effect assertions, 10,000 programmed calls and at most 10,000 expected total calls. Timeout/stall injection
holds cannot exceed 30 seconds. These are input limits, not an overall run
deadline. Keep scenarios synthetic: bodies and headers are served as written,
and the separate /__tungsten/calls endpoint still exposes recorded data.
Stop the server with Ctrl-C or close its stdin when done. Existing
POST /__tungsten/reset clears responses, recordings and scenario assertions;
restart with the same file to rerun it. Without a loaded scenario the assertion
endpoint returns 404. These manual-server controls remain available for local
exploration; the runner keeps its loaded responses and assertions immutable.
Validate without binding a server or launching a process on any platform:
tungsten scenario validate . --scenario retry.jsonOn Linux (including WSL), macOS or Windows, run your integration explicitly:
tungsten --json scenario run . --scenario retry.json --timeout-ms 30000 --expect-exit 0 -- python3 integration.pyThe integration reads its URL from TUNGSTEN_MOCK_BASE_URL. Arguments after
-- are passed directly to the named executable. There is no implicit shell;
commands cannot be supplied by the saved file. An interpreter such as python3
runs only when explicitly named by the caller. On Windows, a bare name resolves
only to an .exe (for example python); name a script shim in full, such as
npm.cmd. Other platforms reject run before input acquisition, port
binding or process creation; validate remains portable.
--timeout-ms bounds the whole command. It starts before input acquisition
and compilation, and covers scenario validation, server startup, process
execution, cleanup and final assertions; a small part of the time left for the
run is reserved for cleanup. Cancellation is also observed during acquisition
and compilation. A run stopped there reports timed_out or cancelled with
validated: false and never binds a port or starts the integration. Inputs
still being acquired are abandoned, not interrupted: a remote fetch continues
in the background only until the process exits or its own acquisition limit
expires. Library callers' RunOptions.timeout remains the run-phase budget.
The runner owns a fresh loopback port and a process group (a Job Object on
Windows), stops
its server before reading final counts, and fails on cancellation, timeout,
cleanup failure, unfinished admitted requests, unexpected exit or unmet counts.
Request completion tracks server-side handler and response-body generation,
independently of when a call was counted; it does not prove transport delivery.
The binary handles SIGINT/SIGTERM for this run (Ctrl-C, Ctrl-Break and console
close on Windows); library callers supply explicit cancellation instead of
installing signal handlers. Linux uses a scoped signal mask; macOS and Windows
install one Ctrl-C handler in the binary on first use. The Linux binary refuses an
unreadable or malformed /proc/self/status and observable ignored/caught SIGCHLD
dispositions before acquiring inputs or creating a server or child. Embeddings
must exclusively reap their runner children, avoid auto-reap dispositions and
keep SIGCHLD disposition stable; the binary preflight does not establish these
conditions for a concurrent embedding or inspect every SA_NOCLDWAIT setting.
macOS has no equivalent preflight; the same embedding requirements apply.
Windows terminates the Job Object on cleanup. Process groups and Job Objects cover ordinary
descendants, not processes that deliberately leave the group. Signal delivery
and leader reaping do not prove that every descendant has settled, and this is
not a sandbox.
Runner connections are capped at 32 before request bodies are read, bodies at
1 MiB, and admitted API calls at 10,000 (--max-calls can lower that limit).
Excess admission or attempts to mutate control state fail the run. Recorded-call
payloads are not retained; the bounded 64-entry idempotency cache can retain
request fingerprints (including body bytes) and responses. Child stdin, stdout
and stderr are disconnected. The machine report includes stable status and
counts without child output, argv, PID, port or timing. result.run.passed requires the expected exit code (default
0), matching counts and consumed responses. The command exits 0 only on success;
run assertion/cancellation failures exit 1. Count assertions still do not prove
provider effects, including applied writes or exactly-once behavior.
capabilities returns contract version 1: target ids/aliases, source implementation,
runtime family, imported-operation policy support and local consumer prerequisites.
The same declarations drive target aliases and doctor --strict, including Kotlin's
Java/Gradle tools, F#'s .NET SDK and Dart's >=3.6 <4.0 SDK. Python's default
minimum is 3.12; doctor resolves the declared python_version option for 3.10
or 3.11 projects. Prerequisites describe local builds, not compiler execution or
browser runtime support. Qualification, platform qualification and artifact
availability are explicitly unknown: these source records do not prove a
tested combination or published package. External emitters are not inspected by
this command. Fields may be added within version 1; consumers should reject
unknown major contract versions before interpreting them.
The TypeScript declaration also includes options_schema, a JSON Schema for
supported raw package, version, runtime, runtime_path and dependencies
values. The ts alias returns the same declaration. Python and Rust declare
their typed options the same way. Every other built-in target declares a field
schema with the JSON type, fixed default and description of each option it
reads (besides out), plus the warning code an invalid value produces; the
emitter keeps the semantic validators, and config inspection covers only the
TypeScript, Python and Rust schemas. The reserved mock target reports null.
This does not change generation policy:
invalid TypeScript scalars still produce TG0711 warnings and use defaults;
invalid custom dependencies produce errors. Unknown keys remain ignored, and
defaults that depend on the compiled API remain in the emitter. This is an
option declaration; existing project target validation remains compatible.
The catalog also registers the IR's protocols under protocols. Every IR
namespace without a protocol binding is HTTP, which every built-in target
generates; graphql, grpc and async_messaging are reserved for future
frontends, which keep the common operation contract and put
protocol-specific contract in the binding's extension rather than in REST
fields. A target is refused with TG0706, before anything is written for it,
when the IR binds a protocol the target does not declare.
Local TypeScript profiles
Select ordered local defaults in tungsten.yml:
profiles:
- path: profiles/sdk.yml
root: profilesBoth selection paths are relative to the project manifest; root is an explicit
existing include directory. A profile outside the project may be deliberately
selected with relative ../ paths. Absolute, drive-relative and rooted Windows
paths are refused. Profile v1 is a YAML or JSON mapping:
tungsten_profile: 1
includes: [base.yml]
targets:
typescript:
package: '@example/sdk'
version: '1.0.0'
runtime: '^0.1.3'
dependencies:
yaml: '^2'Includes resolve relative to their declaring profile and remain inside the selected root after symlink resolution. Traverse selections/includes left to right, depth first, applying parents before their child and each canonical file once at its first encounter. Every reused graph path still checks confinement and depth. Limits per compilation are 1 MiB per file, 16 MiB of unique captured bytes, 64 identities/selections, 256 include edges and depth eight (roots at zero). Profile reads use the shared source provider, including unsaved local files in existing directories, and capture each identity once for that compilation.
Scalars override individually; dependency maps merge by package name. Explicit
project fields take precedence. Missing fields and empty dependency maps add
nothing; profile nulls and unknown fields are errors, with no deletion syntax.
The ts alias is accepted inside profiles, but declaring both aliases is an
error. Profiles cannot set project identity, inputs, agent/tools, executables,
output directories or runtime_path. Existing project invalid/null target
values keep their TG0711 warning/fallback or dependency-error behavior.
Project doctor checks the same composed target configuration, including a
TypeScript target introduced only by a profile. It validates the manifest and
bounded profiles before probing tool versions; it does not compile or acquire
API inputs, load agent content, or execute emitters.
The compiler driver returns typed declarations and winning/shadowed authored
origins in memory; emitter defaults still depend on the compiled API. Profile
source/digest names are portable (profile[0]/base.yml), while physical identity
stays in provenance for confinement. A name collision with another compilation
input is an error. This result is not a replayable approval document. Hosts can
select the external-emitter restrictions described above; executable approval
remains separate.
Transport and account references
Profiles and tungsten.yml can declare where a runtime finds enterprise
transport material, without holding it:
transport:
proxy: { env: CORP_HTTPS_PROXY } # env only: proxy URLs can carry credentials
no_proxy: { env: CORP_NO_PROXY }
ca_bundle: { file: certs/corp-ca.pem } # env or a relative file, relative to the project
client_certificate: { env: CORP_MTLS_CERT }
client_key: { store: "keychain:corp-mtls" } # env or a secure-store item, never a file
credential_provider: secure_store # environment, secure_store or hook
account: build-bot # non-secret names
environment: stagingFields compose like scalar target defaults: later profiles win, explicit project
fields win over profiles, and tungsten config shows each reference with its
winning and shadowed source. Inline values (a proxy URL, a PEM block, a token) are
refused, as is any key that would disable certificate verification; trust a
private CA with ca_bundle instead. References are names only: tungsten never
reads the variables, files or store items they point to, and generated SDKs do not
consume them yet. tungsten doctor <project> reports whether each referenced
environment variable is set and each referenced project file exists, without reading
either; --strict fails on a missing file, while variables may be supplied where
the client runs and store items are left to the runtime's adapter. Wire them into a native or custom transport that keeps TLS
verification, redirect/auth rules, deadlines and admission in place.
tungsten lsp serves profile files too: completion and hover from the profile
schema, diagnostics in the profile that caused them, and watchers for profile
directories outside the workspace folder.
Init presets
tungsten init --preset NAME starts from a named set of outputs; the same names
are accepted at the guided setup's outputs prompt. Explicit --targets and
--strict win, and a preset never selects inputs, reviews operations or changes
agent.yml safety defaults.
| Preset | Outputs | Also |
|---|---|---|
app |
TypeScript SDK, docs | |
library |
TypeScript, Python and Rust SDKs, docs | writes profiles/library.yml (shared package versions) and selects it, so other projects can reuse it |
agent |
MCP server on the TypeScript SDK | generates with --strict
|
tungsten init --no-interactive --preset library --from openapi.json --dir my-sdk
tungsten init --no-interactive --preset app --from openapi.json --generate --local-exampleExit codes are those of init: 0 on success, 1 when the input has errors, 2 for
an unknown preset, 4 when an existing manifest or profile would be overwritten
without --force (nothing is written). --no-interactive never prompts.
Inspect the supported effective configuration without compiling API inputs:
tungsten config .
tungsten config path/to/tungsten.yml --json
tungsten config . --strict --jsonThe command validates the manifest and bounded local profiles through the same
config-only loader as project doctor. It shows the winning and shadowed authored
origins for each supported TypeScript option and custom dependency, its default
and invalid-value policy, and runtime_path precedence over runtime. Package
names derived from the compiled API remain unresolved. Static package/runtime
defaults come from the shared option declaration. Targets without a typed option
declaration are explicitly unsupported_options; external targets are
external_unverified, with opaque ordinals in manifest order.
Runtime specifiers, local runtime paths and dependency specifiers are always
redacted by the option schema. Unknown settings, transport/auth content, physical
paths, source excerpts and invalid authored values are omitted. Sources use
project and profile[n] capture ordinals with profile content digests, so even
profile filenames are not exported. These digests identify captured bytes, not
publisher trust. Plain text and JSON carry the same explanation, without prompts,
color, login, tool probes, input acquisition or emitter execution. Global input
acquisition flags do not cause acquisition during inspection.
Exit 0 means the inspected configuration has no errors; exit 1 means manifest,
profile or supported-option errors, or warnings with --strict. Unsupported
target markers are informational. Invalid scalar project options retain their
warning/default policy; invalid custom dependencies fail. This inspection is
neither a replayable manifest nor evidence that generation or a consumer works.
Editor support
Diagnostics for selected local profiles point to the captured physical file, including invalid or unsaved included profiles outside the manifest directory. Compiler source names and exported configuration provenance remain portable. Dedicated profile completion and navigation remain follow-up work.
tungsten lsp is a language server for tungsten.yml, agent.yml and the overlay files a project lists. It checks the project with the same compiler as tungsten check and shows the same TG diagnostics on their source lines. Unsaved JSON/YAML manifests, specs, overlays and local references participate in the same source snapshot, including new files in an existing input directory. Closing a buffer restores its disk contents; file watchers pick up saved changes and deletions. File-size limits and local-reference confinement still apply. The selected project's .tungsten-inputs acquisition cache is reserved for verified disk content: buffers cannot replace or extend a pinned Git tree, while explicitly selected local overlays outside the cache remain editable. It completes keys, enumerated values and the operation ids, gates and clusters of the compiled project; shows field documentation, operation signatures and diagnostic explanations on hover; jumps from an operation id to its place in the OpenAPI document; and outlines the manifests. Quick fixes replace an unknown operation id (TG0603) or an unknown manifest field or value (TG0602) with the closest name the compiler knows; each fix is a single edit pinned to the buffer version it was computed for, so the editor previews it, refuses it after a later edit and can undo it. An operation without an operationId (TG0403) or with a repeated one (TG0401) gets the overlay action tungsten overlay suggest derives, added to the input's suggested overlay in one previewed edit. When a fix cannot be offered, editors that show disabled code actions show why and the equivalent command. Editors without versioned workspace edits get plain edits for the replacement fixes, still checked against the current buffer. A fix never runs emitters, installs packages or refreshes inputs. Every project in the editor's workspace folders is served at once: projects are found the way tungsten workspace finds them (bounded, never following links), a folder inside a project serves that project, and opening a tungsten.yml elsewhere serves it too; --config <path> serves exactly one project. Each project is checked on its own, one check at a time for the whole session: editing a file re-checks only the projects that read it, so a shared spec updates every project using it and no other, diagnostics of a file read by several projects are merged, and removing a workspace folder cancels its projects' checks and clears their diagnostics. Requests about a file are answered from the project that owns it (its manifest, else the first project reading it, else the innermost project directory holding it).
Edits retain the existing debounce and one active analysis worker. A newer edit cancels obsolete work cooperatively between compiler phases and during reference, operation and component traversals; obsolete results do not replace diagnostics or the last accepted navigation index. Diagnostics for open files carry their document version, and older change notifications are ignored. An in-progress read, parse or indivisible compiler pass finishes before the next cancellation check; this is not a hard execution deadline. No persistent analysis cache is introduced.
Visual Studio Code needs a generic language client extension, for example Generic LSP Client. In settings.json:
{
"glspc.languageId": "yaml",
"glspc.serverCommand": "tungsten",
"glspc.serverCommandArguments": ["lsp"]
}Neovim with nvim-lspconfig:
local configs = require('lspconfig.configs')
local util = require('lspconfig.util')
if not configs.tungsten then
configs.tungsten = {
default_config = {
cmd = { 'tungsten', 'lsp' },
filetypes = { 'yaml', 'json' },
root_dir = util.root_pattern('tungsten.yml', 'tungsten.yaml'),
},
}
end
require('lspconfig').tungsten.setup({})Any other client works the same way: start tungsten lsp as a stdio server for YAML files, and for JSON files too when the project has JSON specs or overlays, so their unsaved edits are checked (the Neovim example attaches to both).
Quick fixes are ordinary code actions: the light bulb or Ctrl+. in Visual Studio Code, vim.lsp.buf.code_action() in Neovim. Overlay fixes create a file and edit tungsten.yml in one workspace edit, so the client must support documentChanges with the create resource operation; clients with change-annotation support ask for confirmation and preview every file first (Visual Studio Code opens its refactor preview). Neovim applies the edit directly; undo it per buffer with u, and delete the created overlay to revert fully. A client that shows disabled code actions explains why a fix is unavailable, with the equivalent tungsten overlay suggest command.
| Exit code | Meaning |
|---|---|
| 0 | Success (also diff when regeneration would change files, and report with warnings) |
| 1 | The input has errors (or warnings with --strict), generated output is stale (generate --check, check --ci), or the item asked for does not exist |
| 2 | Usage error: the command line could not be parsed |
| 3 | I/O or internal failure (for example --out is not writable) |
| 4 | Refused to act without confirmation (init over existing files, generate into a directory tungsten did not write: pass --force) |
The CLI that tungsten generates for a Rust target has its own exit codes (0 success, 2 usage or pre-flight validation, 3 API error, 4 retryable API error, 5 unknown outcome, 6 confirmation required, 7 missing credentials) so a calling agent can branch on them.
Project status
tungsten is early-stage. Ten SDK targets, the generated Rust CLI, MCP, WebMCP, docs and the mock server are implemented. Validation is target-specific; do not infer that every target has the same live pilot coverage or published runtime packages. Check the GitHub releases for available compiler binaries and matching sources. The quickstart installs a runtime from matching source and does not require registry publication; from 0.1.3, compiler and runtime source versions follow the release tag together. Expect breaking changes to manifests, the IR and generated code between releases. Open an issue if a target limitation blocks you.
The test suite, conformance corpus and design documents live in a separate maintainers' repository, which runs against every pull request. This repository is source only.
Licensing
- The compiler (
crates/) is licensed under AGPL-3.0-only. - The runtime libraries that generated code depends on (
runtimes/) and the external Go emitter (emitters/go) are licensed under Apache-2.0. - Output generated by tungsten is not a work covered by the AGPL and carries no licence from tungsten. The owner of the API description chooses the licence of the generated code.
Contributing and security
Read CONTRIBUTING.md before opening a pull request, and open an issue to discuss a large change first. Report vulnerabilities privately as described in SECURITY.md; please do not file them as public issues.
Python interpreter compatibility
Python targets default to Python 3.12+. To generate a client for Python 3.10
or 3.11, set a quoted python_version in targets.python:
targets:
python:
python_version: "3.10"Supported values are "3.10", "3.11" and "3.12" (the default). Both
Pydantic and models: dataclasses support these modes. Compatibility output
uses typing_extensions aliases and retains forward references, validation
and transport behavior. The matching runtime supports Python 3.10 and newer;
compatibility clients require tungsten-runtime>=0.1.3,<0.2.0, including when
an explicit runtime specifier is supplied. A runtime_path checkout must
contain this compatibility support. Publishing the matching runtime remains
part of the coordinated release.
Public CI builds the runtime sdist and wheel on the latest patch of CPython
3.10, 3.11, 3.12, 3.13, 3.14 and 3.15 (macOS and Windows on 3.14), and
installs them with freshly generated SDKs into clean environments: both model
modes, "3.10" output on every minor and default output on 3.12 and newer,
with compile, import, strict Pyright and sync/async requests covering
confirmation, auth, pagination, safe retries, validation, deadlines,
cancellation, unknown outcomes and server-sent events. The 3.10 lane also runs
the lowest declared dependency versions. Generated packages list classifiers
for these minors from their python_version upward. Python 3.10 is
end-of-life upstream: it remains a compatibility lane, not a
security-maintained runtime. Prerelease interpreters are not claimed, and
requires-python sets only a floor.