Skip to content

Project Structure

After scaffolding, your instrument looks like this:

my-instrument/
├── package.json # Manifest + dependencies
├── tsconfig.json # TypeScript config
├── .gitignore
├── src/
│ ├── index.tsx # Frontend entry point
│ └── backend.ts # Backend entry point (optional)
├── dist/ # Build output (git-ignored)
│ ├── index.js
│ └── backend.js
└── tango-env.d.ts # Generated types (git-ignored)

The tango.instrument field is the manifest that tells Tango how to load your instrument:

{
"name": "tango-instrument-my-instrument",
"version": "0.1.0",
"private": true,
"type": "module",
"main": "dist/index.js",
"scripts": {
"dev": "bun node_modules/tango-api/src/cli.ts dev",
"build": "bun node_modules/tango-api/src/cli.ts build",
"sync": "bun node_modules/tango-api/src/cli.ts sync",
"validate": "bun node_modules/tango-api/src/cli.ts validate"
},
"dependencies": {
"tango-api": "github:MartinGonzalez/tango-api#v0.0.1"
},
"devDependencies": {
"@types/react": "^18.0.0",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"tango": {
"instrument": {
"id": "my-instrument",
"name": "My Instrument",
"group": "Custom",
"runtime": "react",
"entrypoint": "./dist/index.js",
"backendEntrypoint": "./dist/backend.js",
"hostApiVersion": "2.0.0",
"launcher": {
"sidebarShortcut": {
"enabled": true,
"label": "My Instrument",
"icon": "puzzle",
"order": 100
}
},
"panels": {
"sidebar": true,
"first": false,
"second": true,
"right": false
},
"permissions": ["storage.properties"]
}
}
}
FieldTypeDescription
idstringUnique identifier for the instrument
namestringDisplay name shown in Tango UI
entrypointstringPath to the built frontend bundle
panelsobjectWhich panel slots are enabled
FieldTypeDescription
groupstringCategory for grouping in the UI
runtime"react" | "vanilla"Rendering runtime (default: "react")
backendEntrypointstringPath to the built backend bundle
hostApiVersionstringSDK version for compatibility
permissionsstring[]Capabilities the instrument needs
settingsarrayUser-configurable settings schema
launcherobjectSidebar shortcut configuration. Required for the instrument to appear in the sidebar.

See the Manifest Reference for full details.

This is a React component that Tango renders inside a panel. It must default-export a TangoInstrumentDefinition created by defineReactInstrument:

import {
defineReactInstrument,
useInstrumentApi,
useHostEvent,
UIRoot,
UIPanelHeader,
UISection,
UIEmptyState,
} from "tango-api";
function SidebarPanel() {
const api = useInstrumentApi();
return (
<UIRoot>
<UIPanelHeader title="My Instrument" subtitle="sidebar panel" />
<UISection>
<UIEmptyState title="Welcome" description="Start building" />
</UISection>
</UIRoot>
);
}
export default defineReactInstrument({
defaults: {
visible: { sidebar: true, second: true },
},
panels: {
sidebar: SidebarPanel,
},
});

Use useHostEvent to react to system events like stage changes, session activity, and more:

import { useHostEvent } from "tango-api";
import { useCallback } from "react";
// Fires when the user switches to a different stage
useHostEvent("stage.selected", useCallback((stageInfo) => {
// stageInfo.path, stageInfo.branch, stageInfo.headSha
// stageInfo.hasVersionControl, stageInfo.hasChanges
// stageInfo.additions, stageInfo.deletions
}, []));
// Fires when the active stage's metadata refreshes (post-commit, file changes)
useHostEvent("stage.updated", useCallback((stageInfo) => {
// Same StageInfo payload as stage.selected
}, []));
// Fires for each Claude streaming chunk
useHostEvent("session.stream", useCallback(({ sessionId, event }) => {
// event: ClaudeStreamEvent
}, []));

See the Events & Hooks tutorial for the full list of available events.

src/backend.ts — Backend entry (optional)

Section titled “src/backend.ts — Backend entry (optional)”

Runs in the host Bun process. Define actions that the frontend can call:

import { defineBackend, type InstrumentBackendContext } from "tango-api/backend";
export default defineBackend({
kind: "tango.instrument.backend.v2",
actions: {
hello: {
input: { type: "object", properties: { name: { type: "string" } } },
output: { type: "object", properties: { greeting: { type: "string" } }, required: ["greeting"] },
handler: async (ctx: InstrumentBackendContext, input?: { name?: string }) => {
return { greeting: `Hello, ${input?.name ?? "world"}!` };
},
},
},
});

Run bun run sync to generate this file. It provides type-safe access to your instrument’s settings values. This file is regenerated on each sync and should be git-ignored.

The bun run build command compiles:

  • src/index.tsxdist/index.js (ESM, browser target)
  • src/backend.tsdist/backend.js (ESM, bun target)

Tango loads these files at runtime. You never need to touch dist/ directly.