Skip to main content

Getting started

This tutorial installs the SDK, defines a backend-neutral resource, renders the standard list lifecycle, and places it inside an SDK page. At the end you will have a useful screen—not only a successful import.

If you first need the architectural vocabulary, read SDK architecture. For the complete export inventory, use the public API map.

1. Install the package​

Run installation from the consuming application's Git and npm root:

npm install @dev-mainsequence/command-center-sdk react react-dom

Main Sequence Vite applications keep package.json, package-lock.json, .env, .agents/, src/, and vite.config.* at that root. Do not create a nested frontend/ application.

React and React DOM are peer dependencies. Keep one compatible React runtime in the host.

2. Load theme and component styles​

Import global SDK CSS once near the browser entrypoint, with theme variables first:

// src/main.tsx
import "@dev-mainsequence/command-center-sdk/theme/styles.css";
import "@dev-mainsequence/command-center-sdk/styles.css";

The theme stylesheet establishes semantic variables. Component styles consume those variables. Do not repeat these imports in individual screens.

3. Define a normalized resource​

The resource definition is backend-neutral. It describes stable identity, labels, columns, and the adapter that returns SDK models:

// src/services/service-resource.ts
import { defineResourceApplication } from "@dev-mainsequence/command-center-sdk/resource";

export interface Service {
uid: string;
name: string;
status: "active" | "paused";
}

export const services = defineResourceApplication<Service, string>({
id: "services",
label: "Services",
itemLabel: "service",
getId: (service) => service.uid,
activation: {
resolve: (service) => ({ resource: "services", uid: service.uid }),
},
columns: [
{ id: "name", header: "Name", getValue: (service) => service.name },
{ id: "status", header: "Status", getValue: (service) => service.status },
],
adapter: {
async list({ pageIndex, pageSize, search, signal }) {
const query = new URLSearchParams({
offset: String(pageIndex * pageSize),
limit: String(pageSize),
...(search ? { search } : {}),
});

const response = await fetch(`/api/services?${query}`, { signal });
if (!response.ok) throw new Error("Services could not be loaded.");

const body = (await response.json()) as {
count: number;
results: Service[];
};

return {
items: body.results,
pageInfo: {
pageIndex,
pageSize,
totalItems: body.count,
hasNextPage: (pageIndex + 1) * pageSize < body.count,
hasPreviousPage: pageIndex > 0,
},
};
},
},
});

In production, route fetch through an application-owned client so authentication headers, base URLs, retries, and error normalization remain outside the definition. Always pass the provided signal; stale list requests must not overwrite current state.

pageInfo is authoritative. Do not infer a server total or next page from the number of rows in the current response.

4. Render the standard list lifecycle​

// src/services/ServicesPage.tsx
import { ResourceListPage } from "@dev-mainsequence/command-center-sdk/views";

import { services } from "./service-resource";

export function ServicesPage() {
return (
<ResourceListPage
definition={services}
searchable
searchPlaceholder="Search services"
refreshable
pageSize={25}
navigation={{
open: ({ uid }) => {
window.location.assign(`/services/${encodeURIComponent(uid)}`);
},
}}
/>
);
}

ResourceListPage now owns loading, failure, empty and no-results states, debounced search, authoritative pagination, refresh, row activation, and stale-request handling. Your application still owns the URL change and backend policy.

Do not wrap the list in a second toolbar, pager, selection bar, or loading system. Extend it through columns, cells, filters, actions, renderCard, and the documented narrow contribution props.

5. Put the screen in an application page​

import {
ApplicationPage,
ApplicationPageHeader,
ApplicationPageStack,
} from "@dev-mainsequence/command-center-sdk/layout";

import { ServicesPage } from "./services/ServicesPage";

export function ServicesRoute() {
return (
<ApplicationPage maxWidth="wide">
<ApplicationPageStack>
<ApplicationPageHeader
eyebrow="Operations"
title="Services"
description="Inspect runtime state and open a service."
/>
<ServicesPage />
</ApplicationPageStack>
</ApplicationPage>
);
}

The layout surface owns responsive page gutters, width, header wrapping, and section rhythm. The resource view owns its internal collection layout. Avoid adding another padded page wrapper or ordinary card around the entire list.

6. Add only the concepts you need​

Choose the highest-level surface that already owns the workflow:

RequirementStart withGuide
Resource list, detail, picker, or actions/resource, /resource/react, /viewsResources
Application hierarchy and destinations/navigationNavigation
Responsive pages and cards/layoutApplication layout
Startup, retry, or terminal failure/feedbackApplication feedback
Buttons, badges, and labelled fields/controlsApplication controls
Theme presets, tokens, and chart colors/themeThemes
Application-owned cross-origin UI/embed, /embed/react, and /vite for local developmentStatic-site embeds
Language-neutral backend payloadsContract manifest and schemasBackend contracts
A chat or AI capabilitiesNot in the SDK: @dev-mainsequence/command-center-aiAdd AI capabilities

Definitions, adapters, controlled props, and narrow renderers are the normal extension seams. Change SDK source only when missing behavior is reusable, backend-neutral, and useful across consumers.

7. Verify the integration​

At minimum, run the consuming application's typecheck, unit tests, and production build. Exercise the screen at narrow and wide viewports and cover loading, error, empty, populated, search, paging, refresh, and row activation states. Include a touch phone viewport; see Mobile and touch.

For standard page geometry, use the real-browser verifier:

import {
assertCommandCenterPageLayout,
} from "@dev-mainsequence/command-center-sdk/layout/testing";

await assertCommandCenterPageLayout(page);

If publishing behavior or package contents matter, install an npm pack tarball into a clean fixture. Workspace symlinks do not prove that declarations, CSS, schemas, fixtures, or docs are in the published artifact.

Use public imports only​

Supported:

import { createHttpResourceAdapter } from "@dev-mainsequence/command-center-sdk/resource";
import { ResourceListPage } from "@dev-mainsequence/command-center-sdk/views";

Unsupported:

import { ResourceListPage } from "@dev-mainsequence/command-center-sdk/dist/views/ResourceListPage.js";

The installed package's package.json exports and .d.ts files are authoritative. A source file or proposed ADR is not automatically a public API.

Install current agent guidance​

Package installation copies version-matched SDK skills to .agents/skills/command-center/. When lifecycle scripts were disabled, refresh them explicitly:

npx command-center-sdk skills install --path . --dry-run
npx command-center-sdk skills install --path .

This namespace is an authoritative mirror of the installed SDK catalog. Refreshing it removes obsolete or locally added entries inside command-center; keep application-specific guidance in a different .agents/skills namespace. Other namespaces are not changed.

Use skills sync when backend-owned platform guidance must also be refreshed. Credential and ownership details are in Application operations.

Add AI capabilities​

The SDK has no AI capabilities: no chat with a Main Sequence Agent, no agent sessions, and no model provider settings. They come from @dev-mainsequence/command-center-ai, a separate package that takes this SDK as a peer, so the application keeps exactly one SDK. Install it only when its peer range includes the installed SDK, and refresh its agent skills explicitly:

npm view @dev-mainsequence/command-center-ai peerDependencies --json
npm install @dev-mainsequence/command-center-ai
npx command-center-ai skills install --path .

Its skills install into .agents/skills/command-center-ai/, starting with use-command-center-ai; they cover the right rail, the expanded page, sessions, model providers, and the platform connection. Never install a second SDK or pass --legacy-peer-deps.

Initialize application documentation​

Preview and create the official same-artifact documentation system from the application root:

npx command-center-sdk application docs init --path . --dry-run
npx command-center-sdk application docs init --path .

It adds an end-user help landing, a schema-version-2 manifest whose hierarchy derives the docs folders from the visible application menu, and a /docs/ Docusaurus build inside the same dist/ artifact. Continue with Application documentation before authoring feature and task pages.

Inspect or update the installed SDK​

Keep declared, locked, installed, npm wanted, and registry latest versions distinct:

npx command-center-sdk application sdk-status --path .
npx command-center-sdk application update-sdk --path . --dry-run
npx command-center-sdk application update-sdk --path .

The update respects the current declaration and does not commit, tag, push, deploy, or change the application version. See Application operations for drift and constraint-blocked cases.

Refresh dependencies and deploy from Git​

After you change dependencies in package.json, refresh the lockfile and the installed packages from the repository root:

npx command-center-sdk code-repository sync --path .

It runs npm install --package-lock-only and then npm ci, and nothing else: it calls no backend and does not version, commit, tag, or push. Commit and push the changes yourself, like any other change.

The platform deploys from Git pushes as the repository's .mainsequence/workflows/*.yaml file says. With tag_regex omitted, every push to the branch deploys; with a regular expression, a matching tag deploys the commit it points at, whether that is the branch's latest commit or an older commit on the branch. The Main Sequence platform no longer provides tag names: release tags come from the repository's own CI. Application operations has an example workflow.