Metadata-Version: 2.1
Name: micronetcode
Version: 0.4.0
Summary: micronetcode: a generic, minimal TCP command channel (connections, threads, channels, codecs)
Home-page: https://gitlab.com/meehai/micronetcode
License: MIT
Requires-Python: >=3.11
Description-Content-Type: text/markdown
Requires-Dist: loggez
Provides-Extra: dev
Requires-Dist: raylib>=6.0; extra == "dev"
Requires-Dist: microspec-py; extra == "dev"
Requires-Dist: pytest>=8.4; extra == "dev"

# micronetcode

A generic, minimal TCP command channel for apps: `ConnectionManager`, `Channel`, `Codec`,
`Message`, `Client`. One thread per client, one channel per connection, one reply per command.
App-agnostic — it moves bytes and routes replies; what a command *means* is yours.

Every action doable via clicks or keyboard should be doable over a socket. `micronetcode` is the
boilerplate for that socket. On the app side the integration goes in the I/O handler, exactly
where `rl.IsKeyPressed` / `rl.IsMousePressed` would sit. Every message must be responded to.

> **0.4.0 removed the `ui_cli_manager` package.** The CLI layer (shlex quoting, typed argument
> validation, `UICLIManager`) was never used as-shipped: robosim drives `ConnectionManager`
> directly, and visual-graph-navigation forked it. Policy diverges per app; transport does not.
> It now lives at [`examples/1-ui-cli-manager/ui_cli_manager.py`](examples/1-ui-cli-manager/) —
> **copy that file into your project and own it.** Nothing else in `micronetcode` changed.

Docs: [meehai.gitlab.io/micronetcode](https://meehai.gitlab.io/micronetcode/) — built by
[`docs/build_docs.sh`](docs/build_docs.sh) (pdoc; no sphinx/config). Build locally with
`bash docs/build_docs.sh` and open the printed `file://` link.

## Try it

```bash
python3 examples/1-ui-cli-manager/app.py --headless   # run the app
printf 'set_text "speed: 12 m/s" 620 340\ndraw_circle 100 100 25\n' | ncat localhost 42069
```

## Usage

Bring your own `Codec` (bytes ↔ `Message`) and poll the manager from your main loop:

```python
from micronetcode import ConnectionManager

manager = ConnectionManager("0.0.0.0", port=42069, codec=MyCodec())
manager.start()                   # background thread: accept + serve TCP clients

while not rl.WindowShouldClose():
    # I/O handling: polls the channels like rl.IsKeyPressed, never blocks
    try:
        msg = manager.get_one_message()
        msg.client.channel.main2tcp.put(handle(msg), timeout=TIMEOUT_S)
    except Empty:
        pass

    rl.BeginDrawing()
    # ... draw
    rl.EndDrawing()
```

Every command gets exactly one reply — the client thread blocks until you answer, so never leave
a message unresponded.

## Examples

- [`examples/1-ui-cli-manager/`](examples/1-ui-cli-manager/) — the CLI layer, **meant to be
  copy-pasted**: `ASCIICodec` + `UICLIManager` driven by a
  [`microspec`](https://gitlab.com/meehai/microspec) `protocol.json`, so `cli_cmd.args` reaches
  the app already converted and range-checked. `app.py` is a raylib app whose entire surface is
  the protocol file. Start here.
- [`examples/2-fast-handler-cli.py`](examples/2-fast-handler-cli.py) — raw `ConnectionManager` with
  a `network_handler`: cheap commands answered straight from the client thread, the rest handed to
  the main loop. Includes a minimal newline `Codec`.

## Concurrency

- **Thread per client**: the listener only accepts connections and hands each one to its own daemon thread. A slow/stalled client can never starve the listener or other clients.
- **`max_connections` cap** (default 10): when every slot is taken, new connections are refused — the client receives `Server is full` and the connection is closed. `0` falls back to the default, `<0` raises `ValueError`. Pass `max_connections=N` to bound the thread count.
- **One channel per slot**: each connection owns a `Channel` (two 1-deep queues,
  `micronetcode.channel`). `get_one_message()` polls the channels in order; each reply routes back
  to the client that sent the command — interleaved clients never cross wires. Strict-channel
  semantics: a client can have at most one outstanding request; answer before sending it the next
  command.
- **Scripted commands** (`script_lines` / `--script`, a `UICLIManager` feature): run first, in order, before any live client command; their responses are journaled, never sent to a client (they have no channel).

## Protocol

The wire format is the `Codec`'s business, not the library's. What the shipped examples do:

- ASCII, newline-delimited; one line = one command.
- Double-quoted arguments with spaces arrive as one argument (`shlex`, example 1).
- Lines starting with `#` are comments (example 1).
- Every command gets exactly one response; invalid input gets an error response.
- Half-close your write side (Ctrl-D / pipe EOF) to disconnect.

## Installation

Python 3.11+. `pip install micronetcode`, or `pip install -e .` from a checkout (add `[dev]` for
pytest + the examples' `raylib` / `microspec-py`). The only runtime dependency is `loggez`.
