Zum Inhalt springen

Extension Quickstart

Dieser Inhalt ist noch nicht in deiner Sprache verfügbar.

This page walks the whole authoring path with real commands: scaffold a project, build it, lint it, sign it, and publish it. It takes a few minutes on a machine that already has Rust. For the concepts behind each step — what describe.json declares, what a component contributes — read Writing Extensions after this.

  • Rust 1.95 or later, with the wasm32-wasip2 target: rustup target add wasm32-wasip2
  • cargo-component: cargo install --locked cargo-component
  • gtdx 1.2.8 or newer (installed below)

gtdx new checks all three before it writes a single file, so a missing tool is reported up front rather than at your first build.

  1. Install the CLI. This downloads a prebuilt binary — no compile:

    Terminal window
    cargo binstall greentic-extension-sdk-cli
    gtdx --version

    Prefer a manual download? Every release archive holds the binary one directory deep, named after the asset, so extract from that path:

    Terminal window
    TAG=v1.2.8
    TARGET=aarch64-apple-darwin # swap for your platform
    curl -L -o gtdx.tgz \
    "https://github.com/greenticai/greentic-designer-sdk/releases/download/$TAG/gtdx-$TAG-$TARGET.tgz"
    tar -xzf gtdx.tgz
    chmod +x "gtdx-$TAG-$TARGET/gtdx"
    mv "gtdx-$TAG-$TARGET/gtdx" ~/.cargo/bin/
  2. Scaffold a project. Pick the kind that matches the surface you are implementing:

    Terminal window
    gtdx new my-ext --kind design --yes
    cd my-ext

    --yes resolves everything from flags and defaults, which is what you want in scripts and CI. Prefer to be asked instead? Run gtdx new with no arguments — see Scaffold with the wizard below.

    Curious what just got written? What gtdx new Generates walks every file — which three you edit, and which are regenerated on each build.

    --kind wasm-component is the one kind that needs an extra flag: it wraps an already-published component, so it needs that component’s digest-pinned OCI reference. Omit it and the scaffold writes a placeholder that gtdx lint --publish refuses:

    Terminal window
    gtdx new my-node --kind wasm-component \
    --component-ref oci://ghcr.io/greenticai/component/component-my-node@sha256:461c6a68…
  3. Build, pack, and install locally. This is the inner loop:

    Terminal window
    gtdx dev --once

    It rebuilds the component, packs dist/<name>-<version>.gtxpack, and installs it into your local dev registry. Drop --once to watch the source tree and repeat on every save.

    The pack carries a manifest.json integrity ledger — a sha256 of every entry — which the runtime verifies at install.

  4. Lint before you publish. Two levels:

    Terminal window
    gtdx lint --dir . # cross-field invariants
    gtdx lint --publish --dir . # also enforces the publish-only rules

    --publish adds E_SHA256_ZERO, which rejects placeholder digests. A scaffold that has been through gtdx dev --once passes: the packer fills in the real digest of the wasm it just produced.

  5. Create a signing key, once, and keep it out of the repo:

    Terminal window
    gtdx keygen --out my-key.pem # PKCS8 PEM ed25519, mode 0600
  6. Publish. Dry-run first — it needs no account and does everything except the registry write:

    Terminal window
    gtdx publish --dry-run --sign --key my-key.pem
    gtdx publish --sign --key my-key.pem

    To publish to the public store rather than your local registry, authenticate first with gtdx login and pass --registry greentic-store. The store is built in — you do not need gtdx registries add for it.

Run gtdx new with no arguments on a terminal and it prompts for everything instead. The wizard is the same code path as the flags — it just asks first, and every answer has a default you can accept with Enter:

Terminal window
$ gtdx new
gtdx new — interactive wizard (press Enter to accept defaults)
Project name (kebab-case): wizard-demo
Extension kind:
> design Designer/agentic tools (default)
mcp MCP router (wasix:mcp/router) — usable as flow node + agentic tool
provider Messaging/event provider
wasm-component Generic WASM flow component
llm LLM provider extension
bundle Bundle extension
deploy Deploy target extension
Extension id (reverse-DNS) [greentic.wizard-demo]:
Version [0.1.0]:
Author [bimapangestu28]:
License (SPDX id) [Apache-2.0]:
About to scaffold:
name wizard-demo
kind design
id greentic.wizard-demo
version 0.1.0
author bimapangestu28
license Apache-2.0
Create this extension? [Y/n]

Nothing is written until you answer that last prompt, so it is safe to walk through and back out. The id defaults to greentic.<name>, and the author comes from your git config user.name.

Two details worth knowing:

  • Flags become the defaults. gtdx new my-ext --wizard pre-fills the name and still asks the rest, which is handy when you know some answers but not all. --wizard also forces the wizard even when you passed enough flags to skip it.
  • Choosing mcp adds a prompt. The wizard offers to seed the extension from an OpenAPI spec, and asks for the spec path if you accept.

The wizard needs a terminal. Without one — in CI, or when stdin is a pipe — gtdx new with no name fails rather than hanging, and tells you to pass a name or use --yes.

gtdx publish has the same option, and it is the friendlier way in while you are still learning the flags:

Terminal window
gtdx publish --wizard

It asks for the registry, then the mode — publish for real, dry-run, or verify-only — then a version override, whether to sign (and where the key comes from: file, key id, or env var), the trust policy, and whether to overwrite an existing version. It prints the full summary and asks Proceed? before anything is uploaded.

Left to itself, publish keeps its scripted defaults — the wizard is strictly opt-in, so existing CI invocations are unaffected.

Three layers, fastest first. Most of your testing belongs in the first one.

LayerCommandWhat it proves
Unit, on the hostcargo testYour logic is right. Milliseconds — no WASM, no Designer.
Full gate./ci/local_check.shfmt + clippy + tests + the wasm actually builds.
Integrationgtdx dev --onceIt packs and installs. Not that it behaves — that is the layer above.

The guest exports are plain Rust functions, so a host test calls them directly. No harness, no runtime:

#[cfg(test)]
mod tests {
use super::*;
#[test]
fn echo_returns_its_arguments() {
let out = <Component as tools::Guest>::invoke_tool(
"echo".to_string(),
r#"{"message":"hi"}"#.to_string(),
)
.expect("echo is implemented");
assert!(out.contains("echoed"));
}
}

Since 1.2.8 the scaffold ships tests like this for design, bundle, deploy and provider — covering the error paths too, not just the happy one. Extend them rather than deleting them: ci/local_check.sh runs cargo test, so an empty test module makes that step green while verifying nothing.

For logic that reaches http, secrets, state, logging or translation, the SDK publishes in-memory mocks:

[dev-dependencies]
greentic-extension-sdk-testing = "1.2.8"
use greentic_extension_sdk_testing::mock_host::{MockHttpClient, MockLogger};

They are ordinary objects — MockHttpClient, MockSecretsBackend, MockLogger, MockTranslator, MockBroker — not automatic implementations of the generated import traits. They help when your code takes the host dependency as a parameter rather than calling the binding directly. Structuring it that way is what keeps the logic testable on the host at all.

Terminal window
gtdx verify dist/my-ext-0.1.0.gtxpack

Without a trusted key this reports integrity only: the pack is self-consistent and signed, but the publisher is not authenticated. Pass --trusted-key to verify who signed it. The tool is deliberate about the distinction — an unauthenticated signature is not the same as a verified one.

To inspect an installed extension:

Terminal window
gtdx list # everything installed, grouped by kind
gtdx info greentic.my-ext # metadata for one
gtdx doctor # toolchain, registries, credentials, installed extensions

gtdx doctor exits non-zero when it finds real problems. It groups failures by cause and summarises the extensions that pass; add --verbose for the full per-extension listing.

All eight scaffold, build, lint, sign, publish, and install:

--kindWhat it is
designA designer extension — nodes, tools, and validation surfaces
bundleBundle-level assets and behaviour
deployA deployment target
providerMessaging and event providers
wasm-componentWraps an already-published component as a palette node
llmAn LLM backend
mcpA wasix:mcp/router component — flow-capable, addressable as dw.mcp.<id>
addonAn infrastructure workload (Qdrant, Redis, Postgres, …) a flow depends on — see Addon Extensions

A freshly built extension that installs cleanly can still be invisible in Designer. In order of likelihood:

The Designer is older than the contract. gtdx scaffolds against greentic.ai/v2, which a Designer below 1.2.0 screens out at boot — the extension never reaches /api/extensions, and nothing in the logs points at the version. See Designer Compatibility.

The Designer binary is older than its version string. This one is worth knowing because the version number will not tell you. A Designer built from a checkout embeds whichever greentic-extension-sdk-contract it was compiled against, and that is what actually decides which manifests it can parse — regardless of what --version reports. A binary that predates the compat field rejects every v2 manifest with:

json: unknown field `compat`, expected one of `$schema`, `apiVersion`, `kind`,
`metadata`, `engine`, `capabilities`, `runtime`, `execution`, `contributions`, `signature`

engine in that list is the tell: it is the v1 shape, and gtdx lint forbids engine precisely because compat replaced it. Check what the binary actually contains rather than what it claims:

Terminal window
strings $(which greentic-designer) | grep -o 'greentic-extension-sdk-contract-[0-9][^/]*' | sort -u

A 0.4.x result means the binary is from before the v2 contract, whatever its --version says. Rebuild or reinstall the Designer.

The Designer reads only ~/.greentic. It ignores GREENTIC_HOME, and there is no flag to point it elsewhere — so an extension installed into an isolated home will not be seen. Install into the default home when you want the Designer to pick it up.