
Security News
Axios Maintainer Confirms Social Engineering Attack Behind npm Compromise
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.
@ainativekit/devtools
Advanced tools
Development tools for building and testing ChatGPT Apps using ChatGPT Apps SDK
Development tools for building and testing ChatGPT Apps using ChatGPT Apps SDK
A powerful, zero-configuration development environment for building ChatGPT apps. Simulate the ChatGPT production environment locally with interactive controls for testing different states, themes, and device types.
window.openai API exactly like production@ainativekit/ui and use AppsSDKUIProvidernpm install --save-dev @ainativekit/devtools
or with yarn:
yarn add -D @ainativekit/devtools
DevTools works out of the box with built-in theme support:
import { DevContainer } from '@ainativekit/devtools';
import App from './App';
// Only use DevContainer in development
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
import.meta.env.DEV ? (
<DevContainer>
<App />
</DevContainer>
) : (
<App />
)
);
For full design system integration and advanced theming:
import { DevContainer } from '@ainativekit/devtools';
import { ThemeProvider } from '@ainativekit/ui';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<ThemeProvider>
{import.meta.env.DEV ? (
<DevContainer>
<App />
</DevContainer>
) : (
<App />
)}
</ThemeProvider>
);
<DevContainer
dataLoader={async () => {
// Return your widget-specific mock data
const response = await fetch('/api/mock-data');
return response.json();
}}
loadingDelay={3000} // Test loading states
theme="dark" // Start with dark theme
>
<YourWidget />
</DevContainer>
DevContainer automatically detects when you have multiple widgets and shows a selector. Each widget can have its own data loader:
import { DevContainer } from '@ainativekit/devtools';
import { AppsSDKUIProvider } from '@ainativekit/ui';
import CarouselWidget from './widgets/CarouselWidget';
import MapWidget from './widgets/MapWidget';
import ListWidget from './widgets/ListWidget';
import AlbumWidget from './widgets/AlbumWidget';
function App() {
return (
<AppsSDKUIProvider linkComponent="a">
<DevContainer
widgets={[
{
id: 'carousel',
name: 'Pizza Carousel',
component: CarouselWidget,
dataLoader: () => carouselData,
emptyDataLoader: () => emptyCarouselData
},
{
id: 'map',
name: 'Pizza Map',
component: MapWidget,
dataLoader: () => mapData
},
{
id: 'list',
name: 'Pizza List',
component: ListWidget,
dataLoader: () => listData,
emptyDataLoader: () => emptyListData
},
{
id: 'album',
name: 'Photo Albums',
component: AlbumWidget,
dataLoader: () => albumData
}
]}
loadingDelay={1500}
theme="light"
autoLoad={true}
defaultWidget="carousel"
/>
</AppsSDKUIProvider>
);
}
Features:
?widget=map) for deep linking| Prop | Type | Default | Description |
|---|---|---|---|
children | React.ReactNode | - | Single widget component |
dataLoader | () => Promise<any> | any | - | Data loader function |
emptyDataLoader | () => Promise<any> | any | - | Empty state data loader |
| Prop | Type | Default | Description |
|---|---|---|---|
widgets | Widget[] | - | Array of widget configurations |
dataLoaders | Record<string, Function> | {} | Map of global data loader functions |
emptyDataLoaders | Record<string, Function> | {} | Map of global empty data loader functions |
defaultDataLoader | string | - | Key for default data loader |
defaultWidget | string | - | ID of default widget to show |
| Property | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Unique identifier for the widget |
name | string | Yes | Display name for the widget selector |
component | React.ComponentType | Yes | The widget component |
dataLoader | () => Promise<any> | any | No | Widget-specific data loader (single, hides dropdown) |
emptyDataLoader | () => Promise<any> | any | No | Widget-specific empty state data loader |
dataLoaders | Record<string, Function> | No | Multiple named data loaders (v1.2.0+, shows dropdown) |
emptyDataLoaders | Record<string, Function> | No | Matching empty state loaders for dataLoaders |
defaultDataLoader | string | No | Default data loader key for the widget |
| Prop | Type | Default | Description |
|---|---|---|---|
loadingDelay | number | 2000 | Delay (ms) before loading data |
theme | 'light' | 'dark' | 'light' | Initial theme |
autoLoad | boolean | true | Auto-load data on mount |
createMockData<T>(fullData: T, config?: MockDataConfig<T>): MockData<T>
Creates type-safe mock data with automatic empty state generation.
Config Options:
emptyData: Explicit empty state dataemptyTransform: Function to derive empty state from full dataThe DevContainer automatically mocks the window.openai API with these methods:
window.openai = {
callTool: async (name, args) => { /* mocked */ },
sendFollowUpMessage: async ({ prompt }) => { /* mocked */ },
openExternal: ({ href }) => { /* mocked */ },
setWidgetState: (state) => { /* mocked */ },
// Plus all OpenAiGlobals properties
theme: 'light' | 'dark',
toolOutput: any,
toolResponseMetadata: any, // Server _meta field (v1.2.0+)
locale: string,
maxHeight: number,
userAgent: { device: { type }, capabilities: { hover, touch } },
// DevTools-specific (v1.1.0+)
widgetState: 'loading' | 'data' | 'empty' | 'error' // Current dev tool state
}
<DevContainer
loadingDelay={5000} // 5 second delay
dataLoader={async () => {
// Simulate slow API
await new Promise(resolve => setTimeout(resolve, 2000));
return { data: 'loaded' };
}}
>
<App />
</DevContainer>
<DevContainer
emptyDataLoader={() => {
// Return widget-specific empty state
return {
type: 'search-results',
properties: [],
searchInfo: { totalResults: 0, location: 'Sydney, NSW' }
};
}}
dataLoader={async () => {
// Regular data when not in empty state
return await fetchMockData();
}}
>
<SearchWidget />
</DevContainer>
<DevContainer
dataLoader={() => {
// Return error data
return { error: 'Something went wrong' };
}}
>
<App />
</DevContainer>
// mockData.ts
export const mockSearchResults = {
type: 'search-results',
items: [
{ id: 1, title: 'Result 1' },
{ id: 2, title: 'Result 2' }
]
};
// App.tsx
import { mockSearchResults } from './mockData';
<DevContainer
dataLoader={() => mockSearchResults}
>
<SearchWidget />
</DevContainer>
The DevContainer follows these principles:
npm run build
npm run dev
npm run type-check
Contributions are welcome! Please feel free to submit a Pull Request.
MIT © Jake Lin
Built with ❤️ for the ChatGPT app developer community. This tool helps developers build and test ChatGPT Apps using the ChatGPT Apps SDK, making development faster and more enjoyable.
FAQs
Development tools for building and testing ChatGPT Apps using ChatGPT Apps SDK
We found that @ainativekit/devtools demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.

Security News
The Axios compromise shows how time-dependent dependency resolution makes exposure harder to detect and contain.