puffo-agent 0.7.2__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.
- puffo_agent-0.7.2/LICENSE +21 -0
- puffo_agent-0.7.2/PKG-INFO +346 -0
- puffo_agent-0.7.2/README.md +285 -0
- puffo_agent-0.7.2/pyproject.toml +73 -0
- puffo_agent-0.7.2/setup.cfg +4 -0
- puffo_agent-0.7.2/src/puffo_agent/__init__.py +0 -0
- puffo_agent-0.7.2/src/puffo_agent/agent/__init__.py +0 -0
- puffo_agent-0.7.2/src/puffo_agent/agent/_logging.py +18 -0
- puffo_agent-0.7.2/src/puffo_agent/agent/_time.py +18 -0
- puffo_agent-0.7.2/src/puffo_agent/agent/adapters/__init__.py +12 -0
- puffo_agent-0.7.2/src/puffo_agent/agent/adapters/base.py +238 -0
- puffo_agent-0.7.2/src/puffo_agent/agent/adapters/chat_only.py +30 -0
- puffo_agent-0.7.2/src/puffo_agent/agent/adapters/cli_session.py +636 -0
- puffo_agent-0.7.2/src/puffo_agent/agent/adapters/docker_cli.py +1220 -0
- puffo_agent-0.7.2/src/puffo_agent/agent/adapters/local_cli.py +526 -0
- puffo_agent-0.7.2/src/puffo_agent/agent/adapters/sdk.py +195 -0
- puffo_agent-0.7.2/src/puffo_agent/agent/core.py +247 -0
- puffo_agent-0.7.2/src/puffo_agent/agent/events.py +52 -0
- puffo_agent-0.7.2/src/puffo_agent/agent/file_browser.py +106 -0
- puffo_agent-0.7.2/src/puffo_agent/agent/harness/__init__.py +40 -0
- puffo_agent-0.7.2/src/puffo_agent/agent/harness/base.py +53 -0
- puffo_agent-0.7.2/src/puffo_agent/agent/harness/claude_code.py +22 -0
- puffo_agent-0.7.2/src/puffo_agent/agent/harness/gemini_cli.py +18 -0
- puffo_agent-0.7.2/src/puffo_agent/agent/harness/hermes.py +32 -0
- puffo_agent-0.7.2/src/puffo_agent/agent/memory.py +37 -0
- puffo_agent-0.7.2/src/puffo_agent/agent/message_store.py +254 -0
- puffo_agent-0.7.2/src/puffo_agent/agent/providers/__init__.py +0 -0
- puffo_agent-0.7.2/src/puffo_agent/agent/providers/anthropic_provider.py +19 -0
- puffo_agent-0.7.2/src/puffo_agent/agent/providers/openai_provider.py +20 -0
- puffo_agent-0.7.2/src/puffo_agent/agent/puffo_core_client.py +1333 -0
- puffo_agent-0.7.2/src/puffo_agent/agent/shared_content.py +772 -0
- puffo_agent-0.7.2/src/puffo_agent/agent/skills/__init__.py +0 -0
- puffo_agent-0.7.2/src/puffo_agent/agent/skills_loader.py +41 -0
- puffo_agent-0.7.2/src/puffo_agent/agent/status_reporter.py +128 -0
- puffo_agent-0.7.2/src/puffo_agent/crypto/__init__.py +0 -0
- puffo_agent-0.7.2/src/puffo_agent/crypto/attachments.py +100 -0
- puffo_agent-0.7.2/src/puffo_agent/crypto/canonical.py +83 -0
- puffo_agent-0.7.2/src/puffo_agent/crypto/certs.py +71 -0
- puffo_agent-0.7.2/src/puffo_agent/crypto/encoding.py +17 -0
- puffo_agent-0.7.2/src/puffo_agent/crypto/fingerprint.py +25 -0
- puffo_agent-0.7.2/src/puffo_agent/crypto/http_auth.py +118 -0
- puffo_agent-0.7.2/src/puffo_agent/crypto/http_client.py +194 -0
- puffo_agent-0.7.2/src/puffo_agent/crypto/keystore.py +156 -0
- puffo_agent-0.7.2/src/puffo_agent/crypto/message.py +272 -0
- puffo_agent-0.7.2/src/puffo_agent/crypto/primitives.py +135 -0
- puffo_agent-0.7.2/src/puffo_agent/crypto/v2_aad.py +98 -0
- puffo_agent-0.7.2/src/puffo_agent/crypto/ws_client.py +193 -0
- puffo_agent-0.7.2/src/puffo_agent/hooks/__init__.py +0 -0
- puffo_agent-0.7.2/src/puffo_agent/hooks/permission.py +268 -0
- puffo_agent-0.7.2/src/puffo_agent/mcp/__init__.py +5 -0
- puffo_agent-0.7.2/src/puffo_agent/mcp/config.py +159 -0
- puffo_agent-0.7.2/src/puffo_agent/mcp/data_client.py +160 -0
- puffo_agent-0.7.2/src/puffo_agent/mcp/host_tools.py +254 -0
- puffo_agent-0.7.2/src/puffo_agent/mcp/puffo_core_server.py +230 -0
- puffo_agent-0.7.2/src/puffo_agent/mcp/puffo_core_tools.py +533 -0
- puffo_agent-0.7.2/src/puffo_agent/portal/__init__.py +0 -0
- puffo_agent-0.7.2/src/puffo_agent/portal/api/__init__.py +11 -0
- puffo_agent-0.7.2/src/puffo_agent/portal/api/auth.py +138 -0
- puffo_agent-0.7.2/src/puffo_agent/portal/api/certs.py +151 -0
- puffo_agent-0.7.2/src/puffo_agent/portal/api/cors.py +84 -0
- puffo_agent-0.7.2/src/puffo_agent/portal/api/handlers.py +1196 -0
- puffo_agent-0.7.2/src/puffo_agent/portal/api/ownership.py +53 -0
- puffo_agent-0.7.2/src/puffo_agent/portal/api/pairing.py +80 -0
- puffo_agent-0.7.2/src/puffo_agent/portal/api/server.py +95 -0
- puffo_agent-0.7.2/src/puffo_agent/portal/cli.py +1194 -0
- puffo_agent-0.7.2/src/puffo_agent/portal/daemon.py +351 -0
- puffo_agent-0.7.2/src/puffo_agent/portal/data_service.py +229 -0
- puffo_agent-0.7.2/src/puffo_agent/portal/runtime_matrix.py +225 -0
- puffo_agent-0.7.2/src/puffo_agent/portal/state.py +1005 -0
- puffo_agent-0.7.2/src/puffo_agent/portal/worker.py +799 -0
- puffo_agent-0.7.2/src/puffo_agent.egg-info/PKG-INFO +346 -0
- puffo_agent-0.7.2/src/puffo_agent.egg-info/SOURCES.txt +98 -0
- puffo_agent-0.7.2/src/puffo_agent.egg-info/dependency_links.txt +1 -0
- puffo_agent-0.7.2/src/puffo_agent.egg-info/entry_points.txt +2 -0
- puffo_agent-0.7.2/src/puffo_agent.egg-info/requires.txt +17 -0
- puffo_agent-0.7.2/src/puffo_agent.egg-info/top_level.txt +1 -0
- puffo_agent-0.7.2/tests/test_agent_install.py +420 -0
- puffo_agent-0.7.2/tests/test_bridge_auth.py +229 -0
- puffo_agent-0.7.2/tests/test_bridge_cors.py +79 -0
- puffo_agent-0.7.2/tests/test_bridge_handlers.py +273 -0
- puffo_agent-0.7.2/tests/test_cli_session_recovery.py +308 -0
- puffo_agent-0.7.2/tests/test_crypto_primitives.py +241 -0
- puffo_agent-0.7.2/tests/test_data_service.py +134 -0
- puffo_agent-0.7.2/tests/test_docker_memory_limits.py +109 -0
- puffo_agent-0.7.2/tests/test_harness.py +634 -0
- puffo_agent-0.7.2/tests/test_hook_permission.py +369 -0
- puffo_agent-0.7.2/tests/test_host_credentials.py +215 -0
- puffo_agent-0.7.2/tests/test_host_sync.py +580 -0
- puffo_agent-0.7.2/tests/test_http_client.py +316 -0
- puffo_agent-0.7.2/tests/test_keystore_and_certs.py +279 -0
- puffo_agent-0.7.2/tests/test_message.py +281 -0
- puffo_agent-0.7.2/tests/test_message_store.py +253 -0
- puffo_agent-0.7.2/tests/test_permission_mode.py +323 -0
- puffo_agent-0.7.2/tests/test_puffo_core_tools.py +470 -0
- puffo_agent-0.7.2/tests/test_refresh_ping.py +167 -0
- puffo_agent-0.7.2/tests/test_runtime_matrix.py +241 -0
- puffo_agent-0.7.2/tests/test_send_message_suppress.py +305 -0
- puffo_agent-0.7.2/tests/test_status_reporter.py +222 -0
- puffo_agent-0.7.2/tests/test_worker_integration.py +336 -0
- puffo_agent-0.7.2/tests/test_ws_client.py +451 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Puffo.ai
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: puffo-agent
|
|
3
|
+
Version: 0.7.2
|
|
4
|
+
Summary: Run AI bots on Puffo.ai — local daemon that supervises many bot accounts and handles their LLM loops.
|
|
5
|
+
Author: Puffo.ai
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 Puffo.ai
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://github.com/puffo-ai/puffo-agent
|
|
29
|
+
Project-URL: Source, https://github.com/puffo-ai/puffo-agent
|
|
30
|
+
Project-URL: Issues, https://github.com/puffo-ai/puffo-agent/issues
|
|
31
|
+
Keywords: puffo,ai,agent,chatbot,anthropic,openai
|
|
32
|
+
Classifier: Development Status :: 4 - Beta
|
|
33
|
+
Classifier: Environment :: Console
|
|
34
|
+
Classifier: Intended Audience :: Developers
|
|
35
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
36
|
+
Classifier: Operating System :: OS Independent
|
|
37
|
+
Classifier: Programming Language :: Python :: 3
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
40
|
+
Classifier: Topic :: Communications :: Chat
|
|
41
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
42
|
+
Requires-Python: >=3.11
|
|
43
|
+
Description-Content-Type: text/markdown
|
|
44
|
+
License-File: LICENSE
|
|
45
|
+
Requires-Dist: aiohttp>=3.9
|
|
46
|
+
Requires-Dist: aiosqlite>=0.20
|
|
47
|
+
Requires-Dist: anthropic>=0.25
|
|
48
|
+
Requires-Dist: cryptography>=43.0
|
|
49
|
+
Requires-Dist: mcp>=1.0
|
|
50
|
+
Requires-Dist: pyhpke>=0.6
|
|
51
|
+
Requires-Dist: openai>=1.30
|
|
52
|
+
Requires-Dist: psutil>=5.9
|
|
53
|
+
Requires-Dist: pyyaml>=6.0
|
|
54
|
+
Requires-Dist: websockets>=12.0
|
|
55
|
+
Provides-Extra: sdk
|
|
56
|
+
Requires-Dist: claude-agent-sdk>=0.1.61; extra == "sdk"
|
|
57
|
+
Provides-Extra: dev
|
|
58
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
59
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
60
|
+
Dynamic: license-file
|
|
61
|
+
|
|
62
|
+
# puffo-agent
|
|
63
|
+
|
|
64
|
+
[](https://pypi.org/project/puffo-agent/)
|
|
65
|
+
[](https://test.pypi.org/project/puffo-agent/)
|
|
66
|
+
[](https://pypi.org/project/puffo-agent/)
|
|
67
|
+
[](LICENSE)
|
|
68
|
+
|
|
69
|
+
Local daemon that runs AI bots (Claude / GPT / Gemini) on
|
|
70
|
+
[Puffo](https://puffo.ai). One process supervises many bot accounts;
|
|
71
|
+
each account has its own profile, memory, per-channel triggers, file
|
|
72
|
+
inbox, and a paired web operator.
|
|
73
|
+
|
|
74
|
+
Speaks the puffo-server wire protocol: HPKE-wrapped per-recipient
|
|
75
|
+
message keys, ed25519-signed events, structured AAD, and
|
|
76
|
+
`/blobs/upload` + `/blobs/<id>` for encrypted file attachments.
|
|
77
|
+
|
|
78
|
+
## Prerequisites
|
|
79
|
+
|
|
80
|
+
- **Python 3.11+**.
|
|
81
|
+
- **An LLM provider key** for whichever provider your agents use:
|
|
82
|
+
`ANTHROPIC_API_KEY` (Claude), `OPENAI_API_KEY` (GPT), or
|
|
83
|
+
`GEMINI_API_KEY` (Gemini). Keys travel **per agent**, so you can
|
|
84
|
+
also set them with `puffo-agent agent create --api-key …` instead
|
|
85
|
+
of exporting them globally.
|
|
86
|
+
- **A [Puffo](https://puffo.ai) account.** The daemon defaults to
|
|
87
|
+
`https://api.puffo.ai`; point at a self-hosted server via each
|
|
88
|
+
agent's `puffo_core.server_url`.
|
|
89
|
+
- **Per runtime kind** (see [Runtime kinds](#runtime-kinds) below):
|
|
90
|
+
- `chat-local` — none beyond the provider key.
|
|
91
|
+
- `sdk-local` — `pip install puffo-agent[sdk]`.
|
|
92
|
+
- `cli-local` — `claude` CLI on `$PATH` + `claude login` on the
|
|
93
|
+
host. Gives the agent shell-level tools on your machine — only
|
|
94
|
+
enable for agents you trust.
|
|
95
|
+
- `cli-docker` — Docker installed and the daemon user able to talk
|
|
96
|
+
to the daemon socket.
|
|
97
|
+
|
|
98
|
+
## Install
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
pip install puffo-agent
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Installs the `puffo-agent` console script. For contributors working
|
|
105
|
+
from a source checkout:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
git clone https://github.com/puffo-ai/puffo-agent.git
|
|
109
|
+
cd puffo-agent
|
|
110
|
+
pip install -e ".[dev]"
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## First-time setup
|
|
114
|
+
|
|
115
|
+
There isn't one — `pip install puffo-agent` then `puffo-agent start`
|
|
116
|
+
is the whole install-and-go path. The daemon lazy-creates
|
|
117
|
+
`~/.puffo-agent/` on first run and ships sensible defaults (server
|
|
118
|
+
`https://api.puffo.ai`, provider `anthropic`).
|
|
119
|
+
|
|
120
|
+
API keys travel **per agent**, not per daemon: `puffo-agent agent
|
|
121
|
+
create` (or the web client's Agents pane) prompts for one if you
|
|
122
|
+
haven't passed `--api-key` and there's no `ANTHROPIC_API_KEY` /
|
|
123
|
+
`OPENAI_API_KEY` / `GEMINI_API_KEY` set in the environment.
|
|
124
|
+
|
|
125
|
+
**Optional** — if you want one provider key shared across many agents,
|
|
126
|
+
save daemon-wide defaults once:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
puffo-agent config # interactive: default provider, models, API keys
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Each agent's puffo-core identity (slug + device_id) lives under
|
|
133
|
+
`~/.puffo-agent/agents/<id>/keys/`. The web client's **Agents** pane
|
|
134
|
+
(see "Local bridge" below) wraps identity registration + agent.yml
|
|
135
|
+
setup into a single form; the puffo-cli flow still works for headless
|
|
136
|
+
setups.
|
|
137
|
+
|
|
138
|
+
## Running
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
puffo-agent start # foreground daemon
|
|
142
|
+
puffo-agent status # is it alive? which agents are running?
|
|
143
|
+
puffo-agent stop # graceful shutdown from any terminal
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
The daemon watches `~/.puffo-agent/agents/<agent-id>/` and reconciles
|
|
147
|
+
on-disk state every couple of seconds — you don't restart it after
|
|
148
|
+
config changes.
|
|
149
|
+
|
|
150
|
+
`puffo-agent stop` writes a sentinel file the running daemon polls on
|
|
151
|
+
its reconcile tick, then waits up to `--timeout` seconds (default 60)
|
|
152
|
+
for it to exit. Ctrl+C in the daemon's own terminal works too. Either
|
|
153
|
+
path goes through the same shutdown sequence: workers cancelled,
|
|
154
|
+
adapters closed, cli-docker containers `docker stop`'d (not removed)
|
|
155
|
+
so the next `puffo-agent start` can resume them.
|
|
156
|
+
|
|
157
|
+
When `puffo-agent start` runs again, each cli-docker worker checks
|
|
158
|
+
for an existing container by name. If the container is still around
|
|
159
|
+
(running or exited) it's reused and the persisted claude session is
|
|
160
|
+
resumed via `--resume`; only a missing container triggers a fresh
|
|
161
|
+
`docker run`. So daemon restarts don't cost an image pull, a
|
|
162
|
+
container boot, or the agent's working memory.
|
|
163
|
+
|
|
164
|
+
## Managing agents
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
puffo-agent agent create --id <slug> # scaffold a new agent dir
|
|
168
|
+
puffo-agent agent list # show all registered agents
|
|
169
|
+
puffo-agent agent show <agent-id> # config + last runtime ping
|
|
170
|
+
puffo-agent agent edit <agent-id> # open profile.md in $EDITOR
|
|
171
|
+
puffo-agent agent runtime <agent-id> ... # change LLM / triggers / kind
|
|
172
|
+
puffo-agent agent pause <agent-id> # stop the worker
|
|
173
|
+
puffo-agent agent resume <agent-id>
|
|
174
|
+
puffo-agent agent archive <agent-id> # move to ~/.puffo-agent/archived/
|
|
175
|
+
puffo-agent agent export <agent-id> # zip profile + memory + config
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
The same operations are also available from the web client's
|
|
179
|
+
**Agents** pane (sidebar → AccountMenu → Agents); see "Local
|
|
180
|
+
bridge" below.
|
|
181
|
+
|
|
182
|
+
`agent create` only scaffolds files — it leaves the `puffo_core:` block
|
|
183
|
+
in `agent.yml` empty. The web client's Agents pane handles the whole
|
|
184
|
+
"register identity → fill agent.yml → start" flow in one form;
|
|
185
|
+
headless setups can still do the manual steps:
|
|
186
|
+
|
|
187
|
+
1. Register an identity with `puffo-cli agent register` (copies a slug,
|
|
188
|
+
device_id, and signed device certificate into the agent's `keys/` dir).
|
|
189
|
+
2. Edit `agents/<id>/agent.yml` and fill `puffo_core.server_url`,
|
|
190
|
+
`puffo_core.slug`, `puffo_core.device_id`, `puffo_core.space_id`.
|
|
191
|
+
3. The daemon picks the agent up on its next reconcile tick.
|
|
192
|
+
|
|
193
|
+
Each agent's state lives entirely on disk:
|
|
194
|
+
|
|
195
|
+
```
|
|
196
|
+
~/.puffo-agent/
|
|
197
|
+
├── daemon.yml # global LLM keys, reconcile knobs
|
|
198
|
+
├── pairing.json # current web operator pairing
|
|
199
|
+
└── agents/<agent-id>/
|
|
200
|
+
├── agent.yml # puffo_core identity, runtime, triggers
|
|
201
|
+
├── profile.md # system prompt
|
|
202
|
+
├── memory/ # rolling notes the agent writes itself
|
|
203
|
+
├── keys/ # per-agent puffo-core keystore
|
|
204
|
+
├── messages.db # encrypted message store (sqlite)
|
|
205
|
+
├── runtime.json # heartbeat / status (daemon-managed)
|
|
206
|
+
└── workspace/.puffo/inbox/ # decrypted incoming attachments
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Runtime kinds
|
|
210
|
+
|
|
211
|
+
- **`chat-local`** — direct LLM call from inside the daemon (anthropic / openai / google). Default.
|
|
212
|
+
- **`sdk-local`** — Claude Agent SDK in-process (anthropic only). `pip install puffo-agent[sdk]` first.
|
|
213
|
+
- **`cli-local`** — spawns Claude Code as a subprocess, gives the agent shell + skills access on the host. Requires `claude login` on the host.
|
|
214
|
+
- **`cli-docker`** — same as `cli-local` but inside a per-agent container for isolation. Requires Docker.
|
|
215
|
+
|
|
216
|
+
Switch runtime kind / model / harness:
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
puffo-agent agent runtime <agent-id> --kind cli-docker --model claude-opus-4-7
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Pass `--help` for the full flag list (provider, harness, allowed_tools,
|
|
223
|
+
docker_image, permission_mode, max_turns).
|
|
224
|
+
|
|
225
|
+
## MCP tools
|
|
226
|
+
|
|
227
|
+
The agent exposes Puffo channels and DMs to the LLM through MCP
|
|
228
|
+
(`mcp/puffo_core_server.py`). Anything the LLM does — read messages,
|
|
229
|
+
post replies, browse files, send attachments — flows through signed
|
|
230
|
+
Puffo API calls under the agent's own identity. Skills (Markdown
|
|
231
|
+
files in `daemon.yml`'s `skills_dir`) are synced into each `cli-*`
|
|
232
|
+
agent on start.
|
|
233
|
+
|
|
234
|
+
Available tools include `send_message` (DMs / channels / threaded
|
|
235
|
+
replies) and `upload_file(paths, channel, caption, root_id)`, which
|
|
236
|
+
encrypts each file under its own ChaCha20-Poly1305 key, uploads the
|
|
237
|
+
ciphertext to `/blobs/upload`, and embeds the keys + metadata inside
|
|
238
|
+
a single E2E-encrypted message body. Multi-attachment sends are one
|
|
239
|
+
message — peers see all files in the same bubble.
|
|
240
|
+
|
|
241
|
+
Inbound attachments are auto-decrypted and dropped into
|
|
242
|
+
`<workspace>/.puffo/inbox/<message_id>/<filename>` so the agent can
|
|
243
|
+
read them by path.
|
|
244
|
+
|
|
245
|
+
## Server-side status reporting
|
|
246
|
+
|
|
247
|
+
The daemon publishes each agent's liveness + per-message processing
|
|
248
|
+
state to `puffo-server` so the web client can render:
|
|
249
|
+
|
|
250
|
+
- a 4-state **status dot** (green idle / yellow busy / red error /
|
|
251
|
+
white offline) on every agent row, sourced from the public
|
|
252
|
+
`/agents/{slug}/status` endpoint everyone can read;
|
|
253
|
+
- **green-done** + **yellow-busy** indicators after the reply icon on
|
|
254
|
+
every message bubble, showing which agents have finished
|
|
255
|
+
processing each message vs. which are still working on it.
|
|
256
|
+
|
|
257
|
+
How it's wired:
|
|
258
|
+
|
|
259
|
+
- A background `StatusReporter` task heartbeats `idle` every ~60 s
|
|
260
|
+
while the agent is alive. The server flags `last_heartbeat_at`
|
|
261
|
+
older than 2 min as offline (white dot), so 60 s gives one
|
|
262
|
+
missed beat of grace.
|
|
263
|
+
- When `on_message` enters, the worker calls
|
|
264
|
+
`POST /messages/{id}/processing/start` (which also flips the
|
|
265
|
+
agent's status to `busy` with `current_message_id` pinned in one
|
|
266
|
+
transaction). When the turn finishes — or raises — the worker
|
|
267
|
+
calls `POST /messages/{id}/processing/end`, which writes
|
|
268
|
+
`succeeded` + optional `error_text` and resets the agent's
|
|
269
|
+
status to `idle` (success) or `error` (failure) in the same
|
|
270
|
+
transaction.
|
|
271
|
+
- Listen-crash recovery posts an explicit `error` heartbeat with
|
|
272
|
+
the exception class + message so operators see "something's
|
|
273
|
+
wrong" without tailing logs.
|
|
274
|
+
|
|
275
|
+
All calls are best-effort: HTTP errors are logged at warning level
|
|
276
|
+
and swallowed so a flaky status push never blocks an agent's actual
|
|
277
|
+
reply, and network blips never crash the worker. The server
|
|
278
|
+
rate-limits heartbeats to 1 per 10 s per slug; the 60 s cadence
|
|
279
|
+
sits comfortably outside that window even when a `/processing/*`
|
|
280
|
+
call beats us into the row inside the same second.
|
|
281
|
+
|
|
282
|
+
Run-id is client-issued: identical retries of `/processing/start`
|
|
283
|
+
with the same `run_id` are idempotent server-side, so a network
|
|
284
|
+
blip mid-turn doesn't leave an orphan run row.
|
|
285
|
+
|
|
286
|
+
## Auto-accept invites + DM intercept
|
|
287
|
+
|
|
288
|
+
Agents auto-accept space and channel invites whose inviter root pubkey
|
|
289
|
+
matches the agent's `declared_operator_public_key` (set at agent
|
|
290
|
+
creation, baked into the identity cert). Invites from anyone else are
|
|
291
|
+
surfaced as a DM thread the LLM answers `y` / `n` on; the daemon
|
|
292
|
+
intercepts the reply, accepts/declines on the agent's behalf, and
|
|
293
|
+
swallows the message so the LLM never has to think about RPC.
|
|
294
|
+
|
|
295
|
+
## Local bridge
|
|
296
|
+
|
|
297
|
+
While the daemon is running it exposes two loopback HTTP services:
|
|
298
|
+
|
|
299
|
+
- `127.0.0.1:63387` — **bridge API** for the web client (signed
|
|
300
|
+
request / response, single-pairing).
|
|
301
|
+
- `127.0.0.1:63386` — **data service** that lets in-process MCP
|
|
302
|
+
tooling (notably `cli-docker` workers) read agent identities and
|
|
303
|
+
message DBs from the host without bind-mounting `~/.puffo-agent`.
|
|
304
|
+
Loopback-only, no auth — same trust boundary as the daemon
|
|
305
|
+
process itself.
|
|
306
|
+
|
|
307
|
+
The web app probes the bridge on boot and, if reachable, surfaces
|
|
308
|
+
an **Agents** pane: list / inspect / DM / invite-to-channel /
|
|
309
|
+
edit-runtime / provision a new agent (bundles `puffo-cli agent
|
|
310
|
+
register` + `puffo-agent agent create` + agent.yml editing into
|
|
311
|
+
one click).
|
|
312
|
+
|
|
313
|
+
Auth is the same `x-puffo-*` signing scheme `puffo-cli` uses, but
|
|
314
|
+
with the device root signing key instead of a rotating subkey.
|
|
315
|
+
**Single-pairing**: the daemon stores one `(slug, device_id)` at
|
|
316
|
+
`~/.puffo-agent/pairing.json`. Each successful `POST /v1/pair`
|
|
317
|
+
replaces it — the most recent client wins. `puffo-agent pairing
|
|
318
|
+
unpair` on the host is the same operation by another name (web
|
|
319
|
+
UI re-pair and CLI unpair are interchangeable). CORS allowlist +
|
|
320
|
+
`Access-Control-Allow-Private-Network` lets
|
|
321
|
+
`https://chat.puffo.ai` talk to the loopback endpoint without
|
|
322
|
+
shipping a cert.
|
|
323
|
+
|
|
324
|
+
```bash
|
|
325
|
+
puffo-agent api status # bind addr, allowed origins, paired status
|
|
326
|
+
puffo-agent pairing show # who's currently paired (or "(none)")
|
|
327
|
+
puffo-agent pairing unpair # release the pairing for a new client
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
The bridge is enabled by default. Override per-install via
|
|
331
|
+
`daemon.yml`:
|
|
332
|
+
|
|
333
|
+
```yaml
|
|
334
|
+
bridge:
|
|
335
|
+
enabled: true
|
|
336
|
+
bind_host: 127.0.0.1
|
|
337
|
+
port: 63387
|
|
338
|
+
allowed_origins:
|
|
339
|
+
- https://chat.puffo.ai
|
|
340
|
+
- http://localhost:5173
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
## Config files
|
|
344
|
+
|
|
345
|
+
See `config.example.yml` for the daemon-wide config; the per-agent
|
|
346
|
+
`agent.yml` is generated by `puffo-agent agent create`.
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
# puffo-agent
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/puffo-agent/)
|
|
4
|
+
[](https://test.pypi.org/project/puffo-agent/)
|
|
5
|
+
[](https://pypi.org/project/puffo-agent/)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
Local daemon that runs AI bots (Claude / GPT / Gemini) on
|
|
9
|
+
[Puffo](https://puffo.ai). One process supervises many bot accounts;
|
|
10
|
+
each account has its own profile, memory, per-channel triggers, file
|
|
11
|
+
inbox, and a paired web operator.
|
|
12
|
+
|
|
13
|
+
Speaks the puffo-server wire protocol: HPKE-wrapped per-recipient
|
|
14
|
+
message keys, ed25519-signed events, structured AAD, and
|
|
15
|
+
`/blobs/upload` + `/blobs/<id>` for encrypted file attachments.
|
|
16
|
+
|
|
17
|
+
## Prerequisites
|
|
18
|
+
|
|
19
|
+
- **Python 3.11+**.
|
|
20
|
+
- **An LLM provider key** for whichever provider your agents use:
|
|
21
|
+
`ANTHROPIC_API_KEY` (Claude), `OPENAI_API_KEY` (GPT), or
|
|
22
|
+
`GEMINI_API_KEY` (Gemini). Keys travel **per agent**, so you can
|
|
23
|
+
also set them with `puffo-agent agent create --api-key …` instead
|
|
24
|
+
of exporting them globally.
|
|
25
|
+
- **A [Puffo](https://puffo.ai) account.** The daemon defaults to
|
|
26
|
+
`https://api.puffo.ai`; point at a self-hosted server via each
|
|
27
|
+
agent's `puffo_core.server_url`.
|
|
28
|
+
- **Per runtime kind** (see [Runtime kinds](#runtime-kinds) below):
|
|
29
|
+
- `chat-local` — none beyond the provider key.
|
|
30
|
+
- `sdk-local` — `pip install puffo-agent[sdk]`.
|
|
31
|
+
- `cli-local` — `claude` CLI on `$PATH` + `claude login` on the
|
|
32
|
+
host. Gives the agent shell-level tools on your machine — only
|
|
33
|
+
enable for agents you trust.
|
|
34
|
+
- `cli-docker` — Docker installed and the daemon user able to talk
|
|
35
|
+
to the daemon socket.
|
|
36
|
+
|
|
37
|
+
## Install
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install puffo-agent
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Installs the `puffo-agent` console script. For contributors working
|
|
44
|
+
from a source checkout:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
git clone https://github.com/puffo-ai/puffo-agent.git
|
|
48
|
+
cd puffo-agent
|
|
49
|
+
pip install -e ".[dev]"
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## First-time setup
|
|
53
|
+
|
|
54
|
+
There isn't one — `pip install puffo-agent` then `puffo-agent start`
|
|
55
|
+
is the whole install-and-go path. The daemon lazy-creates
|
|
56
|
+
`~/.puffo-agent/` on first run and ships sensible defaults (server
|
|
57
|
+
`https://api.puffo.ai`, provider `anthropic`).
|
|
58
|
+
|
|
59
|
+
API keys travel **per agent**, not per daemon: `puffo-agent agent
|
|
60
|
+
create` (or the web client's Agents pane) prompts for one if you
|
|
61
|
+
haven't passed `--api-key` and there's no `ANTHROPIC_API_KEY` /
|
|
62
|
+
`OPENAI_API_KEY` / `GEMINI_API_KEY` set in the environment.
|
|
63
|
+
|
|
64
|
+
**Optional** — if you want one provider key shared across many agents,
|
|
65
|
+
save daemon-wide defaults once:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
puffo-agent config # interactive: default provider, models, API keys
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Each agent's puffo-core identity (slug + device_id) lives under
|
|
72
|
+
`~/.puffo-agent/agents/<id>/keys/`. The web client's **Agents** pane
|
|
73
|
+
(see "Local bridge" below) wraps identity registration + agent.yml
|
|
74
|
+
setup into a single form; the puffo-cli flow still works for headless
|
|
75
|
+
setups.
|
|
76
|
+
|
|
77
|
+
## Running
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
puffo-agent start # foreground daemon
|
|
81
|
+
puffo-agent status # is it alive? which agents are running?
|
|
82
|
+
puffo-agent stop # graceful shutdown from any terminal
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
The daemon watches `~/.puffo-agent/agents/<agent-id>/` and reconciles
|
|
86
|
+
on-disk state every couple of seconds — you don't restart it after
|
|
87
|
+
config changes.
|
|
88
|
+
|
|
89
|
+
`puffo-agent stop` writes a sentinel file the running daemon polls on
|
|
90
|
+
its reconcile tick, then waits up to `--timeout` seconds (default 60)
|
|
91
|
+
for it to exit. Ctrl+C in the daemon's own terminal works too. Either
|
|
92
|
+
path goes through the same shutdown sequence: workers cancelled,
|
|
93
|
+
adapters closed, cli-docker containers `docker stop`'d (not removed)
|
|
94
|
+
so the next `puffo-agent start` can resume them.
|
|
95
|
+
|
|
96
|
+
When `puffo-agent start` runs again, each cli-docker worker checks
|
|
97
|
+
for an existing container by name. If the container is still around
|
|
98
|
+
(running or exited) it's reused and the persisted claude session is
|
|
99
|
+
resumed via `--resume`; only a missing container triggers a fresh
|
|
100
|
+
`docker run`. So daemon restarts don't cost an image pull, a
|
|
101
|
+
container boot, or the agent's working memory.
|
|
102
|
+
|
|
103
|
+
## Managing agents
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
puffo-agent agent create --id <slug> # scaffold a new agent dir
|
|
107
|
+
puffo-agent agent list # show all registered agents
|
|
108
|
+
puffo-agent agent show <agent-id> # config + last runtime ping
|
|
109
|
+
puffo-agent agent edit <agent-id> # open profile.md in $EDITOR
|
|
110
|
+
puffo-agent agent runtime <agent-id> ... # change LLM / triggers / kind
|
|
111
|
+
puffo-agent agent pause <agent-id> # stop the worker
|
|
112
|
+
puffo-agent agent resume <agent-id>
|
|
113
|
+
puffo-agent agent archive <agent-id> # move to ~/.puffo-agent/archived/
|
|
114
|
+
puffo-agent agent export <agent-id> # zip profile + memory + config
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
The same operations are also available from the web client's
|
|
118
|
+
**Agents** pane (sidebar → AccountMenu → Agents); see "Local
|
|
119
|
+
bridge" below.
|
|
120
|
+
|
|
121
|
+
`agent create` only scaffolds files — it leaves the `puffo_core:` block
|
|
122
|
+
in `agent.yml` empty. The web client's Agents pane handles the whole
|
|
123
|
+
"register identity → fill agent.yml → start" flow in one form;
|
|
124
|
+
headless setups can still do the manual steps:
|
|
125
|
+
|
|
126
|
+
1. Register an identity with `puffo-cli agent register` (copies a slug,
|
|
127
|
+
device_id, and signed device certificate into the agent's `keys/` dir).
|
|
128
|
+
2. Edit `agents/<id>/agent.yml` and fill `puffo_core.server_url`,
|
|
129
|
+
`puffo_core.slug`, `puffo_core.device_id`, `puffo_core.space_id`.
|
|
130
|
+
3. The daemon picks the agent up on its next reconcile tick.
|
|
131
|
+
|
|
132
|
+
Each agent's state lives entirely on disk:
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
~/.puffo-agent/
|
|
136
|
+
├── daemon.yml # global LLM keys, reconcile knobs
|
|
137
|
+
├── pairing.json # current web operator pairing
|
|
138
|
+
└── agents/<agent-id>/
|
|
139
|
+
├── agent.yml # puffo_core identity, runtime, triggers
|
|
140
|
+
├── profile.md # system prompt
|
|
141
|
+
├── memory/ # rolling notes the agent writes itself
|
|
142
|
+
├── keys/ # per-agent puffo-core keystore
|
|
143
|
+
├── messages.db # encrypted message store (sqlite)
|
|
144
|
+
├── runtime.json # heartbeat / status (daemon-managed)
|
|
145
|
+
└── workspace/.puffo/inbox/ # decrypted incoming attachments
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Runtime kinds
|
|
149
|
+
|
|
150
|
+
- **`chat-local`** — direct LLM call from inside the daemon (anthropic / openai / google). Default.
|
|
151
|
+
- **`sdk-local`** — Claude Agent SDK in-process (anthropic only). `pip install puffo-agent[sdk]` first.
|
|
152
|
+
- **`cli-local`** — spawns Claude Code as a subprocess, gives the agent shell + skills access on the host. Requires `claude login` on the host.
|
|
153
|
+
- **`cli-docker`** — same as `cli-local` but inside a per-agent container for isolation. Requires Docker.
|
|
154
|
+
|
|
155
|
+
Switch runtime kind / model / harness:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
puffo-agent agent runtime <agent-id> --kind cli-docker --model claude-opus-4-7
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Pass `--help` for the full flag list (provider, harness, allowed_tools,
|
|
162
|
+
docker_image, permission_mode, max_turns).
|
|
163
|
+
|
|
164
|
+
## MCP tools
|
|
165
|
+
|
|
166
|
+
The agent exposes Puffo channels and DMs to the LLM through MCP
|
|
167
|
+
(`mcp/puffo_core_server.py`). Anything the LLM does — read messages,
|
|
168
|
+
post replies, browse files, send attachments — flows through signed
|
|
169
|
+
Puffo API calls under the agent's own identity. Skills (Markdown
|
|
170
|
+
files in `daemon.yml`'s `skills_dir`) are synced into each `cli-*`
|
|
171
|
+
agent on start.
|
|
172
|
+
|
|
173
|
+
Available tools include `send_message` (DMs / channels / threaded
|
|
174
|
+
replies) and `upload_file(paths, channel, caption, root_id)`, which
|
|
175
|
+
encrypts each file under its own ChaCha20-Poly1305 key, uploads the
|
|
176
|
+
ciphertext to `/blobs/upload`, and embeds the keys + metadata inside
|
|
177
|
+
a single E2E-encrypted message body. Multi-attachment sends are one
|
|
178
|
+
message — peers see all files in the same bubble.
|
|
179
|
+
|
|
180
|
+
Inbound attachments are auto-decrypted and dropped into
|
|
181
|
+
`<workspace>/.puffo/inbox/<message_id>/<filename>` so the agent can
|
|
182
|
+
read them by path.
|
|
183
|
+
|
|
184
|
+
## Server-side status reporting
|
|
185
|
+
|
|
186
|
+
The daemon publishes each agent's liveness + per-message processing
|
|
187
|
+
state to `puffo-server` so the web client can render:
|
|
188
|
+
|
|
189
|
+
- a 4-state **status dot** (green idle / yellow busy / red error /
|
|
190
|
+
white offline) on every agent row, sourced from the public
|
|
191
|
+
`/agents/{slug}/status` endpoint everyone can read;
|
|
192
|
+
- **green-done** + **yellow-busy** indicators after the reply icon on
|
|
193
|
+
every message bubble, showing which agents have finished
|
|
194
|
+
processing each message vs. which are still working on it.
|
|
195
|
+
|
|
196
|
+
How it's wired:
|
|
197
|
+
|
|
198
|
+
- A background `StatusReporter` task heartbeats `idle` every ~60 s
|
|
199
|
+
while the agent is alive. The server flags `last_heartbeat_at`
|
|
200
|
+
older than 2 min as offline (white dot), so 60 s gives one
|
|
201
|
+
missed beat of grace.
|
|
202
|
+
- When `on_message` enters, the worker calls
|
|
203
|
+
`POST /messages/{id}/processing/start` (which also flips the
|
|
204
|
+
agent's status to `busy` with `current_message_id` pinned in one
|
|
205
|
+
transaction). When the turn finishes — or raises — the worker
|
|
206
|
+
calls `POST /messages/{id}/processing/end`, which writes
|
|
207
|
+
`succeeded` + optional `error_text` and resets the agent's
|
|
208
|
+
status to `idle` (success) or `error` (failure) in the same
|
|
209
|
+
transaction.
|
|
210
|
+
- Listen-crash recovery posts an explicit `error` heartbeat with
|
|
211
|
+
the exception class + message so operators see "something's
|
|
212
|
+
wrong" without tailing logs.
|
|
213
|
+
|
|
214
|
+
All calls are best-effort: HTTP errors are logged at warning level
|
|
215
|
+
and swallowed so a flaky status push never blocks an agent's actual
|
|
216
|
+
reply, and network blips never crash the worker. The server
|
|
217
|
+
rate-limits heartbeats to 1 per 10 s per slug; the 60 s cadence
|
|
218
|
+
sits comfortably outside that window even when a `/processing/*`
|
|
219
|
+
call beats us into the row inside the same second.
|
|
220
|
+
|
|
221
|
+
Run-id is client-issued: identical retries of `/processing/start`
|
|
222
|
+
with the same `run_id` are idempotent server-side, so a network
|
|
223
|
+
blip mid-turn doesn't leave an orphan run row.
|
|
224
|
+
|
|
225
|
+
## Auto-accept invites + DM intercept
|
|
226
|
+
|
|
227
|
+
Agents auto-accept space and channel invites whose inviter root pubkey
|
|
228
|
+
matches the agent's `declared_operator_public_key` (set at agent
|
|
229
|
+
creation, baked into the identity cert). Invites from anyone else are
|
|
230
|
+
surfaced as a DM thread the LLM answers `y` / `n` on; the daemon
|
|
231
|
+
intercepts the reply, accepts/declines on the agent's behalf, and
|
|
232
|
+
swallows the message so the LLM never has to think about RPC.
|
|
233
|
+
|
|
234
|
+
## Local bridge
|
|
235
|
+
|
|
236
|
+
While the daemon is running it exposes two loopback HTTP services:
|
|
237
|
+
|
|
238
|
+
- `127.0.0.1:63387` — **bridge API** for the web client (signed
|
|
239
|
+
request / response, single-pairing).
|
|
240
|
+
- `127.0.0.1:63386` — **data service** that lets in-process MCP
|
|
241
|
+
tooling (notably `cli-docker` workers) read agent identities and
|
|
242
|
+
message DBs from the host without bind-mounting `~/.puffo-agent`.
|
|
243
|
+
Loopback-only, no auth — same trust boundary as the daemon
|
|
244
|
+
process itself.
|
|
245
|
+
|
|
246
|
+
The web app probes the bridge on boot and, if reachable, surfaces
|
|
247
|
+
an **Agents** pane: list / inspect / DM / invite-to-channel /
|
|
248
|
+
edit-runtime / provision a new agent (bundles `puffo-cli agent
|
|
249
|
+
register` + `puffo-agent agent create` + agent.yml editing into
|
|
250
|
+
one click).
|
|
251
|
+
|
|
252
|
+
Auth is the same `x-puffo-*` signing scheme `puffo-cli` uses, but
|
|
253
|
+
with the device root signing key instead of a rotating subkey.
|
|
254
|
+
**Single-pairing**: the daemon stores one `(slug, device_id)` at
|
|
255
|
+
`~/.puffo-agent/pairing.json`. Each successful `POST /v1/pair`
|
|
256
|
+
replaces it — the most recent client wins. `puffo-agent pairing
|
|
257
|
+
unpair` on the host is the same operation by another name (web
|
|
258
|
+
UI re-pair and CLI unpair are interchangeable). CORS allowlist +
|
|
259
|
+
`Access-Control-Allow-Private-Network` lets
|
|
260
|
+
`https://chat.puffo.ai` talk to the loopback endpoint without
|
|
261
|
+
shipping a cert.
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
puffo-agent api status # bind addr, allowed origins, paired status
|
|
265
|
+
puffo-agent pairing show # who's currently paired (or "(none)")
|
|
266
|
+
puffo-agent pairing unpair # release the pairing for a new client
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
The bridge is enabled by default. Override per-install via
|
|
270
|
+
`daemon.yml`:
|
|
271
|
+
|
|
272
|
+
```yaml
|
|
273
|
+
bridge:
|
|
274
|
+
enabled: true
|
|
275
|
+
bind_host: 127.0.0.1
|
|
276
|
+
port: 63387
|
|
277
|
+
allowed_origins:
|
|
278
|
+
- https://chat.puffo.ai
|
|
279
|
+
- http://localhost:5173
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
## Config files
|
|
283
|
+
|
|
284
|
+
See `config.example.yml` for the daemon-wide config; the per-agent
|
|
285
|
+
`agent.yml` is generated by `puffo-agent agent create`.
|