Add bulk operations and remove collections abstraction

- Add bulk delete, bulk tags, and bulk set-tags engine endpoints
  (POST /api/v1/bulk/delete, /bulk/tags, /bulk/set-tags)
- Filter-based selection: by tags, doc_type, ID list, ID range
- Safety threshold (KB_BULK_SAFETY_PERCENT, default 70%) prevents
  accidental mass operations unless force=true
- Synchronous execution with audit trail via jobs table
- Add kb_bulk_delete, kb_bulk_tags, kb_bulk_set_tags MCP tools
- Add kb bulk-remove, bulk-tag, bulk-set-tags CLI commands
- Remove collection abstraction from MCP server (use tags instead)
- Remove kb_set_collection MCP tool
- Update SKILL.md, MCP.md, README.md documentation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-04 22:34:47 +01:00
parent 0c124c4ab7
commit b5a203d2aa
21 changed files with 1619 additions and 112 deletions
+87
View File
@@ -106,6 +106,93 @@ def update_tags(doc_id: int, add: list[str] | None = None,
return r.json()
def delete_document(doc_id: int) -> dict:
with _client() as c:
r = c.delete(f"/api/v1/documents/{doc_id}")
r.raise_for_status()
return r.json()
def _bulk_body(
document_ids: list[int] | None = None,
tags: list[str] | None = None,
doc_type: str | None = None,
from_id: int | None = None,
to_id: int | None = None,
force: bool = False,
**extra,
) -> dict:
body: dict = {}
if document_ids:
body["document_ids"] = document_ids
if tags:
body["tags"] = tags
if doc_type:
body["doc_type"] = doc_type
if from_id is not None:
body["from_id"] = from_id
if to_id is not None:
body["to_id"] = to_id
if force:
body["force"] = True
body.update(extra)
return body
def bulk_delete(
document_ids: list[int] | None = None,
tags: list[str] | None = None,
doc_type: str | None = None,
from_id: int | None = None,
to_id: int | None = None,
force: bool = False,
) -> dict:
body = _bulk_body(document_ids, tags, doc_type, from_id, to_id, force)
with _client() as c:
r = c.post("/api/v1/bulk/delete", json=body)
r.raise_for_status()
return r.json()
def bulk_tags(
document_ids: list[int] | None = None,
tags: list[str] | None = None,
doc_type: str | None = None,
from_id: int | None = None,
to_id: int | None = None,
add: list[str] | None = None,
remove: list[str] | None = None,
force: bool = False,
) -> dict:
extra = {}
if add:
extra["add"] = add
if remove:
extra["remove"] = remove
body = _bulk_body(document_ids, tags, doc_type, from_id, to_id, force, **extra)
with _client() as c:
r = c.post("/api/v1/bulk/tags", json=body)
r.raise_for_status()
return r.json()
def bulk_set_tags(
document_ids: list[int] | None = None,
tags: list[str] | None = None,
doc_type: str | None = None,
from_id: int | None = None,
to_id: int | None = None,
new_tags: list[str] | None = None,
force: bool = False,
) -> dict:
extra = {"new_tags": new_tags or []}
body = _bulk_body(document_ids, tags, doc_type, from_id, to_id, force, **extra)
with _client() as c:
r = c.post("/api/v1/bulk/set-tags", json=body)
r.raise_for_status()
return r.json()
def upload_file(filename: str, file_bytes: bytes,
tags: list[str] | None = None) -> dict:
fields: dict = {}