Metadata-Version: 2.4
Name: notilens
Version: 0.4.0
Summary: NotiLens — unified SDK + CLI for AI agent notifications
Author: NotiLens
License-Expression: MIT
Project-URL: Homepage, https://www.notilens.com
Project-URL: Documentation, https://www.notilens.com/doc
Keywords: notilens,ai-agent,notifications,monitoring,observability,cli
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Provides-Extra: openai
Requires-Dist: openai>=1.0; extra == "openai"
Provides-Extra: anthropic
Requires-Dist: anthropic>=0.20; extra == "anthropic"
Provides-Extra: langchain
Requires-Dist: langchain-core>=0.1; extra == "langchain"
Provides-Extra: crewai
Requires-Dist: crewai>=0.28; extra == "crewai"
Provides-Extra: pydantic
Requires-Dist: pydantic-ai>=0.0.9; extra == "pydantic"
Provides-Extra: http
Requires-Dist: httpx>=0.24; extra == "http"
Requires-Dist: requests>=2.28; extra == "http"
Provides-Extra: all
Requires-Dist: openai>=1.0; extra == "all"
Requires-Dist: anthropic>=0.20; extra == "all"
Requires-Dist: langchain-core>=0.1; extra == "all"
Requires-Dist: crewai>=0.28; extra == "all"
Requires-Dist: pydantic-ai>=0.0.9; extra == "all"
Requires-Dist: httpx>=0.24; extra == "all"
Requires-Dist: requests>=2.28; extra == "all"

# NotiLens

Send notifications from AI agents and any Python project to [NotiLens](https://www.notilens.com).

Two ways to use it — pick one or both:

- **CLI** — for shell scripts, Claude Code hooks, bash pipelines
- **SDK** — for Python projects, with optional AI framework auto-patching

---

## Installation

```bash
pip install notilens
```

With AI framework auto-patching:
```bash
pip install notilens[openai]       # OpenAI
pip install notilens[anthropic]    # Anthropic
pip install notilens[langchain]    # LangChain
pip install notilens[all]          # all frameworks
```

---

---

# CLI

Use the CLI in shell scripts, Claude Code hooks, or any terminal workflow.

## 1. Setup (required, one time)

Get your token and secret from the [NotiLens dashboard](https://www.notilens.com).

```bash
notilens init --agent my-agent --token YOUR_TOKEN --secret YOUR_SECRET
```

This saves credentials to `~/.notilens_config.json`. All future commands for this agent read from there — no need to pass token/secret again.

**Multiple agents** (each agent notifies a different topic):
```bash
notilens init --agent scraper --token TOKEN_A --secret SECRET_A
notilens init --agent mailer  --token TOKEN_B --secret SECRET_B
```

---

## 2. Commands

`--task` is a semantic label (e.g. `email`, `report`). Each `task.start` creates an isolated run internally — concurrent executions of the same label never conflict.

### Task Lifecycle

```bash
notilens task.queue    --agent my-agent --task email
notilens task.start    --agent my-agent --task email
notilens task.progress "Fetching data"  --agent my-agent --task email
notilens task.loop     "Step 3 of 10"   --agent my-agent --task email
notilens task.retry    --agent my-agent --task email
notilens task.pause    "Rate limited"   --agent my-agent --task email
notilens task.resume   "Resuming"       --agent my-agent --task email
notilens task.wait     "Awaiting tool"  --agent my-agent --task email
notilens task.stop     --agent my-agent --task email
notilens task.complete "All done"       --agent my-agent --task email
notilens task.error    "Step 3 failed"  --agent my-agent --task email
notilens task.fail     "Unrecoverable"  --agent my-agent --task email
notilens task.timeout  "Took too long"  --agent my-agent --task email
notilens task.cancel   "User cancelled" --agent my-agent --task email
notilens task.terminate "Out of memory" --agent my-agent --task email
```

`task.start` prints the internal `run_id` to stdout. You can capture it if needed — but for sequential scripts, just use `--task LABEL` and the SDK handles the rest automatically.

### Input / Human-in-the-loop

```bash
notilens input.required "Please confirm the output" --agent my-agent --task email
notilens input.approve  "Confirmed"                 --agent my-agent --task email
notilens input.reject   "Rejected by user"          --agent my-agent --task email
```

### Output Events

```bash
notilens output.generate "Report ready"     --agent my-agent --task email
notilens output.fail     "Model unavailable" --agent my-agent --task email
```

### Metrics

Pass any key=value pairs — numeric values accumulate across calls:

```bash
notilens metric tokens=512 cost=0.003 --agent my-agent --task email
notilens metric records=1500          --agent my-agent --task email

# Reset one metric
notilens metric.reset tokens --agent my-agent --task email

# Reset all metrics
notilens metric.reset --agent my-agent --task email
```

### Custom Events

Works for any project — AI or not:

```bash
notilens track user.registered "New signup"      --agent my-agent
notilens track disk.space.full "Only 2GB left"   --agent my-agent
notilens track order.placed    "Order #1234"      --agent my-agent
```

---

## 3. Full CLI Example

```bash
# Register once
notilens init --agent summarizer --token my_token --secret my_secret

# Run a job
notilens task.start --agent summarizer --task report

notilens metric tokens=1024 --agent summarizer --task report
notilens metric cost=0.004  --agent summarizer --task report

notilens task.complete "Summary ready" \
  --agent summarizer \
  --task report \
  --open_url https://example.com/summary.pdf \
  --meta pages=12
```

---

## 4. Claude Code Hooks Example

Register the agent once:
```bash
notilens init --agent claude-code --token YOUR_TOKEN --secret YOUR_SECRET
```

Then in `~/.claude/settings.json`:
```json
{
  "hooks": {
    "PreToolUse": [{
      "matcher": "",
      "hooks": [{
        "type": "command",
        "command": "notilens task.progress \"Using tool: $CLAUDE_TOOL_NAME\" --agent claude-code --task $CLAUDE_SESSION_ID"
      }]
    }],
    "Stop": [{
      "matcher": "",
      "hooks": [{
        "type": "command",
        "command": "notilens task.complete \"Session ended\" --agent claude-code --task $CLAUDE_SESSION_ID"
      }]
    }]
  }
}
```

---

## CLI Options

| Flag | Required | Description |
|------|----------|-------------|
| `--agent NAME` | Yes | Agent name |
| `--task LABEL` | Yes | Task label (semantic name, e.g. `email`, `report`) |
| `--level` | No | Override level: `debug` `info` `warning` `error` |
| `--meta key=value` | No | Custom metadata (repeatable) |
| `--image_url URL` | No | Attach an image |
| `--open_url URL` | No | Link to open |
| `--download_url URL` | No | Link to download |
| `--tags "tag1,tag2"` | No | Comma-separated tags |
| `--is_actionable true\|false` | No | Override actionable flag |

---

---

# SDK

Use the SDK in Python projects. Supports manual task lifecycle calls and optional auto-patching of AI frameworks.

## 1. Setup (required)

```python
import notilens

# token/secret can also come from NOTILENS_TOKEN / NOTILENS_SECRET env vars
agent = notilens.init(
    agent="my-agent",    # required — agent name
    token="YOUR_TOKEN",  # required — or set NOTILENS_TOKEN env var
    secret="YOUR_SECRET" # required — or set NOTILENS_SECRET env var
)
```

**Via environment variables:**
```bash
export NOTILENS_TOKEN=your_token
export NOTILENS_SECRET=your_secret
```
```python
agent = notilens.init(agent="my-agent")  # reads token+secret from env
```

**All init options:**
```python
agent = notilens.init(
    agent="my-agent",      # required
    token="...",           # required (or env var)
    secret="...",          # required (or env var)
    patch=False,           # optional — auto-patch AI frameworks (default: False)
    state_ttl=86400,       # optional — orphaned state TTL in seconds (default: 86400 / 24h)
    min_level="info",      # optional — minimum event level to send (default: "info")
    loop_threshold=10,     # optional — AI calls before loop alert (default: 10)
    loop_window=60.0,      # optional — loop detection window in seconds (default: 60)
    call_timeout=30.0,     # optional — alert if AI call exceeds N seconds (default: 30)
    silent=False,          # optional — suppress SDK log output (default: False)
    debug=False,           # optional — verbose logging (default: False)
)
```

---

## 2. Task Lifecycle

`agent.task(label)` creates a `Run` — an isolated execution context with its own state. Multiple concurrent runs of the same label never conflict.

```python
run = agent.task("email")     # create a run for the "email" task
run.queue()                    # optional — pre-start signal
run.start()                    # begin the run

run.progress("Fetching data")  # mid-run update
run.loop("Processing item 42") # loop iteration marker
run.retry()                    # retry signal

# Pause / resume / wait (non-terminal)
run.pause("Rate limited")
run.resume("Resuming work")
run.wait("Waiting for tool response")

run.stop()                     # non-terminal stop

# Non-terminal error (run continues)
run.error("Step 3 failed, retrying")

# Terminal events — pick one to end the run
run.complete("All done")
run.fail("Unrecoverable error")
run.timeout("Exceeded time limit")
run.cancel("User cancelled")
run.terminate("OOM")
```

---

## 3. Input / Human-in-the-loop

```python
run.input_required("Confirm before proceeding")
run.input_approved("User confirmed")
run.input_rejected("User rejected")
```

---

## 4. Output Events

```python
run.output_generated("Summary ready")
run.output_failed("Model unavailable")
```

---

## 5. Metrics

Track any numeric or string values per run — accumulated automatically and included in every notification.

```python
run.metric("tokens", 350)    # set
run.metric("tokens", 210)    # now 560 (numeric values accumulate)
run.metric("cost", 0.0012)
run.metric("records", 1500)
run.metric("model", "gpt-4") # strings are replaced, not accumulated

run.reset_metrics("tokens")  # reset one metric
run.reset_metrics()           # reset all metrics
```

---

## Automatic Timing

NotiLens automatically tracks task timing. These fields are included in every notification's `meta` payload when non-zero:

| Field | Description |
|-------|-------------|
| `total_duration_ms` | Wall-clock time since `start` |
| `queue_ms` | Time between `queue` and `start` |
| `pause_ms` | Cumulative time spent paused |
| `wait_ms` | Cumulative time spent waiting |
| `active_ms` | Active time (`total − pause − wait`) |

---

## 6. Custom Events

Works for any project — AI or not:

```python
run.track("user.registered", "New signup", meta={"plan": "pro"})  # meta optional
run.track("disk.space.full", "Only 2GB left", level="warning")    # level optional
run.track("order.placed", "Order #1234", meta={"amount": 99.99})
```

---

## 7. Auto-patching AI Frameworks

Add `patch=True` to `init()` — no other changes needed. NotiLens will automatically track every AI call.

```python
import notilens
import openai  # or anthropic, langchain, crewai, pydantic-ai

agent = notilens.init(
    agent="my-agent",
    token="YOUR_TOKEN",
    secret="YOUR_SECRET",
    patch=True,           # required to enable auto-patching
    call_timeout=30.0,    # optional — alert if any AI call takes longer than 30s
    loop_threshold=10,    # optional — alert if 10+ AI calls happen within loop_window
)

# From here, use OpenAI / Anthropic etc. normally.
# NotiLens fires ai.call.start, ai.call.complete, task.error, task.timeout, task.loop automatically.
response = openai.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "Summarise this..."}],
)
```

**Multiple agents — only one can own patching:**
```python
scraper = notilens.init(agent="scraper", token="TOKEN_A", secret="SECRET_A", patch=True)
mailer  = notilens.init(agent="mailer",  token="TOKEN_B", secret="SECRET_B")
# patch=True on a second agent raises RuntimeError
```

---

## 8. Full SDK Example

```python
import notilens

agent = notilens.init("summarizer", token="my_token", secret="my_secret")
run   = agent.task("report")
run.start()

try:
    run.progress("Fetching PDF")

    result = llm.complete(prompt)
    run.metric("tokens", result.usage.total_tokens)
    run.metric("cost", result.usage.cost)

    run.output_generated("Summary ready")
    run.complete("All done")

except Exception as e:
    run.fail(str(e))
```

---

---

# Events Reference

| Event | Default Type | Description |
|-------|-------------|-------------|
| `task.queued` | info | Task queued |
| `task.started` | info | Task began |
| `task.progress` | info | Mid-run update |
| `task.loop` | warning | Loop iteration |
| `task.retry` | warning | Retry attempt |
| `task.completed` | success | Task finished successfully |
| `task.stopped` | info | Manually stopped |
| `task.failed` | urgent | Task failed |
| `task.error` | urgent | Non-fatal error |
| `task.timeout` | urgent | Exceeded time limit |
| `task.cancelled` | warning | Task cancelled |
| `task.terminated` | urgent | Force-terminated |
| `task.paused` | warning | Task paused |
| `task.resumed` | info | Task resumed |
| `task.waiting` | warning | Waiting for external response |
| `output.generated` | success | Output produced (AI response, report, file, etc.) |
| `output.failed` | urgent | Output generation failed |
| `input.required` | warning | Waiting for human input |
| `input.approved` | success | Input approved |
| `input.rejected` | warning | Input rejected |

---

## Requirements

- Python >= 3.9

## License

MIT — [notilens.com](https://www.notilens.com)
