Project Registry

Manual project records that group related resources (a Portainer stack, a knowledge-vault subtree, a planner project) into a single bundle. Used by AI tools to ground Claude in "what this project is" — not to be confused with Portainer stacks themselves, which are tracked separately in the stacks table.

What the registry is (and isn't)

The registry is a manual, user-curated list of projects. You decide what counts as a project — a microservice repo, a homelab service, a homelab room, a personal site. Each project can:

  • Optionally link to a Portainer stack (portainer_stack_id + portainer_endpoint_id) for redeploys.
  • Point at a knowledge-vault path so Brain pages under that path count as project context.
  • Be filtered, tagged, owned, and described — fields that show up in the project picker and in the Claude-context bundle.

Portainer stacks are NOT projects. Stacks live in the internal/store stacks table and are managed via the Portainer-backed /api/stacks surface. A project can link to a stack, but the registry itself never auto-discovers stacks.

Creating a project

POST /api/projects (operator role). Fields:

FieldRequiredNotes
nameyesDisplay name (e.g. "ContextBay Master").
keyyesShort slug, unique. Surfaced in URLs and CLI flags.
descriptionnoFree-form prose; included verbatim in the Claude-context bundle.
repo_urlnoLink to source repo; surfaced in the UI but not auto-fetched.
tagsnoString array. Used by the list filter (?tag=foo).
iconnoLucide icon name shown in the picker.
ownernoFree-form owner string.
knowledge_pathnoPath inside the Brain vault to scope KB excerpts to this project.
portainer_stack_idnoNumeric stack id (paired with endpoint id) — enables /redeploy.
portainer_endpoint_idnoPortainer endpoint id where the stack lives.

Validation: name and key are required. key must be unique — duplicates return 409 Conflict. Linking to a stack that another project already owns also returns 409.

curl -sS -X POST http://localhost:7480/api/projects \
  -H "X-API-Key: cb_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Homelab — Media",
    "key": "homelab-media",
    "description": "Plex + ARR stack on the basement node",
    "tags": ["homelab","media"],
    "owner": "ops",
    "knowledge_path": "homelab/media",
    "portainer_stack_id": 7,
    "portainer_endpoint_id": 1
  }'

Listing + filtering

GET /api/projects (viewer role). Query params:

  • tag=<t> — projects with this tag.
  • status=<s> active, archived, etc.
  • has_stack=true|false — only projects with (or without) a Portainer link.
  • q=<text> — substring match on name/description/key.
  • include=system — include CB's built-in system projects (otherwise hidden).

PATCH /api/projects/{id} (partial update — only non-nil fields are written) and DELETE /api/projects/{id} round out the CRUD surface. Both emit project.updated / project.deleted events on the bus.

Claude-context bundle

GET /api/projects/{id}/claude-context returns a JSON bundle that summarises the project's current state. AI tooling injects it into Claude prompts so the model starts grounded.

The bundle (internal/project/context.go) contains:

FieldNotes
projectThe full project record (name, key, description, tags, owner, repo_url).
stackIf linked: stack id, endpoint id, status, compose ref. Null otherwise.
containersContainers in the linked stack with name/image/state/health/uptime.
knowledgeMarkdown excerpt from the project's knowledge_path, plus last-updated timestamp.
plannerRoll-up: open issues, in-progress, done last 7d, recent issues, active sprint.
recent_eventsActivity events filtered to the project's container names (deploys, restarts, etc).
warningsNon-fatal issues encountered while assembling the bundle (KB page missing, stack not found, etc).
metagenerated_at + cache_hint_seconds.

Pass ?verbose=1 to expand the knowledge excerpt (full page rather than the default summary), get more recent events, and disable truncation. Useful for agentic flows that want the maximum grounding.

Local-mode: scan for git repos

GET /api/projects/local is a separate, simpler endpoint. It walks one level deep under a configured root (defaults to /data/projects; override via the projects_scan_path setting) and reports every directory containing a .git.

For each found repo it shells out to git for:

  • branch (current HEAD branch).
  • is_dirty (any git status --porcelain output).
  • remote_url (origin).
  • last_commit (subject + relative time).

This endpoint is read-only and never writes to the registry — it's a quick way for the AI to see what's actually checked out on the master's data volume. Pair it with the registry by importing one of the scan results into a real project record via POST /api/projects when you decide it's worth tracking.

Redeploying the linked stack

POST /api/projects/{id}/redeploy (operator role). The handler:

  1. Looks up the project. Returns 404 if unknown.
  2. Refuses with 409 Conflict if no Portainer stack is linked.
  3. Calls the configured StackRedeployer.RedeployStack(endpointID, stackID) — currently the Portainer client. Portainer pulls fresh images and recreates the stack.
  4. Returns {"status":"redeployed"} on success.

The action is audited and emits a project.redeployed event. Failures from Portainer surface as 502 Bad Gateway with the Portainer error in the body.

Related

  • API Reference — Project endpoints + the AI surface that consumes the claude-context bundle.
  • Architecture — where projects, stacks, and the planner sit in the data model.