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:

GET/manifest

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.

  • Optional

  • 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 is false.

  • 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.

Manifest Endpoint

GET
/manifest
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"]
        }
      }
  }
}
POST/ui/widget

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
/ui/widget
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>
POST/ui/page

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
/ui/page
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>
POST/ai/actions

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
/ai/actions
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.

Was this page helpful?