dacli Tutorial: CLI for Documentation Access

Introduction

This tutorial demonstrates how the dacli (Docs-As-Code CLI) tool works and what capabilities it provides.

Important

This CLI is not intended for manual use by humans. It is designed as an interface for Large Language Models (LLMs) that do not have native MCP (Model Context Protocol) support but can execute shell commands.

The examples in this tutorial serve to illustrate the tool’s functionality and output formats.

Prerequisites

The CLI is included with dacli. Install it globally:

# Clone and install
git clone https://github.com/docToolchain/dacli.git
cd dacli
uv tool install .

# Verify installation
dacli --version

Expected output:

dacli, version 0.1.0
Note
Alternatively, use uv sync and prefix all commands with uv run. See Installation Guide for details.

Global Options

All commands support these global options:

dacli [OPTIONS] COMMAND [ARGS]

Options:
  --docs-root DIRECTORY      Documentation root directory (default: current directory)
  --format [json|yaml|text]  Output format (default: text)
  --pretty                   Pretty-print output for human readability
  --version                  Show the version and exit
  --help                     Show help message

Tutorial Examples

For these examples, we assume a documentation project at /path/to/docs.

Example 1: Exploring Document Structure

Get an overview of the documentation hierarchy:

dacli --docs-root /path/to/docs structure --max-depth 1

Output:

{
  "sections": [
    {"path": "introduction", "title": "Introduction", "level": 1, "children": []},
    {"path": "architecture", "title": "Architecture", "level": 1, "children": []},
    {"path": "api-reference", "title": "API Reference", "level": 1, "children": []}
  ],
  "total_sections": 45
}

To see nested sections, increase the depth:

dacli --docs-root /path/to/docs structure --max-depth 2

Example 2: Reading Section Content

Read a specific section by its path:

dacli --docs-root /path/to/docs section introduction

Output:

{
  "path": "introduction",
  "title": "Introduction",
  "content": "== Introduction\n\nThis document describes...",
  "location": {
    "file": "/path/to/docs/chapters/01_intro.adoc",
    "start_line": 1,
    "end_line": 25
  },
  "format": "asciidoc"
}

For nested sections, use dot notation:

dacli --docs-root /path/to/docs section architecture.decisions

Example 3: Handling Non-Existent Sections

When requesting a section that doesn’t exist, the CLI provides helpful suggestions:

dacli --docs-root /path/to/docs section intro

Output (exit code 3):

{
  "error": {
    "code": "PATH_NOT_FOUND",
    "message": "Section 'intro' not found",
    "details": {
      "requested_path": "intro",
      "suggestions": ["introduction", "intro-page"]
    }
  }
}

Example 4: Searching Documentation

Search for content across all documentation:

dacli --docs-root /path/to/docs search "authentication"

Output:

{
  "query": "authentication",
  "results": [
    {
      "path": "security.authentication",
      "line": 45,
      "context": "...implements OAuth2 authentication for all API endpoints...",
      "score": 0.95
    },
    {
      "path": "api-reference.auth",
      "line": 12,
      "context": "...authentication tokens must be included in the header...",
      "score": 0.82
    }
  ],
  "total_results": 2
}

Limit results and scope the search:

dacli --docs-root /path/to/docs search "database" --scope architecture --max-results 5

Example 5: Getting Sections at a Specific Level

List all chapters (level 1 sections):

dacli --docs-root /path/to/docs sections-at-level 1

Output:

{
  "level": 1,
  "sections": [
    {"path": "introduction", "title": "Introduction"},
    {"path": "architecture", "title": "Architecture"},
    {"path": "deployment", "title": "Deployment"}
  ],
  "count": 3
}

Example 6: Extracting Code Blocks

Get all code blocks from the documentation:

dacli --docs-root /path/to/docs elements --type code

Output:

{
  "elements": [
    {
      "type": "code",
      "parent_section": "examples.python",
      "location": {"file": "/path/to/docs/examples.adoc", "start_line": 25}
    },
    {
      "type": "code",
      "parent_section": "api-reference.endpoints",
      "location": {"file": "/path/to/docs/api.adoc", "start_line": 102}
    }
  ],
  "count": 15
}

Filter by section:

dacli --docs-root /path/to/docs elements --type table --section architecture

Example 7: Getting Metadata

Get project-level metadata:

dacli --docs-root /path/to/docs metadata

Output:

{
  "path": null,
  "total_files": 15,
  "total_sections": 87,
  "total_words": 12450,
  "last_modified": "2026-01-22T10:30:00+00:00",
  "formats": ["asciidoc", "markdown"]
}

Get section-level metadata:

dacli --docs-root /path/to/docs metadata architecture.decisions

Output:

{
  "path": "architecture.decisions",
  "title": "Architecture Decisions",
  "file": "/path/to/docs/chapters/09_decisions.adoc",
  "word_count": 2340,
  "last_modified": "2026-01-19T10:15:00+00:00",
  "subsection_count": 7
}

Example 8: Validating Documentation Structure

Check for structural issues:

dacli --docs-root /path/to/docs validate

Output (when valid):

{
  "valid": true,
  "errors": [],
  "warnings": [
    {
      "type": "orphaned_file",
      "path": "chapters/unused.adoc",
      "message": "File is not included in any document"
    }
  ],
  "validation_time_ms": 45
}

Example 9: Updating Section Content

Update a section’s content:

dacli --docs-root /path/to/docs update introduction --content "== Introduction

This is the updated introduction content.

It now includes more details about the project."

Output:

{
  "success": true,
  "path": "introduction",
  "location": {"file": "/path/to/docs/intro.adoc", "line": 1},
  "previous_hash": "a1b2c3d4",
  "new_hash": "e5f6g7h8"
}

Use optimistic locking to prevent concurrent edit conflicts:

dacli --docs-root /path/to/docs update introduction \
  --content "New content..." \
  --expected-hash a1b2c3d4

Example 10: Inserting New Content

Insert a new section after an existing one:

dacli --docs-root /path/to/docs insert architecture \
  --position after \
  --content "== New Architecture Section

This section was added programmatically."

Output:

{
  "success": true,
  "inserted_at": {"file": "/path/to/docs/architecture.adoc", "line": 150},
  "previous_hash": "a1b2c3d4",
  "new_hash": "e5f6g7h8"
}

Position options: - before - Insert before the section - after - Insert after the section - append - Append at the end of the section (before children)

Output Formats

JSON

Compact JSON for machine parsing:

dacli --docs-root /path/to/docs --format json metadata
{"path":null,"total_files":15,"total_sections":87,"total_words":12450}

Pretty JSON

Formatted JSON for debugging:

dacli --docs-root /path/to/docs --format json --pretty metadata
{
  "path": null,
  "total_files": 15,
  "total_sections": 87,
  "total_words": 12450
}

YAML

YAML format:

dacli --docs-root /path/to/docs --format yaml metadata
path: null
total_files: 15
total_sections: 87
total_words: 12450

Text (Default)

Human-readable text format:

dacli --docs-root /path/to/docs --format text metadata
path:
total_files: 15
total_sections: 87
total_words: 12450

Exit Codes

The CLI uses standardized exit codes for error handling:

Code Meaning

0

Success

1

General error

2

Invalid arguments

3

Path not found

4

Validation error

5

Write error

Piping & Scripting

When using dacli --format json in shell pipelines, keep these tips in mind.

Don’t combine uv sync and dacli in one command

Running uv sync before dacli in the same shell invocation can cause empty stdout:

# BROKEN - dacli may produce empty stdout after uv sync
uv sync --all-extras && dacli --format json structure | python3 -c "..."

# WORKS - run as separate commands
uv sync --all-extras
dacli --format json structure | python3 -c "..."

This is a uv issue: modifying the virtual environment can invalidate tool entrypoints within the same shell session.

Don’t redirect stderr into JSON pipes

Using 2>&1 merges informational messages into the JSON stream, breaking parsers:

# BROKEN - stderr mixes into JSON
dacli --format json structure 2>&1 | jq .

# WORKS - leave stderr alone or discard it
dacli --format json structure | jq .
dacli --format json structure 2>/dev/null | jq .

Capture output to a variable

The safest approach for scripting is to capture output into a variable first:

# Capture JSON, then process
output=$(dacli --format json --docs-root ./docs structure)
echo "$output" | jq '.sections | length'

# Or use a temp file for large results
dacli --format json --docs-root ./docs sections-at-level 2 > /tmp/sections.json
python3 -c "import json; d=json.load(open('/tmp/sections.json')); print(d['count'])"

LLM Integration Pattern

A typical LLM workflow using dacli:

# 1. Understand the documentation structure
structure=$(dacli --docs-root /project/docs structure --max-depth 2)

# 2. Search for relevant content
results=$(dacli --docs-root /project/docs search "database configuration")

# 3. Read the relevant section
content=$(dacli --docs-root /project/docs section deployment.database)

# 4. Update the documentation
dacli --docs-root /project/docs update deployment.database \
  --content "Updated database configuration documentation..."

# 5. Verify the structure is still valid
dacli --docs-root /project/docs validate

Command Reference

Command Description

structure

Get hierarchical document structure

section PATH

Read section content by path

sections-at-level LEVEL

Get all sections at a nesting level

search QUERY

Full-text search across documentation

elements

Get code blocks, tables, images, etc.

metadata [PATH]

Get project or section metadata

validate

Validate documentation structure

update PATH

Update section content

insert PATH

Insert content before/after a section