sessionkit 0.2.0__tar.gz → 0.3.0__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.
- sessionkit-0.3.0/PKG-INFO +233 -0
- sessionkit-0.3.0/README.md +213 -0
- {sessionkit-0.2.0 → sessionkit-0.3.0}/pyproject.toml +17 -10
- {sessionkit-0.2.0 → sessionkit-0.3.0}/src/sessionkit/cli.py +30 -6
- {sessionkit-0.2.0 → sessionkit-0.3.0}/src/sessionkit/setup.py +247 -26
- sessionkit-0.2.0/PKG-INFO +0 -18
- {sessionkit-0.2.0 → sessionkit-0.3.0}/LICENSE +0 -0
- {sessionkit-0.2.0 → sessionkit-0.3.0}/src/sessionkit/__init__.py +0 -0
- {sessionkit-0.2.0 → sessionkit-0.3.0}/src/sessionkit/py.typed +0 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sessionkit
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Session Kit gives your Claude Code sessions voice, mail, a phone line and a tmux sidebar, in one install.
|
|
5
|
+
Author: Mike Bailey
|
|
6
|
+
Author-email: Mike Bailey <mike@sessionkit.ai>
|
|
7
|
+
License-Expression: AGPL-3.0-or-later
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Requires-Dist: click>=8.1
|
|
11
|
+
Requires-Dist: sessionmail>=0.3.0
|
|
12
|
+
Requires-Dist: sessiontools>=0.3.0
|
|
13
|
+
Requires-Dist: sessionphone[media]>=0.3.0
|
|
14
|
+
Requires-Dist: sessionview>=0.3.0
|
|
15
|
+
Requires-Dist: sessionvoice[capture]>=0.3.0
|
|
16
|
+
Requires-Python: >=3.11, <3.15
|
|
17
|
+
Project-URL: Homepage, https://github.com/sessionkit/sessionkit
|
|
18
|
+
Project-URL: Repository, https://github.com/sessionkit/sessionkit
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# Session Kit
|
|
22
|
+
|
|
23
|
+
Session Kit lets your Claude Code sessions mail each other, hear you and
|
|
24
|
+
answer aloud. One install brings the five family packages and their Claude
|
|
25
|
+
Code plugins.
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
You need macOS, [Claude Code][claude-code], and [Homebrew][brew] or at
|
|
30
|
+
least Apple's Command Line Tools. Homebrew installs the Command Line Tools
|
|
31
|
+
for you ([prerequisites][prereqs]), and the kit needs two of their tools:
|
|
32
|
+
the plugin lines clone with git, and the ears build a Swift helper with
|
|
33
|
+
`swiftc`. Without Homebrew, run `xcode-select --install` to get the Command
|
|
34
|
+
Line Tools alone. If you do not have uv yet, install it with
|
|
35
|
+
`curl -LsSf https://astral.sh/uv/install.sh | sh`, then open a new terminal
|
|
36
|
+
(or run `source ~/.local/bin/env`).
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
uv tool install --python 3.14 sessionkit
|
|
40
|
+
claude plugin marketplace add sessionkit/sessionkit
|
|
41
|
+
claude plugin install sessionkit@sessionkit
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The first line puts the commands on your PATH. The other two install the
|
|
45
|
+
kit's plugin and the five family plugins it depends on.
|
|
46
|
+
|
|
47
|
+
- **Keep `--python 3.14`.** Without it, uv takes the Python it finds: 3.15,
|
|
48
|
+
where a voice library has no build yet and the install fails, or the
|
|
49
|
+
Command Line Tools' 3.11. The kit is tested on 3.14.
|
|
50
|
+
- **Upgrade with `uv tool upgrade sessionkit`.** It keeps the Python the
|
|
51
|
+
install chose. To reinstall, give the pin again:
|
|
52
|
+
`uv tool install --python 3.14 --reinstall sessionkit`. Without it, uv
|
|
53
|
+
drops the pin from its record, or, with `--force`, builds the kit again on
|
|
54
|
+
the Python it finds.
|
|
55
|
+
- **A dialog may ask to install the Command Line Tools.** On a Mac without
|
|
56
|
+
them, the first `uv tool install` opens it. Click Install and let it
|
|
57
|
+
finish: the plugin lines need the git it brings.
|
|
58
|
+
- **Phone calls are installed, and walked for a session started with
|
|
59
|
+
`session start <name>`.** A fresh Mac took a real call and passed. A plain
|
|
60
|
+
`claude` session does not hear calls you make to it yet. Whether the phone
|
|
61
|
+
bot signs its mail to your Claude follows your mail carrier:
|
|
62
|
+
[bot mail signing][phone-signing].
|
|
63
|
+
|
|
64
|
+
**Then quit Claude Code and start it again**, every window of it, before you
|
|
65
|
+
run anything else. A plugin installed from the shell loads only when Claude
|
|
66
|
+
Code starts, so a session that was already open has no plugin commands on
|
|
67
|
+
its PATH, and no plugin hooks.
|
|
68
|
+
|
|
69
|
+
The three lines leave the ears without the model they listen with. Fetch it
|
|
70
|
+
once:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
ears install
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
It downloads the 2.3 MB voice-activity model to
|
|
77
|
+
`~/.cache/ears/silero_vad.onnx` (network, no sudo) and prints `done`. Without
|
|
78
|
+
it the ears cannot hear you, and `ears services install`, the step that
|
|
79
|
+
starts them ([hearing and speaking][hearing]), refuses with
|
|
80
|
+
`silero MISSING`. The Swift helper is built later, when those services start.
|
|
81
|
+
|
|
82
|
+
- **The mail hooks start off.** They tell an agent about new mail between
|
|
83
|
+
its tool calls. To turn them on, set `"hooks": true` in
|
|
84
|
+
`~/.sessionkit/mail/settings/mail.json`. On a fresh install the file is
|
|
85
|
+
not there yet, so make it:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
mkdir -p ~/.sessionkit/mail/settings
|
|
89
|
+
echo '{"version": 1, "hooks": true}' > ~/.sessionkit/mail/settings/mail.json
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
If the file is there, change only that key, because it can hold other
|
|
93
|
+
settings:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
perl -pi -e 's/"hooks": *false/"hooks": true/' ~/.sessionkit/mail/settings/mail.json
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Step by step, with every step that needs you marked: [the manual
|
|
100
|
+
install][install].
|
|
101
|
+
|
|
102
|
+
## Quick start
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
sessionkit --version
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
It lists the kit and each family package, with its version:
|
|
109
|
+
|
|
110
|
+
```text
|
|
111
|
+
sessionkit, version 0.3.0
|
|
112
|
+
sessionmail 0.3.0
|
|
113
|
+
sessiontools 0.3.0
|
|
114
|
+
sessionphone 0.3.0
|
|
115
|
+
sessionview 0.3.0
|
|
116
|
+
sessionvoice 0.3.0
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Start Claude with `session start claude`, never a bare `claude`: mail to
|
|
120
|
+
your agent's box, your phone's included, reaches only a session started as
|
|
121
|
+
that agent. `sessionkit setup` makes the box and the agent first: [first
|
|
122
|
+
checks][checks].
|
|
123
|
+
|
|
124
|
+
Then, from your agent inside a Claude Code session:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
session list # your live sessions
|
|
128
|
+
message send <box> -s "PR ready" -f note.md # mail another agent
|
|
129
|
+
mouth say --device default "the build is green" # speak one line
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
- **Mail works inside a session.** `message` and `pager` read who is asking
|
|
133
|
+
from the session. A plain terminal has no session, so they refuse with
|
|
134
|
+
`no seat in the environment ... set an identity` ([first checks][checks]).
|
|
135
|
+
A send never makes a box: the address must have one already ([mail
|
|
136
|
+
between agents][mail]).
|
|
137
|
+
- **The mouth needs a device.** `--device default` is your Mac's default
|
|
138
|
+
output; `mouth devices` lists the others.
|
|
139
|
+
- **To hear you**, the ears need a speech-to-text server: [hearing and
|
|
140
|
+
speaking][hearing].
|
|
141
|
+
- **Your agent talks with the kit's mouth and ears.** sessionvoice's ears
|
|
142
|
+
skill sets the default:
|
|
143
|
+
|
|
144
|
+
> **With the kit, an agent talks through the kit's own mouth and ears: it
|
|
145
|
+
> speaks with `mouth say` and listens by arming the ears with `ears-arm`. That
|
|
146
|
+
> is the default whenever the user asks it to talk, speak, listen or use voice,
|
|
147
|
+
> even on a host that also has VoiceMode's `converse` tool. It uses `converse`
|
|
148
|
+
> only when the user asks for converse by name.**
|
|
149
|
+
|
|
150
|
+
## Uninstall
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
claude plugin marketplace remove sessionkit
|
|
154
|
+
uv tool uninstall sessionkit
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Removing the marketplace removes its six plugins too, but not the copies they
|
|
158
|
+
ran from. Those copies, the services and your data have their own steps:
|
|
159
|
+
[uninstall][uninstall].
|
|
160
|
+
|
|
161
|
+
## Usage
|
|
162
|
+
|
|
163
|
+
Every command answers `--help`, and each has a page: [components][components].
|
|
164
|
+
|
|
165
|
+
## The family
|
|
166
|
+
|
|
167
|
+
Session Kit installs these together:
|
|
168
|
+
|
|
169
|
+
- [sessionmail][pkg-mail]: mail for coding agents. `message`, `pager` and
|
|
170
|
+
`memory` over one maildir.
|
|
171
|
+
- [sessiontools][pkg-tools]: sessions, status and idle. `session`.
|
|
172
|
+
- [sessionvoice][pkg-voice]: ambient voice. `ears` hears; `mouth` speaks.
|
|
173
|
+
- [sessionphone][pkg-phone]: phone calls to a session over Delta Chat.
|
|
174
|
+
Installed, and walked on a fresh Mac for a session started with
|
|
175
|
+
`session start <name>`. A plain `claude` session does not hear calls you
|
|
176
|
+
make to it yet.
|
|
177
|
+
- [sessionview][pkg-view]: a tmux sidebar of your projects and your agents.
|
|
178
|
+
|
|
179
|
+
## Components
|
|
180
|
+
|
|
181
|
+
The commands, services and folders the install adds, and where each
|
|
182
|
+
package logs: [components][components].
|
|
183
|
+
|
|
184
|
+
## Configuration
|
|
185
|
+
|
|
186
|
+
Each package configures itself: [configuration][configuration]. How the
|
|
187
|
+
settings files stack: [the settings guide][settings].
|
|
188
|
+
|
|
189
|
+
## Logs
|
|
190
|
+
|
|
191
|
+
Each package keeps its own log: [logs][logs]. Which one to read for a
|
|
192
|
+
symptom: [troubleshooting][troubleshooting].
|
|
193
|
+
|
|
194
|
+
## Development
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
git clone https://github.com/sessionkit/sessionkit
|
|
198
|
+
cd sessionkit
|
|
199
|
+
make install # editable: uv tool install -e .
|
|
200
|
+
make check # ruff, shellcheck, then the tests
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
The rules for changing the kit: [AGENTS.md][agents].
|
|
204
|
+
|
|
205
|
+
## Status
|
|
206
|
+
|
|
207
|
+
Alpha, 0.3.0. Tested on macOS 27 on Apple silicon; Linux and Windows are
|
|
208
|
+
untested. What you may still hit: [known gaps][gaps].
|
|
209
|
+
|
|
210
|
+
Licence: [AGPL-3.0][licence].
|
|
211
|
+
|
|
212
|
+
[claude-code]: https://claude.ai/code
|
|
213
|
+
[brew]: https://brew.sh
|
|
214
|
+
[install]: INSTALL.md
|
|
215
|
+
[prereqs]: INSTALL.md#1-prerequisites-human
|
|
216
|
+
[checks]: INSTALL.md#4-first-checks
|
|
217
|
+
[hearing]: INSTALL.md#5-hearing-and-speaking-it-hears-me-and-speaks
|
|
218
|
+
[mail]: INSTALL.md#mail-between-agents
|
|
219
|
+
[uninstall]: INSTALL.md#8-uninstall
|
|
220
|
+
[gaps]: INSTALL.md#7-known-gaps-you-will-hit
|
|
221
|
+
[components]: docs/components.md
|
|
222
|
+
[logs]: docs/components.md#logs
|
|
223
|
+
[configuration]: docs/configuration.md
|
|
224
|
+
[settings]: docs/settings.md
|
|
225
|
+
[troubleshooting]: docs/troubleshooting.md
|
|
226
|
+
[agents]: AGENTS.md
|
|
227
|
+
[licence]: LICENSE
|
|
228
|
+
[pkg-mail]: https://github.com/sessionkit/sessionmail
|
|
229
|
+
[pkg-tools]: https://github.com/sessionkit/sessiontools
|
|
230
|
+
[pkg-voice]: https://github.com/sessionkit/sessionvoice
|
|
231
|
+
[pkg-phone]: https://github.com/sessionkit/sessionphone
|
|
232
|
+
[phone-signing]: https://github.com/sessionkit/sessionphone/blob/HEAD/docs/bot-mail-signing.md
|
|
233
|
+
[pkg-view]: https://github.com/sessionkit/sessionview
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# Session Kit
|
|
2
|
+
|
|
3
|
+
Session Kit lets your Claude Code sessions mail each other, hear you and
|
|
4
|
+
answer aloud. One install brings the five family packages and their Claude
|
|
5
|
+
Code plugins.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
You need macOS, [Claude Code][claude-code], and [Homebrew][brew] or at
|
|
10
|
+
least Apple's Command Line Tools. Homebrew installs the Command Line Tools
|
|
11
|
+
for you ([prerequisites][prereqs]), and the kit needs two of their tools:
|
|
12
|
+
the plugin lines clone with git, and the ears build a Swift helper with
|
|
13
|
+
`swiftc`. Without Homebrew, run `xcode-select --install` to get the Command
|
|
14
|
+
Line Tools alone. If you do not have uv yet, install it with
|
|
15
|
+
`curl -LsSf https://astral.sh/uv/install.sh | sh`, then open a new terminal
|
|
16
|
+
(or run `source ~/.local/bin/env`).
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
uv tool install --python 3.14 sessionkit
|
|
20
|
+
claude plugin marketplace add sessionkit/sessionkit
|
|
21
|
+
claude plugin install sessionkit@sessionkit
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The first line puts the commands on your PATH. The other two install the
|
|
25
|
+
kit's plugin and the five family plugins it depends on.
|
|
26
|
+
|
|
27
|
+
- **Keep `--python 3.14`.** Without it, uv takes the Python it finds: 3.15,
|
|
28
|
+
where a voice library has no build yet and the install fails, or the
|
|
29
|
+
Command Line Tools' 3.11. The kit is tested on 3.14.
|
|
30
|
+
- **Upgrade with `uv tool upgrade sessionkit`.** It keeps the Python the
|
|
31
|
+
install chose. To reinstall, give the pin again:
|
|
32
|
+
`uv tool install --python 3.14 --reinstall sessionkit`. Without it, uv
|
|
33
|
+
drops the pin from its record, or, with `--force`, builds the kit again on
|
|
34
|
+
the Python it finds.
|
|
35
|
+
- **A dialog may ask to install the Command Line Tools.** On a Mac without
|
|
36
|
+
them, the first `uv tool install` opens it. Click Install and let it
|
|
37
|
+
finish: the plugin lines need the git it brings.
|
|
38
|
+
- **Phone calls are installed, and walked for a session started with
|
|
39
|
+
`session start <name>`.** A fresh Mac took a real call and passed. A plain
|
|
40
|
+
`claude` session does not hear calls you make to it yet. Whether the phone
|
|
41
|
+
bot signs its mail to your Claude follows your mail carrier:
|
|
42
|
+
[bot mail signing][phone-signing].
|
|
43
|
+
|
|
44
|
+
**Then quit Claude Code and start it again**, every window of it, before you
|
|
45
|
+
run anything else. A plugin installed from the shell loads only when Claude
|
|
46
|
+
Code starts, so a session that was already open has no plugin commands on
|
|
47
|
+
its PATH, and no plugin hooks.
|
|
48
|
+
|
|
49
|
+
The three lines leave the ears without the model they listen with. Fetch it
|
|
50
|
+
once:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
ears install
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
It downloads the 2.3 MB voice-activity model to
|
|
57
|
+
`~/.cache/ears/silero_vad.onnx` (network, no sudo) and prints `done`. Without
|
|
58
|
+
it the ears cannot hear you, and `ears services install`, the step that
|
|
59
|
+
starts them ([hearing and speaking][hearing]), refuses with
|
|
60
|
+
`silero MISSING`. The Swift helper is built later, when those services start.
|
|
61
|
+
|
|
62
|
+
- **The mail hooks start off.** They tell an agent about new mail between
|
|
63
|
+
its tool calls. To turn them on, set `"hooks": true` in
|
|
64
|
+
`~/.sessionkit/mail/settings/mail.json`. On a fresh install the file is
|
|
65
|
+
not there yet, so make it:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
mkdir -p ~/.sessionkit/mail/settings
|
|
69
|
+
echo '{"version": 1, "hooks": true}' > ~/.sessionkit/mail/settings/mail.json
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
If the file is there, change only that key, because it can hold other
|
|
73
|
+
settings:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
perl -pi -e 's/"hooks": *false/"hooks": true/' ~/.sessionkit/mail/settings/mail.json
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Step by step, with every step that needs you marked: [the manual
|
|
80
|
+
install][install].
|
|
81
|
+
|
|
82
|
+
## Quick start
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
sessionkit --version
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
It lists the kit and each family package, with its version:
|
|
89
|
+
|
|
90
|
+
```text
|
|
91
|
+
sessionkit, version 0.3.0
|
|
92
|
+
sessionmail 0.3.0
|
|
93
|
+
sessiontools 0.3.0
|
|
94
|
+
sessionphone 0.3.0
|
|
95
|
+
sessionview 0.3.0
|
|
96
|
+
sessionvoice 0.3.0
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Start Claude with `session start claude`, never a bare `claude`: mail to
|
|
100
|
+
your agent's box, your phone's included, reaches only a session started as
|
|
101
|
+
that agent. `sessionkit setup` makes the box and the agent first: [first
|
|
102
|
+
checks][checks].
|
|
103
|
+
|
|
104
|
+
Then, from your agent inside a Claude Code session:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
session list # your live sessions
|
|
108
|
+
message send <box> -s "PR ready" -f note.md # mail another agent
|
|
109
|
+
mouth say --device default "the build is green" # speak one line
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
- **Mail works inside a session.** `message` and `pager` read who is asking
|
|
113
|
+
from the session. A plain terminal has no session, so they refuse with
|
|
114
|
+
`no seat in the environment ... set an identity` ([first checks][checks]).
|
|
115
|
+
A send never makes a box: the address must have one already ([mail
|
|
116
|
+
between agents][mail]).
|
|
117
|
+
- **The mouth needs a device.** `--device default` is your Mac's default
|
|
118
|
+
output; `mouth devices` lists the others.
|
|
119
|
+
- **To hear you**, the ears need a speech-to-text server: [hearing and
|
|
120
|
+
speaking][hearing].
|
|
121
|
+
- **Your agent talks with the kit's mouth and ears.** sessionvoice's ears
|
|
122
|
+
skill sets the default:
|
|
123
|
+
|
|
124
|
+
> **With the kit, an agent talks through the kit's own mouth and ears: it
|
|
125
|
+
> speaks with `mouth say` and listens by arming the ears with `ears-arm`. That
|
|
126
|
+
> is the default whenever the user asks it to talk, speak, listen or use voice,
|
|
127
|
+
> even on a host that also has VoiceMode's `converse` tool. It uses `converse`
|
|
128
|
+
> only when the user asks for converse by name.**
|
|
129
|
+
|
|
130
|
+
## Uninstall
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
claude plugin marketplace remove sessionkit
|
|
134
|
+
uv tool uninstall sessionkit
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Removing the marketplace removes its six plugins too, but not the copies they
|
|
138
|
+
ran from. Those copies, the services and your data have their own steps:
|
|
139
|
+
[uninstall][uninstall].
|
|
140
|
+
|
|
141
|
+
## Usage
|
|
142
|
+
|
|
143
|
+
Every command answers `--help`, and each has a page: [components][components].
|
|
144
|
+
|
|
145
|
+
## The family
|
|
146
|
+
|
|
147
|
+
Session Kit installs these together:
|
|
148
|
+
|
|
149
|
+
- [sessionmail][pkg-mail]: mail for coding agents. `message`, `pager` and
|
|
150
|
+
`memory` over one maildir.
|
|
151
|
+
- [sessiontools][pkg-tools]: sessions, status and idle. `session`.
|
|
152
|
+
- [sessionvoice][pkg-voice]: ambient voice. `ears` hears; `mouth` speaks.
|
|
153
|
+
- [sessionphone][pkg-phone]: phone calls to a session over Delta Chat.
|
|
154
|
+
Installed, and walked on a fresh Mac for a session started with
|
|
155
|
+
`session start <name>`. A plain `claude` session does not hear calls you
|
|
156
|
+
make to it yet.
|
|
157
|
+
- [sessionview][pkg-view]: a tmux sidebar of your projects and your agents.
|
|
158
|
+
|
|
159
|
+
## Components
|
|
160
|
+
|
|
161
|
+
The commands, services and folders the install adds, and where each
|
|
162
|
+
package logs: [components][components].
|
|
163
|
+
|
|
164
|
+
## Configuration
|
|
165
|
+
|
|
166
|
+
Each package configures itself: [configuration][configuration]. How the
|
|
167
|
+
settings files stack: [the settings guide][settings].
|
|
168
|
+
|
|
169
|
+
## Logs
|
|
170
|
+
|
|
171
|
+
Each package keeps its own log: [logs][logs]. Which one to read for a
|
|
172
|
+
symptom: [troubleshooting][troubleshooting].
|
|
173
|
+
|
|
174
|
+
## Development
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
git clone https://github.com/sessionkit/sessionkit
|
|
178
|
+
cd sessionkit
|
|
179
|
+
make install # editable: uv tool install -e .
|
|
180
|
+
make check # ruff, shellcheck, then the tests
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
The rules for changing the kit: [AGENTS.md][agents].
|
|
184
|
+
|
|
185
|
+
## Status
|
|
186
|
+
|
|
187
|
+
Alpha, 0.3.0. Tested on macOS 27 on Apple silicon; Linux and Windows are
|
|
188
|
+
untested. What you may still hit: [known gaps][gaps].
|
|
189
|
+
|
|
190
|
+
Licence: [AGPL-3.0][licence].
|
|
191
|
+
|
|
192
|
+
[claude-code]: https://claude.ai/code
|
|
193
|
+
[brew]: https://brew.sh
|
|
194
|
+
[install]: INSTALL.md
|
|
195
|
+
[prereqs]: INSTALL.md#1-prerequisites-human
|
|
196
|
+
[checks]: INSTALL.md#4-first-checks
|
|
197
|
+
[hearing]: INSTALL.md#5-hearing-and-speaking-it-hears-me-and-speaks
|
|
198
|
+
[mail]: INSTALL.md#mail-between-agents
|
|
199
|
+
[uninstall]: INSTALL.md#8-uninstall
|
|
200
|
+
[gaps]: INSTALL.md#7-known-gaps-you-will-hit
|
|
201
|
+
[components]: docs/components.md
|
|
202
|
+
[logs]: docs/components.md#logs
|
|
203
|
+
[configuration]: docs/configuration.md
|
|
204
|
+
[settings]: docs/settings.md
|
|
205
|
+
[troubleshooting]: docs/troubleshooting.md
|
|
206
|
+
[agents]: AGENTS.md
|
|
207
|
+
[licence]: LICENSE
|
|
208
|
+
[pkg-mail]: https://github.com/sessionkit/sessionmail
|
|
209
|
+
[pkg-tools]: https://github.com/sessionkit/sessiontools
|
|
210
|
+
[pkg-voice]: https://github.com/sessionkit/sessionvoice
|
|
211
|
+
[pkg-phone]: https://github.com/sessionkit/sessionphone
|
|
212
|
+
[phone-signing]: https://github.com/sessionkit/sessionphone/blob/HEAD/docs/bot-mail-signing.md
|
|
213
|
+
[pkg-view]: https://github.com/sessionkit/sessionview
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sessionkit"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.3.0"
|
|
4
4
|
description = "Session Kit gives your Claude Code sessions voice, mail, a phone line and a tmux sidebar, in one install."
|
|
5
|
+
readme = "README.md"
|
|
5
6
|
license = "AGPL-3.0-or-later"
|
|
6
7
|
license-files = ["LICENSE"]
|
|
7
8
|
classifiers = ["Development Status :: 3 - Alpha"]
|
|
@@ -9,16 +10,22 @@ authors = [
|
|
|
9
10
|
{ name = "Mike Bailey", email = "mike@sessionkit.ai" }
|
|
10
11
|
]
|
|
11
12
|
requires-python = ">=3.11,<3.15"
|
|
12
|
-
# The family, one install. Floored at 0.
|
|
13
|
-
#
|
|
13
|
+
# The family, one install. Floored at 0.3.0: the kit is released and tested
|
|
14
|
+
# with the family's 0.3.0, so it needs every family package at 0.3.0 or
|
|
15
|
+
# later, and PyPI's 0.0.1 name placeholders and the 0.1.0 alphas must never
|
|
16
|
+
# satisfy these.
|
|
17
|
+
# sessionphone with its media extra: the bot, calls and `sessionphone web` need it.
|
|
14
18
|
# sessionvoice with its capture extra, so the ears can hear the room.
|
|
19
|
+
# This is the RULED list, kept in tests/_ruled_deps.py with the ruling;
|
|
20
|
+
# tests/test_dependency_gate.py fails when these lines, the built wheel or
|
|
21
|
+
# uv.lock drift from it.
|
|
15
22
|
dependencies = [
|
|
16
23
|
"click>=8.1",
|
|
17
|
-
"sessionmail>=0.
|
|
18
|
-
"sessiontools>=0.
|
|
19
|
-
"sessionphone>=0.
|
|
20
|
-
"sessionview>=0.
|
|
21
|
-
"sessionvoice[capture]>=0.
|
|
24
|
+
"sessionmail>=0.3.0",
|
|
25
|
+
"sessiontools>=0.3.0",
|
|
26
|
+
"sessionphone[media]>=0.3.0",
|
|
27
|
+
"sessionview>=0.3.0",
|
|
28
|
+
"sessionvoice[capture]>=0.3.0",
|
|
22
29
|
]
|
|
23
30
|
|
|
24
31
|
[project.urls]
|
|
@@ -31,8 +38,8 @@ Repository = "https://github.com/sessionkit/sessionkit"
|
|
|
31
38
|
# `uv tool install sessionkit` would put `sessionkit` alone on the PATH.
|
|
32
39
|
# Each entry point is the child's own target, copied verbatim; re-measure them
|
|
33
40
|
# at each child's HEAD whenever a child adds, renames or drops a command.
|
|
34
|
-
# Measured from each child's
|
|
35
|
-
# tools
|
|
41
|
+
# Measured from each child's 0.3.0 wheel (entry_points.txt), by wheel sha256:
|
|
42
|
+
# mail 8e4d9476, tools 8d089699, phone ac402a39, view 3942f119, voice ebc1b65c.
|
|
36
43
|
# sessionview's four commands share one target: `sessionview:main` reads the
|
|
37
44
|
# name it was run as and runs that command.
|
|
38
45
|
# The kit itself still ships no `session` of its own: `session` below is
|
|
@@ -90,13 +90,22 @@ def _shape(check):
|
|
|
90
90
|
help="Your own full mail address (name@host), written as the operator "
|
|
91
91
|
"instead of a mail box.",
|
|
92
92
|
)
|
|
93
|
+
@click.option(
|
|
94
|
+
"--agent",
|
|
95
|
+
metavar="NAME",
|
|
96
|
+
default=operator_setup.DEFAULT_AGENT,
|
|
97
|
+
show_default=True,
|
|
98
|
+
callback=_shape(operator_setup.check_agent_shape),
|
|
99
|
+
help="Your assistant's name: setup writes its agent file and makes its "
|
|
100
|
+
"mail box, so `session start NAME` starts Claude as NAME, hearing mail to NAME.",
|
|
101
|
+
)
|
|
93
102
|
@click.option(
|
|
94
103
|
"--dry-run",
|
|
95
104
|
is_flag=True,
|
|
96
105
|
help="Run every check and print what would be written; write nothing.",
|
|
97
106
|
)
|
|
98
|
-
def setup(mailbox: str | None, address: str | None, dry_run: bool) -> None:
|
|
99
|
-
"""Set up mail on this Mac,
|
|
107
|
+
def setup(mailbox: str | None, address: str | None, agent: str, dry_run: bool) -> None:
|
|
108
|
+
"""Set up mail on this Mac, write you as the operator, and define your assistant.
|
|
100
109
|
|
|
101
110
|
Mail first: it runs sessionmail's mail setup (`message setup`), which
|
|
102
111
|
makes the mail root and your box, and prints what it did and what is
|
|
@@ -109,13 +118,28 @@ def setup(mailbox: str | None, address: str | None, dry_run: bool) -> None:
|
|
|
109
118
|
confirm who you are: then it says how to name yourself, with --address
|
|
110
119
|
or --mailbox. It writes nothing, too, while the operator file an earlier
|
|
111
120
|
release wrote is still in place and none is at the new path: it names
|
|
112
|
-
both.
|
|
113
|
-
|
|
114
|
-
|
|
121
|
+
both.
|
|
122
|
+
|
|
123
|
+
Then your assistant (--agent, default claude): it writes NAME.md where
|
|
124
|
+
`session start` looks for agents (~/.session/agents, unless
|
|
125
|
+
$AGENTS_DIR or ~/.session/agent/config says otherwise), not keyed, and
|
|
126
|
+
asks `message setup` for the box NAME. A file already there, in either
|
|
127
|
+
layout, is left as it is. It ends with the one launch step:
|
|
128
|
+
`session start NAME`.
|
|
129
|
+
|
|
130
|
+
Before that step it asks `ears backends`, read only. If the ears'
|
|
131
|
+
voice-activity model is missing, it says one line naming `ears install`.
|
|
132
|
+
It never runs `ears install`, and the line never changes the exit.
|
|
133
|
+
|
|
134
|
+
Exit 0 all done, 1 refused or failed, 2 usage, 4 done what it could,
|
|
135
|
+
mail steps left for you. See INSTALL.md, "Operator and administrators"
|
|
136
|
+
and "Mail between agents".
|
|
115
137
|
"""
|
|
116
138
|
if mailbox is not None and address is not None:
|
|
117
139
|
raise click.UsageError("give --mailbox or --address, not both")
|
|
118
|
-
outcome = operator_setup.run_setup(
|
|
140
|
+
outcome = operator_setup.run_setup(
|
|
141
|
+
mailbox=mailbox, address=address, agent=agent, dry_run=dry_run
|
|
142
|
+
)
|
|
119
143
|
for line in outcome.lines:
|
|
120
144
|
click.echo(line, err=outcome.code != 0)
|
|
121
145
|
raise SystemExit(outcome.code)
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
"""`sessionkit setup`: make this host's mail ready,
|
|
1
|
+
"""`sessionkit setup`: make this host's mail ready, write the operator, then
|
|
2
|
+
define the assistant (`run_agent`: the file `session start NAME` resolves, and
|
|
3
|
+
the box it hears, so a seat started that way hears mail to NAME). Last, it
|
|
4
|
+
asks `ears backends` whether the ears' model is there, and says so when it is
|
|
5
|
+
not (`ears_model_missing`): read only, never `ears install`, never the exit.
|
|
2
6
|
|
|
3
7
|
Mail first. sessionmail's mail setup verb (argv in `MAIL_SETUP_ARGV`) makes
|
|
4
8
|
the mail root and the first boxes, the operator's box among them, and the
|
|
@@ -132,6 +136,21 @@ def check_mailbox_shape(name: str) -> str:
|
|
|
132
136
|
return name
|
|
133
137
|
|
|
134
138
|
|
|
139
|
+
def check_agent_shape(name: str) -> str:
|
|
140
|
+
"""NAME names the agent's mail box, so it is a mail box name. It names
|
|
141
|
+
its file too (`<agents folder>/NAME.md`), so it may not start with `.`:
|
|
142
|
+
`session start --list-agents` hides a dot-file, and a picker never offers it."""
|
|
143
|
+
try:
|
|
144
|
+
check_mailbox_shape(name)
|
|
145
|
+
except ShapeError:
|
|
146
|
+
raise ShapeError(
|
|
147
|
+
f"{name!r} is not an agent name: one plain name, no '/', no '@', no spaces"
|
|
148
|
+
) from None
|
|
149
|
+
if name.startswith("."):
|
|
150
|
+
raise ShapeError(f"{name!r} is not an agent name: it may not start with '.'")
|
|
151
|
+
return name
|
|
152
|
+
|
|
153
|
+
|
|
135
154
|
def check_address_shape(address: str) -> str:
|
|
136
155
|
"""ADDRESS is `local@domain`: exactly one `@`, both sides non-empty, no
|
|
137
156
|
whitespace, and not an example domain (a pasted placeholder)."""
|
|
@@ -303,8 +322,14 @@ def drop_in_text(block: Mapping[str, str]) -> str:
|
|
|
303
322
|
def write_exclusive(target: Path, text: str) -> str | None:
|
|
304
323
|
"""Create TARGET holding TEXT, only if it does not exist. None on
|
|
305
324
|
success, else why not. Never a partial target and never a stray
|
|
306
|
-
`*.json`: the temp's name ends in `.tmp-<pid>`, and it is removed.
|
|
307
|
-
|
|
325
|
+
`*.json`: the temp's name ends in `.tmp-<pid>`, and it is removed. A
|
|
326
|
+
folder that cannot be made (a file in the way, no permission) is a
|
|
327
|
+
reason too, never a traceback: the caller's other halves have run, and
|
|
328
|
+
their lines must still print."""
|
|
329
|
+
try:
|
|
330
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
331
|
+
except OSError as e:
|
|
332
|
+
return f"could not make {target.parent} ({e.strerror or e.__class__.__name__})"
|
|
308
333
|
temp = target.with_name(f".{target.name}.tmp-{os.getpid()}")
|
|
309
334
|
try:
|
|
310
335
|
# A temp by this pid's name is a dead run's leftover (no live process
|
|
@@ -344,17 +369,22 @@ MAIL_LEFT = 4
|
|
|
344
369
|
MAIL_DOC = 'INSTALL.md, "Mail between agents"'
|
|
345
370
|
|
|
346
371
|
|
|
347
|
-
def mail_setup_boxes(
|
|
372
|
+
def mail_setup_boxes(
|
|
373
|
+
mailbox: str | None, address: str | None, agent: str | None
|
|
374
|
+
) -> tuple[str, ...]:
|
|
348
375
|
"""The boxes the installer asks the verb for, as `--box NAME` each.
|
|
349
376
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
377
|
+
`--mailbox NAME` passes NAME, so a named box on a fresh install is made
|
|
378
|
+
before the operator check looks for it. Then the agent's box (`--agent`,
|
|
379
|
+
default `claude`): a seat started with `session start NAME` hears the
|
|
380
|
+
box NAME, so the box must exist for mail to it to land. The verb's own
|
|
381
|
+
floor (the operator's box, `operator.mailbox`, else the installing
|
|
382
|
+
account's name) is made besides. A session's own box is made by its
|
|
383
|
+
pager, never here. The verb is idempotent: a box that exists stays.
|
|
355
384
|
"""
|
|
356
385
|
del address # an address needs no box
|
|
357
|
-
|
|
386
|
+
boxes = [name for name in (mailbox, agent) if name is not None]
|
|
387
|
+
return tuple(dict.fromkeys(boxes))
|
|
358
388
|
|
|
359
389
|
|
|
360
390
|
def mail_setup_argv(boxes: Sequence[str], dry_run: bool) -> list[str]:
|
|
@@ -371,15 +401,18 @@ def _verb() -> str:
|
|
|
371
401
|
return " ".join(MAIL_SETUP_ARGV)
|
|
372
402
|
|
|
373
403
|
|
|
374
|
-
def _no_verb(why: str) -> Outcome:
|
|
375
|
-
"""This sessionmail cannot run the verb: print the exact step instead
|
|
404
|
+
def _no_verb(why: str, boxes: Sequence[str] = ()) -> Outcome:
|
|
405
|
+
"""This sessionmail cannot run the verb: print the exact step instead,
|
|
406
|
+
with the `--box NAME` setup would have passed (the agent's among them),
|
|
407
|
+
so running it later makes the same boxes a run of the verb would."""
|
|
408
|
+
step = " ".join(mail_setup_argv(boxes, dry_run=False))
|
|
376
409
|
return Outcome(
|
|
377
410
|
EXIT_STEPS_LEFT,
|
|
378
411
|
[
|
|
379
412
|
f"Mail not set up: {why}.",
|
|
380
413
|
(
|
|
381
414
|
f"A newer sessionmail has it. Until you have that, set mail up by "
|
|
382
|
-
f"hand ({MAIL_DOC}); once you do, run: {
|
|
415
|
+
f"hand ({MAIL_DOC}); once you do, run: {step}"
|
|
383
416
|
),
|
|
384
417
|
],
|
|
385
418
|
)
|
|
@@ -410,14 +443,15 @@ def run_mail_setup(
|
|
|
410
443
|
"""
|
|
411
444
|
program = MAIL_SETUP_ARGV[0]
|
|
412
445
|
if _find(program, environ) is None:
|
|
413
|
-
return _no_verb(f"`{program}` is not on the PATH")
|
|
446
|
+
return _no_verb(f"`{program}` is not on the PATH", boxes)
|
|
414
447
|
probe = _run([*MAIL_SETUP_ARGV, "--help"], environ)
|
|
415
448
|
if probe is None:
|
|
416
|
-
return _no_verb(f"`{_verb()} --help` did not answer")
|
|
449
|
+
return _no_verb(f"`{_verb()} --help` did not answer", boxes)
|
|
417
450
|
if probe.returncode != 0:
|
|
418
451
|
return _no_verb(
|
|
419
452
|
f"this sessionmail has no `{_verb()}` "
|
|
420
|
-
f"(`{_verb()} --help` exited {probe.returncode})"
|
|
453
|
+
f"(`{_verb()} --help` exited {probe.returncode})",
|
|
454
|
+
boxes,
|
|
421
455
|
)
|
|
422
456
|
|
|
423
457
|
argv = mail_setup_argv(boxes, dry_run)
|
|
@@ -463,6 +497,175 @@ def run_mail_setup(
|
|
|
463
497
|
)
|
|
464
498
|
|
|
465
499
|
|
|
500
|
+
# --- the agent: the file `session start NAME` resolves -------------------------
|
|
501
|
+
|
|
502
|
+
# The assistant setup defines when no `--agent` is given.
|
|
503
|
+
DEFAULT_AGENT = "claude"
|
|
504
|
+
|
|
505
|
+
# Where `session start NAME` looks for agents: sessiontools'
|
|
506
|
+
# `start/agents.py:agents_root`, the same rule, mirrored here so the kit does
|
|
507
|
+
# not import sessiontools' internals. `$AGENTS_DIR`, else
|
|
508
|
+
# `~/.session/agent/config` if that is a directory, else `~/.session/agents`.
|
|
509
|
+
# tests/test_setup.py holds this rule to the installed sessiontools'.
|
|
510
|
+
AGENTS_DIR_ENV = "AGENTS_DIR"
|
|
511
|
+
SESSION_HOME = ".session"
|
|
512
|
+
AGENT_CONFIG_DIR = ("agent", "config")
|
|
513
|
+
AGENTS_DIR_NAME = "agents"
|
|
514
|
+
|
|
515
|
+
# The reader: every agent `session start` would accept, one name a line.
|
|
516
|
+
LIST_AGENTS_ARGV: tuple[str, ...] = ("session", "start", "--list-agents")
|
|
517
|
+
|
|
518
|
+
# The one launch step, in sessionphone's final words: the same line its Delta
|
|
519
|
+
# bot prints. Spelled once: change it here only.
|
|
520
|
+
LAUNCH_SENTENCE = "To hear Delta messages, start Claude with: session start {agent}"
|
|
521
|
+
|
|
522
|
+
# The agent file. No `start:` keys: the seat is NOT keyed. No front matter at
|
|
523
|
+
# all: Claude Code (2.1.304, measured) names an agent by its file, so `name:`
|
|
524
|
+
# and `description:` are not needed. For any NAME but `claude` this text is
|
|
525
|
+
# the agent's prompt; for `claude`, Claude Code's own built-in agent of that
|
|
526
|
+
# name answers, and the file only has to exist for `session start` to find it.
|
|
527
|
+
AGENT_TEXT = """\
|
|
528
|
+
You are {agent}, a Claude Code assistant.
|
|
529
|
+
|
|
530
|
+
Written by `sessionkit setup`. `session start {agent}` starts Claude as
|
|
531
|
+
{agent}, so the session hears mail to the box {agent}. Edit this file as you
|
|
532
|
+
like: setup never writes over it.
|
|
533
|
+
"""
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
def agents_root(home: Path, environ: Mapping[str, str]) -> Path:
|
|
537
|
+
"""The agents folder `session start` reads, by sessiontools' rule."""
|
|
538
|
+
named = environ.get(AGENTS_DIR_ENV)
|
|
539
|
+
if named:
|
|
540
|
+
return Path(os.path.expanduser(named))
|
|
541
|
+
config = home / SESSION_HOME / Path(*AGENT_CONFIG_DIR)
|
|
542
|
+
if config.is_dir():
|
|
543
|
+
return config
|
|
544
|
+
return home / SESSION_HOME / AGENTS_DIR_NAME
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
def agent_file(home: Path, environ: Mapping[str, str], agent: str) -> Path:
|
|
548
|
+
"""The file setup writes for AGENT: the flat layout, `<root>/NAME.md`."""
|
|
549
|
+
return agents_root(home, environ) / f"{agent}.md"
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
def agent_text(agent: str) -> str:
|
|
553
|
+
return AGENT_TEXT.format(agent=agent)
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
def launch_sentence(agent: str) -> str:
|
|
557
|
+
return LAUNCH_SENTENCE.format(agent=agent)
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
def read_listed_agents(environ: Mapping[str, str]) -> list[str] | None:
|
|
561
|
+
"""`session start --list-agents`, one name a line, or None when it does
|
|
562
|
+
not answer (absent, non-zero, a timeout): "not verified", never "absent"."""
|
|
563
|
+
done = _run(LIST_AGENTS_ARGV, environ)
|
|
564
|
+
if done is None or done.returncode != 0:
|
|
565
|
+
return None
|
|
566
|
+
return [line.strip() for line in done.stdout.splitlines() if line.strip()]
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
def run_agent(
|
|
570
|
+
*,
|
|
571
|
+
agent: str,
|
|
572
|
+
dry_run: bool = False,
|
|
573
|
+
home: Path | None = None,
|
|
574
|
+
environ: Mapping[str, str] | None = None,
|
|
575
|
+
) -> Outcome:
|
|
576
|
+
"""Write AGENT's file where `session start` looks, once; then verify.
|
|
577
|
+
|
|
578
|
+
Never clobbers: either layout `session start` reads (`<root>/NAME/NAME.md`,
|
|
579
|
+
which wins, or `<root>/NAME.md`) already there means write nothing. A
|
|
580
|
+
folder `<root>/NAME/` without its NAME.md, or a flat path that is not a
|
|
581
|
+
file, is refused: `session start` could not start NAME from it, and
|
|
582
|
+
writing beside it would not change that. AGENT is already shape-checked.
|
|
583
|
+
"""
|
|
584
|
+
environ = os.environ if environ is None else environ
|
|
585
|
+
home = Path.home() if home is None else home
|
|
586
|
+
root = agents_root(home, environ)
|
|
587
|
+
folder = root / agent
|
|
588
|
+
flat = agent_file(home, environ, agent)
|
|
589
|
+
if folder.is_dir():
|
|
590
|
+
inside = folder / f"{agent}.md"
|
|
591
|
+
if inside.is_file():
|
|
592
|
+
return Outcome(
|
|
593
|
+
EXIT_OK, [f"Agent {agent!r} is already in {inside}; left as it is."]
|
|
594
|
+
)
|
|
595
|
+
return Outcome(
|
|
596
|
+
EXIT_REFUSED,
|
|
597
|
+
[
|
|
598
|
+
(
|
|
599
|
+
f"No agent file written: {folder} exists without {agent}.md "
|
|
600
|
+
f"in it, so session start cannot start {agent!r}."
|
|
601
|
+
),
|
|
602
|
+
f"Put {agent}.md in that folder, or remove it, then run sessionkit setup again.",
|
|
603
|
+
],
|
|
604
|
+
)
|
|
605
|
+
if flat.is_file():
|
|
606
|
+
return Outcome(
|
|
607
|
+
EXIT_OK, [f"Agent {agent!r} is already in {flat}; left as it is."]
|
|
608
|
+
)
|
|
609
|
+
if os.path.lexists(flat):
|
|
610
|
+
return Outcome(
|
|
611
|
+
EXIT_REFUSED,
|
|
612
|
+
[
|
|
613
|
+
f"No agent file written: {flat} exists and is not a file session start can read.",
|
|
614
|
+
"Remove it, then run sessionkit setup again.",
|
|
615
|
+
],
|
|
616
|
+
)
|
|
617
|
+
text = agent_text(agent)
|
|
618
|
+
if dry_run:
|
|
619
|
+
return Outcome(EXIT_OK, [f"Would write {flat}:", text.rstrip("\n")])
|
|
620
|
+
|
|
621
|
+
failed = write_exclusive(flat, text)
|
|
622
|
+
if failed is not None:
|
|
623
|
+
return Outcome(EXIT_REFUSED, [f"No agent file written: {failed}."])
|
|
624
|
+
lines = [f"Wrote {flat}: the agent {agent!r}, not keyed."]
|
|
625
|
+
|
|
626
|
+
listed = read_listed_agents(environ)
|
|
627
|
+
verb = " ".join(LIST_AGENTS_ARGV)
|
|
628
|
+
if listed is None:
|
|
629
|
+
lines.append(f"Written; not verified: `{verb}` did not answer.")
|
|
630
|
+
return Outcome(EXIT_OK, lines)
|
|
631
|
+
if agent not in listed:
|
|
632
|
+
lines.append(
|
|
633
|
+
f"WARNING: `{verb}` does not list {agent!r}: the `session` on your "
|
|
634
|
+
"PATH looks in another agents folder ($AGENTS_DIR, or another sessiontools)."
|
|
635
|
+
)
|
|
636
|
+
return Outcome(EXIT_REFUSED, lines)
|
|
637
|
+
lines.append(f"{verb}: lists {agent!r}.")
|
|
638
|
+
return Outcome(EXIT_OK, lines)
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
# --- the ears' model: read, never fetched -------------------------------------
|
|
642
|
+
|
|
643
|
+
# sessionvoice's read-only probe. Its last line is `silero ok: PATH` or
|
|
644
|
+
# `silero no model: ears install` (sessionvoice 0.2.1, `ears backends`).
|
|
645
|
+
EARS_BACKENDS_ARGV = ("ears", "backends")
|
|
646
|
+
# The probe answers in well under a second; a slow one is never waited on.
|
|
647
|
+
EARS_PROBE_TIMEOUT = 5.0
|
|
648
|
+
EARS_MODEL_LINE = (
|
|
649
|
+
"The ears have no voice-activity model, so they cannot hear you: "
|
|
650
|
+
"run `ears install` to fetch it."
|
|
651
|
+
)
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
def ears_model_missing(environ: Mapping[str, str]) -> bool:
|
|
655
|
+
"""True only when `ears backends` answers, exit 0, with a `silero` line
|
|
656
|
+
saying `no model`. `ears` not on the PATH (no voice here), a timeout, a
|
|
657
|
+
failure or output we do not know all read as False: say nothing. Setup
|
|
658
|
+
never runs `ears install`; the user does."""
|
|
659
|
+
done = _run(EARS_BACKENDS_ARGV, environ, timeout=EARS_PROBE_TIMEOUT)
|
|
660
|
+
if done is None or done.returncode != 0:
|
|
661
|
+
return False
|
|
662
|
+
for line in (done.stdout or "").splitlines():
|
|
663
|
+
name, _, state = line.strip().partition(" ")
|
|
664
|
+
if name == "silero":
|
|
665
|
+
return state.strip().startswith("no model")
|
|
666
|
+
return False
|
|
667
|
+
|
|
668
|
+
|
|
466
669
|
# --- the run ------------------------------------------------------------------
|
|
467
670
|
|
|
468
671
|
|
|
@@ -513,21 +716,25 @@ def run_setup(
|
|
|
513
716
|
*,
|
|
514
717
|
mailbox: str | None = None,
|
|
515
718
|
address: str | None = None,
|
|
719
|
+
agent: str = DEFAULT_AGENT,
|
|
516
720
|
dry_run: bool = False,
|
|
517
721
|
home: Path | None = None,
|
|
518
722
|
environ: Mapping[str, str] | None = None,
|
|
519
723
|
) -> Outcome:
|
|
520
|
-
"""The stranded check, then mail,
|
|
724
|
+
"""The stranded check, then mail, the operator and the agent; all print.
|
|
521
725
|
|
|
522
726
|
An operator file stranded at the 0.1.0 place, with none at the new one,
|
|
523
727
|
stops the whole run before mail: nothing is made or written, one line.
|
|
524
728
|
The verb would otherwise make the operator's box from settings that
|
|
525
|
-
cannot see that file. Past it,
|
|
729
|
+
cannot see that file. Past it, all three always run. Mail goes first
|
|
526
730
|
because the operator is written only for a box that exists, and the
|
|
527
|
-
mail setup verb is what makes the operator's box
|
|
528
|
-
of the
|
|
529
|
-
for you, else 0.
|
|
530
|
-
|
|
731
|
+
mail setup verb is what makes the operator's box and the agent's. The
|
|
732
|
+
exit is the worst of the three: 1 if any refused or failed, else 4 if
|
|
733
|
+
mail left steps for you, else 0. Then `ears backends` is asked, read
|
|
734
|
+
only: when the ears' model is missing, one line (EARS_MODEL_LINE) names
|
|
735
|
+
`ears install`; it never changes the exit. With the agent defined, the
|
|
736
|
+
run ends on the one launch step (LAUNCH_SENTENCE). MAILBOX / ADDRESS /
|
|
737
|
+
AGENT are already shape-checked by the caller.
|
|
531
738
|
"""
|
|
532
739
|
environ = os.environ if environ is None else environ
|
|
533
740
|
resolved = Path.home() if home is None else home
|
|
@@ -538,18 +745,32 @@ def run_setup(
|
|
|
538
745
|
# sessionmail's default carrier, and postfix is the user's opt-in.
|
|
539
746
|
# `--write-maps` is never passed: writing postfix maps is opt-in too.
|
|
540
747
|
mail = run_mail_setup(
|
|
541
|
-
boxes=mail_setup_boxes(mailbox, address
|
|
748
|
+
boxes=mail_setup_boxes(mailbox, address, agent),
|
|
749
|
+
dry_run=dry_run,
|
|
750
|
+
environ=environ,
|
|
542
751
|
)
|
|
543
752
|
operator = run_operator(
|
|
544
753
|
mailbox=mailbox, address=address, dry_run=dry_run, home=home, environ=environ
|
|
545
754
|
)
|
|
546
|
-
|
|
755
|
+
defined = run_agent(agent=agent, dry_run=dry_run, home=resolved, environ=environ)
|
|
756
|
+
codes = (mail.code, operator.code, defined.code)
|
|
757
|
+
if EXIT_REFUSED in codes:
|
|
547
758
|
code = EXIT_REFUSED
|
|
548
|
-
elif EXIT_STEPS_LEFT in
|
|
759
|
+
elif EXIT_STEPS_LEFT in codes:
|
|
549
760
|
code = EXIT_STEPS_LEFT
|
|
550
761
|
else:
|
|
551
762
|
code = EXIT_OK
|
|
552
|
-
|
|
763
|
+
lines = mail.lines + operator.lines + defined.lines
|
|
764
|
+
if dry_run:
|
|
765
|
+
lines.append(
|
|
766
|
+
f"Would ask `{_verb()}` for the agent's box {agent!r} (--box {agent})."
|
|
767
|
+
)
|
|
768
|
+
# Read only, and never part of the exit: 4 means mail steps are left.
|
|
769
|
+
if ears_model_missing(environ):
|
|
770
|
+
lines.append(EARS_MODEL_LINE)
|
|
771
|
+
if defined.code == EXIT_OK:
|
|
772
|
+
lines.append(launch_sentence(agent))
|
|
773
|
+
return Outcome(code, lines)
|
|
553
774
|
|
|
554
775
|
|
|
555
776
|
def run_operator(
|
sessionkit-0.2.0/PKG-INFO
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: sessionkit
|
|
3
|
-
Version: 0.2.0
|
|
4
|
-
Summary: Session Kit gives your Claude Code sessions voice, mail, a phone line and a tmux sidebar, in one install.
|
|
5
|
-
Author: Mike Bailey
|
|
6
|
-
Author-email: Mike Bailey <mike@sessionkit.ai>
|
|
7
|
-
License-Expression: AGPL-3.0-or-later
|
|
8
|
-
License-File: LICENSE
|
|
9
|
-
Classifier: Development Status :: 3 - Alpha
|
|
10
|
-
Requires-Dist: click>=8.1
|
|
11
|
-
Requires-Dist: sessionmail>=0.1.0
|
|
12
|
-
Requires-Dist: sessiontools>=0.1.0
|
|
13
|
-
Requires-Dist: sessionphone>=0.1.0
|
|
14
|
-
Requires-Dist: sessionview>=0.1.0
|
|
15
|
-
Requires-Dist: sessionvoice[capture]>=0.1.0
|
|
16
|
-
Requires-Python: >=3.11, <3.15
|
|
17
|
-
Project-URL: Homepage, https://github.com/sessionkit/sessionkit
|
|
18
|
-
Project-URL: Repository, https://github.com/sessionkit/sessionkit
|
|
File without changes
|
|
File without changes
|
|
File without changes
|