Metadata-Version: 2.4
Name: pictie
Version: 0.1.4
Summary: Python client for the Pictie local state control plane
Author: Pictie
License: MIT
Keywords: agents,menu-bar,observability,pictie
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Requires-Python: >=3.9
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == 'dev'
Description-Content-Type: text/markdown

# pictie — Python SDK

Report what your scripts and AI agents are doing to the [Pictie](https://github.com/Togetheart/Pictie) macOS
menu bar, in one line.

```python
import pictie

pictie.configure(source="claude")

with pictie.working(label="Writing draft"):
    do_work()          # menu bar shows "working"
                       # -> "done" on success, "error" (with the message) on exception
```

## Install

```bash
pip install pictie
# or, from this repo:
pip install -e sdk/python
```

Python ≥ 3.9. Zero runtime dependencies.

## The contract

Pictie is an **observability client** — it never breaks your program.

- **Never raises.** If the Pictie app isn't running, emits are silently dropped.
- **Non-blocking.** Sends happen on a background thread; your code never waits.
- **Short timeout.** Each send gives up after 500 ms.
- **Debug opt-in.** `PICTIE_DEBUG=1` (or `pictie.set_debug(True)`) logs failures to
  stderr.
- **Explicit check.** `pictie.is_available()` returns `True`/`False` if you want to
  know. Unlike the non-blocking emits, this is the one synchronous call — it
  blocks up to ~500 ms while it does a live health check.

## API

```python
pictie.configure(source="claude", host="127.0.0.1", port=4567, debug=False)
#   env overrides: PICTIE_SOURCE, PICTIE_HOST, PICTIE_PORT, PICTIE_DEBUG

pictie.idle(label=None, **fields)
pictie.working(label=None, **fields)
pictie.waiting(label=None, **fields)     # e.g. waiting(action="approve", urgency="now")
pictie.done(label=None, **fields)
pictie.error(label=None, **fields)

pictie.emit("attention.approval_needed", action="approve", label="Needs OK")  # semantic

pictie.is_available()   # -> bool
pictie.set_debug(True)
```

Optional `**fields` map to the protocol: `phase`, `urgency`, `action`, `subject`,
`confidence`, `delta`, `sensitive`, plus a per-call `source`.

Each helper both emits immediately and returns a context manager, so
`with pictie.working(...):` reports `done`/`error` automatically on block exit
(override with `on_success=` / `on_error=`).

For multiple sources in one process, use an explicit client:

```python
p = pictie.Client(source="cursor")
p.waiting(action="approve")
```

## Command line

`pip install pictie` also installs a `pictie` command.

```bash
pictie run npm test      # shows "npm: working" -> "done"/"error" in the menu bar
pictie run pytest -v     # source auto-detected from the command (here: "pytest")
```

`pictie run` wraps any command: it reports `working` while the command runs and
`done`/`error` when it exits, passes the command's output through untouched, and
**exits with the command's own exit code** — so it's safe to drop in front of
anything (`pictie run ./deploy.sh`). The source name is the command's basename
(override with `--source`), and it is exported as `PICTIE_SOURCE` to the child so
any in-process `pictie` SDK calls share the same source.

One-shot commands talk to the app directly (synchronous; they report and set an
exit code):

```bash
pictie emit --source ci --state done      # 0 ok / 1 app down / 2 rejected
pictie list                               # table of current sources (--json for scripts)
pictie reset [--source ci]                # clear one or all
pictie health                             # "ok"/"down"; exit 0 up / 1 down
pictie health && ./deploy.sh              # gate a script on Pictie being up
```

The pip-installed `pictie` is the canonical CLI. (The repo also has a Swift
`pictie` used only for development via `swift run pictie`; the macOS app itself
installs no CLI binary — it speaks HTTP.)

## Known limitations (v1)

- No native `async`/`await` API (the non-blocking sender makes asyncio callers safe
  anyway).
- The background sender does not survive `os.fork()`.
- One background sender per process. Concurrent `emit()` from many threads is
  crash-safe, but a saturated queue may drop more than intended — the tested
  path is a single producer per sender.
- Under sustained backpressure a full send queue drops the OLDEST pending
  update (bounded memory) — the freshest state always wins.
