Manual Development
Antispace CLI is not the only way to develop apps. You can also develop apps manually using the Antispace SDK or any set of tools you prefer or even whatever programming language you prefer.
At it's core, any Antispace app is a web server that exposes the following endpoints:
Manifest (Required)
Manifest is a JSON object that describes the app. It should return the following structure:
Required
- Name
name
- Type
- string
- Description
Display name of the app.
- Name
slug
- Type
- string
- Description
Unique, human-readable identifier of the app.
- Name
description
- Type
- string
- Description
Description of the app.
- Name
image
- Type
- string
- Description
URL of the app icon.
- Name
endpoint
- Type
- string
- Description
Base URL of the app.
- Name
wantsPage
- Type
- boolean
- Description
Whether the app intends to display a dedicated page.
- Name
wantsRefresh
- Type
- boolean
- Description
Whether the app wants to be polled for new UI updates.
- Name
hotkey
- Type
- string
- Description
Ctrl+ hotkey to open the app page. Will be ignored if
wantsPage
isfalse
.
- Name
functions
- Type
- Actions
- Description
An object containing functions that the AI can call, with the following structure:
- Name
type
- Type
- "function"
- Description
Required field. Should always be
"function"
.
- Name
description
- Type
- string
- Description
Description of the function.
- Name
parameters
- Type
- Parameters
- Description
An object containing parameters that the function accepts:
- Name
type
- Type
- "object"
- Description
Required field. Should always be
"object"
.
- Name
properties
- Type
- Properties
- Description
An object containing properties that the parameter accepts:
- Name
type
- Type
- "string" | "number" | "boolean" | "array"
- Description
Property type
- Name
description
- Type
- string
- Description
Description of the parameter.
- Name
required
- Type
- string[]
- Description
An array of required parameters.
Optional
Manifest Endpoint
GET https://<your-app-url>/manifest
Example Response
{
"name": "My App",
"slug": "my-app",
"description": "My Awesome Antispace App",
"endpoint": "https://my-app.com",
"image": "https://my-app.com/app-image.webp",
"wantsPage": true,
"wantsRefresh": false,
"hotkey": "m",
"functions": {
"echo": {
"type": "function",
"function": {
"name": "echo",
"description": "Echo back the text provided",
"parameters": {
"type": "object",
"properties": {
"test_text": {
"type": "string",
"description": "Text that will be echoed back to the user",
}
},
"required": ["test_text"]
}
}
}
}
Widget UI
Widget endpoint returns the UI for the widget to be rendered on the main dashboard page. UI endpoints should return a string of valid JSX markup using supported UI Components.
Manifest Endpoint
POST https://<your-app-url>/ui/widget
Content-Type: application/json
Body:
{
"action": string,
"values": {
[key: string]: any
},
"anti": {
"user": {
"id": string,
"name": string
}
}
}
Example Response
<Column align="center" justify="center">
<Text align="center">
▼ Antispace
</Text>
<Divider />
<Row justify="space-between">
<Input name="textToEcho" placeholder="Type something..." />
<Button action="run_echo" text="Echo" />
</Row>
</Column>
Page UI
Page endpoint returns the UI for the page to be rendered on a separate page if wantsPage
is true
in the manifest. UI endpoints should return a string of valid JSX markup using supported UI Components.
Manifest Endpoint
POST https://<your-app-url>/ui/page
Content-Type: application/json
Body:
{
"action": string,
"values": {
[key: string]: any
},
"anti": {
"user": {
"id": string,
"name": string
}
}
}
Example Response
<Column align="center" justify="center">
<Text align="center">
▼ Antispace
</Text>
<Divider />
<Row justify="space-between">
<Input name="textToEcho" placeholder="Type something..." />
<Button action="run_echo" text="Echo" />
</Row>
</Column>
Action
Action endpoint is called when the user interacts with the app via AI, using one of the functions defined in the manifest.
Action endpoint should return a valid HTTP status code and a textual response (JSON, XML or plaintext), that will be used by the AI to respond to the user.
Manifest Endpoint
POST https://<your-app-url>/ai/actions
Content-Type: application/json
Body:
{
[key: string]: any
}
Example Response
{
"result": "todo_created",
"data": {
"todo_id": 1234,
"content": "Buy milk"
}
}
Using the SDK
If you're developing in JavaScript, regardless of your framework of choice, you can use the Antispace SDK to make development easier.
Components SDK
The SDK provides a set of components that you can use to build your app UI. To install the SDK, you can use the following command:
Install Antispace SDK
bun install @antispace/sdk
Unlike the Antispace CLI, your JavaScript setup will likely not handle the rendering of the markup string correctly. In order to do that, you can use the Renderer provided by the SDK.
The renderer is a function that you can use to render the UI into the string format that Antispace expects. Here's how you would do it in a Next.js API route:
Usage of the Renderer
import { renderUI, components as Anti } from '@antispace/sdk'
export async function POST(request: Request) {
return new Response(
renderUI(
<Anti.Column align="center" justify="center">
<Anti.Text align="center">
▼ Antispace
</Anti.Text>
<Anti.Row justify="space-between">
<Anti.Input name="textToEcho" placeholder="Type something..." />
<Anti.Button action="run_echo" text="Echo" />
</Anti.Row>
</Anti.Column>
),
{
status: 200,
}
)
}
Note: If you're using the Hono framework, you don't have to use the Renderer, you can use Hono's c.text()
method to render the UI from the Components SDK directly, since Hono has first-class support for JSX.
Other Languages
At the moment Antispace does not provide SDKs for languages other than JavaSctipt/TypeScript. However, you can still develop Antispace apps in any language as long as it has a way to serve plaintext HTTP responses on the required endpoints.
Here's an example of a server-side Swift app from the Quickstart guide, implemented using Vapor:
import Vapor
let app = try await Application.make(.detect())
// MARK: - Data Structures
struct AppManifest: Content {
let name: String
let slug: String
let description: String
let image: String
let endpoint: String
let wantsPage: Bool
let wantsRefresh: Bool
// let hotkey: String? // Optional
// let functions: [String: FunctionDefinition]? // Optional, not needed for UI echo
}
struct UIRequestBody: Content {
let action: String? // Action name (e.g., "run_echo")
let values: UIRequestValues? // Form values
let anti: AntiContext? // User context (optional but good practice)
}
struct UIRequestValues: Content {
// Name matches the <Input name="textToEcho">
let textToEcho: String?
}
// Basic user context structure
struct AntiContext: Content {
let user: AntiUser?
}
struct AntiUser: Content {
let id: String?
let name: String?
}
// MARK: - Routes
func routes(_ app: Application) throws {
// GET /manifest
app.get("manifest") { req -> AppManifest in
let appBaseUrl = "https://your-tunnel-or-deploy-url.com"
let appImageUrl = "\(appBaseUrl)/icon.png"
return AppManifest(
name: "Echo App (Swift)",
slug: "swift-echo-app",
description: "A simple Antispace echo app built with Swift Vapor.",
image: appImageUrl,
endpoint: appBaseUrl,
wantsPage: false, // This app only uses the widget
wantsRefresh: false
)
}
// POST /ui/widget
app.post("ui", "widget") { req -> Response in
// Decode the incoming request body
// Use try? to handle cases where the body might be empty or invalid on initial load
let requestBody = try? req.content.decode(UIRequestBody.self)
// Get the action name and the text to echo (if available)
let action = requestBody?.action
let textToEcho = requestBody?.values?.textToEcho
// Determine the text to display in the header
let displayText: String
if action == "run_echo", let text = textToEcho, !text.isEmpty {
displayText = text
} else {
displayText = "Antispace" // Default text
}
// --- Generate the UI String (JSX-like markup) ---
// Note: We are building a raw string here, exactly as Antispace expects.
let uiMarkup = """
<Column align="center" justify="center" spacing="medium">
<Text align="center" type="heading1">
▼ \(displayText)
</Text>
<Row justify="space-between" width="full" spacing="small">
<Input name="textToEcho" placeholder="To echo or not to echo..." width="full" />
<Button action="run_echo" text="Echo" />
</Row>
</Column>
"""
// Create the response with the UI markup string and set the Content-Type header appropriately
let response = Response(status: .ok, body: .init(string: uiMarkup))
response.headers.replaceOrAdd(name: .contentType, value: "text/plain; charset=utf-8") // Note the content type
return response
}
}
try await app.execute()
Deployment
You cannot deploy your custom app to Antispace's infrastructure using the CLI. You will need to host your app somewhere and update the endpoint in the Manage Dashboard.