npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

galaxybrain-api

v0.9.1

Published

Local WebSocket API server for GalaxyBrain — read and write to your workspace programmatically.

Readme

galaxybrain-api

Local WebSocket API server for GalaxyBrain — an information operating system powered by local files. Read and write to your workspace programmatically.

  • Website: https://galaxybrain.com
  • About GalaxyBrain: https://galaxybrain.com/docs/galaxybrain/
  • API documentation: https://galaxybrain.com/docs/api/

Prerequisites

  • Bun runtime installed
  • GalaxyBrain open in a Chromium-based browser (Chrome or Edge)

Usage

npx galaxybrain-api

By default it listens on port 1924. Override with:

npx galaxybrain-api 3000        # command-line argument
GB_API_PORT=3000 npx galaxybrain-api  # environment variable

Connect

const ws = new WebSocket("ws://localhost:1924");

ws.onopen = () => {
  ws.send(JSON.stringify({
    type: "command",
    requestId: "1",
    cmd: "ORIENTATION"
  }));
};

ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);
  console.log(msg);
};

GalaxyBrain API Reference

The GalaxyBrain API is a local WebSocket server that lets you programmatically read and write to your GalaxyBrain workspace. You can use it to build scripts, automations, and integrations that interact with your pages, templates, and workspace layout.


Table of Contents


Getting Started

1. Install and start the API server

This downloads the server from npm and starts it on port 1924:

npx galaxybrain-api

To use a different port:

GB_API_PORT=3000 npx galaxybrain-api

2. Open GalaxyBrain in the browser

The app automatically connects to ws://localhost:1924 and registers itself as an instance.

3. Connect your client

const ws = new WebSocket("ws://localhost:1924");

ws.onopen = () => {
  // Get an overview of the workspace
  ws.send(JSON.stringify({
    type: "command",
    requestId: "1",
    cmd: "ORIENTATION"
  }));
};

ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);
  console.log(msg);
};

Architecture

The API has three moving pieces:

  1. The API server — a local WebSocket server, started with npx galaxybrain-api. An HTTP GET / returns the health check string "GalaxyBrain API Server/{protocolVersion}" (e.g. "GalaxyBrain API Server/1"). The protocol version increments on breaking changes to the WebSocket command/event format.
  2. GalaxyBrain browser tabs — each tab connects to the server and registers as an addressable instance.
  3. External clients — your scripts, tools, or integrations connect to the same server and send commands.

External clients do not connect directly to the browser tab. Commands are routed through the server to a specific instance, and responses are routed back. External clients never send identify messages — the browser app handles that automatically.

GalaxyBrain instances can be in one of three states:

| State | Description | |-------|-------------| | picker | No project is open | | folder | A folder-backed project is open | | demo | A demo project is open |

Commands divide into two groups:

Always available (any state): LIST_INSTANCES, LIST_FOLDERS, OPEN_FOLDER, OPEN_DEMO, CLOSE_PROJECT, REMOVE_RECENT_FOLDER, FILES_WATCH, FILES_UNWATCH, SUBSCRIBE, UNSUBSCRIBE

Require an open project: READ_PAGES, CREATE_PAGES, UPDATE_PAGES, DELETE_PAGES, QUERY, READ_TEMPLATES, CREATE_TEMPLATES, UPDATE_TEMPLATES, DELETE_TEMPLATES, CREATE_FROM_TEMPLATE, PUSH_PAGE_ITEMS, PUSH_TEMPLATE_ITEMS, POP_PAGE_ITEMS, POP_TEMPLATE_ITEMS, READ_WORKSPACE, WRITE_WORKSPACE, MAP, ANCESTORS, ORIENTATION

Sending a project-level command with no project open returns:

{
  "type": "response",
  "requestId": "1",
  "cmd": "READ_PAGES",
  "ok": false,
  "error": "NO_PROJECT",
  "message": "No project is open"
}

App URL Parameters

The browser app reads these query parameters:

gb_port

API server port to connect to. Default: 1924.

?gb_port=3000

gb_id

Instance ID the browser tab registers with. If omitted, a random 6-character ID is generated.

?gb_id=desk-main

Use a stable gb_id when you want to target a specific browser tab from a script, or run multiple tabs simultaneously. If two tabs use the same gb_id, the older one is evicted.

demo

Startup demo selection. An app convenience — API clients use OPEN_DEMO instead.


Transport

Message Format

All messages are JSON objects with a type field. There are five message types on the wire:

  • identify — sent by the browser app to register with the server (see below)
  • command — sent by external clients to the server
  • response — sent back for commands and identify
  • error — server-level routing or protocol errors
  • event — server-pushed subscription events

External clients only use the last four. The identify handshake is documented here because it governs instance registration and affects behavior visible to external clients.

Identify (Browser App → Server)

When a GalaxyBrain browser tab connects, it identifies itself:

{
  "type": "identify",
  "instanceId": "desk-main",
  "protocolVersion": 1,
  "state": "picker",
  "folder": null,
  "demo": null,
  "offline": false,
  "version": "0.5.0"
}

The server responds with:

{ "type": "response", "requestId": "identify", "ok": true, "serverVersion": "0.2.1" }

The serverVersion field contains the API server's npm package version string.

If protocolVersion does not match the server's version, the connection receives a PROTOCOL_MISMATCH error. If another tab is already registered with the same instanceId, the older tab receives an eviction event and is disconnected — the new tab takes over.

External clients never send identify.

Command (Client → Server)

{
  "type": "command",
  "requestId": "req-1",
  "instance": "desk-main",
  "cmd": "READ_PAGES",
  "...": "command-specific fields"
}

| Field | Type | Required | Description | |-------|------|----------|-------------| | type | "command" | Yes | Message type | | requestId | string | Yes | Unique ID for correlating the response | | cmd | string | Yes | Command name | | instance | string | No | Target instance ID (see Instance Routing) |

Response (Server → Client)

{
  "type": "response",
  "requestId": "req-1",
  "cmd": "READ_PAGES",
  "...": "command-specific result"
}

Every response includes type, requestId, and cmd. The rest of the shape depends on the command (see Response Patterns).

Error (Server → Client)

Server-level errors (routing failures, protocol mismatches) — distinct from command-level errors in responses:

{
  "type": "error",
  "requestId": "req-1",
  "code": "INSTANCE_REQUIRED",
  "message": "Multiple instances connected. Specify \"instance\" field.",
  "instances": [ /* instance list */ ]
}

Event (Server → Client)

Pushed to subscribed clients:

{
  "type": "event",
  "event": "pages_updated",
  "seq": 42,
  "instanceId": "desk-main",
  "timestamp": 1712766240,
  "...": "event-specific fields"
}

Instance Routing

  • One instance connected: instance is optional — auto-routed.
  • Multiple instances: instance is required, otherwise INSTANCE_REQUIRED (response includes the instance list).
  • No instances: NO_INSTANCES error.
  • Unknown instance: UNKNOWN_INSTANCE error.
  • Instance disconnects mid-request: INSTANCE_DISCONNECTED error.

Response Patterns

Commands use three distinct response shapes:

1. Singleton — top-level ok with command-specific fields:

{ "type": "response", "requestId": "1", "cmd": "OPEN_FOLDER", "ok": true, "folder": "Work Notes", "skippedFiles": [] }

Used by: LIST_FOLDERS, OPEN_FOLDER, OPEN_DEMO, CLOSE_PROJECT, REMOVE_RECENT_FOLDER, QUERY, MAP, ANCESTORS, ORIENTATION, READ_WORKSPACE, WRITE_WORKSPACE, SUBSCRIBE, UNSUBSCRIBE, CREATE_FROM_TEMPLATE.

2. Batch — top-level results array aligned to input order, each entry succeeds or fails independently:

{
  "type": "response",
  "requestId": "1",
  "cmd": "DELETE_PAGES",
  "results": [
    { "ok": true },
    { "ok": false, "error": "PAGE_NOT_FOUND", "message": "Page not found: ..." }
  ]
}

Used by: READ_PAGES, CREATE_PAGES, UPDATE_PAGES, DELETE_PAGES, READ_TEMPLATES, CREATE_TEMPLATES, UPDATE_TEMPLATES, DELETE_TEMPLATES, PUSH_PAGE_ITEMS, PUSH_TEMPLATE_ITEMS, POP_PAGE_ITEMS, POP_TEMPLATE_ITEMS.

3. Top-level parse failure — when the payload is malformed before execution can begin:

{ "type": "response", "requestId": "1", "cmd": "READ_PAGES", "ok": false, "error": "PARSE_ERROR", "message": "pageIds must be an array" }

Ordering and Delivery

  • Commands are processed FIFO per target instance. If you send several commands to the same instance, they execute and respond in order.
  • Batch arrays execute in array order — later operations can observe earlier mutations from the same batch.
  • For API-triggered mutations, the response is sent first, then any resulting events are flushed. For example: OPEN_FOLDER responds, then emits project_opened.

IDs, Versions, and Snapshots

Page IDs

20-character alphanumeric strings (e.g. AbcDef1234567890GhIj). Templates also use page IDs.

Block IDs

Non-negative integers, unique within a page.

Variable IDs

Non-negative integers, unique within a page.

Page Versions

Each page and template has an independent integer version. You receive it from read and mutation commands. Mutation commands that accept readVersion compare it against the current version:

  • Match → mutation proceeds.
  • MismatchCONFLICT error.
  • Omitted or null → conflict check is skipped.

Workspace Version

Independent from page versions. Received from READ_WORKSPACE and WRITE_WORKSPACE.

snapshotSeq

Read commands include snapshotSeq — the instance's latest committed event sequence number at read time. Use it to detect whether your event subscription is still in sync.

Commands that include snapshotSeq: READ_PAGES, READ_TEMPLATES, READ_WORKSPACE, QUERY, MAP, ANCESTORS, ORIENTATION.

Write commands do not include snapshotSeq.


Data Model

Writable Schemas

These shapes are used when creating or updating pages and templates.

Page Body (Create / Full Replace)

PageBody = {
  icon: string                    -- single emoji
  title: TitleUnitInput[]         -- title units (text or templateValue only)
  subtitle: UnitInput[]           -- subtitle units (all unit types)
  blocks: BlockInput[]            -- non-empty array
}

BlockInput = {
  blockId: number                 -- non-negative integer, unique within page
  items: ItemInput[]              -- non-empty array
  linkOrder?: string | null       -- sort rule (see Link Order)
  lastSelectedTemplateId?: string | null
}

CREATE_PAGES and CREATE_TEMPLATES accept null entries in the pages array to create default blank pages (default icon "📄" for pages, "📋" for templates, empty title/subtitle, one empty text block).

Item Input

TextItemInput = {
  type: "text"
  style: "" | "#" | "##" | "###" | "*" | "[ ]" | "[X]" | "ol"
  content: UnitInput[]
  indentLevel?: number            -- clamped to 0-8, default 0
  orderedListStart?: number | null  -- only for "ol" style
}

VarItemInput = {
  type: "var"
  id: number                      -- non-negative, unique within page
  name: string
  formula: FormulaUnitInput[]     -- text, metaRef, or templateValue only
}

ImageItemInput =
  | { type: "image", imageId: string }     -- existing image by ID
  | { type: "image", imageFile: string }   -- reference into images map

PageLinkItemInput = {
  type: "pageLink"
  pageId: string                  -- cannot self-link
}

Unit Input

TextUnitInput = {
  type: "text"
  text: string                    -- non-empty
  unitStyle?: "bold" | "italic" | "boldItalic"
}

WebLinkUnitInput = {
  type: "webLink"
  text: string
  url: string
  unitStyle?: "bold" | "italic" | "boldItalic"
}

PageLinkUnitInput = {
  type: "pageLink"
  pageId: string
}

MetaRefUnitInput = {
  type: "metaRef"
  ref: string                     -- see Meta References
}

TemplateValueUnitInput = {
  type: "templateValue"
  valueType: "nextNumber" | "nextDay" | "dateToday"
}

Restrictions by context:

  • Titles accept only unstyled text and templateValue units.
  • Formulas accept only unstyled text, metaRef, and templateValue units.
  • Subtitle and text item content accept all unit types.

Surgical Update

UPDATE_PAGES and UPDATE_TEMPLATES support surgical block-level edits as an alternative to full block replacement:

SurgicalUpdate = {
  icon?: string
  title?: TitleUnitInput[]
  subtitle?: UnitInput[]
  updateBlocks?: [{ blockId, items?, linkOrder?, lastSelectedTemplateId? }]
  insertBlocks?: [{ blockId, items, linkOrder?, lastSelectedTemplateId? }]
  deleteBlockIds?: number[]
  blockOrder?: number[]           -- must list every surviving block exactly once
}

blocks (full replacement) is mutually exclusive with all surgical fields.

In surgical updateBlocks, tri-state behavior applies for linkOrder and lastSelectedTemplateId:

  • Omitted → keep current value.
  • null → clear to null.
  • String → set new value.

Serialized (Read) Schemas

Read operations return these shapes. Fields marked (read-only) are computed by the server and must not be round-tripped back into writes.

Page

{
  "pageId": "AbcDef1234567890GhIj",
  "icon": "📄",
  "title": [ /* serialized units */ ],
  "subtitle": [ /* serialized units */ ],
  "blocks": [ /* serialized blocks */ ],
  "blockOrder": [0, 1, 2],
  "createdAt": 1712766240,
  "updatedAt": 1712766300,
  "templateValues": { "nextNumber": "5" },
  "counts": {
    "words": 150, "characters": 820, "blocks": 3,
    "checkboxes": 2, "checkboxesChecked": 1, "checkboxesUnchecked": 1,
    "pageLinks": 4, "listItems": 5
  }
}
  • blockOrder always lists all block IDs, even when blockIds filtering is applied.
  • counts always reflect the full page.
  • icon, title, subtitle, and blocks are optional based on read options.
  • templateValues is populated on regular pages with resolved values; template reads return {}.

(read-only): pageId, blockOrder, createdAt, updatedAt, templateValues, counts.

Block

{
  "blockId": 0,
  "linkOrder": "A.M.tt",
  "lastSelectedTemplateId": null,
  "items": [ /* serialized items */ ],
  "createdAt": 1712766240,
  "updatedAt": 1712766300,
  "counts": { "words": 50, "characters": 280, "checkboxes": 0, "checkboxesChecked": 0, "checkboxesUnchecked": 0, "pageLinks": 2, "listItems": 1 }
}

(read-only): createdAt, updatedAt, counts.

Serialized Items

indentLevel is only present when > 0 (default 0). orderedListStart is only present when style is "ol".

// Text (indentLevel omitted when 0; orderedListStart only for "ol" style)
{ "type": "text", "style": "", "content": [ /* units */ ], "indentLevel": 2 }
{ "type": "text", "style": "ol", "content": [ /* units */ ], "orderedListStart": 3 }

// Var (value is read-only, computed from formula)
{ "type": "var", "id": 0, "name": "Total", "formula": [ /* units */ ], "value": "150" }

// Image
{ "type": "image", "imageId": "a1b2c3...64hex.png" }

// Page Link (title is read-only, resolved at read time)
{ "type": "pageLink", "pageId": "AbcDef1234567890GhIj", "title": "My Page" }

(read-only): var value, pageLink title.

Serialized Units

// Text
{ "type": "text", "text": "Hello", "unitStyle": "bold" }

// Web Link
{ "type": "webLink", "text": "Example", "url": "https://example.com" }

// Page Link (title is read-only)
{ "type": "pageLink", "pageId": "AbcDef1234567890GhIj", "title": "My Page" }

// Meta Ref — success
{ "type": "metaRef", "ref": "M.tw", "value": "150" }
// Meta Ref — error
{ "type": "metaRef", "ref": "V.AbcDef1234567890GhIj.0", "value": null, "error": "NOT_FOUND" }

// Template Value — on template page (no resolved value)
{ "type": "templateValue", "valueType": "dateToday" }
// Template Value — on regular page (resolved)
{ "type": "templateValue", "valueType": "dateToday", "value": "2026-04-10" }

(read-only): unitStyle is preserved but not semantically read-only. metaRef.value, metaRef.error, pageLink.title, templateValue.value on regular pages.

Meta ref errors: NOT_FOUND, VAR_MISSING_REFERENCE, VAR_CIRCULAR_REFERENCE.

Normalization

The parser applies these normalization steps to written content:

  • Adjacent compatible text and web-link units are merged.
  • A lone page-link unit inside an unstyled text item is converted into a standalone pageLink item.
  • Bullet, checkbox, and ordered-list items keep a lone inline page-link unit as text content.

Link Order

linkOrder controls automatic sorting of page-link items within a block. Format: "{direction}.{sortKey}".

Direction: A (ascending) or D (descending).

Sort keys:

  • Metadata: M.tt (title), M.ca (created at), M.ua (updated at), M.tb (total blocks), M.tw (total words), M.tc (total characters), M.tli (total list items), M.tpl (total page links), M.tr (reference count), M.tcb (total checkboxes), M.tcbc (checked), M.tcbu (unchecked)
  • Variable: V.{varName} (sort by a named variable's value)

Examples: A.M.tt, D.M.ua, A.V.score.

Meta References

metaRef units reference live computed values. The ref string uses dot-separated segments:

| Pattern | Description | Example | |---------|-------------|---------| | CT.{format} | Current time (absolute formats only) | CT.a.d | | CA.{pageId}.{format} | Page created-at | CA.AbcDef1234567890GhIj.a.d | | CA.{pageId}.{blockId}.{format} | Block created-at | CA.AbcDef1234567890GhIj.0.a.d | | UA.{pageId}.{format} | Page updated-at | UA.AbcDef1234567890GhIj.r.d | | UA.{pageId}.{blockId}.{format} | Block updated-at | UA.AbcDef1234567890GhIj.0.r.d | | V.{pageId}.{varId} | Variable value | V.AbcDef1234567890GhIj.0 | | PLCV.{pageId}.{blockId}.{fn}.{varName} | Page-link common variable aggregation | PLCV.AbcDef1234567890GhIj.0.sum.score | | M.{type} | Global metadata | M.tp | | M.{type}.{pageId} | Page-level metadata | M.tw.AbcDef1234567890GhIj | | M.{type}.{pageId}.{blockId} | Block-level metadata | M.tw.AbcDef1234567890GhIj.0 |

Time formats: a.ut (UNIX timestamp), a.d (date), a.dow (day of week), r.s (seconds ago), r.m (minutes ago), r.h (hours ago), r.d (days ago). Relative formats (r.*) only valid for CA/UA.

Metadata types: tp (total pages), tb (total blocks), tw (total words), tc (total characters), tcb (total checkboxes), tcbc (checked), tcbu (unchecked), tli (total list items), tpl (total page links), tr (total references — page-level only).

PLCV derivations: cnt, sum, avg, min, max.

Images

Image IDs are content-addressed: {64 lowercase hex sha256}.{ext}.

Allowed extensions: png, jpg, jpeg, webp, gif, svg.

Commands that accept new images (CREATE_PAGES, CREATE_TEMPLATES, UPDATE_PAGES, UPDATE_TEMPLATES, PUSH_PAGE_ITEMS, PUSH_TEMPLATE_ITEMS) support a top-level images map:

{
  "images": {
    "cover.png": "data:image/png;base64,iVBORw0KGgo..."
  }
}

Image items reference these via imageFile (key into the map) or imageId (existing image). Cannot use both on the same item.


Commands

Server Commands

LIST_INSTANCES

Lists all connected GalaxyBrain browser instances. Handled directly by the API server router — does not require an instance target or an open project.

Request:

{ "type": "command", "requestId": "1", "cmd": "LIST_INSTANCES" }

Response:

{
  "type": "response", "requestId": "1", "cmd": "LIST_INSTANCES",
  "ok": true,
  "instances": [
    { "instanceId": "desk-main", "connectedAt": 1712766240, "state": "folder", "folder": "Work Notes", "demo": null, "offline": false, "version": "0.4.0" },
    { "instanceId": "desk-side", "connectedAt": 1712766300, "state": "picker", "folder": null, "demo": null, "offline": false, "version": "0.4.0" }
  ]
}

Each instance entry includes:

  • instanceId — the instance's self-assigned ID
  • connectedAt — when the instance connected (Unix seconds)
  • state — current application state ("picker", "folder", "demo")
  • folder — workspace folder name when state is "folder", null otherwise
  • demo — demo name when state is "demo", null otherwise
  • offline — whether the instance is running in offline (file://) mode
  • version — application version string

Project Management

LIST_FOLDERS

Returns recent folders and available demos.

Request:

{ "type": "command", "requestId": "1", "cmd": "LIST_FOLDERS" }

Response:

{
  "type": "response", "requestId": "1", "cmd": "LIST_FOLDERS",
  "ok": true,
  "recentFolders": [
    { "id": 1, "name": "My Project", "lastUsed": 1712766240, "permission": "granted" },
    { "id": 2, "name": "Old Project", "lastUsed": 1712600000, "permission": "prompt" }
  ],
  "demos": ["Simple", "F1", "The Premier League", "The Universe"]
}
  • permission: "granted" — can open via API immediately.
  • permission: "prompt" — requires user gesture in the browser first; OPEN_FOLDER returns PERMISSION_REQUIRED.
  • There is no API command for opening an arbitrary filesystem path directly.

OPEN_FOLDER

Opens a recent folder by its numeric ID.

Request:

{ "type": "command", "requestId": "1", "cmd": "OPEN_FOLDER", "id": 1 }

Response:

{ "type": "response", "requestId": "1", "cmd": "OPEN_FOLDER", "ok": true, "folder": "My Project", "skippedFiles": [] }
  • skippedFiles lists project files that failed to load during initialization.
  • On success, the instance enters folder state and emits project_opened.
  • Errors: PARSE_ERROR, FOLDER_NOT_FOUND, PERMISSION_REQUIRED, FOLDER_UNAVAILABLE, FOLDER_LOAD_FAILED.

Note: FOLDER_NOT_FOUND, PERMISSION_REQUIRED, and FOLDER_UNAVAILABLE are checked before the current project is closed. FOLDER_LOAD_FAILED occurs after teardown, leaving the instance in picker state.

OPEN_DEMO

Opens a demo project by name.

Request:

{ "type": "command", "requestId": "1", "cmd": "OPEN_DEMO", "name": "Simple" }

Response:

{ "type": "response", "requestId": "1", "cmd": "OPEN_DEMO", "ok": true, "demo": "Simple" }
  • The app accepts any string, but only configured demo names load successfully.
  • On success, emits project_opened.
  • Errors: PARSE_ERROR, OFFLINE, DEMO_LOAD_FAILED.

CLOSE_PROJECT

Closes the current project and returns to picker state.

Request:

{ "type": "command", "requestId": "1", "cmd": "CLOSE_PROJECT" }

Response:

{ "type": "response", "requestId": "1", "cmd": "CLOSE_PROJECT", "ok": true }

No-op if already in picker (returns success). Emits project_closed when closing a real project.

REMOVE_RECENT_FOLDER

Removes a folder from the recent folders list. Does not delete the filesystem folder.

Request:

{ "type": "command", "requestId": "1", "cmd": "REMOVE_RECENT_FOLDER", "id": 2 }

Response:

{ "type": "response", "requestId": "1", "cmd": "REMOVE_RECENT_FOLDER", "ok": true }

Errors: PARSE_ERROR, FOLDER_NOT_FOUND.


File Watching

FILES_WATCH

Subscribes to filesystem change events inside a subfolder of the open project. Wraps the Chromium FileSystemObserver API.

Request:

{ "type": "command", "requestId": "1", "cmd": "FILES_WATCH",
  "subpath": "inbox", "recursive": true }

subpath is forward-slash-delimited and must resolve to an existing directory under the project root. Leading /, \, Windows-absolute paths (C:\…), backslashes, .., ., and empty segments are rejected.

Success:

{ "type": "response", "requestId": "1", "cmd": "FILES_WATCH",
  "ok": true, "subscriptionId": "fwatch_..." }

Errors: PARSE_ERROR, FOLDER_NOT_FOUND, NO_PROJECT, FOLDER_UNAVAILABLE, UNSUPPORTED_BROWSER.

  • FOLDER_UNAVAILABLE — project is open but is a demo / in-memory (no real folder to observe).
  • UNSUPPORTED_BROWSER — the browser does not expose FileSystemObserver.

Subscriptions live on the GalaxyBrain instance (the open browser tab) until one of: explicit FILES_UNWATCH, project close, folder switch, demo switch, app reload, or Chrome emits an errored record (which tears the subscription down server-side).

FILES_UNWATCH

Stops a previously started folder watch.

Request:

{ "type": "command", "requestId": "2", "cmd": "FILES_UNWATCH",
  "subscriptionId": "fwatch_..." }

Unknown subscription IDs are intentionally no-ops; the server always returns ok: true.

Response:

{ "type": "response", "requestId": "2", "cmd": "FILES_UNWATCH", "ok": true }

Errors: PARSE_ERROR (subscriptionId missing or not a string).

Sharp edges

  • changeType: "unknown" means the OS dropped events. Rescan the folder if correctness matters.
  • changeType: "errored" means the observation is dead. GalaxyBrain removes the subscription; call FILES_WATCH again to resume.
  • Windows reports cross-directory moves as disappeared + appeared, not moved. Same-directory renames are moved.
  • File events are not file-readiness events. A large file copy produces appeared (and/or modified) before the writer has finished. Use temp-then-rename or wait for size/mtime stability if completion matters.
  • Per-origin observation limit exists, is OS-dependent, and surfaces as errored.

Pages Commands

READ_PAGES

Reads regular pages by ID or by open tabs.

Request (by IDs):

{
  "type": "command", "requestId": "1", "cmd": "READ_PAGES",
  "pageIds": ["AbcDef1234567890GhIj"],
  "icon": true, "title": true, "subtitle": true,
  "blockIds": [0, 4]
}

Request (by tabs):

{ "type": "command", "requestId": "1", "cmd": "READ_PAGES", "tabs": true }

pageIds and tabs are mutually exclusive.

Read options (all optional, default true): icon, title, subtitle, blocks. When blocks is false, the blocks key is omitted entirely from the page object (lightweight read). blockOrder and counts are always included regardless. Optional blockIds array filters which blocks are serialized. blocks: false and blockIds are mutually exclusive (PARSE_ERROR).

Response:

{
  "type": "response", "requestId": "1", "cmd": "READ_PAGES",
  "results": [
    {
      "ok": true,
      "version": 3,
      "page": { "pageId": "AbcDef1234567890GhIj", "..." : "..." }
    }
  ],
  "snapshotSeq": 42
}

When using tabs: true, the response also includes tabs:

{ "tabs": [{ "tabIndex": 0, "pageId": "AbcDef1234567890GhIj" }] }
  • Template IDs in explicit pageIds mode return per-entry TEMPLATE_PAGE.
  • tabs: true mode can return both regular pages and templates (deduplicated in first-seen tab order).

CREATE_PAGES

Creates one or more regular pages.

Request:

{
  "type": "command", "requestId": "1", "cmd": "CREATE_PAGES",
  "returnPages": true,
  "pages": [
    null,
    {
      "icon": "📝",
      "title": [{ "type": "text", "text": "New Page" }],
      "subtitle": [],
      "blocks": [{
        "blockId": 0,
        "items": [{ "type": "text", "style": "", "content": [{ "type": "text", "text": "Hello!" }] }]
      }]
    }
  ],
  "images": { "photo.png": "data:image/png;base64,..." }
}
  • null entries create default blank pages (icon "📄", empty title/subtitle, one empty text block).
  • returnPages: true includes the serialized page in each successful result.
  • Partial success is allowed — each entry succeeds or fails independently.

Response:

{
  "type": "response", "requestId": "1", "cmd": "CREATE_PAGES",
  "results": [
    { "ok": true, "pageId": "NewPage1234567890AbC", "version": 0, "page": { "..." : "..." } },
    { "ok": true, "pageId": "NewPage1234567890XyZ", "version": 0, "page": { "..." : "..." } }
  ]
}

Errors (per entry): PARSE_ERROR, INVALID_ICON, NO_BLOCKS, NO_ITEMS, INVALID_BLOCK_ID, DUPLICATE_BLOCK_ID, INVALID_VAR_ID, DUPLICATE_VAR_ID, INVALID_STYLE, EMPTY_TEXT, SELF_LINK, IMAGE_NOT_FOUND, IMAGE_FILE_NOT_FOUND, IMAGE_AMBIGUOUS, INVALID_META_REF, INVALID_TEMPLATE_VALUE, INVALID_LINK_ORDER.

UPDATE_PAGES

Updates one or more regular pages. Supports full block replacement or surgical edits.

Full-replace request:

{
  "type": "command", "requestId": "1", "cmd": "UPDATE_PAGES",
  "pages": [{
    "pageId": "AbcDef1234567890GhIj",
    "readVersion": 3,
    "icon": "🔥",
    "blocks": [{ "blockId": 0, "items": [{ "type": "text", "style": "", "content": [{ "type": "text", "text": "New content" }] }] }]
  }]
}

Surgical request:

{
  "type": "command", "requestId": "1", "cmd": "UPDATE_PAGES",
  "pages": [{
    "pageId": "AbcDef1234567890GhIj",
    "readVersion": 3,
    "updateBlocks": [{ "blockId": 0, "items": [{ "type": "text", "style": "", "content": [{ "type": "text", "text": "Edited" }] }] }],
    "insertBlocks": [{ "blockId": 4, "linkOrder": "A.M.tt", "items": [{ "type": "pageLink", "pageId": "ZyxWvu9876543210TsRq" }] }],
    "blockOrder": [0, 4]
  }]
}

Response:

{
  "type": "response", "requestId": "1", "cmd": "UPDATE_PAGES",
  "results": [{ "ok": true, "pageId": "AbcDef1234567890GhIj", "version": 4 }]
}
  • readVersion is optional; omit to skip conflict checking.
  • returnPages: true adds serialized page to successful entries.
  • Errors: All CREATE_PAGES errors plus PAGE_NOT_FOUND, CONFLICT, TEMPLATE_PAGE, NO_UPDATES, BLOCK_NOT_FOUND, BLOCK_ALREADY_EXISTS, BLOCK_ORDER_MISMATCH, DUPLICATE_BLOCK_OP.

DELETE_PAGES

Deletes one or more regular pages.

Request:

{ "type": "command", "requestId": "1", "cmd": "DELETE_PAGES", "pageIds": ["AbcDef1234567890GhIj"] }

Response:

{ "type": "response", "requestId": "1", "cmd": "DELETE_PAGES", "results": [{ "ok": true, "pageId": "AbcDef1234567890GhIj" }] }

Errors (per entry): PAGE_NOT_FOUND, TEMPLATE_PAGE, LAST_PAGE.

QUERY

Searches and filters pages with sorting, pagination, and field projection.

Request:

{
  "type": "command", "requestId": "1", "cmd": "QUERY",
  "scope": "pages",
  "search": { "text": "invoice", "sections": ["title", "blocks"] },
  "fields": ["icon", "title", "counts", "outboundPageLinks"],
  "sortBy": "updatedAt",
  "sortDirection": "desc",
  "maxResults": 20
}

All parameters (optional):

| Field | Type | Default | Description | |-------|------|---------|-------------| | pageIds | string[] | null | Restrict to specific pages (deduplicated, invalid IDs skipped) | | scope | string | "pages" | "pages", "templates", or "all" | | search | object | null | Text search or error search (see below) | | fields | string[] | [] | Fields to include (see below) | | sortBy | string | null | Sort mode (see below) | | sortDirection | string | null | "asc" or "desc" | | offset | number | 0 | Pagination offset | | maxResults | number | null | Max results; must be a positive integer (null = unlimited) |

Search (mutually exclusive — exactly one of text, errors, or references):

  • Text: { "text": "query", "caseSensitive": false, "sections": ["title", "subtitle", "blocks"] }
  • Errors: { "errors": "all" | "brokenPageLinks" | "brokenValues" }
  • References: { "references": { <target> }, "sections": ["subtitle", "blocks"] }

Note: text: "" and sections: [] match nothing. Error search ignores titles.

References search targets — exactly one of these five shapes inside references:

| Target | Shape | Matches | |--------|-------|---------| | imageId | { "imageId": "<64hex>" } or { "imageId": "<64hex>.<ext>" } | image items in blocks | | varName | { "varName": "...", "prefix": false } | var definitions, V refs (live name lookup), PLCV refs, block linkOrder keys (prefix: true enables trailing-* match) | | metaRef | { "metaRef": <MetaRefQuery> } | metaValue units in subtitles, block text items, and var formulas | | pageLink | { "pageLink": { "pageId": "..." } } | standalone pageLink items and inline pageLink units | | webLinkUrl | { "webLinkUrl": "..." } | webLink units (exact URL match) |

MetaRef query objecthead is required, every other field is optional but must be valid for head:

| Field | Applies to heads | Notes | |-------|------------------|-------| | head | all | "CT", "CA", "UA", "V", "PLCV", or "M" | | pageId | CA, UA, V, PLCV, M | 20-char page ID | | blockId | CA, UA, PLCV, M | non-negative integer | | varId | V | non-negative integer | | type | M | one of the metaType codes (tp, tb, …) | | derivation | PLCV | one of cnt, sum, avg, min, max | | varName | PLCV | exact match (no prefix wildcard here) | | format | CT, CA, UA | one of the time-format codes (a.ut, a.d, r.h, …). CT accepts absolute formats only |

A meta ref matches iff every non-null field on the query equals its parsed component. { "head": "M" } alone matches every M ref.

References sections — defaults to ["subtitle", "blocks"]. Titles are accepted but never produce matches. Empty sections array matches nothing.

Note: matchCount counts structural occurrences (one per matching unit, item, or linkOrder key), not distinct target IDs. Invalid input (unknown target kind, multiple targets, malformed IDs, head-incompatible or invalid component values) returns PARSE_ERROR.

Graph filters (arrays of page IDs, all AND together):

| Filter | Description | |--------|-------------| | linksToPage / notLinksToPage | Pages with/without outbound links to these targets | | linkedFromPage / notLinkedFromPage | Pages linked/not linked from these source pages | | reachesPage / notReachesPage | Pages that can/cannot transitively reach these targets | | reachableFromPage / notReachableFromPage | Pages reachable/not reachable from these sources |

Count filters (non-negative integers, min cannot exceed max): minOutboundPageLinks, maxOutboundPageLinks, minInboundPageLinks, maxInboundPageLinks, minInboundReferences, maxInboundReferences

  • outboundPageLinks / inboundPageLinks refer to standalone pageLink items.
  • inboundReferences refers to inline page-link units in text content.

Fields:

| Value | Result key(s) | Description | |-------|---------------|-------------| | "icon" | icon | Page icon emoji | | "title" | title | Page title as plain text string | | "subtitle" | subtitle | Page subtitle as plain text string | | "blocks" | blocks | Array of block preview strings | | "outboundPageLinks" | outboundPageLinks | Array of linked page IDs | | "inboundPageLinks" | inboundPageLinks | Array of page IDs linking to this page | | "inboundReferences" | inboundReferences | Array of page IDs referencing this page in text | | "timestamps" | createdAt, updatedAt | Timestamps | | "vars" | vars | Array of { id, name, value } | | "counts" | counts | Page statistics |

Default fields is [], so base results contain only kind and pageId.

Note: title and subtitle here are plain text strings, not unit arrays (unlike READ_PAGES).

Sort modes: title, createdAt, updatedAt, outboundPageLinkCount, inboundPageLinkCount, pageReach, pageReachDistinct.

  • pageReach sorts by transitive reach and adds pageReach to result entries. Regular pages ranked first; templates appended.
  • pageReachDistinct deduplicates — pages covered by a higher-reach page are excluded. Ignores sortDirection.

Response:

{
  "type": "response", "requestId": "1", "cmd": "QUERY",
  "ok": true,
  "results": [{
    "kind": "page",
    "pageId": "AbcDef1234567890GhIj",
    "icon": "📄",
    "title": "Invoice 17",
    "counts": { "..." : "..." },
    "outboundPageLinks": ["ZyxWvu9876543210TsRq"],
    "matches": [{ "section": "title", "matchCount": 1, "units": [{ "type": "text", "text": "Invoice 17" }] }]
  }],
  "totalResults": 1,
  "truncated": false,
  "snapshotSeq": 42
}

matches is present when search was used. Match sections: "title", "subtitle", "block". Title/subtitle matches include units; block matches include a serialized block.


Templates Commands

Template commands mirror their page counterparts. Key differences:

  • Template pages can contain templateValue units.
  • Regular page IDs return NOT_TEMPLATE_PAGE errors.
  • Template reads serialize templateValue units without resolved value fields.
  • Default blank template icon is "📋".

READ_TEMPLATES

Same as READ_PAGES (page-ID mode only, no tabs mode).

{ "type": "command", "requestId": "1", "cmd": "READ_TEMPLATES", "pageIds": ["TplPage1234567890AbC"] }

CREATE_TEMPLATES

Same as CREATE_PAGES. Templates may freely use all three templateValue types.

{
  "type": "command", "requestId": "1", "cmd": "CREATE_TEMPLATES",
  "pages": [{
    "icon": "📋",
    "title": [{ "type": "templateValue", "valueType": "dateToday" }, { "type": "text", "text": " — Daily Note" }],
    "subtitle": [],
    "blocks": [{ "blockId": 0, "items": [{ "type": "text", "style": "#", "content": [{ "type": "text", "text": "Tasks" }] }] }]
  }]
}

UPDATE_TEMPLATES

Same structure and options as UPDATE_PAGES.

DELETE_TEMPLATES

Same as DELETE_PAGES, but no "last page" restriction (templates are separate from the required real-page set).

{ "type": "command", "requestId": "1", "cmd": "DELETE_TEMPLATES", "pageIds": ["TplPage1234567890AbC"] }

CREATE_FROM_TEMPLATE

Creates a regular page from a template and inserts a page link into a source block.

Request:

{
  "type": "command", "requestId": "1", "cmd": "CREATE_FROM_TEMPLATE",
  "pageId": "AbcDef1234567890GhIj",
  "blockId": 0,
  "templateId": "TplPage1234567890AbC",
  "insertAtTop": false,
  "returnPage": true
}

| Field | Type | Required | Description | |-------|------|----------|-------------| | pageId | string | Yes | Source page receiving the inserted link | | blockId | number | Yes | Block within the source page | | templateId | string | Yes | Template to clone | | insertAtTop | boolean | No | Insert link at top of block (default: false) | | returnPage | boolean | No | Include the created page in response (default: false) |

Template values are resolved at creation time:

  • nextNumber — Increments based on existing page links in the source block.
  • nextDay — Advances one day from the most recent date in neighboring links.
  • dateToday — Current date.

Response:

{
  "type": "response", "requestId": "1", "cmd": "CREATE_FROM_TEMPLATE",
  "ok": true,
  "createdPageId": "NewPage1234567890AbC",
  "version": 0,
  "sourceVersion": 4,
  "sourcePageId": "AbcDef1234567890GhIj",
  "templateId": "TplPage1234567890AbC",
  "page": { "..." : "..." }
}
  • version — created page's version. sourceVersion — source page's version after link insertion.
  • sourcePageId — the source page ID. templateId — the template page ID used.
  • The source block's lastSelectedTemplateId is updated to templateId.
  • Errors: PARSE_ERROR, PAGE_NOT_FOUND, BLOCK_NOT_FOUND, INVALID_TEMPLATE_ID.

Items Commands

These commands add or remove items from individual blocks without replacing the entire page.

Anchor + Offset

All push/pop commands use anchor and offset to specify position:

  • "top" anchor: insertion/removal index is min(offset, itemCount).
  • "bottom" anchor: insertion index is max(itemCount - offset, 0); removal starts before the skipped tail.

Offsets are clamped to valid bounds automatically.

Examples: top + 0 = before first item. bottom + 0 = after last item. bottom + 1 = before last item.

PUSH_PAGE_ITEMS

Inserts items into a block on a regular page.

Request:

{
  "type": "command", "requestId": "1", "cmd": "PUSH_PAGE_ITEMS",
  "operations": [{
    "pageId": "AbcDef1234567890GhIj",
    "blockId": 0,
    "anchor": "bottom",
    "offset": 0,
    "readVersion": 4,
    "items": [{ "type": "text", "style": "[ ]", "content": [{ "type": "text", "text": "New task" }] }]
  }]
}

Response (per operation):

{
  "ok": true, "pageId": "AbcDef1234567890GhIj", "version": 5,
  "insertedAt": 3, "totalItemCount": 4,
  "didReorderPageLinks": false,
  "block": { "blockId": 0, "..." : "..." }
}
  • didReorderPageLinks reports whether automatic link-order sorting changed page-link positions after the mutation.
  • Operations execute in array order; later operations observe earlier version bumps.
  • Errors: PARSE_ERROR, TEMPLATE_PAGE, PAGE_NOT_FOUND, BLOCK_NOT_FOUND, CONFLICT, NO_ITEMS, DUPLICATE_VAR_ID, plus item/unit validation errors.

PUSH_TEMPLATE_ITEMS

Identical to PUSH_PAGE_ITEMS but targets template pages. Regular page IDs return NOT_TEMPLATE_PAGE.

POP_PAGE_ITEMS

Removes items from a block on a regular page.

Request:

{
  "type": "command", "requestId": "1", "cmd": "POP_PAGE_ITEMS",
  "operations": [{
    "pageId": "AbcDef1234567890GhIj",
    "blockId": 0,
    "anchor": "bottom",
    "offset": 0,
    "count": 2,
    "readVersion": 5,
    "expectedItemType": "text"
  }]
}

Response (per operation):

{
  "ok": true, "pageId": "AbcDef1234567890GhIj", "version": 6,
  "removedFrom": 2, "removedCount": 2, "totalItemCount": 2,
  "didReorderPageLinks": false,
  "removedItems": [{ "type": "text", "style": "", "content": [{ "type": "text", "text": "Removed" }] }],
  "block": { "blockId": 0, "..." : "..." }
}
  • If the computed range is empty, succeeds as a no-op (removedCount: 0, unchanged version).
  • Removing all items from a block is rejected with NO_REMAINING_ITEMS.
  • expectedItemType validates that all items in the removal range match ("text", "var", "image", "pageLink").
  • Errors: PARSE_ERROR, TEMPLATE_PAGE, PAGE_NOT_FOUND, BLOCK_NOT_FOUND, CONFLICT, NO_REMAINING_ITEMS, UNEXPECTED_ITEM_TYPE.

POP_TEMPLATE_ITEMS

Identical to POP_PAGE_ITEMS but targets template pages. Regular page IDs return NOT_TEMPLATE_PAGE.


Workspace Commands

READ_WORKSPACE

Returns the current workspace layout.

Request:

{ "type": "command", "requestId": "1", "cmd": "READ_WORKSPACE" }

Response:

{
  "type": "response", "requestId": "1", "cmd": "READ_WORKSPACE",
  "ok": true,
  "version": 2,
  "workspace": {
    "scrollLeftPx": 0,
    "tabs": [{
      "pageId": "AbcDef1234567890GhIj",
      "path": [],
      "displayWidthPx": 500,
      "scrollTopPx": 120,
      "isLinkedRefsExpanded": false
    }, {
      "pageId": "ZyxWvu9876543210TsRq",
      "path": [{ "pageId": "AbcDef1234567890GhIj", "icon": "📄", "title": "Inbox" }],
      "displayWidthPx": 650,
      "scrollTopPx": 0,
      "isLinkedRefsExpanded": true
    }]
  },
  "snapshotSeq": 42
}
  • path entries are always resolved objects on read. Template tabs use path: [].
  • version is the workspace version, not a page version.
  • Errors: NO_PROJECT.

WRITE_WORKSPACE

Atomically replaces the workspace layout.

Request:

{
  "type": "command", "requestId": "1", "cmd": "WRITE_WORKSPACE",
  "readVersion": 2,
  "workspace": {
    "scrollLeftPx": 0,
    "tabs": [
      { "pageId": "AbcDef1234567890GhIj", "path": [], "displayWidthPx": 500, "scrollTopPx": 0 },
      { "pageId": "ZyxWvu9876543210TsRq", "path": ["AbcDef1234567890GhIj"], "displayWidthPx": 650, "scrollTopPx": 0, "isLinkedRefsExpanded": true }
    ]
  }
}

Response:

{ "type": "response", "requestId": "1", "cmd": "WRITE_WORKSPACE", "ok": true, "version": 3 }

Validation rules:

  • tabs must be a non-empty array.
  • scrollLeftPx and scrollTopPx must be non-negative integers.
  • displayWidthPx must be an integer between 350 and 5000.
  • Every pageId and path entry must resolve to an existing page or template.
  • path entries may be strings or { pageId: string } objects.
  • Template tabs must have an empty path.
  • Path must not end with the tab's own pageId.
  • isLinkedRefsExpanded defaults to false when omitted.

Errors: PARSE_ERROR, NO_TABS, PAGE_NOT_FOUND, INVALID_TAB_PATH, CONFLICT, NO_PROJECT.


Discovery Commands

MAP

Builds a recursive page-link tree rooted at one page.

Request:

{
  "type": "command", "requestId": "1", "cmd": "MAP",
  "pageId": "AbcDef1234567890GhIj",
  "limits": [12, 4, 1],
  "subtitle": true,
  "blockText": true
}

| Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | pageId | string | Yes | — | Root page ID | | limits | number[] | null | No | null | Per-layer link budgets (each element must be a positive integer). null = auto mode (~200 page budget). [] = leaf only | | subtitle | boolean | No | true | Include subtitle text | | blockText | boolean | No | true | Include text preview from first text item (max 100 chars) |

Response:

{
  "type": "response", "requestId": "1", "cmd": "MAP",
  "ok": true,
  "root": {
    "pageId": "AbcDef1234567890GhIj",
    "icon": "📄",
    "title": "Inbox",
    "subtitle": "Open items",
    "blocks": [{
      "blockId": 0,
      "text": "Projects",
      "templateId": "TplPage1234567890AbC",
      "templateName": "📋 Daily Note",
      "links": [{ "pageId": "ZyxWvu9876543210TsRq", "icon": "📄", "title": "Alpha" }],
      "+": 5
    }]
  },
  "snapshotSeq": 42
}
  • Only regular pages can be mapped; template pages return TEMPLATE_PAGE.
  • "+" indicates how many links were truncated at that block.
  • Recursion stops on exhausted limits, path cycles, or no valid child pages.
  • Errors: PAGE_NOT_FOUND, TEMPLATE_PAGE, PARSE_ERROR.

ANCESTORS

Walks UP the page-link tree from a target page, returning a recursive ancestor tree. The complement to MAP (which walks down).

Request:

{
  "type": "command", "requestId": "1", "cmd": "ANCESTORS",
  "pageId": "AbcDef1234567890GhIj",
  "limits": [10, 5, 3],
  "subtitle": true,
  "blockText": true
}

| Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | pageId | string | Yes | — | Target page ID | | limits | number[] | Yes | — | Per-layer parent budgets (each element must be a positive integer). [] = leaf only (no parent expansion). No auto-mode — limits is always required. | | subtitle | boolean | No | true | Include subtitle text on page entries | | blockText | boolean | No | true | Include text preview on viaBlocks entries |

Response:

{
  "type": "response", "requestId": "1", "cmd": "ANCESTORS",
  "ok": true,
  "root": {
    "pageId": "AbcDef1234567890GhIj",
    "icon": "🔴",
    "title": "Erling Haaland",
    "subtitle": "#9 – 30 goals, 5 assists",
    "parents": [
      {
        "pageId": "ZyxWvu9876543210TsRq",
        "icon": "🏃",
        "title": "Squad",
        "subtitle": "Manchester City 2025-26",
        "viaBlocks": [
          { "blockId": 0, "text": "All Players", "templateId": "TplPage1234567890AbC", "templateName": "📋 Player" }
        ],
        "parents": [
          {
            "pageId": "Abcdef12345678901234",
            "icon": "🩵",
            "title": "Manchester City",
            "viaBlocks": [{ "blockId": 3, "text": "Team Pages" }]
          }
        ]
      }
    ],
    "+": 2
  },
  "snapshotSeq": 42
}
  • Only regular pages can be queried; template pages return TEMPLATE_PAGE.
  • viaBlocks on each parent entry lists blocks in that parent containing a page-link item pointing to the child page. blockId is the stable block identifier (not a positional index). Entries are ordered by block position within the parent page.
  • "+" indicates how many parent pages were truncated at that level.
  • parents is absent (not an empty array) when the page has no qualifying parents or is a leaf.
  • Recursion stops on exhausted limits, path cycles, or no qualifying parent pages.
  • Template source pages and non-existent source pages are silently skipped.
  • Errors: PAGE_NOT_FOUND, TEMPLATE_PAGE, PARSE_ERROR.

ORIENTATION

Returns a high-level structural overview of the workspace.

Request:

{ "type": "command", "requestId": "1", "cmd": "ORIENTATION" }

No parameters. Automatically chooses between two modes:

Hub mode — when the workspace has well-connected pages (hub coverage >= 60%):

{
  "type": "response", "requestId": "1", "cmd": "ORIENTATION",
  "ok": true,
  "mode": "hubs",
  "totalPages": 48,
  "totalTemplatePages": 6,
  "tabs": [{
    "pageId": "AbcDef1234567890GhIj", "kind": "page", "icon": "📄",
    "title": "Inbox", "subtitle": "Open items",
    "path": [], "displayWidthPx": 500
  }],
  "templates": [{ "pageId": "TplPage1234567890AbC", "icon": "📋", "title": "Daily Note Template" }],
  "hubs": [{
    "pageId": "HubPage1234567890AbC", "icon": "📄", "title": "Projects",
    "pageReach": 31, "pageReachPercent": 64.6,
    "map": { "pageId": "HubPage1234567890AbC", "icon": "📄", "title": "Projects", "blocks": [] }
  }],
  "coveredPages": 31,
  "coveragePercent": 64.6,
  "snapshotSeq": 42
}

Listing mode — when pages are mostly disconnected:

{
  "type": "response", "requestId": "1", "cmd": "ORIENTATION",
  "ok": true,
  "mode": "listing",
  "totalPages": 12,
  "totalTemplatePages": 1,
  "tabs": [],
  "templates": [],
  "pages": [{
    "pageId": "AbcDef1234567890GhIj", "icon": "📄", "title": "Inbox",
    "subtitle": "Open items",
    "blocks": ["First block preview"],
    "outboundPageLinks": [{ "pageId": "ZyxWvu9876543210TsRq", "title": "Alpha" }],
    "inboundPageLinks": [],
    "counts": { "outboundPageLinkCount": 1, "inboundPageLinkCount": 0, "pageReach": 4 }
  }],
  "snapshotSeq": 42
}

Key details:

  • tabs preserves workspace order. Subtitles truncated to 100 chars.
  • templates capped at 30 entries, sorted by creation time.
  • Hub mode: up to 3 hubs selected greedily by reach. Coverage target 80%, minimum 60%.
  • Listing mode: up to 200 pages. Detail tiers: < 30 pages (full detail), < 100 (subtitle), >= 100 (minimal).

Events Commands

SUBSCRIBE

Subscribes to real-time event notifications. Events are delivered as push messages on the same WebSocket connection.

Request:

{ "type": "command", "requestId": "1", "cmd": "SUBSCRIBE", "categories": ["pages", "project", "workspace", "files"] }

Valid categories: "pages", "project", "workspace", "files".

Subscriptions are additive — calling SUBSCRIBE again adds to existing subscriptions.

Response:

{
  "type": "response", "requestId": "1", "cmd": "SUBSCRIBE",
  "ok": true,
  "activeCategories": ["pages", "project", "workspace"],
  "seq": 42
}
  • activeCategories is the full set after the update.
  • seq is the instance's current event sequence number — use it to detect gaps.

UNSUBSCRIBE

Removes event subscriptions for specified categories.

Request:

{ "type": "command", "requestId": "1", "cmd": "UNSUBSCRIBE", "categories": ["workspace"] }

Response:

{ "type": "response", "requestId": "1", "cmd": "UNSUBSCRIBE", "ok": true, "activeCategories": ["pages", "project"] }

Unsubscribing from a category you're not subscribed to is a no-op.


Event System

Subscribed clients receive event messages:

{
  "type": "event",
  "event": "pages_updated",
  "seq": 43,
  "instanceId": "desk-main",
  "timestamp": 1712766300,
  "...": "event-specific fields"
}

All events include type, event, seq, instanceId, and timestamp (UNIX seconds) — with one exception: the eviction event (see Eviction Event) omits seq, instanceId, and timestamp because it is a server-generated signal, not an instance-scoped event.

Sequence Numbers and Gap Detection

Each instance maintains a monotonic seq counter. When you subscribe, the response includes the current seq. Compare it with seq on incoming events and with snapshotSeq from read commands to detect gaps.

Debounce behavior: High-frequency events are debounced before delivery:

  • User page edits: 300ms trailing debounce, 1000ms max wait.
  • Scroll events: 2000ms trailing debounce.
  • API-originated mutations: delivered immediately after response.

Debounce merging can produce seq gaps — this is expected and does not mean events were lost.

Important: Events are mutation-backed, not output-backed. If page B's title changes and page A has a page link to B, page A's resolved pageLink.title changes but page A does not emit an event. Cached reads can go stale without a direct event for the dependent page.

Pages Events

All events include source ("user", "api", "system") and requestId (when source is "api").

pages_created

Includes the page's icon, serialized title, and source template ID (if created from a template).

{
  "type": "event", "event": "pages_created",
  "seq": 43, "instanceId": "desk-main", "timestamp": 1712766300,
  "source": "api", "requestId": "req-1",
  "pages": [{
    "kind": "page",
    "pageId": "NewPage1234567890AbC",
    "icon": "📋",
    "title": [{ "type": "text", "text": "Meeting: Sprint Review" }],
    "sourceTemplateId": "TemplABCDEF123456780"
  }]
}

sourceTemplateId is always present: a page ID string when created from a template, null otherwise.

pages_updated

Every changed region includes before/after diffs — the serialized state before and after the mutation.

{
  "type": "event", "event": "pages_updated",
  "seq": 44, "instanceId": "desk-main", "timestamp": 1712766310,
  "source": "user",
  "pages": [{
    "kind": "page", "pageId": "AbcDef1234567890GhIj",
    "role": "direct",
    "scope": ["icon", "blocks"],
    "icon": { "before": "📄", "after": "📝" },
    "blockChanges": [{
      "blockId": 0, "op": "updated",
      "before": { "blockId": 0, "items": ["...old..."] },
      "after": { "blockId": 0, "items": ["...new..."] }
    }]
  }]
}

Page entry fields:

  • role: "direct" (edited) or "cascade" (side-effect, e.g. backlink updated).
  • scope: which regions changed — "icon", "title", "subtitle", "blocks".
  • icon: { before, after } — present when "icon" is in scope.
  • title: { before, after } — present when "title" is in scope. Values are serialized title unit arrays.
  • subtitle: { before, after } — present when "subtitle" is in scope. Values are serialized subtitle unit arrays.
  • blockChanges: per-block changes with before/after diffs:
    • op: "created"before: null, after: { serialized block }
    • op: "deleted"before: { serialized block }, after: null
    • op: "updated"before: { old block }, after: { new block }
    • op: "reordered"before: { old block }, after: { new block }

pages_deleted

Includes the page's icon and title at deletion time.

{
  "type": "event", "event": "pages_deleted",
  "seq": 45, "instanceId": "desk-main", "timestamp": 1712766320,
  "source": "api", "requestId": "req-1",
  "pages": [{
    "kind": "page",
    "pageId": "AbcDef1234567890GhIj",
    "icon": "📝",
    "title": [{ "type": "text", "text": "Old Notes" }]
  }]
}

Project Events

All project events include source and requestId (when triggered by an API command).

project_opened

{
  "type": "event", "event": "project_opened",
  "seq": 1, "instanceId": "desk-main", "timestamp": 1712766240,
  "source": "api", "requestId": "req-1",
  "state": "folder", "folder": "Work Notes", "demo": null
}

project_closed

{
  "type": "event", "event": "project_closed",
  "seq": 50, "instanceId": "desk-main", "timestamp": 1712766400,
  "source": "api", "requestId": "req-1"
}

concurrent_access_detected

{
  "type": "event", "event": "concurrent_access_detected",
  "seq": 51, "instanceId": "desk-main", "timestamp": 1712766410,
  "source": "system",
  "detectedBy": "api_server"
}

detectedBy: "workspace_poll", "api_server", or "both".

Workspace Events

All workspace events include source and requestId (when triggered by an API command).

workspace_tab_opened

{
  "type": "event", "event": "workspace_tab_opened",
  "seq": 49, "instanceId": "desk-main", "timestamp": 1712766450,
  "source": "user",
  "tabIndex": 1, "pageId": "ZyxWvu9876543210TsRq", "kind": "page"
}

workspace_tab_closed

{
  "type": "event", "event": "workspace_tab_closed",
  "seq": 50, "instanceId": "desk-main", "timestamp": 1712766460,
  "source": "user",
  "tabIndex": 1, "pageId": "ZyxWvu9876543210TsRq", "kind": "page"
}

workspace_tab_navigated

{
  "type": "event", "event": "workspace_tab_navigated",
  "seq": 51, "instanceId": "desk-main", "timestamp": 1712766470,
  "source": "user",
  "tabIndex": 0, "previousPageId": "AbcDef1234567890GhIj", "newPageId": "ZyxWvu9876543210TsRq", "kind": "page"
}

workspace_tab_resized

{
  "type": "event", "event": "workspace_tab_resized",
  "seq": 52, "instanceId": "desk-main", "timestamp": 1712766480,
  "source": "user",
  "tabIndex": 0, "pageId": "AbcDef1234567890GhIj", "displayWidthPx": 640
}

workspace_scrolled

{
  "type": "event", "event": "workspace_scrolled",
  "seq": 53, "instanceId": "desk-main", "timestamp": 1712766490,
  "source": "user",
  "tabIndex": 0, "pageId": "AbcDef1234567890GhIj", "scrollTopPx": 420
}

workspace_changed

A coarse fallback, suppressed when granular workspace events fire. WRITE_WORKSPACE emits only this event.

{
  "type": "event", "event": "workspace_changed",
  "seq": 54, "instanceId": "desk-main", "timestamp": 1712766500,
  "source": "user",
  "workspaceVersion": 3
}

Files Events

Subscribe with category files.

files_changed

Emitted for each change record produced by an active FILES_WATCH subscription.

{ "type": "event", "event": "files_changed",
  "seq": 99, "instanceId": "desk-main", "timestamp": 1712766500,
  "source": "system",
  "subscriptionId": "fwatch_...",
  "subpath": "inbox",
  "recursive": true,
  "changeType": "appeared",
  "path": ["recipes", "curry.png"],
  "movedFrom": null }
  • subscriptionId — the subscription that produced the record.
  • subpath — the watched folder, verbatim as passed to FILES_WATCH.
  • recursive — whether the watch was started in recursive mode.
  • changeType — one of appeared, disappeared, modified, moved, unknown, errored.
  • path — path components of the changed entry, relative to subpath. Empty array when the subpath itself changed.
  • movedFrom — the former path components when changeType is moved, otherwise null.

See FILES_WATCH for the full list of sharp edges (unknown, errored, Windows cross-directory moves, partial-write timing, per-origin limits).

Eviction Event

Always delivered regardless of subscriptions. Sent when another tab connects with the same gb_id:

{
  "type": "event", "event": "eviction",
  "reason": "Another instance connected with the same ID"
}

The evicted app disconnects and stops reconnecting.


Error Reference

Server-Level Errors

Arrive as type: "error" messages.

| Code | Description | |------|-------------| | INVALID_JSON | Message is not valid JSON | | MISSING_REQUEST_ID | Command missing requestId or cmd | | NO_INSTANCES | No GalaxyBrain instances connected | | UNKNOWN_INSTANCE | Specified instance ID not found | | INSTANCE_REQUIRED | Multiple instances connected; must specify instance | | INSTANCE_DISCONNECTED | Instance disconnected while processing command | | PROTOCOL_MISMATCH | Browser app protocol version mismatch | | UNKNOWN_MESSAGE_TYPE | Unrecognized message type |

PROTOCOL_MISMATCH errors include additional fields:

  • serverProtocolVersion — the server's protocol version (number)
  • clientProtocolVersion — the client's claimed protocol version (number or null)

Command-Level Errors

Arrive inside response messages as { ok: false, error, message }.

General:

| Code | Description | |------|-------------| | NO_PROJECT | Project-level command sent with no project open | | CONFLICT | readVersion mismatch | | INTERNAL_ERROR | Unexpected exception |

Parse and validation:

| Code | Description | |------|-------------| | PARSE_ERROR | Invalid payload shape, field types, or unrecognized command name | | INVALID_ICON | Icon is not a valid emoji | | INVALID_STYLE | Unsupported text item style | | NO_BLOCKS | Page has zero blocks | | NO_ITEMS | Block or push payload has zero items | | INVALID_BLOCK_ID | Invalid block ID | | INVALID_VAR_ID | Invalid variable ID | | DUPLICATE_BLOCK_ID | Block ID duplicated within a page | | DUPLICATE_VAR_ID | Var ID duplicated within a page or collides with existing | | INVALID_LINK_ORDER | linkOrder string does not match grammar | | INVALID_TITLE_UNIT | Title used unsupported unit type or styled text | | INVALID_FORMULA_UNIT | Formula used unsupported unit type or styled text | | EMPTY_TEXT | Text or URL field is empty | | INVALID_META_REF | Meta reference string is malformed | | SELF_LINK | Page link points to the page being written | | INVALID_TEMPLATE_ID | Template ID invalid, missing, or is a regular page | | INVALID_TEMPLATE_VALUE | Template value type invalid or unavailable | | NO_UPDATES | Surgical update has no actual changes | | DUPLICATE_BLOCK_OP | Same block in multiple surgical operations | | BLOCK_ORDER_MISMATCH | blockOrder doesn't match final block set |

Images:

| Code | Description | |------|-------------| | IMAGE_NOT_FOUND | imageId is malformed | | IMAGE_FILE_NOT_FOUND | imageFile key missing from images map | | IMAGE_AMBIGUOUS | Image item has both imageId and imageFile |

Page and template targeting:

| Code | Description | |------|-------------| | PAGE_NOT_FOUND | Page or template not found | | TEMPLATE_PAGE | Regular-page command sent to a template | | NOT_TEMPLATE_PAGE | Template command sent to a regular page | | BLOCK_NOT_FOUND | Block ID doesn't exist on target page | | BLOCK_ALREADY_EXISTS | Inserted block ID already exists | | LAST_PAGE | Cannot delete the last remaining real page | | NO_REMAINING_ITEMS | Pop would remove every item from a block | | UNEXPECTED_ITEM_TYPE | expectedItemType mismatch |

Workspace:

| Code | Description | |------|-------------| | NO_TABS | Empty or missing tabs array | | INVALID_TAB_PATH | Tab path structurally invalid |

Folder and demo lifecycle:

| Code | Description | |------|-------------| | FOLDER_NOT_FOUND | Recent-folder ID doesn't exist | | PERMISSION_REQUIRED | Browser requires user gesture for folder access | | FOLDER_UNAVAILABLE | Saved folder handle no longer accessible | | FOLDER_LOAD_FAILED | Folder could not be loaded as a project | | DEMO_LOAD_FAILED | Demo could not be loaded | | OFFLINE | Command unavailable in offline mode |

File watching:

| Code | Description | |------|-------------| | UNSUPPORTED_BROWSER | Browser does not expose FileSystemObserver |


Recommended Client Flow

  1. Connect to ws://localhost:<port>.
  2. Send LIST_FOLDERS to see available folders and demos.
  3. If you expect multiple app tabs, include instance on every command.
  4. Open a project with OPEN_FOLDER or OPEN_DEMO.
  5. Get your