Skip to content

Getting started

This guide takes you from zero to a registered Tango Vision module.

What you need

  • A developer accountregister here (self-service)
  • Node 22+ and pnpm 10 — pnpm is the platform standard; every scaffolder and template targets it
  • Access to the private registry https://npm.k8s.tangovision.dev/
  • A sandbox API key — see Developer account → Get a sandbox API key

Install the SDK

The @tv/* scope lives on a private registry and does not serve anonymous downloads — an unauthenticated fetch returns 401. Put both the registry and your token in .npmrc:

ini
# .npmrc
@tv:registry=https://npm.k8s.tangovision.dev/
//npm.k8s.tangovision.dev/:_authToken=${TV_NPM_TOKEN}

Export the token your registration email gave you, then install:

bash
export TV_NPM_TOKEN=...        # keep it in your shell profile or secret manager
pnpm add @tv/extension-sdk

Docker builds

.npmrc with a registry URL but no token is the single most common CI failure — the registry answers 401 to any request without a token, metadata included. In a Dockerfile, mount the token as a build secret rather than baking it into a layer:

dockerfile
RUN --mount=type=secret,id=npm_token \
    echo "@tv:registry=https://npm.k8s.tangovision.dev/" > .npmrc && \
    if [ -f /run/secrets/npm_token ]; then \
      echo "//npm.k8s.tangovision.dev/:_authToken=$(cat /run/secrets/npm_token)" >> .npmrc; \
    fi && \
    pnpm install --frozen-lockfile

What's in the package

The SDK ships everything in one package, exposed via subpaths:

SubpathWhat it gives youTier
@tv/extension-sdkModuleManifest types + Zod validator + PlatformContext types@stable
@tv/extension-sdk/manifestManifest schema, validator, scaffolders, checkExposes@stable
@tv/extension-sdk/contextPlatformContext runtime types@stable
@tv/extension-sdk/eventsEvent catalog, envelope, subjects@stable
@tv/extension-sdk/apiPlatformApiClient types@stable
@tv/extension-sdk/react<PlatformProvider>, usePlatformContext(), useBuilding(), useOptionalBuilding(), useCurrentUser()@stable
@tv/extension-sdk/nestjs@ModuleCapability(), @RequiresLicense() decorators@stable
@tv/extension-sdk/testingcreateMockPlatformContext()@stable
@tv/extension-sdk/permissionsextractCatalog(), PermissionCatalog, SubjectEntry@stable
@tv/extension-sdk/heartbeatLiveness reporting to the shell@experimental
@tv/extension-sdk/version-checkuseVersionCheck(), <UpdatePrompt> — deployed-client update prompts@experimental
tv-sdk (bin)The CLI — see belowmixed

Also published as plain JSON you can consume directly: manifest.schema.json, permissions.snapshot.json, tv-events.snapshot.json, openapi.snapshot.json.

Which tier a symbol carries decides whether it can change under you — see Stability tiers.

The CLI

bash
npx @tv/extension-sdk --help

Invoke the CLI by its full package name

The CLI's bin is named tv-sdk, and inside a project that has the SDK installed npx tv-sdk happens to work. Don't rely on that form: no tv-sdk package exists on the public npm registry, so outside such a project npx tv-sdk asks public npm for an unclaimed name — anyone who publishes a package called tv-sdk would get their code executed on your machine. Always spell npx @tv/extension-sdk <command>; it resolves through the authenticated @tv:registry you configured above, wherever you run it.

CommandWhat it does
init-module <slug> --category=<c>Scaffold a whole module: manifest, vite.config.ts, src/Shell.tsx, package.json
init-manifest <slug> --category=<c>Scaffold just a module-manifest.json
validate <manifest>Validate a manifest against the schema
check-exposes <manifest>Verify the canonical ./Shell federation expose in vite.config.ts
check-pact <manifest>Verify your events.subscribes contracts fit the producers' schemas
check-events <snapshot.json>Fail on breaking changes vs. a committed event-schema snapshot
check-api <snapshot.json>Fail on breaking changes vs. a committed OpenAPI snapshot
write-versionEmit version.json (version + commit + build time) as a postbuild step
ingest <manifest>Upsert the manifest into the platform registry
sandbox <subcommand>Manage sandbox tenants — create, list, connect, extend, reset, delete
copilotInteractive AI assistant that scaffolds and wires a module for you

init-module, init-manifest, validate, check-exposes, write-version are @stable. The rest are @experimental — useful, but the flags may move.

The anatomy of a module

my-module/
├── module-manifest.json     ← the contract
├── frontend/                ← React, exposes a federated "Shell"
│   └── src/Shell.tsx
└── backend/                 ← optional NestJS service
    └── src/

The manifest is the heart of it. It declares your module's id, the permissions it needs, the events it speaks, the Copilot tools it contributes, and where its UI mounts. The platform reads it three times:

  1. In your CInpx @tv/extension-sdk validate module-manifest.json
  2. At publish — the registry rejects an invalid manifest
  3. At runtime — the Building OS shell composes your module from it

The golden rule

Your module talks to the platform only through PlatformContext.

No localStorage. No manual tokens. No hand-built API URLs. The context gives you a pre-authenticated, tenant-scoped API client. This is what lets the same code run in your sandbox and in a customer's production tenant unchanged.

tsx
import { usePlatformContext, useBuilding } from '@tv/extension-sdk/react';
import { useQuery } from '@tanstack/react-query';

export function WorkOrderList() {
  const { api } = usePlatformContext();   // already authenticated + scoped
  const building = useBuilding();          // the active building
  return useQuery({
    queryKey: ['work-orders', building.id],
    queryFn: () => api.get(`/api/v1/buildings/${building.id}/work-orders`),
  });
}

Next

Your first module builds a working hello-world end to end.

Built on the Tango Vision platform. Questions? developers@tango.vision