paperclip-agent 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- paperclip_agent-0.1.1/.env.example +85 -0
- paperclip_agent-0.1.1/.gitignore +76 -0
- paperclip_agent-0.1.1/PKG-INFO +289 -0
- paperclip_agent-0.1.1/README.md +267 -0
- paperclip_agent-0.1.1/paperclip/__init__.py +3 -0
- paperclip_agent-0.1.1/paperclip/__main__.py +40 -0
- paperclip_agent-0.1.1/paperclip/app.py +75 -0
- paperclip_agent-0.1.1/paperclip/config.py +115 -0
- paperclip_agent-0.1.1/paperclip/db.py +83 -0
- paperclip_agent-0.1.1/paperclip/gateway/__init__.py +0 -0
- paperclip_agent-0.1.1/paperclip/gateway/cli.py +50 -0
- paperclip_agent-0.1.1/paperclip/gateway/telegram.py +91 -0
- paperclip_agent-0.1.1/paperclip/gateway/voice.py +287 -0
- paperclip_agent-0.1.1/paperclip/loop/__init__.py +0 -0
- paperclip_agent-0.1.1/paperclip/loop/agent.py +105 -0
- paperclip_agent-0.1.1/paperclip/loop/errors.py +99 -0
- paperclip_agent-0.1.1/paperclip/loop/models.py +232 -0
- paperclip_agent-0.1.1/paperclip/memory/__init__.py +134 -0
- paperclip_agent-0.1.1/paperclip/memory/consolidation.py +65 -0
- paperclip_agent-0.1.1/paperclip/memory/episodic/__init__.py +0 -0
- paperclip_agent-0.1.1/paperclip/memory/episodic/store.py +50 -0
- paperclip_agent-0.1.1/paperclip/memory/procedural/__init__.py +0 -0
- paperclip_agent-0.1.1/paperclip/memory/procedural/installer.py +51 -0
- paperclip_agent-0.1.1/paperclip/memory/procedural/loader.py +73 -0
- paperclip_agent-0.1.1/paperclip/memory/retrieval_gate.py +40 -0
- paperclip_agent-0.1.1/paperclip/memory/semantic/__init__.py +0 -0
- paperclip_agent-0.1.1/paperclip/memory/semantic/store.py +68 -0
- paperclip_agent-0.1.1/paperclip/memory/semantic/supabase_store.py +42 -0
- paperclip_agent-0.1.1/paperclip/ops/__init__.py +0 -0
- paperclip_agent-0.1.1/paperclip/ops/api.py +865 -0
- paperclip_agent-0.1.1/paperclip/ops/brief.py +27 -0
- paperclip_agent-0.1.1/paperclip/ops/release_gate.py +76 -0
- paperclip_agent-0.1.1/paperclip/ops/tracing.py +103 -0
- paperclip_agent-0.1.1/paperclip/runtime/__init__.py +0 -0
- paperclip_agent-0.1.1/paperclip/runtime/session.py +91 -0
- paperclip_agent-0.1.1/paperclip/tools/__init__.py +51 -0
- paperclip_agent-0.1.1/paperclip/tools/apple.py +121 -0
- paperclip_agent-0.1.1/paperclip/tools/calendar.py +198 -0
- paperclip_agent-0.1.1/paperclip/tools/experimental.py +32 -0
- paperclip_agent-0.1.1/paperclip/tools/github.py +256 -0
- paperclip_agent-0.1.1/paperclip/tools/mcp_client.py +85 -0
- paperclip_agent-0.1.1/paperclip/tools/memory_admin.py +129 -0
- paperclip_agent-0.1.1/paperclip/tools/messages.py +34 -0
- paperclip_agent-0.1.1/paperclip/tools/notes.py +35 -0
- paperclip_agent-0.1.1/paperclip/tools/registry.py +41 -0
- paperclip_agent-0.1.1/paperclip/tools/search.py +87 -0
- paperclip_agent-0.1.1/pyproject.toml +42 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# First launch writes this file to ~/.paperclip/.env (your user home, every OS).
|
|
2
|
+
# A .env in the current directory fills keys that file left blank.
|
|
3
|
+
# Only ANTHROPIC_API_KEY is required for the default provider.
|
|
4
|
+
# Leave a line commented to keep the code default.
|
|
5
|
+
|
|
6
|
+
# --- Model provider ----------------------------------------------------------
|
|
7
|
+
# anthropic | openai | openrouter | gemini
|
|
8
|
+
# PAPERCLIP_PROVIDER=anthropic
|
|
9
|
+
|
|
10
|
+
# Used when PAPERCLIP_API_KEY is empty. Set the one that matches the provider.
|
|
11
|
+
ANTHROPIC_API_KEY=
|
|
12
|
+
# OPENAI_API_KEY=
|
|
13
|
+
# OPENROUTER_API_KEY=
|
|
14
|
+
# GEMINI_API_KEY=
|
|
15
|
+
|
|
16
|
+
# Optional override of the provider key above.
|
|
17
|
+
# PAPERCLIP_API_KEY=
|
|
18
|
+
|
|
19
|
+
# Empty model fields fall back to the provider default:
|
|
20
|
+
# anthropic claude-sonnet-5 / claude-haiku-4-5-20251001
|
|
21
|
+
# openai gpt-5.5 / gpt-4.1-mini
|
|
22
|
+
# openrouter nvidia/nemotron-nano-9b-v2:free / google/gemma-3-4b-it:free
|
|
23
|
+
# gemini gemini-3.8-flash / gemini-3.5-flash-lite
|
|
24
|
+
# PAPERCLIP_MODEL=
|
|
25
|
+
# PAPERCLIP_SMALL_MODEL=
|
|
26
|
+
# PAPERCLIP_BASE_URL=
|
|
27
|
+
# PAPERCLIP_LLM_TIMEOUT=120
|
|
28
|
+
# PAPERCLIP_MAX_TOKENS=2048
|
|
29
|
+
# PAPERCLIP_MAX_ITERATIONS=10
|
|
30
|
+
|
|
31
|
+
# Judge evals and the release gate look at ANTHROPIC_API_KEY only.
|
|
32
|
+
|
|
33
|
+
# --- Home and memory ---------------------------------------------------------
|
|
34
|
+
# PAPERCLIP_HOME=~/.paperclip
|
|
35
|
+
# PAPERCLIP_SEMANTIC_STORE=sqlite
|
|
36
|
+
# PAPERCLIP_RETRIEVAL_TOP_K=4
|
|
37
|
+
# PAPERCLIP_CONSOLIDATE_EVERY=6
|
|
38
|
+
|
|
39
|
+
# Only when PAPERCLIP_SEMANTIC_STORE=supabase. Embeddings use OPENAI_API_KEY.
|
|
40
|
+
# SUPABASE_URL=
|
|
41
|
+
# SUPABASE_SERVICE_KEY=
|
|
42
|
+
# OPENAI_EMBED_MODEL=text-embedding-3-small
|
|
43
|
+
|
|
44
|
+
# --- Search ------------------------------------------------------------------
|
|
45
|
+
# Tavily when set; otherwise DuckDuckGo. PAPERCLIP_SEARCH_API_KEY is an alias.
|
|
46
|
+
# TAVILY_API_KEY=
|
|
47
|
+
# PAPERCLIP_SEARCH_API_KEY=
|
|
48
|
+
|
|
49
|
+
# --- Telegram ----------------------------------------------------------------
|
|
50
|
+
# TELEGRAM_BOT_TOKEN=
|
|
51
|
+
# Numeric user id. Unset means anyone who finds the bot can use it.
|
|
52
|
+
# TELEGRAM_ALLOWED_USER=
|
|
53
|
+
|
|
54
|
+
# --- macOS -------------------------------------------------------------------
|
|
55
|
+
# 1, true, or yes. Calendar.app sync is separate from the AppleScript tools.
|
|
56
|
+
# PAPERCLIP_APPLE_CALENDAR=
|
|
57
|
+
# PAPERCLIP_APPLE_TOOLS=
|
|
58
|
+
# PAPERCLIP_APPLE_CALENDARS=Work,Home
|
|
59
|
+
|
|
60
|
+
# --- Voice -------------------------------------------------------------------
|
|
61
|
+
# PAPERCLIP_WHISPER_MODEL=base
|
|
62
|
+
# cpu avoids a missing cublas64_12.dll on machines without the CUDA 12 runtime.
|
|
63
|
+
# PAPERCLIP_WHISPER_DEVICE=cpu
|
|
64
|
+
# PAPERCLIP_WHISPER_LANG=
|
|
65
|
+
# Default wake phrases: paperclip paperclip,paperclippaperclip,paperclip,paper clip,paper-clip
|
|
66
|
+
# Set empty for push-to-talk.
|
|
67
|
+
# PAPERCLIP_WAKE_WORD=
|
|
68
|
+
# PAPERCLIP_WAKE_LANG=
|
|
69
|
+
# PAPERCLIP_WAKE_ACK=Yes?
|
|
70
|
+
# PAPERCLIP_FOLLOWUP_SECONDS=8
|
|
71
|
+
# PAPERCLIP_MIC_THRESHOLD=0.005
|
|
72
|
+
# PAPERCLIP_TTS=
|
|
73
|
+
# PAPERCLIP_VOICE=
|
|
74
|
+
|
|
75
|
+
# --- Dashboard and traces ----------------------------------------------------
|
|
76
|
+
# PAPERCLIP_DASHBOARD_PORT=7777
|
|
77
|
+
# PORT=7777
|
|
78
|
+
# PAPERCLIP_DASHBOARD_ORIGINS=http://localhost:3000,http://127.0.0.1:3000
|
|
79
|
+
# PAPERCLIP_EDITOR=
|
|
80
|
+
# OTEL_EXPORTER_OTLP_ENDPOINT=
|
|
81
|
+
|
|
82
|
+
# --- Tools -------------------------------------------------------------------
|
|
83
|
+
# PAPERCLIP_EXPERIMENTAL=
|
|
84
|
+
# Default owner/name when github_read is called without a repo. Auth stays in gh.
|
|
85
|
+
# PAPERCLIP_GITHUB_REPO=
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# Virtual environments
|
|
7
|
+
.venv/
|
|
8
|
+
venv/
|
|
9
|
+
env/
|
|
10
|
+
ENV/
|
|
11
|
+
|
|
12
|
+
# uv
|
|
13
|
+
.python-version
|
|
14
|
+
|
|
15
|
+
# Build artifacts
|
|
16
|
+
build/
|
|
17
|
+
dist/
|
|
18
|
+
*.egg-info/
|
|
19
|
+
|
|
20
|
+
# Testing
|
|
21
|
+
.pytest_cache/
|
|
22
|
+
.coverage
|
|
23
|
+
htmlcov/
|
|
24
|
+
|
|
25
|
+
# Type checking
|
|
26
|
+
.mypy_cache/
|
|
27
|
+
.pyre/
|
|
28
|
+
.pytype/
|
|
29
|
+
|
|
30
|
+
# Linting
|
|
31
|
+
.ruff_cache/
|
|
32
|
+
|
|
33
|
+
# Jupyter
|
|
34
|
+
.ipynb_checkpoints/
|
|
35
|
+
|
|
36
|
+
# IDEs
|
|
37
|
+
.vscode/
|
|
38
|
+
.idea/
|
|
39
|
+
|
|
40
|
+
# OS files
|
|
41
|
+
.DS_Store
|
|
42
|
+
Thumbs.db
|
|
43
|
+
|
|
44
|
+
# Logs
|
|
45
|
+
*.log
|
|
46
|
+
|
|
47
|
+
# Environment variables
|
|
48
|
+
.env
|
|
49
|
+
|
|
50
|
+
# Generated runtime data (FAISS index + sidecar, SQLite db, etc.)
|
|
51
|
+
data/
|
|
52
|
+
*.db
|
|
53
|
+
*.sqlite
|
|
54
|
+
*.sqlite3
|
|
55
|
+
|
|
56
|
+
# Website (Next.js)
|
|
57
|
+
web/node_modules/
|
|
58
|
+
web/.next/
|
|
59
|
+
web/.env.local
|
|
60
|
+
|
|
61
|
+
node_modules/
|
|
62
|
+
.next/
|
|
63
|
+
|
|
64
|
+
# Coverage
|
|
65
|
+
coverage.xml
|
|
66
|
+
|
|
67
|
+
# Hatch
|
|
68
|
+
.hatch/
|
|
69
|
+
|
|
70
|
+
# PyInstaller
|
|
71
|
+
*.manifest
|
|
72
|
+
*.spec
|
|
73
|
+
|
|
74
|
+
# .paperclip
|
|
75
|
+
.paperclip
|
|
76
|
+
.paperclip-*
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: paperclip-agent
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Local personal assistant
|
|
5
|
+
Requires-Python: >=3.13
|
|
6
|
+
Requires-Dist: anthropic>=1.7.0
|
|
7
|
+
Requires-Dist: deepeval>=4.3.1
|
|
8
|
+
Requires-Dist: faster-whisper>=1.2.1
|
|
9
|
+
Requires-Dist: google-api-python-client>=2.200.0
|
|
10
|
+
Requires-Dist: google-auth-oauthlib>=1.4.1
|
|
11
|
+
Requires-Dist: google-auth>=2.58.0
|
|
12
|
+
Requires-Dist: kokoro>=0.9.4
|
|
13
|
+
Requires-Dist: mcp[cli]>=2.2.0
|
|
14
|
+
Requires-Dist: notion-client>=3.1.0
|
|
15
|
+
Requires-Dist: numpy>=2.5.3
|
|
16
|
+
Requires-Dist: openai>=3.16.2
|
|
17
|
+
Requires-Dist: pytest>=9.1.1
|
|
18
|
+
Requires-Dist: python-dotenv>=1.2.3
|
|
19
|
+
Requires-Dist: python-telegram-bot>=22.8
|
|
20
|
+
Requires-Dist: sounddevice>=0.5.6
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# Paperclip
|
|
24
|
+
|
|
25
|
+
Paperclip is a local personal assistant. The terminal, a microphone, and Telegram all reach the same agent. What it remembers stays in a folder on that machine (`PAPERCLIP_HOME`, default `~/.paperclip`). Nothing is copied to a hosted account.
|
|
26
|
+
|
|
27
|
+
One turn is: decide whether to search memory, inject matching skills, call the model with tools until it replies, then log the exchange and sometimes file what it learned.
|
|
28
|
+
|
|
29
|
+
## Requirements
|
|
30
|
+
|
|
31
|
+
- Python 3.13 or newer
|
|
32
|
+
- An API key for one model provider. The default is Anthropic (`ANTHROPIC_API_KEY`).
|
|
33
|
+
|
|
34
|
+
Voice also needs a microphone. The packages for speech (`faster-whisper`, `sounddevice`, `numpy`, `kokoro`) are project dependencies.
|
|
35
|
+
|
|
36
|
+
## Install
|
|
37
|
+
|
|
38
|
+
[uv](https://docs.astral.sh/uv/) installs the published package. The PyPI name is `paperclip-agent`. The command is `paperclip`.
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
uv tool install paperclip-agent
|
|
42
|
+
paperclip
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The first launch creates `~/.paperclip/.env` (your user home on every OS, `%USERPROFILE%\.paperclip` on Windows). Put your key on `ANTHROPIC_API_KEY` in that file and run `paperclip` again. That is the only line the default setup needs. The same folder holds memory, wherever you start the command.
|
|
46
|
+
|
|
47
|
+
From a checkout of this repo, dependencies come from `pyproject.toml`:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
uv sync
|
|
51
|
+
uv run paperclip
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
That creates `.venv` and installs this package in editable mode. A `.env` in the repo fills keys the home file left blank. Set `PAPERCLIP_HOME` to keep memory somewhere else (this repo's old default was a `.paperclip` folder next to the code).
|
|
55
|
+
|
|
56
|
+
Run every Python command with `uv run`. A bare `python` on PATH is a different interpreter and does not have the packages from `pyproject.toml`. `python -m paperclip` is the same program. The help text says `paperclip`.
|
|
57
|
+
|
|
58
|
+
The banner shows where memory lives and which model is answering. Empty lines are ignored. Leave with `/quit`, `/exit`, or Ctrl-D. Closing the terminal does not delete memory.
|
|
59
|
+
|
|
60
|
+
## Commands
|
|
61
|
+
|
|
62
|
+
| Command | What it does |
|
|
63
|
+
| --- | --- |
|
|
64
|
+
| `uv run paperclip` | Terminal chat |
|
|
65
|
+
| `uv run paperclip voice` | Microphone in, speech out |
|
|
66
|
+
| `uv run paperclip telegram` | Telegram long-polling (laptop stays the host) |
|
|
67
|
+
| `uv run paperclip api` | Local JSON API for the cockpit (`127.0.0.1`, default port `7777`) |
|
|
68
|
+
| `uv run paperclip brief` | One morning-brief turn, saved under the outbox |
|
|
69
|
+
| `uv run paperclip skill install <url>` | Download a `SKILL.md` into `{PAPERCLIP_HOME}/skills/` |
|
|
70
|
+
|
|
71
|
+
On Unix, the `Makefile` wraps the same entry points (`make run`, `make voice`, `make telegram`, `make brief`, `make api`, `make eval`, `make eval-judge`, `make gate`). It uses `.venv/bin/python` when `uv sync` has created that interpreter.
|
|
72
|
+
|
|
73
|
+
## Model providers
|
|
74
|
+
|
|
75
|
+
Paperclip speaks one dialect (Anthropic Messages). Other providers go through an OpenAI-compatible adapter. Set `PAPERCLIP_PROVIDER` and the matching key. `PAPERCLIP_API_KEY` overrides the provider key when set. Empty `PAPERCLIP_MODEL` and `PAPERCLIP_SMALL_MODEL` fall back to the defaults below.
|
|
76
|
+
|
|
77
|
+
| `PAPERCLIP_PROVIDER` | Key | Default model | Small model (gate and consolidation) |
|
|
78
|
+
| --- | --- | --- | --- |
|
|
79
|
+
| `anthropic` (default) | `ANTHROPIC_API_KEY` | `claude-sonnet-5` | `claude-haiku-4-5-20251001` |
|
|
80
|
+
| `openai` | `OPENAI_API_KEY` | `gpt-5.5` | `gpt-4.1-mini` |
|
|
81
|
+
| `openrouter` | `OPENROUTER_API_KEY` | `nvidia/nemotron-nano-9b-v2:free` | `google/gemma-3-4b-it:free` |
|
|
82
|
+
| `gemini` | `GEMINI_API_KEY` | `gemini-3.8-flash` | `gemini-3.5-flash-lite` |
|
|
83
|
+
|
|
84
|
+
`PAPERCLIP_BASE_URL` overrides the provider URL. `PAPERCLIP_LLM_TIMEOUT` defaults to 120 seconds. `PAPERCLIP_MAX_TOKENS` defaults to 2048. `PAPERCLIP_MAX_ITERATIONS` defaults to 10 (tool rounds per turn).
|
|
85
|
+
|
|
86
|
+
Judge evals and the release gate look at `ANTHROPIC_API_KEY` only.
|
|
87
|
+
|
|
88
|
+
## How a turn works
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
user message
|
|
92
|
+
→ retrieval gate (small model: search memory this turn?)
|
|
93
|
+
→ matching skills (SKILL.md)
|
|
94
|
+
→ agent loop: reason → tool calls → observe, until a reply or the iteration cap
|
|
95
|
+
→ chat log + in-memory history
|
|
96
|
+
→ consolidation every N exchanges (facts + a dated episode)
|
|
97
|
+
→ rewrite MEMORY.md
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
A failed model call is not stored as the assistant’s answer. The reply comes back, and the prompt returns.
|
|
101
|
+
|
|
102
|
+
The persona is `{PAPERCLIP_HOME}/SOUL.md`. If the file is missing, a default is written. Editing it changes the next turn. The system prompt also includes the local clock so relative times (“tomorrow at 3”) can be resolved.
|
|
103
|
+
|
|
104
|
+
## Memory
|
|
105
|
+
|
|
106
|
+
Three stores, all local:
|
|
107
|
+
|
|
108
|
+
| Kind | Question | Where |
|
|
109
|
+
| --- | --- | --- |
|
|
110
|
+
| Semantic | What do I know? | `facts` in `state.db` (FTS5). Optional Supabase vectors. |
|
|
111
|
+
| Episodic | What happened, when? | `episodes` in `state.db` |
|
|
112
|
+
| Procedural | How should I act? | `SKILL.md` under repo `skills/` and `{home}/skills/` |
|
|
113
|
+
|
|
114
|
+
Default search is SQLite FTS5 (BM25). No embeddings unless `PAPERCLIP_SEMANTIC_STORE=supabase`, which also needs `SUPABASE_URL`, `SUPABASE_SERVICE_KEY`, and `OPENAI_API_KEY`.
|
|
115
|
+
|
|
116
|
+
A small model decides whether this turn needs a search (`PAPERCLIP_RETRIEVAL_TOP_K`, default 4). After `PAPERCLIP_CONSOLIDATE_EVERY` exchanges (default 6), another small-model pass files facts and an episode, then rewrites `{home}/MEMORY.md`. You can also ask it to remember something on purpose (`save_note` / `manage_memory`).
|
|
117
|
+
|
|
118
|
+
`MEMORY.md` is a readable export of SQL. The database is the source of truth: `{PAPERCLIP_HOME}/state.db`.
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
sqlite3 ~/.paperclip/state.db ".tables"
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Tools
|
|
125
|
+
|
|
126
|
+
You do not pick a tool by name. Ask in the conversation, and the model calls the one that fits.
|
|
127
|
+
|
|
128
|
+
| Tool | What it does |
|
|
129
|
+
| --- | --- |
|
|
130
|
+
| `create_event`, `list_events` | Local calendar rows in `state.db` |
|
|
131
|
+
| `save_note` | A fact you asked it to keep |
|
|
132
|
+
| `send_message` | A text file in `{home}/outbox`. It does not send email or chat. |
|
|
133
|
+
| `search_web` | Tavily when `TAVILY_API_KEY` (or `PAPERCLIP_SEARCH_API_KEY`) is set; otherwise DuckDuckGo |
|
|
134
|
+
| `github_read` | Read-only GitHub via the `gh` CLI (issues, pull requests, files). It will not merge, comment, close, or push. |
|
|
135
|
+
| `manage_memory`, `update_soul`, `create_skill` | Edit facts, `SOUL.md`, and skills |
|
|
136
|
+
|
|
137
|
+
Opt-in:
|
|
138
|
+
|
|
139
|
+
- `PAPERCLIP_GITHUB_REPO=owner/name` is the default repo when `github_read` is called without one. Public repos need no extra auth. Private repos need `gh auth login`.
|
|
140
|
+
- `PAPERCLIP_APPLE_CALENDAR=1` also tries to put `create_event` on Calendar.app (macOS).
|
|
141
|
+
- `PAPERCLIP_APPLE_TOOLS=1` registers AppleScript tools (Calendar and Mail). Useful for the morning brief.
|
|
142
|
+
- `PAPERCLIP_EXPERIMENTAL=1` registers stub tools.
|
|
143
|
+
- `{PAPERCLIP_HOME}/mcp.json` starts MCP servers and exposes their tools. If the `mcp` package is missing, Paperclip prints an install hint and continues.
|
|
144
|
+
|
|
145
|
+
Morning brief:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
uv run paperclip brief
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
The file is `{PAPERCLIP_HOME}/outbox/brief-YYYY-MM-DD.txt`.
|
|
152
|
+
|
|
153
|
+
## Voice
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
uv run paperclip voice
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Speech is transcribed on the machine (faster-whisper). By default it waits for a wake phrase (`paperclip`, `paper clip`, `paperclip paperclip`, and a few variants), answers `Yes?` (`PAPERCLIP_WAKE_ACK`), then listens. After a reply it stays open for `PAPERCLIP_FOLLOWUP_SECONDS` (default 8) so you can continue without the phrase.
|
|
160
|
+
|
|
161
|
+
Set `PAPERCLIP_WAKE_WORD` empty for push-to-talk: Enter starts recording, Enter stops it. Clips shorter than a quarter of a second are ignored.
|
|
162
|
+
|
|
163
|
+
`PAPERCLIP_WHISPER_MODEL` defaults to `base`. `PAPERCLIP_WHISPER_DEVICE=cpu` avoids a missing CUDA runtime. `PAPERCLIP_TTS` and `PAPERCLIP_VOICE` pick how replies are spoken. On macOS, speech uses `say` unless `PAPERCLIP_TTS` is set.
|
|
164
|
+
|
|
165
|
+
The conversation is stored the same way as terminal chat (`session_id` `voice`).
|
|
166
|
+
|
|
167
|
+
## Telegram
|
|
168
|
+
|
|
169
|
+
The assistant still runs on your computer. The process calls Telegram, so you do not need a public address.
|
|
170
|
+
|
|
171
|
+
1. In Telegram, open @BotFather and send `/newbot`. Copy the token.
|
|
172
|
+
2. Set `TELEGRAM_BOT_TOKEN` in `.env`.
|
|
173
|
+
3. Set `TELEGRAM_ALLOWED_USER` to your numeric Telegram id (from @userinfobot). If this is empty, anyone who finds the bot can talk to your assistant.
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
uv run paperclip telegram
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Only plain messages are answered. Bot commands are ignored. If the allowlist is set, other people get a short refusal. The laptop prints the message and the reply; you read the reply in Telegram.
|
|
180
|
+
|
|
181
|
+
If the API is running and `TELEGRAM_BOT_TOKEN` is set, the poller also starts in the background.
|
|
182
|
+
|
|
183
|
+
## API
|
|
184
|
+
|
|
185
|
+
The API and the UI are separate.
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
uv run paperclip api
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
That binds a stdlib HTTP server to `127.0.0.1`. Port is `PAPERCLIP_DASHBOARD_PORT`, else `PORT`, else `7777`. If the port is busy, the next ports up to +9 are tried. Startup prints `Paperclip API -> http://localhost:<port>`.
|
|
192
|
+
|
|
193
|
+
The cockpit (turns, memory, tools, database, evals, spend, settings, chat) is the Next.js app in `dashboard/`:
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
cd dashboard
|
|
197
|
+
bun run dev
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
It calls `NEXT_PUBLIC_PAPERCLIP_API` (default `http://127.0.0.1:7777`). The browser origin must be in `PAPERCLIP_DASHBOARD_ORIGINS` (default includes `localhost:3000` and `:3001`). `/api/settings` can write API keys, and `/api/reveal` opens files on the host, so CORS is an allowlist.
|
|
201
|
+
|
|
202
|
+
Chat streaming is `POST /api/chat/stream` (SSE). Call it directly; a Next.js rewrite can buffer the stream.
|
|
203
|
+
|
|
204
|
+
Traces always append to `{home}/traces/YYYY-MM-DD.jsonl`. Token use is appended to `{home}/usage.jsonl`. Set `OTEL_EXPORTER_OTLP_ENDPOINT` to also export OpenTelemetry spans (the `opentelemetry` packages are optional).
|
|
205
|
+
|
|
206
|
+
`web/` is the user-facing docs site, separate from the cockpit.
|
|
207
|
+
|
|
208
|
+
## Home directory
|
|
209
|
+
|
|
210
|
+
`ensure_home()` creates `home`, `home/traces`, and `home/outbox`. A running install typically looks like this:
|
|
211
|
+
|
|
212
|
+
```
|
|
213
|
+
~/.paperclip/
|
|
214
|
+
.env # API keys. Created on first launch.
|
|
215
|
+
state.db # facts, episodes, chat_log, calendar
|
|
216
|
+
MEMORY.md # export of memory
|
|
217
|
+
SOUL.md # persona
|
|
218
|
+
skills/ # skills installed on this machine
|
|
219
|
+
mcp.json # optional MCP servers
|
|
220
|
+
outbox/ # send_message drafts and briefs
|
|
221
|
+
traces/ # daily JSONL
|
|
222
|
+
usage.jsonl # token ledger
|
|
223
|
+
eval_report.json # latest release-gate verdict
|
|
224
|
+
eval_runs.jsonl # gate history
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
`PAPERCLIP_HOME` changes the path. A `.paperclip` folder inside this repo is gitignored.
|
|
228
|
+
|
|
229
|
+
## Skills
|
|
230
|
+
|
|
231
|
+
A skill is a `SKILL.md` with YAML frontmatter (`name`, `description`) and instructions. The loader keeps a skill when the user message shares at least two words (3+ letters) with the name and description. Copy `skills/TEMPLATE.md` into `skills/<name>/SKILL.md` (repo) or `{home}/skills/<name>/SKILL.md` (this machine).
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
uv run paperclip skill install <url>
|
|
235
|
+
uv run python scripts/validate_skills.py
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Tests
|
|
239
|
+
|
|
240
|
+
Deterministic tests use a scripted model and do not need an API key. Judge tests score real replies with DeepEval and run only when `ANTHROPIC_API_KEY` is set.
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
uv run python -m pytest -q evals/deterministic
|
|
244
|
+
uv run python -m pytest -q evals/judge
|
|
245
|
+
uv run python -m paperclip.ops.release_gate
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
The release gate requires deterministic tests to pass. If the Anthropic key is missing, the judge is skipped and the process still exits 0. A passing run prints `GATE OPEN` and writes `{home}/eval_report.json`.
|
|
249
|
+
|
|
250
|
+
`uv run python scripts/demo_seed.py` backs up `PAPERCLIP_HOME` and writes a small curated demo state.
|
|
251
|
+
|
|
252
|
+
## Layout
|
|
253
|
+
|
|
254
|
+
```
|
|
255
|
+
paperclip/ # the agent
|
|
256
|
+
app.py # one running agent; gateways call respond()
|
|
257
|
+
config.py # Settings from the environment
|
|
258
|
+
db.py # state.db schema
|
|
259
|
+
__main__.py # CLI entry
|
|
260
|
+
gateway/ # cli, voice, telegram
|
|
261
|
+
loop/ # providers and the tool loop
|
|
262
|
+
memory/ # facts, episodes, skills, gate, consolidation
|
|
263
|
+
runtime/ # SOUL.md and in-memory history
|
|
264
|
+
tools/ # functions the model can call
|
|
265
|
+
ops/ # JSON API, traces, brief, release gate
|
|
266
|
+
evals/ # deterministic tests and LLM judge
|
|
267
|
+
dashboard/ # Next.js cockpit
|
|
268
|
+
web/ # user docs site
|
|
269
|
+
docs/ # engineering reference (canonical for behavior)
|
|
270
|
+
scripts/ # demo seed and skill check
|
|
271
|
+
skills/ # repo skills
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
## Environment
|
|
275
|
+
|
|
276
|
+
`.env.example` lists every variable. The ones you usually touch:
|
|
277
|
+
|
|
278
|
+
| Variable | Default | Role |
|
|
279
|
+
| --- | --- | --- |
|
|
280
|
+
| `ANTHROPIC_API_KEY` | empty | Required for the default provider |
|
|
281
|
+
| `PAPERCLIP_PROVIDER` | `anthropic` | `anthropic`, `openai`, `openrouter`, `gemini` |
|
|
282
|
+
| `PAPERCLIP_HOME` | `~/.paperclip` | Memory, traces, outbox, skills |
|
|
283
|
+
| `PAPERCLIP_MODEL` | provider default | Main loop model |
|
|
284
|
+
| `PAPERCLIP_SMALL_MODEL` | provider default | Retrieval gate and consolidation |
|
|
285
|
+
| `TAVILY_API_KEY` | empty | Web search. Unset uses DuckDuckGo |
|
|
286
|
+
| `TELEGRAM_BOT_TOKEN` | empty | Telegram gateway |
|
|
287
|
+
| `TELEGRAM_ALLOWED_USER` | empty | Numeric user id. Unset means anyone |
|
|
288
|
+
| `PAPERCLIP_GITHUB_REPO` | empty | Default `owner/name` for `github_read` |
|
|
289
|
+
| `PAPERCLIP_DASHBOARD_PORT` | `7777` | Dashboard API port |
|