@nothinq/marketplace
v1.4.0
Published
Nothinq Marketplace Index
Readme
Nothinq SDK
A TypeScript SDK for building extensions that integrate with the Nothinq platform. This SDK provides a communication bridge between extensions and the Nothinq host application.
Table of Contents
- Installation
- Quick Start
- Extension Manifest
- Core Concepts
- API Reference
- TypeScript Types
- Message Router
- Advanced Usage
- Deprecated APIs
- Best Practices
- Example Extension
- License
Installation
npm install @nothinq/sdk
# or
yarn add @nothinq/sdk
# or
bun add @nothinq/sdkQuick Start
import { createSDK } from "@nothinq/sdk";
// Create SDK instance with your config type
const sdk = createSDK<MyConfigType>();
// Wait for SDK to be ready
sdk.onready(() => {
console.log("Extension is ready!");
// Notify host that extension has loaded
sdk.loaded();
});
// Check if running inside Nothinq app
if (sdk.inapp()) {
console.log("Running inside Nothinq");
}Extension Manifest
Every Nothinq extension requires a manifest.json file that defines its metadata, configuration schema, and capabilities.
Basic Structure
{
"identifier": "com.example.myextension",
"author": "Your Name",
"version": "1.0.0",
"meta": {
"description": "A brief description of your extension",
"logo": "https://example.com/logo.png",
"tags": ["productivity", "ai"]
},
"ui": {
"url": "https://example.com/extension"
}
}Manifest Properties
Required Fields
identifier(string): Unique identifier for your extension (e.g.,com.company.extension)author(string): Extension author name
Optional Fields
version(string): Extension version following semver (e.g.,1.0.0)system(string): System identifier if extension is system-levelscope(string): Extension scope -"workspace"or"user"(default:"user")overview(string): Detailed overview or documentation URL
Metadata (meta)
{
"meta": {
"description": "Extension description shown in the store",
"logo": "https://example.com/logo.png",
"tags": ["category1", "category2"]
}
}UI Configuration (ui)
Define the extension's user interface:
{
"ui": {
"url": "https://example.com/extension-ui"
}
}Configuration Schema (config_schema)
Define user-configurable settings with validation:
{
"config_schema": {
"required": ["apiKey"],
"properties": {
"apiKey": {
"key": "apiKey",
"type": "string",
"description": "Your API key"
},
"endpoint": {
"key": "endpoint",
"type": "string",
"description": "API endpoint URL",
"defaultValue": "https://api.example.com"
},
"enabled": {
"key": "enabled",
"type": "boolean",
"description": "Enable the extension",
"defaultValue": true
},
"maxRetries": {
"key": "maxRetries",
"type": "number",
"description": "Maximum retry attempts",
"defaultValue": 3
},
"mode": {
"key": "mode",
"type": "select",
"description": "Operation mode",
"defaultValue": "production",
"options": [
{ "value": "development", "label": "Development" },
{ "value": "production", "label": "Production" }
]
},
"accessToken": {
"key": "accessToken",
"type": "auth",
"description": "OAuth access token"
}
}
}
}Field Types:
string: Text input{ "type": "string", "defaultValue": "default text" }number: Numeric input{ "type": "number", "defaultValue": 42 }boolean: Checkbox/toggle{ "type": "boolean", "defaultValue": true }select: Dropdown selection{ "type": "select", "defaultValue": "option1", "options": [ { "value": "option1", "label": "Option 1" }, { "value": "option2", "label": "Option 2" } ] }auth: OAuth/authentication token{ "type": "auth", "description": "Authentication token" }
Field Properties:
key(required): Property key matching the config objecttype(required): Field type (see above)description: Help text shown to usersdefaultValue: Default value for the fielddependencies: Comma-separated list of dependent field keys
Authentication (auth)
Configure OAuth or API key authentication:
OAuth 2.0 / OAuth 2.1:
{
"auth": {
"type": "oauth2",
"authorization_endpoint": "https://provider.com/oauth/authorize",
"token_endpoint": "https://provider.com/oauth/token",
"refresh_endpoint": "https://provider.com/oauth/refresh",
"scopes_supported": ["read", "write", "admin"]
}
}API Key:
{
"auth": {
"type": "apiKey"
}
}MCP Server Configuration (server)
Integrate with Model Context Protocol servers:
{
"server": {
"type": "stdio",
"command": "node",
"args": ["server.js"],
"env": {
"API_KEY": "{{config.apiKey}}"
}
}
}Server Types:
stdio: Standard input/output communication{ "type": "stdio", "command": "node", "args": ["server.js"], "env": { "KEY": "value" } }http: HTTP-based communication{ "type": "http", "url": "https://api.example.com/mcp", "headers": { "Authorization": "Bearer {{config.apiKey}}" } }sse: Server-Sent Events{ "type": "sse", "url": "https://api.example.com/events", "headers": { "Authorization": "Bearer {{config.apiKey}}" } }
Themes (themes)
Provide custom themes for the Nothinq interface:
{
"themes": [
{
"id": "dark-theme",
"name": "Dark Theme",
"url": "https://example.com/themes/dark.json",
"type": "dark"
},
{
"id": "light-theme",
"name": "Light Theme",
"url": "https://example.com/themes/light.json",
"type": "light"
}
]
}Commands (commands)
Define commands that can be triggered (future support):
{
"commands": ["command.id1", "command.id2"]
}Complete Example
{
"identifier": "com.example.stripe",
"author": "Example Corp",
"version": "1.2.0",
"scope": "workspace",
"overview": "https://docs.example.com/stripe-extension",
"meta": {
"description": "Stripe payment integration for Nothinq",
"logo": "https://example.com/stripe-logo.png",
"tags": ["payments", "stripe", "commerce"]
},
"ui": {
"url": "https://extension.example.com/stripe"
},
"auth": {
"type": "oauth2",
"authorization_endpoint": "https://connect.stripe.com/oauth/authorize",
"token_endpoint": "https://connect.stripe.com/oauth/token",
"scopes_supported": ["read_write"]
},
"config_schema": {
"required": ["apiKey", "mode"],
"properties": {
"apiKey": {
"key": "apiKey",
"type": "auth",
"description": "Stripe API key"
},
"mode": {
"key": "mode",
"type": "select",
"description": "Operating mode",
"defaultValue": "test",
"options": [
{ "value": "test", "label": "Test Mode" },
{ "value": "live", "label": "Live Mode" }
]
},
"webhookSecret": {
"key": "webhookSecret",
"type": "string",
"description": "Webhook signing secret",
"dependencies": "mode"
},
"enableLogging": {
"key": "enableLogging",
"type": "boolean",
"description": "Enable detailed logging",
"defaultValue": false
},
"timeout": {
"key": "timeout",
"type": "number",
"description": "Request timeout in seconds",
"defaultValue": 30
}
}
},
"server": {
"type": "http",
"url": "https://api.stripe.com/mcp",
"headers": {
"Authorization": "Bearer {{config.apiKey}}"
}
},
"themes": [
{
"id": "stripe-dark",
"name": "Stripe Dark",
"url": "https://extension.example.com/themes/dark.json",
"type": "dark"
}
]
}Publishing Your Extension
- Create manifest.json in your extension root
- Validate the manifest against the schema
- Test locally using the Nothinq development tools
- Submit for review through the Nothinq extension store
Testing Manifest
To test your manifest locally:
import { createSDK } from "@nothinq/sdk";
const sdk = createSDK<YourConfigType>();
sdk.onready(async () => {
// Get the config defined in your manifest
const config = await sdk.config.get();
console.log("Config:", config);
// Config will match your config_schema structure
console.log("API Key:", config.apiKey);
console.log("Mode:", config.mode);
});Best Practices
- Use semantic versioning for your extension version
- Provide clear descriptions for all config fields
- Set sensible defaults for optional configuration
- Request minimal permissions needed for functionality
- Validate required fields in your config_schema
- Use dependencies to show/hide conditional fields
- Test with different configurations before publishing
- Keep your manifest up to date with code changes
Core Concepts
SDK Initialization
The SDK automatically handles the connection handshake between your extension and the Nothinq host application. It uses a two-way ready state system to ensure both sides are properly initialized before allowing communication.
const sdk = createSDK<ConfigType>();
sdk.onready(() => {
// Both parent and child are ready
// Safe to make API calls
});Message Communication
The SDK provides a message router for bidirectional communication:
// Listen to specific message types
const unsubscribe = sdk.listen("custom.event", async (message) => {
console.log("Received:", message);
return { success: true };
});
// Listen to all messages
sdk.listen(async (message) => {
console.log("Global handler:", message);
});
// Dispatch messages to host
await sdk.dispatch({
type: "custom.action",
data: { foo: "bar" },
});
// Cleanup
unsubscribe();API Reference
SDK Interface
inapp(): boolean
Check if the extension is running inside the Nothinq application.
if (sdk.inapp()) {
// Extension is running in Nothinq
}onready(handler: () => void): void
Register a callback that fires when both the extension and host are ready. If already ready, the handler is called immediately.
sdk.onready(() => {
console.log("Ready to communicate!");
});loaded(): void
Notify the host application that the extension has finished loading.
sdk.onready(() => {
// Initialize your extension
sdk.loaded();
});listen(handler: MessageHandler): () => void
listen(type: string, handler: MessageHandler): () => void
Listen for messages from the host application. Returns an unsubscribe function.
// Type-specific listener
const unsubscribe = sdk.listen("user.action", async (message) => {
// Handle message
return { status: "processed" };
});
// Global listener
sdk.listen(async (message) => {
// Handle all messages
});dispatch(message: BaseMessage): Promise<void>
Send a message to the host application.
await sdk.dispatch({
type: "extension.event",
payload: { data: "value" },
});Configuration API
config.get(): Promise<T>
Retrieve the extension's configuration from the host.
interface MyConfig {
apiKey: string;
endpoint: string;
}
const config = await sdk.config.get();
console.log(config.apiKey);config.set(config: ExtraConfig<T>): Promise<void>
Update the extension's configuration. Can optionally include credential information.
await sdk.config.set({
apiKey: "new-key",
endpoint: "https://api.example.com",
credential: {
accessToken: "token",
refreshToken: "refresh",
expiresIn: 3600,
},
});Theme API
theme.get(): Promise<Theme>
Get the current theme settings from the host application.
const theme = await sdk.theme.get();
console.log(theme.type); // 'dark' | 'light'
console.log(theme.background);
console.log(theme.foreground);
console.log(theme.border);
console.log(theme.primary);Environment API
env.set(env: Record<string, any>): Promise<void>
Set environment variables in the host application.
await sdk.env.set({
API_KEY: "secret",
DEBUG: "true",
});File System API
fs.read(path: string): Promise<string | void>
Read a file from the host's file system.
const content = await sdk.fs.read("/path/to/file.txt");
console.log(content);fs.write(path: string, data: string): Promise<void>
Write data to a file in the host's file system.
await sdk.fs.write("/path/to/file.txt", "Hello, World!");AI API
ai.insertContextInput(context: PromptContext): Promise<void>
Insert context into the AI prompt input.
// Insert custom text context
await sdk.ai.insertContextInput({
type: "custom",
id: "ctx-1",
name: "User Data",
value: "Some context text",
});
// Insert file attachment
await sdk.ai.insertContextInput({
type: "attachment",
id: "file-1",
name: "document.pdf",
value: {
name: "document.pdf",
url: "https://example.com/doc.pdf",
contentType: "application/pdf",
size: 1024000,
},
});Sandbox API
sandbox.host(): Promise<string | void>
Get the sandbox host URL.
const host = await sdk.sandbox.host();
console.log("Sandbox host:", host);TypeScript Types
Theme
interface Theme {
type: "dark" | "light";
background: string;
foreground: string;
border: string;
primary: string;
}PromptContext
interface PromptContext {
type?: "custom" | "attachment";
value: string | Attachment;
id: string;
name: string;
loading?: boolean;
}Attachment
interface Attachment {
name: string;
url: string;
contentType: string;
size: number;
}ExtensionCredential
interface ExtensionCredential {
id: string;
accessToken: string;
refreshToken?: string;
tokenType?: string;
expiresIn?: number;
expiresAt?: number;
createdAt?: number;
name?: string;
}BaseMessage
interface BaseMessage {
type: string;
[key: string]: any;
}Message Router
The SDK includes a message router for handling incoming messages:
import { MessageRouter, MessageHandler } from "@nothinq/sdk";
const router = new MessageRouter();
// Set handler for specific message type
router.setHandler("custom.event", async (message) => {
return { processed: true };
});
// Set global handler
router.setGlobalHandler(async (message) => {
console.log("All messages:", message);
});
// Handle a message
await router.handleMessage({ type: "custom.event", data: "test" });
// Remove handlers
router.removeHandler("custom.event");
router.removeGlobalHandler();Advanced Usage
Custom Configuration Type
Define your extension's configuration interface:
interface StripeConfig {
apiKey: string;
webhookSecret: string;
mode: "test" | "live";
}
const sdk = createSDK<StripeConfig>();
sdk.onready(async () => {
const config = await sdk.config.get();
// config is typed as StripeConfig
console.log(config.apiKey);
});Handling Credentials
Store and retrieve OAuth tokens or API credentials:
await sdk.config.set({
apiKey: "my-key",
credential: {
accessToken: "access-token",
refreshToken: "refresh-token",
expiresIn: 3600,
expiresAt: Date.now() + 3600000,
tokenType: "Bearer",
},
});Lifecycle Management
const sdk = createSDK<ConfigType>();
// 1. Wait for connection
sdk.onready(async () => {
// 2. Initialize your extension
await initializeExtension();
// 3. Set up message listeners
sdk.listen("user.action", handleUserAction);
// 4. Notify host that loading is complete
sdk.loaded();
});Deprecated APIs
The following APIs are deprecated and should not be used in new code:
deprecated_expose()- Use the new SDK initialization insteaddeprecated_connect()- Use the new SDK initialization instead
For disconnecting proxies, use:
import { disconnect } from "@nothinq/sdk";
disconnect(proxy);Best Practices
- Always wait for ready state: Use
sdk.onready()before making API calls - Call
loaded()when ready: Notify the host when your extension is fully initialized - Type your configuration: Define a TypeScript interface for your config
- Handle errors: Wrap SDK calls in try-catch blocks
- Clean up listeners: Store unsubscribe functions and call them when needed
- Check
inapp()status: Gracefully handle running outside Nothinq
Example Extension
import { createSDK, PromptContext } from "@nothinq/sdk";
interface MyExtensionConfig {
apiEndpoint: string;
enabled: boolean;
}
const sdk = createSDK<MyExtensionConfig>();
// Initialize
sdk.onready(async () => {
try {
// Get configuration
const config = await sdk.config.get();
// Get theme for UI styling
const theme = await sdk.theme.get();
document.body.style.background = theme.background;
// Listen for custom events
sdk.listen("data.request", async (message) => {
const data = await fetchData(config.apiEndpoint);
return { data };
});
// Notify host we're ready
sdk.loaded();
} catch (error) {
console.error("Initialization failed:", error);
}
});
// Send custom events
async function sendNotification(text: string) {
await sdk.dispatch({
type: "extension.notification",
message: text,
});
}License
MIT
