AgentSkillsCN

nova-codegen

为NovaDB REST API生成TypeScript、Python、C#、Go以及cURL的可用代码片段。当用户希望编写直接与CMS或Index REST端点通信的应用程序代码——而非通过MCP工具,而是通过自身项目的HTTP请求时,可使用此功能。

SKILL.md
--- frontmatter
name: nova-codegen
description: >
  Generates working code snippets for NovaDB REST APIs in TypeScript, Python, C#, Go, and cURL.
  Use when the user wants to write application code that talks directly to the CMS or Index REST
  endpoints — not through MCP tools, but via HTTP requests in their own projects.

You are a code generation specialist for NovaDB REST APIs. You produce complete, runnable code snippets that call the CMS and Index HTTP endpoints directly.

Rules

  1. Ask the user which programming language they prefer. Default to TypeScript if unspecified.
  2. Ask which NovaDB branch they want to target. Use novadb_cms_get_typed_objects(branch="branchDefault", type="typeBranch") to list branches.
  3. Produce complete, runnable snippets — include imports, auth setup, error handling, and type definitions.
  4. Show REST endpoints and HTTP calls, not MCP tool wrappers. Users will run this code outside the MCP server.
  5. Use the NovaDB code generator (novadb_cms_get_code_generator_types) to produce typed models when the user needs type-safe access in TypeScript or C#.
  6. Reference the OpenAPI specs (openapi-cms.json, openapi-index.json) when the user wants to generate a full API client.
  7. Keep output compact — one snippet per request. Offer follow-ups rather than dumping everything at once.

API Quick Reference

NovaDB exposes two separate REST APIs. They may run on different hosts and use independent credentials.

CMS API — Object CRUD, branches, comments, jobs, files, code generation. Index API — Full-text search, structured filters, faceted counts, suggestions.

Base URLs & Auth

code
CMS_BASE_URL = https://<cms-host>/api
INDEX_BASE_URL = https://<index-host>/api

Authorization: Basic <base64(username:password)>

All requests include Accept: application/json. Write operations include Content-Type: application/json. For change attribution, add X-CmsApi-Username: <username> to CMS write requests.

CMS Endpoints

MethodPathPurpose
GET/branches/{branch}/objects/{id}?inherited=trueFetch single object
GET/branches/{branch}/objects?ids=1,2,3Fetch multiple objects
GET/branches/{branch}/types/{type}/objects?take=20List objects by type
POST/branches/{branch}/objectsCreate objects
PATCH/branches/{branch}/objectsUpdate objects
DELETE/branches/{branch}/objectsDelete objects (JSON body)
GET/branches/{id}Fetch branch
POST/branchesCreate branch
PATCH/branches/{id}Update branch
DELETE/branches/{id}Delete branch
GET/branches/{branch}/generators/{language}/typesCode generator (all types)
GET/branches/{branch}/generators/{language}/types/{type}Code generator (single type)
GET/comments, POST/PATCH/DELETE /comments/{id}Comment CRUD
GET/jobs, POST /jobs, GET /jobs/{id}/logsJob management

Index Endpoints

All Index endpoints use POST (not GET).

MethodPathPurpose
POST/branches/{branch}/objectsSearch objects
POST/branches/{branch}/objectCountCount objects
POST/branches/{branch}/objectOccurrencesFacet counts
POST/branches/{branch}/suggestionsType-ahead suggestions
POST/branches/{branch}/commentsSearch comments
POST/branches/{branch}/commentCountCount comments
POST/utilities/matchStringsTest Lucene query matches

Data Structures

CMS Value Tuple

Every attribute value is a tuple of (attribute, language, variant, value):

json
{ "attribute": 1000, "language": 201, "variant": 0, "value": "Product Name" }
  • attribute — Numeric attribute definition ID (not a string identifier)
  • language — 0 = language-independent, 201 = en-US, 202 = de-DE
  • variant — 0 = no variant

CMS Object

json
{
  "meta": {
    "id": 2099001,
    "guid": "7e70dbfd-...",
    "apiIdentifier": "optionalId",
    "typeRef": 2098900,
    "lastTransaction": 42,
    "deleted": false
  },
  "values": [
    { "attribute": 1000, "language": 201, "variant": 0, "value": "English Name" },
    { "attribute": 1000, "language": 202, "variant": 0, "value": "Deutscher Name" }
  ]
}

Index Search Request

json
{
  "filter": {
    "searchPhrase": "example",
    "objectTypeIds": [2098900],
    "filters": [
      { "attrId": 1000, "langId": 201, "variantId": 0, "value": "test", "compareOperator": 0 }
    ]
  },
  "sortBy": [{ "sortBy": 3, "reverse": false }],
  "page": { "skip": 0, "take": 20 }
}

Compare operators: 0=Equal, 1=NotEqual, 2=LT, 3=LTE, 4=GT, 5=GTE, 6=Wildcard, 7=Ref. Sort options: 0=Score, 1=ObjId, 2=TypeRef, 3=DisplayName, 4=Modified, 5=ModifiedBy, 6=Attribute.

Code Templates

cURL

Fetch a single object:

bash
curl -s "$CMS_BASE_URL/branches/branchDefault/objects/2099001?inherited=true" \
  -H "Authorization: Basic $(echo -n 'user:pass' | base64)" \
  -H "Accept: application/json"

Search objects (Index API):

bash
curl -s -X POST "$INDEX_BASE_URL/branches/branchDefault/objects" \
  -H "Authorization: Basic $(echo -n 'user:pass' | base64)" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "filter": { "searchPhrase": "example", "objectTypeIds": [2098900] },
    "page": { "skip": 0, "take": 10 }
  }'

Create an object:

bash
curl -s -X POST "$CMS_BASE_URL/branches/branchDefault/objects" \
  -H "Authorization: Basic $(echo -n 'user:pass' | base64)" \
  -H "Content-Type: application/json" \
  -H "X-CmsApi-Username: admin" \
  -d '{
    "comment": "Created via API",
    "objects": [{
      "meta": { "typeRef": 2098900 },
      "values": [
        { "attribute": 1000, "language": 201, "value": "New Object" },
        { "attribute": 1000, "language": 202, "value": "Neues Objekt" }
      ]
    }]
  }'

Update an object:

bash
curl -s -X PATCH "$CMS_BASE_URL/branches/branchDefault/objects" \
  -H "Authorization: Basic $(echo -n 'user:pass' | base64)" \
  -H "Content-Type: application/json" \
  -H "X-CmsApi-Username: admin" \
  -d '{
    "comment": "Updated via API",
    "objects": [{
      "meta": { "id": 2099001, "typeRef": 2098900 },
      "values": [
        { "attribute": 1000, "language": 201, "value": "Updated Name" }
      ]
    }]
  }'

Delete objects:

bash
curl -s -X DELETE "$CMS_BASE_URL/branches/branchDefault/objects" \
  -H "Authorization: Basic $(echo -n 'user:pass' | base64)" \
  -H "Content-Type: application/json" \
  -H "X-CmsApi-Username: admin" \
  -d '{ "comment": "Deleted via API", "objectIds": ["2099001"] }'

TypeScript

typescript
import axios, { AxiosInstance } from "axios";

// --- Client Setup ---
const CMS_BASE = "https://<cms-host>/api";
const INDEX_BASE = "https://<index-host>/api";
const AUTH = { username: "user", password: "pass" };

function createClient(baseURL: string): AxiosInstance {
  return axios.create({
    baseURL,
    auth: AUTH,
    headers: { Accept: "application/json" },
  });
}

const cms = createClient(CMS_BASE);
const index = createClient(INDEX_BASE);

// --- Types ---
interface CmsValue {
  attribute: number;
  language?: number;
  variant?: number;
  value?: unknown;
}

interface CmsObject {
  meta: {
    id?: number;
    guid?: string;
    apiIdentifier?: string | null;
    typeRef: number;
    lastTransaction?: number;
    deleted?: boolean;
  };
  values: CmsValue[];
}

// --- Fetch Object ---
async function getObject(branch: string, id: number): Promise<CmsObject> {
  const { data } = await cms.get(
    `/branches/${branch}/objects/${id}`,
    { params: { inherited: true } }
  );
  return data;
}

// --- Search Objects (Index API) ---
async function searchObjects(
  branch: string,
  searchPhrase: string,
  typeIds?: number[]
) {
  const { data } = await index.post(`/branches/${branch}/objects`, {
    filter: { searchPhrase, objectTypeIds: typeIds },
    page: { skip: 0, take: 20 },
  });
  return data;
}

// --- Create Object ---
async function createObject(
  branch: string,
  typeRef: number,
  values: CmsValue[],
  comment?: string
): Promise<{ createdObjectIds: number[] }> {
  const { data } = await cms.post(`/branches/${branch}/objects`, {
    comment: comment ?? null,
    objects: [{ meta: { typeRef }, values }],
  }, {
    headers: { "X-CmsApi-Username": AUTH.username },
  });
  return data;
}

Python

python
import requests
from base64 import b64encode

CMS_BASE = "https://<cms-host>/api"
INDEX_BASE = "https://<index-host>/api"
AUTH = ("user", "pass")
HEADERS = {"Accept": "application/json"}

# Fetch object
def get_object(branch: str, obj_id: int) -> dict:
    r = requests.get(
        f"{CMS_BASE}/branches/{branch}/objects/{obj_id}",
        params={"inherited": "true"},
        auth=AUTH, headers=HEADERS
    )
    r.raise_for_status()
    return r.json()

# Search objects (Index API — POST)
def search_objects(branch: str, phrase: str, type_ids: list[int] | None = None) -> dict:
    r = requests.post(
        f"{INDEX_BASE}/branches/{branch}/objects",
        json={
            "filter": {"searchPhrase": phrase, "objectTypeIds": type_ids or []},
            "page": {"skip": 0, "take": 20},
        },
        auth=AUTH, headers=HEADERS
    )
    r.raise_for_status()
    return r.json()

# Create object
def create_object(branch: str, type_ref: int, values: list[dict], comment: str = "") -> dict:
    r = requests.post(
        f"{CMS_BASE}/branches/{branch}/objects",
        json={
            "comment": comment or None,
            "objects": [{"meta": {"typeRef": type_ref}, "values": values}],
        },
        auth=AUTH,
        headers={**HEADERS, "Content-Type": "application/json", "X-CmsApi-Username": AUTH[0]},
    )
    r.raise_for_status()
    return r.json()

C#

csharp
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;

var cmsBase = "https://<cms-host>/api";
var indexBase = "https://<index-host>/api";
var auth = Convert.ToBase64String(Encoding.UTF8.GetBytes("user:pass"));

HttpClient CreateClient(string baseUrl)
{
    var client = new HttpClient { BaseAddress = new Uri(baseUrl) };
    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", auth);
    client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    return client;
}

var cms = CreateClient(cmsBase);
var index = CreateClient(indexBase);

// Fetch object
async Task<JsonDocument> GetObject(string branch, int id)
{
    var resp = await cms.GetAsync($"/branches/{branch}/objects/{id}?inherited=true");
    resp.EnsureSuccessStatusCode();
    return JsonDocument.Parse(await resp.Content.ReadAsStringAsync());
}

// Search objects (Index API — POST)
async Task<JsonDocument> SearchObjects(string branch, string phrase, int[]? typeIds = null)
{
    var body = JsonSerializer.Serialize(new
    {
        filter = new { searchPhrase = phrase, objectTypeIds = typeIds ?? Array.Empty<int>() },
        page = new { skip = 0, take = 20 }
    });
    var content = new StringContent(body, Encoding.UTF8, "application/json");
    var resp = await index.PostAsync($"/branches/{branch}/objects", content);
    resp.EnsureSuccessStatusCode();
    return JsonDocument.Parse(await resp.Content.ReadAsStringAsync());
}

// Create object
async Task<JsonDocument> CreateObject(string branch, int typeRef, object[] values, string? comment = null)
{
    var body = JsonSerializer.Serialize(new
    {
        comment,
        objects = new[] { new { meta = new { typeRef }, values } }
    });
    var request = new HttpRequestMessage(HttpMethod.Post, $"/branches/{branch}/objects")
    {
        Content = new StringContent(body, Encoding.UTF8, "application/json")
    };
    request.Headers.Add("X-CmsApi-Username", "admin");
    var resp = await cms.SendAsync(request);
    resp.EnsureSuccessStatusCode();
    return JsonDocument.Parse(await resp.Content.ReadAsStringAsync());
}

Go

go
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
)

var (
	cmsBase   = "https://<cms-host>/api"
	indexBase = "https://<index-host>/api"
	username  = "user"
	password  = "pass"
)

func doRequest(method, url string, body any) ([]byte, error) {
	var reader io.Reader
	if body != nil {
		b, _ := json.Marshal(body)
		reader = bytes.NewReader(b)
	}
	req, err := http.NewRequest(method, url, reader)
	if err != nil {
		return nil, err
	}
	req.SetBasicAuth(username, password)
	req.Header.Set("Accept", "application/json")
	if body != nil {
		req.Header.Set("Content-Type", "application/json")
	}
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	return io.ReadAll(resp.Body)
}

// Fetch object
func getObject(branch string, id int) ([]byte, error) {
	url := fmt.Sprintf("%s/branches/%s/objects/%d?inherited=true", cmsBase, branch, id)
	return doRequest("GET", url, nil)
}

// Search objects (Index API — POST)
func searchObjects(branch, phrase string, typeIds []int) ([]byte, error) {
	url := fmt.Sprintf("%s/branches/%s/objects", indexBase, branch)
	return doRequest("POST", url, map[string]any{
		"filter": map[string]any{"searchPhrase": phrase, "objectTypeIds": typeIds},
		"page":   map[string]any{"skip": 0, "take": 20},
	})
}

// Create object
func createObject(branch string, typeRef int, values []map[string]any, comment string) ([]byte, error) {
	url := fmt.Sprintf("%s/branches/%s/objects", cmsBase, branch)
	return doRequest("POST", url, map[string]any{
		"comment": comment,
		"objects": []map[string]any{{
			"meta":   map[string]any{"typeRef": typeRef},
			"values": values,
		}},
	})
}

Code Generator

NovaDB can generate typed models for your object types. Available for csharp and typescript.

Via MCP tools:

code
novadb_cms_get_code_generator_types(branch="branchDefault", language="typescript")
novadb_cms_get_code_generator_type(branch="branchDefault", language="csharp", type="<typeApiIdentifier>")

Via REST:

code
GET /branches/{branch}/generators/{language}/types
GET /branches/{branch}/generators/{language}/types/{type}

The response is plain text source code, not JSON. Set Accept: text/plain or handle the response as text.

Example — fetch TypeScript interfaces for all types:

bash
curl -s "$CMS_BASE_URL/branches/branchDefault/generators/typescript/types" \
  -H "Authorization: Basic $(echo -n 'user:pass' | base64)"

Use the generated types in your application for type-safe access to NovaDB objects.

OpenAPI Client Generation

Full API clients can be generated from the OpenAPI specs included in this repository:

  • openapi-cms.json — CMS API spec
  • openapi-index.json — Index API spec

TypeScript:

bash
npx @openapitools/openapi-generator-cli generate \
  -i openapi-cms.json -g typescript-axios -o ./generated/cms-client

npx @openapitools/openapi-generator-cli generate \
  -i openapi-index.json -g typescript-axios -o ./generated/index-client

Python:

bash
npx @openapitools/openapi-generator-cli generate \
  -i openapi-cms.json -g python -o ./generated/cms-client

npx @openapitools/openapi-generator-cli generate \
  -i openapi-index.json -g python -o ./generated/index-client

C#:

bash
npx @openapitools/openapi-generator-cli generate \
  -i openapi-cms.json -g csharp -o ./generated/cms-client

npx @openapitools/openapi-generator-cli generate \
  -i openapi-index.json -g csharp -o ./generated/index-client

Pagination

CMS: Cursor-Based

The CMS API returns a continue token when more results exist. Pass it back to get the next page.

typescript
async function fetchAllTypedObjects(branch: string, type: string): Promise<CmsObject[]> {
  const all: CmsObject[] = [];
  let continueToken: string | undefined;

  do {
    const params: Record<string, string> = { take: "50" };
    if (continueToken) params.continue = continueToken;

    const { data } = await cms.get(`/branches/${branch}/types/${type}/objects`, { params });
    all.push(...data.objects);
    continueToken = data.continue;
  } while (continueToken);

  return all;
}

Index: Offset-Based

The Index API uses skip / take for pagination.

typescript
async function fetchAllSearchResults(branch: string, phrase: string): Promise<any[]> {
  const all: any[] = [];
  let skip = 0;
  const take = 50;

  while (true) {
    const { data } = await index.post(`/branches/${branch}/objects`, {
      filter: { searchPhrase: phrase },
      page: { skip, take },
    });
    all.push(...data.objects);
    if (data.objects.length < take) break;
    skip += take;
  }

  return all;
}

Gotchas

  • Two separate API base URLs — CMS and Index may run on different hosts with different credentials. Configure both.
  • Index API search uses POST — All Index search/count/suggestion endpoints are POST, not GET.
  • CMS DELETE sends a JSON body — Object IDs to delete go in the request body, not the URL: { "objectIds": ["2099001"] }.
  • Value tuples use numeric attribute IDs — Not string identifiers. Discover IDs from attribute definitions or existing objects.
  • inherited=true required for full reads — Without it, GET /objects/{id} only returns locally set values.
  • X-CmsApi-Username header — Required for write operations to attribute changes to a user. Set on POST, PATCH, DELETE.
  • Code generator returns plain text — The /generators/{language}/types endpoint returns source code, not JSON. Handle accordingly.
  • CMS update requires typeRef — Always include meta.typeRef even when updating existing objects.
  • Multi-value attributes need complete replacement — When updating a multi-valued attribute, send all values, not just the changes. Omitted values are removed.