Skip to content
Merged
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
7 changes: 6 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ src/keboola_agent_cli/
changelog.py # LAYER 1: Changelog display
context.py # LAYER 1: Agent usage instructions
doctor.py # LAYER 1: Health check command
hints/
__init__.py # HintRegistry + render_hint() public API
models.py # HintMode, ClientCall, ServiceCall, HintStep, CommandHint
renderer.py # ClientRenderer + ServiceRenderer (Python code generation)
definitions/ # One file per command group (config.py, storage.py, job.py, ...)
services/
base.py # LAYER 2: BaseService - shared parallel execution infrastructure
project_service.py # LAYER 2: Business logic for projects
Expand Down Expand Up @@ -219,7 +224,7 @@ Note: `SKILL.md` instructs Claude to run `kbagent context` as its first step, wh
## All CLI Commands

```
# Global options: --json, --verbose, --no-color, --config-dir
# Global options: --json, --verbose, --no-color, --config-dir, --hint client|service

kbagent project add --project NAME --url URL --token TOKEN
kbagent project list
Expand Down
2 changes: 2 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,8 @@ When adding a new command (e.g., `kbagent storage create-foo`), you must update
- [ ] **Client method** in `client.py` (or `manage_client.py`) -- HTTP layer
- [ ] **Service method** in `services/` -- business logic, validation, orchestration
- [ ] **Command function** in `commands/` -- Typer options, formatter, error handling
- [ ] **Hint definition** in `hints/definitions/` -- register a `CommandHint` for `--hint` code generation (see existing files for pattern)
- [ ] **Hint short-circuit** in the command function -- add `if should_hint(ctx): emit_hint(...)` before service call
- [ ] **Permission registration** in `permissions.py` (`OPERATION_REGISTRY` dict)
- [ ] **Service wiring** in `cli.py` if adding a new service class

Expand Down
114 changes: 114 additions & 0 deletions docs/hint-mode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# --hint Mode: Use kbagent as a Python SDK

The `--hint` flag generates equivalent Python code for any CLI command, without
executing it. This lets you use `kbagent` not just as a CLI tool, but as a
programming reference and SDK.

## Quick start

```bash
# Show how to call the API directly (client layer)
kbagent --hint client config list --project myproj

# Show how to use the service layer (with CLI config)
kbagent --hint service config list --project myproj
```

## Two modes

### `--hint client` (direct API calls)

Generates code using `KeboolaClient` with explicit URL and token.
Best for: standalone scripts, CI/CD, when you don't want CLI config dependency.

```python
import os
from keboola_agent_cli.client import KeboolaClient

client = KeboolaClient(
base_url="https://connection.eu-central-1.keboola.com",
token=os.environ["KBC_STORAGE_TOKEN"],
)
try:
components = client.list_components()
finally:
client.close()
```

### `--hint service` (service layer with CLI config)

Generates code using the service layer, which reads project configuration
from the same `config.json` that `kbagent` uses. Best for: scripts that
work with multiple projects, need branch resolution, or want error accumulation.

```python
from pathlib import Path
from keboola_agent_cli.config_store import ConfigStore
from keboola_agent_cli.services.config_service import ConfigService

store = ConfigStore(config_dir=Path("/path/to/.kbagent"))
service = ConfigService(config_store=store)
result = service.list_configs(aliases=["myproj"])
```

The `config_dir` path is always explicit -- no hidden CWD resolution.
The generated code uses the actual path from your current CLI configuration.

## What it does NOT do

- Does NOT execute any API calls
- Does NOT include real tokens (always uses `os.environ[...]`)
- Does NOT trigger auto-update checks
- Does NOT require a valid token (only needs project config for URL resolution)

## Supported commands

All API-backed commands support `--hint` (45 commands total), including:
- `config list/detail/search`
- `storage buckets/tables/files` and all CRUD operations
- `job list/detail/run` (including poll loop pattern for `--wait`)
- `branch list/create/delete`
- `workspace create/list/detail/delete/load/query`
- `sharing list/share/unshare/link/unlink`
- `component list/detail`
- `encrypt values`
- `lineage show`
- `org setup`

Local-only commands (`project add/list/remove`, `branch use/reset`, etc.) do
not support `--hint` because they don't make API calls.

## Examples

### Multi-step command (job run with polling)

```bash
kbagent --hint client job run --project myproj \
--component-id keboola.ex-http --config-id 123 --wait
```

Generates code with a polling loop:

```python
job = client.create_job(component_id="keboola.ex-http", config_id="123")
while not job.get("isFinished"):
time.sleep(5.0)
job = client.get_job_detail(job_id=str(job["id"]))
```

### Manage API command

```bash
kbagent --hint client org setup --org-id 42 --url https://connection.keboola.com
```

Generates code with `ManageClient` and the correct token env var:

```python
from keboola_agent_cli.manage_client import ManageClient

manage_client = ManageClient(
base_url="https://connection.keboola.com",
token=os.environ["KBC_MANAGE_API_TOKEN"],
)
```
1 change: 1 addition & 0 deletions plugins/kbagent/skills/kbagent/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ If kbagent is not installed or you need the full standalone reference, run `kbag
3. **Multi-project by default**: read commands query ALL connected projects in parallel -- no need to loop
4. **Write commands need `--project`**: specify the target project alias
5. **Tokens are always masked** in output -- this is expected, not an error
6. **Use `--hint` for Python code generation**: `kbagent --hint client <command>` generates Python code using `KeboolaClient` (direct API), `kbagent --hint service <command>` generates code using the service layer with CLI config. See [programming-with-cli.md](references/programming-with-cli.md) for details.

## Choosing the right approach

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ All commands support `--json` for structured output. Multi-project flags (`--pro
| `--verbose / -v` | Verbose output |
| `--no-color` | Disable colors |
| `--config-dir` | Override config directory |
| `--hint client\|service` | Generate Python code instead of executing (see [programming-with-cli.md](programming-with-cli.md)) |

## Environment Variables
| Variable | Purpose |
Expand Down
18 changes: 18 additions & 0 deletions plugins/kbagent/skills/kbagent/references/gotchas.md
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,24 @@ scope to that branch:
This means you can have production and dev branch configs side by side on disk
without them overwriting each other.

## --hint mode: generate Python code

Use `--hint` to generate equivalent Python code instead of executing a command:

```bash
kbagent --hint client config list --project myproj # direct API calls
kbagent --hint service config list --project myproj # service layer with CLI config
```

Two modes:
- **`--hint client`**: generates code using `KeboolaClient` with explicit URL + token
- **`--hint service`**: generates code using the service layer with `ConfigStore`

Important: `--hint` requires a value (`client` or `service`). Writing just `--hint`
without a value will cause a parsing error.

See [docs/hint-mode.md](../../../../../docs/hint-mode.md) for full documentation.

## Common mistakes

- **Forgetting `--json`**: without it, output is human-formatted Rich text, not parseable
Expand Down
172 changes: 172 additions & 0 deletions plugins/kbagent/skills/kbagent/references/programming-with-cli.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
# Programming with kbagent CLI as a Python SDK

kbagent is not just a command-line tool -- it is also a Python SDK. When you
install kbagent (`uv tool install keboola-agent-cli`), you get importable
Python modules that you can use directly in your scripts.

Use `--hint` on any command to see how to do the same thing in Python:

```bash
kbagent --hint client config list --project myproj
kbagent --hint service job run --project myproj --component-id keboola.ex-http --config-id 123
```

## Two layers: client vs service

### Client layer (`--hint client`)

Direct API calls with explicit URL and token. No dependency on CLI config.

```python
import os
from keboola_agent_cli.client import KeboolaClient

client = KeboolaClient(
base_url="https://connection.eu-central-1.keboola.com",
token=os.environ["KBC_STORAGE_TOKEN"],
)
try:
# List all components with their configurations
components = client.list_components()

# Get a specific config
detail = client.get_config_detail("keboola.ex-db-snowflake", "12345")

# List tables in a bucket
tables = client.list_tables(bucket_id="in.c-demo")

# Run a job via Queue API
job = client.create_job(
component_id="keboola.ex-http",
config_id="456",
)
finally:
client.close()
```

**When to use**: standalone scripts, CI/CD pipelines, Lambda functions,
or any context where you manage tokens and URLs yourself.

**Available clients**:
- `KeboolaClient` -- Storage API + Queue API + Encryption API
- `ManageClient` -- Manage API (organization operations, token management)
- `AiServiceClient` -- AI Service API (component search, schema summaries)

### Service layer (`--hint service`)

Higher-level abstraction that uses CLI config for project resolution.

```python
from pathlib import Path
from keboola_agent_cli.config_store import ConfigStore
from keboola_agent_cli.services.config_service import ConfigService

# Explicit path to config directory (no CWD magic)
store = ConfigStore(config_dir=Path("/Users/you/.kbagent"))
service = ConfigService(config_store=store)

# Use project aliases -- service resolves URL + token from config
result = service.list_configs(aliases=["myproj"])
# Returns: {"configs": [...], "errors": [...]}

# Query all projects at once (parallel execution)
result = service.list_configs() # aliases=None means all projects
```

**When to use**: scripts that work with multiple projects, need automatic
branch resolution, or want error accumulation (one project fails, others
continue).

**Key difference from client layer**: you pass project **aliases** (like
"myproj") instead of URLs and tokens. The service resolves them from the
same config file that `kbagent` uses.

## Comparison table

| Feature | Client layer | Service layer |
|---------|-------------|---------------|
| Authentication | Explicit URL + token | Project alias from config |
| Multi-project | Manual loop | Built-in parallel execution |
| Branch resolution | Explicit branch_id | Auto-resolves active branch |
| Error handling | Exceptions | Error accumulation (partial success) |
| Dependencies | None (just URL + token) | Requires CLI config (`kbagent project add`) |
| Config path | N/A | Explicit `config_dir=Path(...)` |

## Available services

| Service class | Import from | Key methods |
|---------------|-------------|-------------|
| `ConfigService` | `services.config_service` | `list_configs`, `get_config_detail`, `search_configs` |
| `StorageService` | `services.storage_service` | `list_buckets`, `list_tables`, `upload_table`, `download_table` |
| `JobService` | `services.job_service` | `list_jobs`, `get_job_detail`, `run_job` |
| `BranchService` | `services.branch_service` | `list_branches`, `create_branch`, `delete_branch` |
| `WorkspaceService` | `services.workspace_service` | `create_workspace`, `execute_query`, `load_tables` |
| `SharingService` | `services.sharing_service` | `list_shared`, `share`, `link`, `unlink` |
| `LineageService` | `services.lineage_service` | `get_lineage` |
| `ComponentService` | `services.component_service` | `list_components`, `get_component_detail` |
| `EncryptService` | `services.encrypt_service` | `encrypt` |
| `OrgService` | `services.org_service` | `setup_organization` |

## Common patterns

### Poll a job until completion

```python
import time
job = client.create_job(component_id="keboola.ex-http", config_id="123")
while not job.get("isFinished"):
time.sleep(5)
job = client.get_job_detail(str(job["id"]))
print(f"Job finished with status: {job['status']}")
```

Or with the service layer (handles polling internally):

```python
result = job_service.run_job(
alias="myproj",
component_id="keboola.ex-http",
config_id="123",
wait=True,
timeout=300.0,
)
```

### Upload a CSV file to a table

```python
result = storage_service.upload_table(
alias="myproj",
table_id="in.c-demo.my-table",
file_path="/tmp/data.csv",
incremental=True,
)
```

### Execute SQL in a workspace

```python
result = workspace_service.execute_query(
alias="myproj",
workspace_id=12345,
sql="SELECT * FROM my_table LIMIT 10",
)
# result contains query results as dicts
```

### Search across all project configurations

```python
result = config_service.search_configs(
query="snowflake",
ignore_case=True,
)
# result = {"matches": [...], "errors": [...], "stats": {...}}
```

## Security notes

- Never hardcode tokens in scripts. Use `os.environ["KBC_STORAGE_TOKEN"]`.
- The client automatically masks tokens in error messages.
- Built-in retry logic handles 429/5xx errors with exponential backoff.
- ConfigStore files use 0600 permissions to protect stored tokens.
Loading
Loading