# CoMapping Agent API

This document describes how an AI agent (or any programmatic client) can interact with a CoMapping instance as a collaborator. All endpoints accept and return JSON over HTTP.

Replace `BASE_URL` with the server address (e.g. `http://localhost:8000`).

## Authentication

None. Maps are identified by a 12-character hex ID. Anyone with the ID can read and write.

## Data Model

A map is a tree of nodes:

```json
{
  "id": "abc123def456",
  "version": 5,
  "meta": { "name": "My Map", "mode": "meeting" },
  "data": {
    "id": "root",
    "topic": "Central Topic",
    "note": "Optional note text. Supports [[Wiki Links]] to other nodes.",
    "done": true,
    "children": [
      { "id": "a1b2c3d4e5f6", "topic": "Child Node", "children": [] }
    ]
  }
}
```

Each node has:
- `id` — unique identifier (12-char hex, or `"root"` for the root)
- `topic` — the visible label text
- `note` — (optional) free-text note, supports `[[Node Topic]]` cross-references
- `done` — (optional) boolean, used in meeting mode for level-1 nodes
- `children` — array of child nodes

The map has:
- `version` — integer, increments on every write. Used for conflict detection.
- `meta.mode` — `"review"` (default) or `"meeting"` (shows checkboxes on level-1 nodes)

## Workflow

A typical agent session:

1. **Create or join** a map
2. **Read** the current tree
3. **Make changes** (add, update, delete nodes)
4. **Poll for changes** from other collaborators
5. **Export** when done

## Endpoints

### Create a Map

```
POST {BASE_URL}/api/create.php
Content-Type: application/json

{ "name": "Sprint Planning" }
```

Response (201):
```json
{ "id": "f6019cfcc22d" }
```

The returned `id` is used in all subsequent requests.

### Read a Map

```
GET {BASE_URL}/api/get.php?id={MAP_ID}
```

Response (200): the full map JSON (see data model above).

Returns 404 if the map doesn't exist.

### Update a Map

```
POST {BASE_URL}/api/update.php
Content-Type: application/json

{
  "id": "{MAP_ID}",
  "version": 5,
  "operation": "add",
  "node": { ... }
}
```

The `version` field must match the server's current version. If it doesn't, the server returns **409 Conflict** with the current map state — the agent should re-read and retry.

Response (200):
```json
{ "ok": true, "version": 6 }
```

Always update your local version number from the response.

#### Operations

**Add a child node:**
```json
{
  "operation": "add",
  "node": {
    "parentId": "root",
    "id": "a1b2c3d4e5f6",
    "topic": "New Node"
  }
}
```

Generate the node `id` as 12 random hex characters (e.g. `bin2hex(random_bytes(6))` or equivalent).

**Update a node's topic, note, or done status:**
```json
{
  "operation": "update",
  "node": {
    "id": "a1b2c3d4e5f6",
    "topic": "Updated Topic",
    "note": "See also [[Other Node]]",
    "done": true
  }
}
```

`note` and `done` are optional. Send `"note": ""` to clear a note. Send `"done": false` to uncheck.

**Delete a node (and its subtree):**
```json
{
  "operation": "delete",
  "node": { "id": "a1b2c3d4e5f6" }
}
```

Cannot delete the root node.

**Move a node to a new parent:**
```json
{
  "operation": "move",
  "node": {
    "id": "a1b2c3d4e5f6",
    "parentId": "newParentId"
  }
}
```

**Replace the entire tree (use sparingly):**
```json
{
  "operation": "replace",
  "node": {
    "data": { "id": "root", "topic": "...", "children": [...] },
    "mode": "meeting"
  }
}
```

`mode` is optional. Set to `"meeting"` or `"review"`, or omit to keep unchanged.

### Handling Conflicts

When the server returns 409:

```json
{
  "error": "Version conflict",
  "currentVersion": 7,
  "map": { ... }
}
```

The response includes the full current map. The agent should:
1. Update its local state from `map`
2. Update its local `version` from `currentVersion`
3. Re-apply its intended change against the new state
4. Retry the request with the new version

### Poll for Changes

For agents, simple polling is recommended over SSE:

```
GET {BASE_URL}/api/get.php?id={MAP_ID}
```

Poll every 3-5 seconds. Compare the returned `version` to your local version. If higher, the map was changed by another collaborator — update your local state.

### Upload a File

```
POST {BASE_URL}/api/upload.php
Content-Type: multipart/form-data

id={MAP_ID}
file=@/path/to/image.png
```

Response (201):
```json
{
  "ok": true,
  "filename": "e6b8c28a8d7e7d13.png",
  "original": "image.png",
  "mime": "image/png",
  "isImage": true,
  "url": "api/file.php?id={MAP_ID}&file=e6b8c28a8d7e7d13.png"
}
```

To embed in a note, insert markdown-style syntax:
- Images: `![alt text]({url})`
- Files: `[filename]({url})`

Allowed types: JPEG, PNG, GIF, WebP, SVG, PDF, TXT, CSV, Markdown. Max 5MB.

### Export as Markdown

```
GET {BASE_URL}/api/export.php?id={MAP_ID}&format=bullets
GET {BASE_URL}/api/export.php?id={MAP_ID}&format=headings
```

Returns a `.md` file. `bullets` produces nested lists, `headings` uses `#` through `######`.

Notes are rendered as blockquotes under each node.

## Example: Agent Session

```python
import requests, secrets

BASE = "http://localhost:8000"

# 1. Create a map
r = requests.post(f"{BASE}/api/create.php", json={"name": "Agent Notes"})
map_id = r.json()["id"]
version = 1

# 2. Read it
r = requests.get(f"{BASE}/api/get.php", params={"id": map_id})
tree = r.json()
version = tree["version"]

# 3. Add nodes
def add_node(parent_id, topic):
    global version
    node_id = secrets.token_hex(6)
    r = requests.post(f"{BASE}/api/update.php", json={
        "id": map_id,
        "version": version,
        "operation": "add",
        "node": {"parentId": parent_id, "id": node_id, "topic": topic}
    })
    resp = r.json()
    if r.status_code == 409:
        version = resp["currentVersion"]
        # retry...
    else:
        version = resp["version"]
    return node_id

idea1 = add_node("root", "First Idea")
add_node(idea1, "Sub-point A")
add_node(idea1, "Sub-point B")
add_node("root", "Second Idea")

# 4. Add a note with a cross-reference
requests.post(f"{BASE}/api/update.php", json={
    "id": map_id,
    "version": version,
    "operation": "update",
    "node": {
        "id": idea1,
        "topic": "First Idea",
        "note": "This relates to [[Second Idea]]"
    }
})

# 5. Poll for collaborator changes
import time
while True:
    r = requests.get(f"{BASE}/api/get.php", params={"id": map_id})
    new_version = r.json()["version"]
    if new_version > version:
        print("Map updated by someone else!")
        version = new_version
    time.sleep(3)
```

## Notes for Agent Developers

- **Generate unique IDs**: Always use 12 hex characters for node IDs (e.g. `secrets.token_hex(6)`)
- **Track version**: Every successful write returns a new version number. Always use it in your next request.
- **Don't replace when you can update**: Use `add`, `update`, `delete` for precise changes. Reserve `replace` for bulk restructuring.
- **Wiki links**: Write `[[Exact Node Topic]]` in notes to create visual cross-reference lines. Matching is case-insensitive.
- **Meeting mode**: Set `mode: "meeting"` via a `replace` operation to enable checkboxes. Then set `done: true` on level-1 nodes via `update`.
- **Cleanup**: Maps auto-delete after 30 days of inactivity. No need to clean up.
- **No auth**: Anyone with the map ID has full read/write access. Treat IDs as secrets if the content is sensitive.
