Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ The CLI uses resource groups as top-level subcommands (e.g. `folders`, `document

Top-level commands outside resource groups: `login`, `logout`, `whoami`, `settings`.

Resource groups: `folders`, `documents`, `document-versions`, `sections`, `chunks`, `tags`, `workflows`, `tenants`, `users`, `permissions`, `invites`, `threads`, `thread-messages`, `chunk-lineages`, `path-parts`.
Resource groups: `folders`, `documents`, `document-versions`, `sections`, `chunks`, `tags`, `workflows`, `workflow-definitions`, `workflow-memory`, `events`, `tenants`, `users`, `permissions`, `invites`, `threads`, `thread-messages`, `chunk-lineages`, `path-parts`.

### Resource command modules (`src/kscli/commands/`)

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ urls = { Repository = "https://github.com/knowledgestack/ks-cli" }
dependencies = [
"certifi>=2026.1.4",
"click>=8.3.1",
"ksapi>=1.25.0",
"ksapi>=1.84.0",
"rich>=14.3.3",
]

Expand Down
6 changes: 6 additions & 0 deletions src/kscli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from kscli.commands.chunks import chunks
from kscli.commands.document_versions import document_versions
from kscli.commands.documents import documents
from kscli.commands.events import events
from kscli.commands.folders import folders
from kscli.commands.invites import invites
from kscli.commands.path_parts import path_parts
Expand All @@ -21,6 +22,8 @@
from kscli.commands.thread_messages import thread_messages
from kscli.commands.threads import threads
from kscli.commands.users import users
from kscli.commands.workflow_definitions import workflow_definitions
from kscli.commands.workflow_memory import workflow_memory
from kscli.commands.workflows import workflows
from kscli.config import ensure_config, get_default_format

Expand Down Expand Up @@ -133,6 +136,9 @@ def main(ctx, format_, no_header, base_url): # noqa: ARG001 — params required
main.add_command(chunks)
main.add_command(tags)
main.add_command(workflows)
main.add_command(workflow_definitions)
main.add_command(workflow_memory)
main.add_command(events)
main.add_command(tenants)
main.add_command(users)
main.add_command(permissions)
Expand Down
36 changes: 25 additions & 11 deletions src/kscli/commands/chunk_lineages.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@

from kscli.client import get_api_client, handle_client_errors
from kscli.output import print_result
from kscli.utils.checkout import (
resolve_ancestor_document_path_part_id,
with_document_checkout,
)


@click.group("chunk-lineages")
Expand All @@ -29,16 +33,21 @@ def describe_chunk_lineage(ctx, chunk_id):
@click.option("--child-chunk-id", type=click.UUID, required=True)
@click.pass_context
def create_chunk_lineage(ctx, parent_chunk_id, child_chunk_id):
"""Create a lineage link."""
"""Create a lineage link. Acquires a document checkout for the duration."""
api_client = get_api_client(ctx)
with handle_client_errors():
child = ksapi.ChunksApi(api_client).get_chunk(child_chunk_id)
doc_path_part_id = resolve_ancestor_document_path_part_id(
api_client, child.path_part_id
)
api = ksapi.ChunkLineagesApi(api_client)
result = api.create_chunk_lineage(
ksapi.CreateChunkLineageRequest(
chunk_id=child_chunk_id,
parent_chunk_ids=[parent_chunk_id],
with with_document_checkout(api_client, doc_path_part_id):
result = api.create_chunk_lineage(
ksapi.CreateChunkLineageRequest(
chunk_id=child_chunk_id,
parent_chunk_ids=[parent_chunk_id],
)
)
)
print_result(ctx, [r.model_dump(mode="json") for r in result])


Expand All @@ -47,12 +56,17 @@ def create_chunk_lineage(ctx, parent_chunk_id, child_chunk_id):
@click.option("--child-chunk-id", type=click.UUID, required=True)
@click.pass_context
def delete_chunk_lineage(ctx, parent_chunk_id, child_chunk_id):
"""Delete a lineage link."""
"""Delete a lineage link. Acquires a document checkout for the duration."""
api_client = get_api_client(ctx)
with handle_client_errors():
api = ksapi.ChunkLineagesApi(api_client)
api.delete_chunk_lineage(
parent_chunk_id=parent_chunk_id,
chunk_id=child_chunk_id,
child = ksapi.ChunksApi(api_client).get_chunk(child_chunk_id)
doc_path_part_id = resolve_ancestor_document_path_part_id(
api_client, child.path_part_id
)
api = ksapi.ChunkLineagesApi(api_client)
with with_document_checkout(api_client, doc_path_part_id):
api.delete_chunk_lineage(
parent_chunk_id=parent_chunk_id,
chunk_id=child_chunk_id,
)
click.echo("Deleted chunk lineage link")
67 changes: 44 additions & 23 deletions src/kscli/commands/chunks.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@

from kscli.client import get_api_client, handle_client_errors
from kscli.output import print_result
from kscli.utils.checkout import (
resolve_ancestor_document_path_part_id,
with_document_checkout,
)

_SEARCH_FILTER_KEYS = {
"model",
Expand Down Expand Up @@ -52,28 +56,31 @@ def describe_chunk(ctx, chunk_id):
@click.option("--metadata", "meta", default=None, help="JSON string of metadata")
@click.pass_context
def create_chunk(ctx, content, version_id, section_id, chunk_type, meta):
"""Create a chunk."""
"""Create a chunk. Acquires a document checkout for the duration."""
if version_id is not None and section_id is not None:
raise click.UsageError("Provide only one of --version-id or --section-id")
parent_path_id = version_id or section_id
if parent_path_id is None:
raise click.UsageError("Provide either --version-id or --section-id")
api_client = get_api_client(ctx)
with handle_client_errors():
doc_path_part_id = resolve_ancestor_document_path_part_id(
api_client, parent_path_id
)
api = ksapi.ChunksApi(api_client)
metadata = json.loads(meta) if meta else None
chunk_metadata = (
ksapi.ChunkMetadataInput.from_dict(metadata or {})
or ksapi.ChunkMetadataInput()
ksapi.ChunkMetadata.from_dict(metadata or {}) or ksapi.ChunkMetadata()
)
result = api.create_chunk(
ksapi.CreateChunkRequest(
parent_path_id=parent_path_id,
content=content,
chunk_type=chunk_type,
chunk_metadata=chunk_metadata,
with with_document_checkout(api_client, doc_path_part_id):
result = api.create_chunk(
ksapi.CreateChunkRequest(
parent_path_id=parent_path_id,
content=content,
chunk_type=chunk_type,
chunk_metadata=chunk_metadata,
)
)
)
print_result(ctx, result.model_dump(mode="json"))


Expand All @@ -82,19 +89,23 @@ def create_chunk(ctx, content, version_id, section_id, chunk_type, meta):
@click.option("--metadata", "meta", default=None, help="JSON string of metadata")
@click.pass_context
def update_chunk(ctx, chunk_id, meta):
"""Update chunk metadata."""
"""Update chunk metadata. Acquires a document checkout for the duration."""
api_client = get_api_client(ctx)
with handle_client_errors():
api = ksapi.ChunksApi(api_client)
chunk = api.get_chunk(chunk_id)
doc_path_part_id = resolve_ancestor_document_path_part_id(
api_client, chunk.path_part_id
)
metadata = json.loads(meta) if meta else None
chunk_metadata = (
ksapi.ChunkMetadataInput.from_dict(metadata or {})
or ksapi.ChunkMetadataInput()
)
result = api.update_chunk_metadata(
chunk_id,
ksapi.UpdateChunkMetadataRequest(chunk_metadata=chunk_metadata),
ksapi.ChunkMetadata.from_dict(metadata or {}) or ksapi.ChunkMetadata()
)
with with_document_checkout(api_client, doc_path_part_id):
result = api.update_chunk_metadata(
chunk_id,
ksapi.UpdateChunkMetadataRequest(chunk_metadata=chunk_metadata),
)
print_result(ctx, result.model_dump(mode="json"))


Expand All @@ -103,26 +114,36 @@ def update_chunk(ctx, chunk_id, meta):
@click.option("--content", required=True)
@click.pass_context
def update_chunk_content(ctx, chunk_id, content):
"""Update chunk content."""
"""Update chunk content. Acquires a document checkout for the duration."""
api_client = get_api_client(ctx)
with handle_client_errors():
api = ksapi.ChunksApi(api_client)
result = api.update_chunk_content(
chunk_id,
ksapi.UpdateChunkContentRequest(content=content),
chunk = api.get_chunk(chunk_id)
doc_path_part_id = resolve_ancestor_document_path_part_id(
api_client, chunk.path_part_id
)
with with_document_checkout(api_client, doc_path_part_id):
result = api.update_chunk_content(
chunk_id,
ksapi.UpdateChunkContentRequest(content=content),
)
print_result(ctx, result.model_dump(mode="json"))


@chunks.command("delete")
@click.argument("chunk_id", type=click.UUID)
@click.pass_context
def delete_chunk(ctx, chunk_id):
"""Delete a chunk."""
"""Delete a chunk. Acquires a document checkout for the duration."""
api_client = get_api_client(ctx)
with handle_client_errors():
api = ksapi.ChunksApi(api_client)
api.delete_chunk(chunk_id)
chunk = api.get_chunk(chunk_id)
doc_path_part_id = resolve_ancestor_document_path_part_id(
api_client, chunk.path_part_id
)
with with_document_checkout(api_client, doc_path_part_id):
api.delete_chunk(chunk_id)
click.echo(f"Deleted chunk {chunk_id}")


Expand Down
35 changes: 24 additions & 11 deletions src/kscli/commands/document_versions.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@

from kscli.client import get_api_client, handle_client_errors
from kscli.output import print_result
from kscli.utils.checkout import (
resolve_document_path_part_id,
resolve_version_document_path_part_id,
with_document_checkout,
)

COLUMNS = ["id", "document_id", "name", "created_at"]

Expand Down Expand Up @@ -75,11 +80,13 @@ def version_contents(ctx, version_id, show_content, sections_only):
@click.option("--document-id", type=click.UUID, required=True)
@click.pass_context
def create_version(ctx, document_id):
"""Create a new version."""
"""Create a new version. Acquires a document checkout for the duration."""
api_client = get_api_client(ctx)
with handle_client_errors():
doc_path_part_id = resolve_document_path_part_id(api_client, document_id)
api = ksapi.DocumentVersionsApi(api_client)
result = api.create_document_version(document_id=document_id)
with with_document_checkout(api_client, doc_path_part_id):
result = api.create_document_version(document_id=document_id)
print_result(ctx, result.model_dump(mode="json"))


Expand All @@ -88,36 +95,42 @@ def create_version(ctx, document_id):
@click.option("--source-s3", default=None)
@click.pass_context
def update_version(ctx, version_id, source_s3):
"""Update version metadata."""
"""Update version metadata. Acquires a document checkout for the duration."""
api_client = get_api_client(ctx)
with handle_client_errors():
doc_path_part_id = resolve_version_document_path_part_id(api_client, version_id)
api = ksapi.DocumentVersionsApi(api_client)
result = api.update_document_version_metadata(
version_id,
ksapi.DocumentVersionMetadataUpdate(source_s3=source_s3),
)
with with_document_checkout(api_client, doc_path_part_id):
result = api.update_document_version_metadata(
version_id,
ksapi.DocumentVersionMetadataUpdate(source_s3=source_s3),
)
print_result(ctx, result.model_dump(mode="json"))


@document_versions.command("delete")
@click.argument("version_id", type=click.UUID)
@click.pass_context
def delete_version(ctx, version_id):
"""Delete a version."""
"""Delete a version. Acquires a document checkout for the duration."""
api_client = get_api_client(ctx)
with handle_client_errors():
doc_path_part_id = resolve_version_document_path_part_id(api_client, version_id)
api = ksapi.DocumentVersionsApi(api_client)
api.delete_document_version(version_id)
with with_document_checkout(api_client, doc_path_part_id):
api.delete_document_version(version_id)
click.echo(f"Deleted version {version_id}")


@document_versions.command("clear-contents")
@click.argument("version_id", type=click.UUID)
@click.pass_context
def clear_version_contents(ctx, version_id):
"""Clear all contents under a version."""
"""Clear all contents under a version. Acquires a document checkout for the duration."""
api_client = get_api_client(ctx)
with handle_client_errors():
doc_path_part_id = resolve_version_document_path_part_id(api_client, version_id)
api = ksapi.DocumentVersionsApi(api_client)
api.clear_document_version_contents(version_id)
with with_document_checkout(api_client, doc_path_part_id):
api.clear_document_version_contents(version_id)
click.echo(f"Cleared contents of version {version_id}")
30 changes: 19 additions & 11 deletions src/kscli/commands/documents.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@

from kscli.client import get_api_client, handle_client_errors
from kscli.output import print_result
from kscli.utils.checkout import (
resolve_document_path_part_id,
with_document_checkout,
)

COLUMNS = ["id", "name", "type", "origin", "parent_path_part_id", "created_at"]

Expand Down Expand Up @@ -98,30 +102,34 @@ def create_document(ctx, name, parent_path_part_id, doc_type, origin):
@click.option("--active-version-id", type=click.UUID, default=None)
@click.pass_context
def update_document(ctx, document_id, name, parent_path_part_id, active_version_id):
"""Update a document."""
"""Update a document. Acquires a document checkout for the duration."""
api_client = get_api_client(ctx)
with handle_client_errors():
doc_path_part_id = resolve_document_path_part_id(api_client, document_id)
api = ksapi.DocumentsApi(api_client)
result = api.update_document(
document_id,
ksapi.UpdateDocumentRequest(
name=name,
parent_path_part_id=parent_path_part_id,
active_version_id=active_version_id,
),
)
with with_document_checkout(api_client, doc_path_part_id):
result = api.update_document(
document_id,
ksapi.UpdateDocumentRequest(
name=name,
parent_path_part_id=parent_path_part_id,
active_version_id=active_version_id,
),
)
print_result(ctx, result.model_dump(mode="json"))


@documents.command("delete")
@click.argument("document_id", type=click.UUID)
@click.pass_context
def delete_document(ctx, document_id):
"""Delete a document."""
"""Delete a document. Acquires a document checkout for the duration."""
api_client = get_api_client(ctx)
with handle_client_errors():
doc_path_part_id = resolve_document_path_part_id(api_client, document_id)
api = ksapi.DocumentsApi(api_client)
api.delete_document(document_id)
with with_document_checkout(api_client, doc_path_part_id):
api.delete_document(document_id)
click.echo(f"Deleted document {document_id}")


Expand Down
Loading
Loading