AgentStack
SKILL unreviewed MIT Self-run

Zo Space

skill-zocomputer-skills-zo-space · by zocomputer

>

No reviews yet
0 installs
16 views
0.0% view→install

Install

$ agentstack add skill-zocomputer-skills-zo-space

Open-source listing — not yet scanned by AgentStack. Follow the source repository for install instructions.

Security review

⚠ Flagged

1 finding(s); flagged for manual review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures
  • high Reads credentials/environment and may exfiltrate them.

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution Used
  • Environment & secrets Used
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Zo Space? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

zo.space Development Guide

Read this skill before creating or editing any zo.space route. For deeper reference on specific topics, read the files in references/.

Architecture Overview

zo.space is a per-user web presence at {handle}.zo.space. It runs:

  • Runtime: Bun 1.2.x
  • Server framework: Hono (all HTTP routing)
  • Frontend: React 19 + Vite 7 (SPA with client-side routing via react-router-dom)
  • Styling: Tailwind CSS 4 with @tailwindcss/vite plugin + tw-animate-css
  • Icons: lucide-react and @tabler/icons-react are both available

Routes are NOT files in the user's workspace. They exist only in the space system at /__substrate/space/routes/. Manage them exclusively through the space tools.

Tools

| Tool | Purpose | |------|---------| | list_space_routes() | List all routes (paths, types, visibility) | | get_space_route(path) | Get a route's full source code | | update_space_route(path, route_type, code, public) | Create or update a route | | delete_space_route(path) | Delete a route | | list_space_assets() | List uploaded static assets | | update_space_asset(source_file, asset_path) | Upload a workspace file as a static asset | | delete_space_asset(asset_path) | Delete an asset | | get_space_errors() | Check for build/runtime errors |

Route Types

Page Routes (route_type="page")

React/TSX components rendered client-side. Default export must be a React component.

import { useState } from "react";
import { Globe } from "lucide-react";

export default function MyPage() {
  const [count, setCount] = useState(0);
  return (
    
      
      Hello
       setCount(c => c + 1)} className="mt-4 px-4 py-2 bg-primary text-primary-foreground rounded-lg">
        Clicked {count} times
      
    
  );
}

Visibility:

  • public=false (default): requires auth (only the user can see it)
  • public=true: anyone can view

Pages are compiled by Vite into the SPA bundle. They run in BrowserRouter, wrapped in ErrorBoundary and Suspense.

API Routes (route_type="api")

Hono handler functions. Default export must be a function that takes a Hono Context and returns a Response.

import type { Context } from "hono";
export default async (c: Context) => {
  return c.json({ message: "Hello from the API" });
};

API routes are always public regardless of the public parameter.

The handler receives ALL HTTP methods through app.all(). Check c.req.method to handle different methods:

import type { Context } from "hono";
export default async (c: Context) => {
  if (c.req.method === "GET") {
    return c.json({ items: [] });
  }
  if (c.req.method === "POST") {
    const body = await c.req.json();
    return c.json({ created: true });
  }
  return c.json({ error: "Method not allowed" }, 405);
};

How the Build Pipeline Works

When you call update_space_route():

  1. The route code is written to /__substrate/space/routes/api/{name}.ts or /__substrate/space/routes/pages/{name}.tsx
  2. An index file is regenerated that imports all routes
  3. For pages: vite build runs, compiling all page routes into the SPA bundle at /__substrate/space/dist/
  4. For APIs: routes are dynamically imported by the Hono server at startup
  5. The zo-space supervisor service is restarted

This means:

  • Page route updates trigger a full Vite rebuild. This takes a few seconds.
  • All page routes are compiled together. A syntax error in one page can break all pages.
  • API routes are loaded via dynamic import. An error in one API route won't break others.

Path-to-filename mapping:

  • / becomes _home
  • /hello becomes hello
  • /api/hello becomes api-hello
  • /users/list becomes users-list

Reserved routes that cannot be overwritten:

  • /api/_health (health check)
  • /api/_error (client-side error reporting)

Available Packages

These are pre-installed and available without any extra setup:

Server-side (API routes):

| Package | Version | Notes | |---------|---------|-------| | hono | ^4.10.x | Web framework. Import Context type from "hono" | | stripe | ^17.7.x | Stripe SDK for payment processing | | marked | ^17.0.x | Markdown parser | | zod | ^4.1.x | Schema validation |

API routes also have access to:

  • All Bun APIs: Bun.spawn(), Bun.file(), Bun.write(), Bun.serve(), etc.
  • Node.js built-ins: node:fs, node:path, node:child_process, etc.
  • process.env: All environment variables including secrets from Settings > Advanced
  • File system: Full read/write access to /home/workspace and the rest of the system
  • Shell commands: Via Bun.spawn() -- can run duckdb, python, etc.
  • Network: Can make outbound HTTP requests via fetch()
  • ZO_CLIENT_IDENTITY_TOKEN: For calling the Zo API (https://api.zo.computer/zo/ask)

Client-side (page routes):

| Package | Use | |---------|-----| | react / react-dom | ^19.2.x | | react-router-dom | ^7.9.x -- client-side routing | | lucide-react | ^0.562.x -- icons | | @tabler/icons-react | ^3.35.x -- icons | | recharts | ^3.6.x -- charts and data viz | | @tanstack/react-table | ^8.21.x -- table component | | @dnd-kit/* | Drag and drop | | class-variance-authority | Variant styling utility | | clsx + tailwind-merge | Class name utilities | | sonner | Toast notifications | | vaul | Drawer component | | reveal.js | ^5.2.x -- presentations | | next-themes | Theme switching (dark/light/system) | | @tailwindcss/typography | Prose styling for rendered content |

Radix UI Primitives (via shadcn/ui pattern):

avatar, checkbox, dialog, dropdown-menu, label, select, separator, slot, tabs, toggle, toggle-group, tooltip

Note: Only card and chart shadcn components are pre-built in src/components/ui/. For other Radix primitives, import directly from @radix-ui/react-*.

Styling

Tailwind CSS 4

Tailwind 4 uses CSS-first configuration. The theme is defined in /__substrate/space/src/styles.css using CSS variables and @theme inline.

Key semantic color classes (use these, not raw colors):

  • bg-background / text-foreground -- main page colors
  • bg-card / text-card-foreground -- card surfaces
  • bg-primary / text-primary-foreground -- primary actions
  • bg-secondary / text-secondary-foreground -- secondary elements
  • bg-muted / text-muted-foreground -- subdued content
  • bg-accent / text-accent-foreground -- accents
  • bg-destructive -- destructive/error actions
  • border-border -- standard border color
  • bg-input -- input backgrounds
  • ring-ring -- focus rings

Chart colors: text-chart-1 through text-chart-5

Dark mode is class-based (.dark on ``). The ThemeProvider handles this automatically. All semantic color tokens have dark mode variants defined.

Border radius uses --radius variable: rounded-sm, rounded-md, rounded-lg, rounded-xl.

tw-animate-css

Animation utilities are available. The @import "tw-animate-css" is already included.

@tailwindcss/typography

The prose class is available for rendering rich text/markdown content:


  {/* rendered markdown here */}

Patterns and Best Practices

Fetching data from API routes in pages

Pages can call API routes on the same domain using relative URLs:

const [data, setData] = useState(null);
useEffect(() => {
  fetch("/api/my-endpoint")
    .then(r => r.json())
    .then(setData);
}, []);

Querying DuckDB from API routes

async function queryDb(sql: string): Promise {
  const proc = Bun.spawn(["duckdb", "/home/workspace/Data/tasks.db", "-json", "-c", sql], {
    stdout: "pipe",
    stderr: "pipe",
  });
  const stdout = await new Response(proc.stdout).text();
  const stderr = await new Response(proc.stderr).text();
  await proc.exited;
  if (proc.exitCode !== 0) throw new Error(stderr);
  return JSON.parse(stdout || "[]");
}

Calling the Zo API from API routes

const token = process.env.ZO_CLIENT_IDENTITY_TOKEN;
const response = await fetch("https://api.zo.computer/zo/ask", {
  method: "POST",
  headers: {
    "authorization": token,
    "content-type": "application/json",
  },
  body: JSON.stringify({ input: "Your prompt here" }),
});
const result = await response.json();

Using Stripe webhooks

The stripe package is pre-installed. Use process.env.STRIPE_SECRET_KEY and process.env.STRIPE_WEBHOOK_SECRET from Settings > Advanced secrets.

Reading workspace files from API routes

const file = Bun.file("/home/workspace/path/to/file.json");
const content = await file.json();

Using static assets

  1. Upload from workspace: update_space_asset("/home/workspace/image.png", "/images/hero.png")
  2. Reference in pages: ``

Assets are served from /__substrate/space/assets/ and are always public.

Gotchas and Limitations

Build failures break all pages

A syntax error in any page route will cause the Vite build to fail, breaking ALL pages. API routes are more resilient since they're imported individually. Always check get_space_errors() after updating page routes.

No hot reload

Route updates require a full rebuild + server restart. The server is managed by supervisord as zo-space.

API routes get ALL methods

Handlers are registered with app.all(), not method-specific routes. You must check c.req.method yourself if you want to restrict methods.

No shared state between API routes

Each API route file is a standalone module. There's no shared in-memory state between routes (except through file system, databases, or env vars). The server does maintain module-level state within a single route between requests.

Server crash loop

The zo-space server can enter a crash loop if it fails to start (e.g., port conflict, bad route code). Check /dev/shm/zo-space.log and /dev/shm/zo-space_err.log for diagnostics. The supervisor will keep trying to restart it.

Page routes are single-file

Each page route is one TSX file. You can't split a page into multiple component files. Put all components inline in the same file, or fetch shared data from API routes.

No custom npm packages in routes

You can only use packages already installed in /__substrate/space/node_modules/. You cannot add new npm dependencies via route code. Work with what's available or implement logic inline.

import.meta.env.VITE_HANDLE in pages

To get the user's handle in page routes, use import.meta.env.VITE_HANDLE.

process.env only works server-side

Environment variables via process.env are only available in API routes, not in page routes. For pages, use import.meta.env.VITE_* variables.

No path aliases in routes

The @/ alias resolves to /__substrate/space/src/. Routes live in /__substrate/space/routes/. Importing from @/components/ui/card works but it's reaching into the SPA source. Use it for the pre-built shadcn components, but don't rely on it for custom code.

Error reporting

  • Build errors: written to /__substrate/space/.errors/_build.json
  • Runtime errors: written to /__substrate/space/.errors/{route}.json
  • Client errors: posted to /api/_error by the ErrorBoundary
  • All retrievable via get_space_errors()

Proxy logs

Request logs are at /dev/shm/zosite-3099-proxy.log. Server logs at /dev/shm/zo-space.log.

Debugging Workflow

  1. get_space_errors() -- check for build or runtime errors
  2. Read /dev/shm/zo-space.log for server output
  3. Read /dev/shm/zo-space_err.log for crash/restart info
  4. Read /dev/shm/zosite-3099-proxy.log for HTTP request logs
  5. If server is in crash loop, check error logs and fix the offending route
  6. After fixing, the supervisor will automatically restart the server

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.