samagotchi 0.118.0 → 0.119.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 06322a414267628aff61367b71dc4a6ecb76d4789673d594aff987a8da678d94
4
- data.tar.gz: 12cc40d770cfd7072ccb1a64543033da5619f19d472ae69e7db880ae9a7f867f
3
+ metadata.gz: 67772cbed2c590109f257a85de25cc009250b6fb3a78c1bf4e1b2194561846b7
4
+ data.tar.gz: ac023d3b8f28c1196b234e528eae9faf9947181ee6f44111c7f00723eb92b196
5
5
  SHA512:
6
- metadata.gz: 0db2e221e335c74dcb3770ec17d92a2ded1b4e92589e958e33f95008e0ab5a9e13d72aed2ebb3bc2875fa6800e5cba6e19898b68515fda951a1709b5fc864a09
7
- data.tar.gz: 7d8e40e0f7966a75d0914229a6e7a94b626ed9a045451ab65c154eef2eea3a5e8ba81eb20fc90cff703986e063fbfbef55225919a4be47e92b3b6b28ae611bbf
6
+ metadata.gz: e818ad092946ec8ca8d1765079824a0cd7f540734fc9ad52b1a28d2ad70f12be7ff21324f348eda1461474d2924e99f97fa5f45fe25e11e16cc65c7e2a7f63af
7
+ data.tar.gz: 2c852f4b48dcee1bd4c742eb3acdbe34ee802a53407b562b09398b13be34a9de3bcbdeebb058781691c07a7c696797ea660133e84155ccb2247ab6e9e5f72596
data/CHANGELOG.md CHANGED
@@ -7,6 +7,15 @@ and commands may change between minor versions. How releases are made:
7
7
  [docs/releasing.md](docs/releasing.md).
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.119.0] - 2026-10-26
11
+
12
+ ### Added
13
+
14
+ - claude-peer (0.4.3): `pair --inbox-socket PATH` pairs both ways with chi on
15
+ another machine through an `ssh -R` forward of Claude's socket
16
+ ([docs/plugins.md](docs/plugins.md)); docs/cli.md: remote sessions through an
17
+ SSH tunnel. Update with `chi update`.
18
+
10
19
  ## [0.118.0] - 2026-10-26
11
20
 
12
21
  ### Changed
@@ -4445,7 +4454,8 @@ and long-lived sessions.
4445
4454
  - A macOS desktop helper (`chi desktop install`): a "Send to chi" Service and a
4446
4455
  hotkey panel that send selected text or the clipboard to your sessions.
4447
4456
 
4448
- [Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.118.0...HEAD
4457
+ [Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.119.0...HEAD
4458
+ [0.119.0]: https://github.com/dm1try/samagotchi/compare/v0.118.0...v0.119.0
4449
4459
  [0.118.0]: https://github.com/dm1try/samagotchi/compare/v0.117.0...v0.118.0
4450
4460
  [0.117.0]: https://github.com/dm1try/samagotchi/compare/v0.116.0...v0.117.0
4451
4461
  [0.116.0]: https://github.com/dm1try/samagotchi/compare/v0.115.0...v0.116.0
data/docs/cli.md CHANGED
@@ -1336,6 +1336,35 @@ chi --remote studio --dir /Users/me/app -p "hi" # a new session there (--
1336
1336
  has installed (`chi --remote studio sessions restart ab12`), this terminal
1337
1337
  older than studio (`chi update` here).
1338
1338
 
1339
+ #### Through an SSH tunnel
1340
+
1341
+ When that machine's chi web is only reachable over SSH — behind a sign-in proxy
1342
+ (`chi --remote` through one isn't supported yet), or bound to 127.0.0.1 —
1343
+ forward its port and add it as a remote like any other:
1344
+
1345
+ ```sh
1346
+ ssh -N -L 14567:127.0.0.1:4567 studio # the host's web.port, 4567 by default
1347
+ chi remote add work http://127.0.0.1:14567 --token-file work-token
1348
+ # work-token: the host's $XDG_STATE_HOME/samagotchi/web-token
1349
+ chi --remote work sessions list
1350
+ ```
1351
+
1352
+ - Pick a local port other than 14567 when this machine runs its own chi web.
1353
+ - Forward only chi web's port, never a worker's: a session's Bridge is
1354
+ unauthenticated.
1355
+ - Without `web.public_url` there, a request that arrives through the tunnel
1356
+ looks local and needs no token, so any process here reaching the forwarded
1357
+ port acts as you there. With it set, every request needs the token, which
1358
+ `chi --remote` sends as Bearer.
1359
+ - `-L` binds 127.0.0.1 unless you write `-g` or `0.0.0.0:`: don't.
1360
+ - Attaching works through the tunnel too (`chi --remote work --attach ab12`),
1361
+ plain http, no proxy.
1362
+
1363
+ SSH can forward a Unix socket as well, which is how claude-peer's pairing makes
1364
+ a Claude session on this laptop reachable from chi on that machine
1365
+ (`pair --inbox-socket <path>`): see [plugins.md](plugins.md), "chi on another
1366
+ machine".
1367
+
1339
1368
  How it works inside (`SessionAccess`, chi web's routes, where each check runs):
1340
1369
  [internals/remote-sessions.md](internals/remote-sessions.md).
1341
1370
 
@@ -1659,6 +1659,8 @@ What changes once `web.public_url` is set:
1659
1659
  `X-Forwarded-For` or `Forwarded` (403 `proxied`). That is a heuristic, not a lock: a forward that adds no such header
1660
1660
  (nginx's `proxy_pass` with its defaults, a plain TCP forward) still reaches a worker, as the owner.
1661
1661
  - `chi --remote` to a chi web behind a sign-in proxy isn't supported yet.
1662
+ SSH to that machine works instead: forward its port and add it as a remote
1663
+ ([Through an SSH tunnel](cli.md#through-an-ssh-tunnel)).
1662
1664
 
1663
1665
  > **Warning.** Until Bridges get a secret of their own (planned), don't run chi web behind a proxy or tunnel that can
1664
1666
  > reach any local port, such as a pod forwarding every `https://<port>.<pod>…`, unless it adds `X-Forwarded-For` or
data/docs/plugins.md CHANGED
@@ -2249,6 +2249,26 @@ Claude pairs; without one the bundle costs one tool declaration.
2249
2249
  them (or ask it to run `chi --remote studio send --wait <id>`). A session
2250
2250
  that already has a `claude-inbox` it can't reach:
2251
2251
  `chi --remote studio context rm claude-inbox <id>`.
2252
+ **Two ways to make it two-way over SSH.** SSH can forward a Unix socket, so
2253
+ the inbox leg works too. On the laptop:
2254
+
2255
+ ```sh
2256
+ ssh studio rm -f /tmp/claude-peer-ab12.sock
2257
+ ssh -N -o ExitOnForwardFailure=yes -R /tmp/claude-peer-ab12.sock:"$CLAUDE_CODE_MESSAGING_SOCKET" studio
2258
+ ```
2259
+
2260
+ the second running in the background while chi may send (the `rm -f` first:
2261
+ a remote forward's stale socket is replaced only when the **server's** sshd
2262
+ has `StreamLocalBindUnlink yes`). Then
2263
+ `CHI_REMOTE=studio sh <bundle>/scripts/pair --inbox-socket /tmp/claude-peer-ab12.sock ab12`
2264
+ attaches both sources, with `claude-inbox`'s hint that path on the Studio. A
2265
+ re-pair this way replaces an outbox-only pairing's `claude-outbox` and its
2266
+ why. Claude's socket changes when that Claude session restarts: stop the ssh,
2267
+ run both lines, pair again. A path chi quotes (a `payload:` file, a diff to
2268
+ check) is on the Studio, not the laptop. Whoever can write to that socket on
2269
+ the remote machine can send into this Claude session (sshd makes it the
2270
+ user's, mode 0600 by default): fine on your own machine or pod, not a shared
2271
+ host. Claude's own skill has the same under "chi on another machine over SSH".
2252
2272
  - **Asking first.** The tool has no `targets:`, so guardrails match it by name
2253
2273
  only: to be asked before each send, add a rule ([Rules in
2254
2274
  config.yml](guardrails.md#rules-in-configyml)):
@@ -2353,6 +2373,8 @@ skill install card) works the same.
2353
2373
  <bundle>/scripts/pair <id>` (or `--remote NAME` in `CHI`) pairs through
2354
2374
  the Studio's chi web, and only the outbox leg works: `codex_send` runs
2355
2375
  `codex queue` on the machine chi runs on, where this Codex's thread isn't.
2376
+ Claude's `--inbox-socket` trick doesn't apply here: `codex-inbox`'s hint is a
2377
+ thread id, not a socket, so there is nothing to forward through SSH.
2356
2378
  So `pair` attaches only `codex-outbox` and prints one line saying chi there
2357
2379
  can't send to this Codex. Through a remote chi talks HTTP, not files, so
2358
2380
  `writable_roots` doesn't matter; Codex's sandbox may block the network
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: claude-peer
3
- version: 0.4.2
3
+ version: 0.4.3
4
4
  scope: system
5
5
  description: "Pair this session with a Claude Code session you work with: the claude_send tool and /claude send it a part of the work (a plan step, a diff, a finding) to validate or as context, and Claude's answer comes back as the claude-outbox context; /claude install-skill installs Claude's chi-peer skill, which pairs"
6
6
  trust_level: reviewed
@@ -12,8 +12,8 @@ plugin:
12
12
  file: plugin.rb
13
13
  sha256: sha256:50f427d3862ae9d0d15693b4d887c528162b96c0320cc663df89d59d830d4660
14
14
  scripts:
15
- pair: sha256:2e66103c43fce5eab05f897682ba943f394743c1ad47bf439be3ce529f1b2058
16
- chi-peer-SKILL.md: sha256:e991a91139a127a60f9a216043eb5864945fb211952a1e3c8db2068c6cc254f1
15
+ pair: sha256:bd850e8091098e249ac5b9b04a95a4d8a0dd8501cf9d58639a47d4f3bce1d757
16
+ chi-peer-SKILL.md: sha256:b9366f8f4aee9c54d209368ce421eab7b971c03c75efab0415d1988fd40f9e60
17
17
  # The plugin needs Samagotchi::Plugin::Peer and chi.peer_source (chi 0.67.0).
18
18
  # An agreeing answer's closing wake (the skills say an OK wakes chi once)
19
19
  # and the plugin's OUTBOX (Plugin::Peer's rename of REVIEW) need chi 0.70.0.
@@ -89,6 +89,54 @@ EOF
89
89
  - Never answer a chi message with `chi send`: it counts as the user typing,
90
90
  outside the round's limit. Use it only when the user asks you to.
91
91
 
92
+ ## chi on another machine over SSH
93
+
94
+ When the chi session runs on a machine this Claude session reaches with SSH
95
+ (the user says so, or `CHI_REMOTE` or a remote URL is in play) and chi should
96
+ send here too, pair with `--inbox-socket`: the socket chi writes to is then one
97
+ on chi's machine, an `ssh -R` forward of this session's socket.
98
+
99
+ You need a chi remote for that machine first (`chi remote add <name> <url>`,
100
+ through an `ssh -L` tunnel if that is how you reach it — docs/cli.md "Through
101
+ an SSH tunnel"). Then, with the host and the chi session's first 8 chars:
102
+
103
+ ```sh
104
+ ssh <host> rm -f /tmp/claude-peer-<id8>.sock
105
+ ssh -N -o ExitOnForwardFailure=yes -R /tmp/claude-peer-<id8>.sock:"$CLAUDE_CODE_MESSAGING_SOCKET" <host>
106
+ ```
107
+
108
+ Run the second in the background: it must keep running while chi may send. If
109
+ ssh wants a password or 2FA, print both lines with the socket spelled out and
110
+ ask the user to run them in a terminal. The `rm -f` matters: a stale remote
111
+ socket is not replaced unless the **server's** sshd has `StreamLocalBindUnlink
112
+ yes` (the client's `-o StreamLocalBindUnlink` covers `-L` only). A server with
113
+ `AllowStreamLocalForwarding no` refuses the forward at start
114
+ (`ExitOnForwardFailure`): tell the user, there is no fallback.
115
+
116
+ Then pair through the remote:
117
+
118
+ ```sh
119
+ CHI_REMOTE=<name> sh "__CHI_PEER_PAIR__" --inbox-socket /tmp/claude-peer-<id8>.sock <id>
120
+ ```
121
+
122
+ The socket must be visible to chi's process on that machine (chi runs as the
123
+ SSH user, in the same `/tmp`, not inside a separate container): if `/claude`
124
+ in chi shows the socket **missing** while ssh is running, that is why.
125
+
126
+ Replies are the same push, through the remote:
127
+ `CHI_REMOTE=<name> chi context push claude-outbox <id>`.
128
+
129
+ A path in a chi message — a `payload: <path>` file, a file or a diff to check —
130
+ is on `<host>`, not here: read it with `ssh <host> cat <path>` (or the repo
131
+ there). The `Read(...)` permission note for payload files does not apply.
132
+
133
+ After this Claude session restarts its socket changes: stop the old ssh, run
134
+ both lines again, pair again.
135
+
136
+ Security: whoever can write to that socket on the remote machine can send
137
+ prompts into this session (sshd makes it the user's, mode 0600 by default).
138
+ Fine on the user's own machine or pod, not on a shared host.
139
+
92
140
  ## Don't loop
93
141
 
94
142
  Never reply to an acknowledgement, a thanks, or a message that only confirms
@@ -10,22 +10,45 @@
10
10
  # only refreshes the socket: the push carries the new hint. CHI names the chi
11
11
  # command (default: chi on the PATH). Through a remote (CHI_REMOTE, or
12
12
  # --remote NAME in CHI) chi there can't reach this machine's socket: it
13
- # attaches claude-outbox only, and says so.
13
+ # attaches claude-outbox only, and says so. With --inbox-socket PATH the hint
14
+ # is PATH instead, a socket on chi's machine made by forwarding this Claude's
15
+ # socket there with `ssh -R`, so chi can send here too (docs/plugins.md).
14
16
  set -eu
15
17
 
16
18
  usage() {
17
- echo "usage: pair <chi session id or URL (http://127.0.0.1:4567/#/s/<id>)>" >&2
19
+ echo "usage: pair [--inbox-socket PATH] <chi session id or URL (http://127.0.0.1:4567/#/s/<id>)>" >&2
18
20
  exit 2
19
21
  }
20
22
 
21
- [ $# -eq 1 ] || usage
22
- case "$1" in
23
- -h | --help) usage ;;
24
- http://* | https://*)
25
- id=$(printf '%s\n' "$1" | sed -n 's|.*/s/\([0-9A-Za-z-][0-9A-Za-z-]*\).*|\1|p')
26
- [ -n "$id" ] || { echo "pair: no session id in $1 (a chi web URL ends in #/s/<id>)" >&2; exit 2; }
27
- ;;
28
- *) id=$1 ;;
23
+ # The id (or URL) and, optionally, the socket on chi's machine to send through.
24
+ inbox= target=
25
+ while [ $# -gt 0 ]; do
26
+ case "$1" in
27
+ --inbox-socket)
28
+ [ $# -ge 2 ] || usage
29
+ inbox=$2 shift 2
30
+ [ -n "$inbox" ] || usage
31
+ ;;
32
+ --inbox-socket=*)
33
+ inbox=${1#--inbox-socket=} shift
34
+ [ -n "$inbox" ] || usage
35
+ ;;
36
+ -h | --help) usage ;;
37
+ *)
38
+ [ -z "$target" ] || usage
39
+ target=$1 shift
40
+ ;;
41
+ esac
42
+ done
43
+ [ -n "$target" ] || usage
44
+
45
+ # A relative path would be chi's cwd, not the forward's remote end: the
46
+ # plugin's check refuses it, so refuse it here with the reason.
47
+ case "$inbox" in
48
+ "") ;;
49
+ /*) ;;
50
+ *) echo "pair: --inbox-socket needs an absolute socket path on chi's machine (the ssh -R forward's remote end): $inbox" >&2
51
+ exit 2 ;;
29
52
  esac
30
53
 
31
54
  sock=${CLAUDE_CODE_MESSAGING_SOCKET:-}
@@ -33,6 +56,7 @@ if [ -z "$sock" ]; then
33
56
  echo "pair: CLAUDE_CODE_MESSAGING_SOCKET isn't set: run this from a Claude Code session (2.1.224 or later)" >&2
34
57
  exit 2
35
58
  fi
59
+
36
60
  chi=${CHI:-chi}
37
61
 
38
62
  # The remote, as SessionAccess::Dispatch names it: a --remote NAME (or
@@ -52,6 +76,14 @@ for word in $chi; do
52
76
  esac
53
77
  done
54
78
 
79
+ case "$target" in
80
+ http://* | https://*)
81
+ id=$(printf '%s\n' "$target" | sed -n 's|.*/s/\([0-9A-Za-z-][0-9A-Za-z-]*\).*|\1|p')
82
+ [ -n "$id" ] || { echo "pair: no session id in $target (a chi web URL ends in #/s/<id>)" >&2; exit 2; }
83
+ ;;
84
+ *) id=$target ;;
85
+ esac
86
+
55
87
  # A JSON string: backslashes, quotes, tabs and newlines escaped.
56
88
  json() {
57
89
  printf '%s' "$1" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' -e 's/ /\\t/g' |
@@ -70,26 +102,51 @@ attach() {
70
102
  fi
71
103
  }
72
104
 
73
- if [ -n "$via" ]; then
105
+ # Without a socket chi can reach, only the outbox leg works.
106
+ if [ -n "$via" ] && [ -z "$inbox" ]; then
74
107
  attach claude-outbox --why "Claude's answers to what you sent it (claude-peer); Claude runs on another machine: claude_send can't reach it" \
75
108
  --hint "latest Claude reply"
76
109
  ask=$chi
77
110
  [ "$via" = chi ] || ask="CHI_REMOTE=$remote $chi"
78
111
  printf '%s\n' "pair: chi is on $remote, so chi can't send to this Claude session (claude_send needs its socket on chi's machine): paired with chi $id, claude-outbox only. Answer chi with $ask context push claude-outbox $id; chi's replies reach you only when the user relays them (or asks you to run $ask send --wait $id)"
112
+ printf '%s\n' "pair: to let chi send here too, forward this Claude's socket to $remote with ssh -R and pair again with --inbox-socket (the chi-peer skill's \"chi on another machine over SSH\")"
79
113
  $chi context ls "$id"
80
114
  exit 0
81
115
  fi
82
116
 
83
- attach claude-inbox --why "the Claude Code session paired with you: send it parts with claude_send" --hint "$sock"
84
- text="Claude Code peer inbox
117
+ hint=${inbox:-$sock}
118
+ if [ -z "$inbox" ]; then
119
+ text="Claude Code peer inbox
85
120
  socket: $sock
86
121
  claude cwd: $PWD
87
122
  paired: $(date '+%Y-%m-%d %H:%M')
88
123
  Send to it with the claude_send tool (your skill_claude_peer memory); its answers come as the claude-outbox context."
89
- printf '{"text":%s,"hint":%s,"summary":%s}\n' "$(json "$text")" "$(json "$sock")" \
90
- "$(json "Claude Code paired from $PWD")" | $chi context push claude-inbox "$id" >/dev/null
124
+ summary="Claude Code paired from $PWD"
125
+ else
126
+ text="Claude Code peer inbox
127
+ socket: $inbox (on this machine: an ssh -R forward of Claude's socket $sock on Claude's machine)
128
+ claude cwd: $PWD
129
+ paired: $(date '+%Y-%m-%d %H:%M')
130
+ Send to it with the claude_send tool (your skill_claude_peer memory); its answers come as the claude-outbox context.
131
+ If claude_send says the Claude session is gone, the ssh tunnel may be down: tell the user to restart it and pair again."
132
+ summary="Claude Code paired from $PWD over ssh"
133
+ fi
134
+
135
+ attach claude-inbox --why "the Claude Code session paired with you: send it parts with claude_send" --hint "$hint"
136
+ printf '{"text":%s,"hint":%s,"summary":%s}\n' "$(json "$text")" "$(json "$hint")" "$(json "$summary")" \
137
+ | $chi context push claude-inbox "$id" >/dev/null
91
138
 
139
+ # An outbox attached by an earlier outbox-only pairing carries the why that
140
+ # says claude_send can't reach Claude; context add never updates an existing
141
+ # source, so replace it (Claude's last answer goes with it).
142
+ if $chi context show claude-outbox --json "$id" 2>/dev/null | grep -q "can't reach it"; then
143
+ $chi context rm claude-outbox "$id" >/dev/null
144
+ fi
92
145
  attach claude-outbox --why "Claude's answers to what you sent it (claude-peer)" --hint "latest Claude reply"
93
146
 
94
- echo "paired with chi $id (socket $sock)"
147
+ if [ -z "$inbox" ]; then
148
+ echo "paired with chi $id (socket $sock)"
149
+ else
150
+ echo "paired with chi $id (socket $inbox on chi's machine, forwarded from $sock)"
151
+ fi
95
152
  $chi context ls "$id"
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: samagotchi-system
3
- version: 0.118.0
3
+ version: 0.119.0
4
4
  scope: system
5
5
  description: Default system memories — identity, self map, config modification protocol, memory guide and the delegated-session rules
6
6
  files:
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Samagotchi
4
- VERSION = "0.118.0"
4
+ VERSION = "0.119.0"
5
5
  # Sent as the User-Agent of every HTTP request chi makes.
6
6
  USER_AGENT = "chi/#{VERSION}"
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: samagotchi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.118.0
4
+ version: 0.119.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dmitry Dedov