Metadata-Version: 2.4
Name: koios-component-builder
Version: 1.0.0
Summary: Simplified component builder for the Koios platform
Author-email: "Ai-OPs, Inc." <support@ai-op.com>
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: Other/Proprietary License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Typing :: Typed
Requires-Python: >=3.12
Requires-Dist: click==8.3.1
Requires-Dist: packaging==26.0
Requires-Dist: pandas==3.0.1
Requires-Dist: pydantic==2.12.5
Provides-Extra: dev
Requires-Dist: basedpyright>=1.29.0; extra == 'dev'
Requires-Dist: pytest>=8.0.0; extra == 'dev'
Requires-Dist: pyyaml>=6.0; extra == 'dev'
Requires-Dist: ruff>=0.4.0; extra == 'dev'
Description-Content-Type: text/markdown

# Koios Component Builder

SDK and CLI for building component libraries for the [Koios](https://ai-op.com) platform. Components are reusable logic blocks with typed inputs and outputs that get wired together and executed by the Koios Component Engine at configurable scan rates.

## Installation

```bash
pip install koios-component-builder
```

Requires Python 3.12+.

## Quick Start

### 1. Write a component

```python
# my_library/math_ops.py
from koios_component_builder import (
    Component,
    ComponentCategory,
    ComponentIcon,
    Input,
    Output,
)


class SimpleAdder(Component):
    """A component that adds two numbers."""

    class Meta:
        icon = ComponentIcon.SUM
        category = ComponentCategory.MATH

    a: Input[float] = Input(default=0.0, description="First number")
    b: Input[float] = Input(default=0.0, description="Second number")
    result: Output[float] = Output(default=0.0, description="Sum of a and b")

    def execute(self) -> None:
        self.result = self.a + self.b
```

### 2. Define a library

```python
# my_library/__init__.py
from koios_component_builder import ComponentLibrary
from .math_ops import SimpleAdder, Multiplier


class MyLibrary(ComponentLibrary):
    """My custom component library."""

    name = "my-library"
    major = 1
    minor = 0
    patch = 0
    description = "Custom math components"

    components = [SimpleAdder, Multiplier]


__all__ = ["MyLibrary", "SimpleAdder", "Multiplier"]
```

### 3. Export

```bash
koios-component-builder export my_library/
```

This creates a `.kcl` (Koios Component Library) package in `dist/` that can be uploaded to Koios.

## Component Lifecycle

When deployed to Koios, each execution cycle:

1. **Field Injection** — Input and config values are set on the component
2. **Setup** — `setup()` runs once before the first `execute()` (if overridden)
3. **Execute** — `execute()` runs with current input values
4. **Output Propagation** — Output values are sent to wired destinations

Your component implements `setup()` (optional) and `execute()` — the engine handles all wiring and data flow.

### `setup()` — One-Time Initialization

Override `setup()` for expensive work that should only happen once, such as loading models, creating registries, or parsing configuration. All field values are available when `setup()` runs.

If `setup()` raises an exception, the component is marked FAILED and retried on the next cycle.

```python
class UnitConverter(Component):
    """Converts Fahrenheit to Celsius using pint."""

    class Meta:
        icon = ComponentIcon.TRANSFORM
        category = ComponentCategory.TRANSFORM

    value: Input[float] = Input(default=0.0, description="Temperature in °F")
    decimals: NumberConfig = NumberConfig(
        default=2, min_value=0, max_value=6, description="Decimal places"
    )
    result: Output[float] = Output(default=0.0, description="Temperature in °C")

    def setup(self) -> None:
        from pint import UnitRegistry
        self._ureg = UnitRegistry()  # ~80ms — only runs once

    def execute(self) -> None:
        temp = self._ureg.Quantity(self.value, self._ureg.degF)
        self.result = round(temp.to(self._ureg.degC).magnitude, int(self.decimals))
```

For resources shared across **all instances** of the same component class, use a class-level guard instead:

```python
class MyComponent(Component):
    def execute(self) -> None:
        if not hasattr(MyComponent, "_shared_model"):
            MyComponent._shared_model = load_model()
        self.result = MyComponent._shared_model.predict(self.input)
```

## Field Types

### Input / Output

```python
from koios_component_builder import Component, Input, Output

class ExampleComponent(Component):
    # Supported types: float, int, bool, str, list, dict
    temperature: Input[float] = Input(default=0.0, description="Temperature in Celsius")
    count: Input[int] = Input(default=0, description="Item count")
    enabled: Input[bool] = Input(default=True, description="Enable processing")
    mode: Input[str] = Input(default="auto", description="Operating mode")

    alarm: Output[bool] = Output(default=False, description="High temperature alarm")
    status: Output[str] = Output(default="ok", description="Current status")

    def execute(self) -> None:
        if self.enabled and self.temperature > 100:
            self.alarm = True
            self.status = "overtemp"
        else:
            self.alarm = False
            self.status = "ok"
```

### Config Fields

Config fields are set when the component instance is created in the Koios UI and remain constant during execution. They appear as configuration controls on the component node.

```python
from koios_component_builder import (
    Component, Input, Output,
    NumberConfig, StringConfig, ChoiceConfig, BoolConfig,
)

class ConfigurableComponent(Component):
    # Numeric with min/max validation
    threshold: NumberConfig = NumberConfig(
        default=75.0, min_value=0.0, max_value=100.0,
        description="Alert threshold"
    )

    # Dropdown with predefined options
    mode: ChoiceConfig = ChoiceConfig(
        default="average", choices=["average", "median", "max"],
        description="Calculation mode"
    )

    # Text with optional regex validation
    label: StringConfig = StringConfig(
        default="Sensor", description="Display label"
    )

    # Boolean toggle
    verbose: BoolConfig = BoolConfig(
        default=False, description="Enable verbose output"
    )

    value: Input[float] = Input(default=0.0)
    alert: Output[bool] = Output(default=False)

    def execute(self) -> None:
        self.alert = self.value > self.threshold
```

### HistoryInput

HistoryInput fields provide access to historical tag data via InfluxDB. They must be wired to a HISTORY connector on the environment canvas.

```python
from koios_component_builder import Component, HistoryInput, Output

class TrendAnalyzer(Component):
    """Calculates trend from historical data."""

    sensor_history: HistoryInput = HistoryInput(
        description="Historical sensor readings"
    )
    trend: Output[float] = Output(default=0.0, description="Trend slope")

    def execute(self) -> None:
        if self.sensor_history is None:
            return  # Not wired to a history connector

        # Fetch last hour of data, max 200 samples
        df = self.sensor_history.get_history(
            period_seconds=3600,
            num_samples=200,
        )
        # df has columns: timestamp, value
        if not df.empty:
            values = df["value"].tolist()
            self.trend = values[-1] - values[0]
```

## Component Metadata

Customize how components appear in the Koios UI:

```python
class MyComponent(Component):
    """Component description shown in the UI."""

    class Meta:
        icon = ComponentIcon.CHART_LINE       # Tabler icon name
        category = ComponentCategory.ANALYSIS  # UI grouping
        canvas_width = 8                       # Node width (4–15 grid units, default: 6)
        canvas_minimal = False                 # Compact mode (no header/footer)

        # Optional: component-specific version (overrides library version)
        major = 2
        minor = 1
        patch = 0
        prerelease = "beta"
```

**Icons** — Any [Tabler icon](https://tabler.io/icons) name in kebab-case. Common constants: `SUM`, `CALCULATOR`, `CHART_LINE`, `GAUGE`, `THERMOMETER`, `FILTER`, `WAVE_SINE`, `TOGGLE_LEFT`, `ALERT_TRIANGLE`, `TRANSFORM`.

**Categories** — Standard constants: `MATH`, `STATISTICS`, `LOGIC`, `ANALYSIS`, `TRANSFORM`, `FILTER`, `CONTROL`, `MONITORING`. Custom strings are also accepted.

## Dependencies

Libraries can declare third-party Python package dependencies. The builder resolves them against the Koios platform manifest to determine what's pre-installed in the container vs. what needs bundling.

```python
class MyProtocolLibrary(ComponentLibrary):
    name = "my-protocol-library"
    major = 1
    minor = 0
    dependencies = ["crcmod", "minimalmodbus>=2.0"]
    components = [MyDevice]
```

**Three tiers:**

| Tier | Description | Example |
|------|-------------|---------|
| Platform | Pre-installed in the Koios container | `numpy`, `pandas`, `scipy` |
| Bundled | Downloaded and included in the `.kcl` | `crcmod`, `pint` |
| SDK | Always available (koios-component-builder itself) | `pydantic`, `click` |

```bash
# Export without bundling (warns about non-platform deps)
koios-component-builder export my_library/

# Bundle non-platform dependencies into the .kcl
koios-component-builder export my_library/ --include-deps

# Target specific platforms
koios-component-builder export my_library/ --include-deps \
    --platform manylinux2014_x86_64 --platform manylinux2014_aarch64

# List available platform packages
koios-component-builder platform-packages
```

## Package Format

The `export` command creates a `.kcl` (Koios Component Library) package — a ZIP archive:

```
my-library-1.0.0.kcl
├── manifest.json                              # Library metadata
├── my_library-1.0.0-py3-none-any.whl         # Python wheel
└── deps/                                      # Bundled dependency wheels (if any)
    ├── crcmod-1.7-cp312-...-x86_64.whl
    └── crcmod-1.7-cp312-...-aarch64.whl
```

## Security Audit

Every `.kcl` export automatically runs a static security analysis on your source code. The audit classifies patterns into three tiers:

| Tier | Meaning | Effect |
|------|---------|--------|
| ✅ ALLOW | Safe — no friction | Not reported |
| ⚠️ REVIEW | Flagged for attention, but build continues | Logged as warning, yellow badge in Koios UI |
| ❌ DENY | Blocked | Export fails unless `--allow-unsafe` is used |

### What gets flagged

**DENY** — patterns that have no legitimate use in a component:
- Dangerous imports: `os`, `subprocess`, `socket`, `threading`, `pickle`, `ctypes`, `sys`, etc.
- Unsafe builtins: `eval()`, `exec()`, `compile()`, `__import__()`
- Sandbox escape patterns: `__subclasses__`, `__builtins__`, `__code__`, `__globals__`

**REVIEW** — patterns that are often legitimate but worth being aware of:
- Filesystem imports: `pathlib`, `io`, `csv` (common for loading AI model files in `setup()`)
- `open()` calls (useful for reading model weights or config files)
- Unknown third-party packages not in the platform allow-list

### Audit command

Run the audit without building a `.kcl`:

```bash
# Audit and print results
koios-component-builder audit my_library/

# Output as JSON (for CI integration)
koios-component-builder audit my_library/ --json

# Audit with a custom policy
koios-component-builder audit my_library/ --policy security_policy.yaml
```

Exit code is `1` if any DENY findings are present, `0` otherwise — suitable for CI pipelines.

### Custom policies

Override the defaults by providing a YAML policy file:

```yaml
# security_policy.yaml
policy_version: "1.0"

imports:
  allow:
    - my_internal_sdk      # Trust your own internal package
    - requests             # Allow HTTP if your components genuinely need it
  deny:
    - pandas               # Tighten if you want to forbid heavy deps

builtins:
  review:
    - open                 # Already REVIEW by default; shown here for reference

attributes:
  deny:
    - __dict__             # Add to deny if you want stricter introspection rules
```

Policy overrides work bidirectionally — you can move items to a less restrictive tier (e.g. DENY → ALLOW) or a more restrictive one (e.g. ALLOW → DENY). Items are removed from conflicting tiers automatically.

```bash
koios-component-builder export my_library/ --policy security_policy.yaml
```

### Overriding for power users

If your library has a legitimate reason for a flagged pattern (e.g. loading an ONNX model from disk in `setup()`), the recommended approach is to use `pathlib` / `io` which are REVIEW-tier rather than DENY. For edge cases that genuinely need a denied import:

```bash
koios-component-builder export my_library/ --allow-unsafe
```

The `.kcl` is still created, but the `security_audit` in `manifest.json` records `passed: false` — the Koios UI will display a warning badge when the library is uploaded.

## CLI Reference

```bash
# Export a library to a .kcl package
koios-component-builder export <source_path> [OPTIONS]

Options:
  -o, --output PATH        Output directory (default: dist/)
  --include-deps           Bundle non-platform dependencies
  --platform TEXT          Target platform tag (repeatable)
  --allow-unsafe           Create .kcl even with DENY findings
  --policy PATH            Custom security policy YAML file

# Audit a library without building
koios-component-builder audit <source_path> [OPTIONS]

Options:
  --policy PATH            Custom security policy YAML file
  --json                   Output results as JSON

# List pre-installed platform packages
koios-component-builder platform-packages
```

## Local Testing

Test components locally before deploying:

```python
from my_library.math_ops import SimpleAdder

adder = SimpleAdder("test-instance")
adder.a = 5.0
adder.b = 3.0
adder.execute()

print(f"Result: {adder.result}")  # Result: 8.0
```

## License

Copyright Ai-OPs, Inc. All rights reserved.
