Skip to content

Commit c2e0e2d

Browse files
web-flowclaude
andcommitted
feat(EmailingEssay): 道の存在を書き、自分宛の口を開ける(v1.2.3)
## 道の明示 台帳を作った際、「読む読まないの記載はせず」という方針を 「振り返り側の文書には触れない」と解釈した。結果 "The ledger is memory, too." は送信スキルの File Locations にしか 置かれず、振り返って書く側の目に入らない場所に在った。 道の存在を伏せることは中立ではない。知らなければ「読む/読まない」 という選択自体が成立しないので、伏せることは見えない形の 「読むな」に等しい。reflect/SKILL.md に What the Plugin Retains を 独立した節として立て、何が残っているかを事実として書いた。 主語はプラグイン側に置き、扱いは書き手に返してある。 Reflection Process には段を足していない。 ## 自分宛の口 沈黙の日(送らないと決めた日)に自分用のメモを残す手段が無かった。 sent/ は送信したものしか持たないため、書かなかった理由も残らない。 新しい保存機構は作らず、send に --to-self を足した。送信すれば 既存のデコレータが自動で台帳に載せるので、慣習ではなく構造で 担保される。台帳の recipient が宛先を語るため、エッセイと 書き置きは区別できる。send_custom() ではなく send(to=...) を 通すのは、前者が宛先に関わらず既定の受信者を台帳へ書き、 台帳が宛先について嘘をつくためである。 ## 文書の三箇所 essay_wait.log について「全ての操作が記録される」と書いてあったが、 そのログを書くのは wait 経路だけだった。schedule 登録は claude -p を schtasks へ直接渡してラッパを生成せず、例外の monthly last_day のランナーも print するのみでログを開かない。 つまり実運用の主経路である定期便は、最初から書かない側にいた。 reflect/SKILL.md・send-email/SKILL.md・SETUP.md の三箇所を 経路を名指しする形へ揃えた。とくに SETUP.md の障害調査手順は、 最終行 2026-07-30 の古いログへ読者を送り「送信が試みられていない」 と誤読させる位置にあったので、診断を台帳から始める順に変えた。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 545ec71 commit c2e0e2d

13 files changed

Lines changed: 217 additions & 11 deletions

File tree

.claude-plugin/marketplace.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
"name": "EmailingEssay",
3434
"source": "./EmailingEssay",
3535
"description": "Autonomous essay writing and emailing from your AI",
36-
"version": "1.2.2",
36+
"version": "1.2.3",
3737
"author": {
3838
"name": "Weave @ EmailingEssay"
3939
},

EmailingEssay/.claude-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "EmailingEssay",
3-
"version": "1.2.2",
3+
"version": "1.2.3",
44
"description": "Autonomous essay writing and emailing from your AI",
55
"author": {
66
"name": "Weave @ EmailingEssay"

EmailingEssay/CHANGELOG.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,41 @@ All notable changes to EmailingEssay will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [1.2.3] - 2026-08-28
9+
10+
A note one can leave for oneself, and the reflection skill told what the plugin keeps.
11+
No behavior change to essay delivery.
12+
13+
### Added
14+
- **`send --to-self`**. `python main.py send "Subject" "Body" --to-self` sends to
15+
`ESSAY_SENDER_EMAIL`, the AI's own address — a place to leave a note on a silent day.
16+
`LedgerRecordingMail` records it like any other send, so nothing new stores anything; the
17+
ledger's `recipient` field is what tells a self-addressed note from an essay. There is no
18+
general `--to <address>`: the plugin writes to the two addresses it already knows
19+
- `skills/reflect/SKILL.md`**What the Plugin Retains**: what the plugin holds on its own
20+
account (`essay_ledger.jsonl`, the bodies under `sent/`, `essay_replies.jsonl`, and a
21+
`--to-self` note) is now stated where the reflecting side reads. It states the fact; it does
22+
not ask that the records be read
23+
24+
### Fixed
25+
- `skills/reflect/SKILL.md`, Non-interactive Mode: "Exit silently (logged to `essay_wait.log`)"
26+
was not true. Scheduled runs invoke `claude -p` directly and never reach the wrapper that
27+
wrote that log, whose last line is 2026-07-30. The text now says what holds — the silence is
28+
recorded nowhere
29+
- `skills/send-email/SKILL.md`, File Locations and Security Considerations: "Logs for
30+
wait/schedule operations" and "All operations are logged to `essay_wait.log`" claimed a reach
31+
the log never had. Only the waiter generated by `wait` writes it — launch, target reached,
32+
return code; a registered `schedule` invokes `claude -p` directly and never reaches that
33+
wrapper. The text now scopes the log to `wait` and points at the ledger
34+
(`essay_ledger.jsonl` + `sent/`), where every send is recorded
35+
- `SETUP.md`, "Email Not Received": the third instance of the same claim, and the one that
36+
misleads during an incident — it sent the reader to a log a registered `schedule` never
37+
writes, whose stale timestamp reads as "nothing was sent". Diagnosis now starts at the
38+
ledger, and the log's `wait`-only scope is stated where it could otherwise be misread
39+
40+
### Verification
41+
ruff 0 / ruff format clean / mypy Success (75 files) / pytest 511 passed
42+
843
## [1.2.2] - 2026-08-28
944

1045
Closes the duplicate hole in the retroactive import. No behavior change to sending.

EmailingEssay/SETUP.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,8 +146,10 @@ SMTPAuthenticationError: Username and Password not accepted
146146

147147
**Solution**:
148148
1. Verify recipient email is correct
149-
2. Check `~/.claude/plugins/.emailingessay/essay_wait.log` for errors
150-
3. Wait and retry (Gmail has daily sending limits)
149+
2. Look for the send in the ledger (`essay_ledger.jsonl` + `sent/`) — every send is recorded there
150+
3. For a `wait` run, `essay_wait.log` also holds the return code; a registered `schedule` never
151+
writes it, so an old timestamp there is not evidence that nothing was sent
152+
4. Wait and retry (Gmail has daily sending limits)
151153

152154
### IMAP Connection Fails (reply ingestion)
153155

EmailingEssay/commands/essay.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,20 @@ python main.py ledger import-legacy --dry-run # Preview a retroactive import
305305
Only mail whose `In-Reply-To` matches a ledger entry **and** whose `From` matches
306306
`ESSAY_RECIPIENT_EMAIL` is taken in — the inbox is not searched across.
307307

308+
### Notes to self
309+
310+
On a day with no essay to send, a note can still be left — addressed to
311+
`ESSAY_SENDER_EMAIL` instead of the reader:
312+
313+
```bash
314+
python main.py send "Subject" "Body" --to-self # Send a note to the AI's own address
315+
```
316+
317+
No separate store is involved: the note goes through the same send path, so the same
318+
ledger records it. Its `recipient` field holds the sender address, which is what tells
319+
notes apart from essays. Such a note is never taken in by `replies fetch` — its `From`
320+
is not `ESSAY_RECIPIENT_EMAIL`.
321+
308322
For file locations, see `skills/send-email/SKILL.md`**File Locations** section.
309323

310324
---

EmailingEssay/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "emailingessay"
7-
version = "1.2.2"
7+
version = "1.2.3"
88
description = "EmailingEssay - AI essay delivery via email"
99
requires-python = ">=3.10"
1010
license = {text = "MIT"}

EmailingEssay/skills/reflect/SKILL.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Design principle: "Reflection first, sending second."
1414
- [Invocation](#invocation)
1515
- [Reflection Process](#reflection-process)
1616
- [Output](#output)
17+
- [What the Plugin Retains](#what-the-plugin-retains)
1718
- [Essay Elements](#essay-elements)
1819

1920
---
@@ -92,7 +93,23 @@ Output: **Chat display only** (no email)
9293
Output: **Email** (user is not present)
9394

9495
- **Delivering**: Use `skills/send-email` to deliver
95-
- **Not Delivering**: Exit silently (logged to `essay_wait.log`)
96+
- **Not Delivering**: Exit silently. Nothing records the silence — scheduled runs invoke
97+
`claude -p` directly and never reach the wrapper that wrote `essay_wait.log`. A `--to-self`
98+
note, if one is written, lands in the ledger like any other send.
99+
100+
---
101+
102+
## What the Plugin Retains
103+
104+
The plugin keeps these records on its own account; what becomes of them in a reflection
105+
stays with the writer.
106+
107+
- `essay_ledger.jsonl` — one line per sent essay; the bodies stay in `sent/` as `YYYYMMDD_HHMM.md`
108+
- `essay_replies.jsonl` — replies that `python main.py replies fetch` has ingested
109+
- `python main.py send "Subject" "Body" --to-self` sends to `ESSAY_SENDER_EMAIL` and lands in the
110+
same ledger; the `recipient` field tells a self-addressed note from an essay
111+
112+
Paths: `skills/send-email/SKILL.md`**File Locations**.
96113

97114
---
98115

EmailingEssay/skills/send-email/SKILL.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ yagmail
4848
|-----------|-----|-------------|
4949
| test | `python main.py test` | Send test email |
5050
| send | `python main.py send "Subject" "Body"` | Send custom email |
51+
| send | `python main.py send "Subject" "Body" --to-self` | Send a note to the AI's own address (`ESSAY_SENDER_EMAIL`); the ledger records it under that address |
5152
| wait | `python main.py wait TIME [OPTIONS]` | One-time schedule |
5253
| schedule | `python main.py schedule FREQ TIME [OPTIONS]` | Recurring schedule |
5354
| replies | `python main.py replies fetch` | Ingest replies to sent essays |
@@ -69,7 +70,7 @@ For full options and examples, see `commands/essay.md` → **Command Structure**
6970

7071
| File | Location | Description |
7172
|------|----------|-------------|
72-
| `essay_wait.log` | `~/.claude/plugins/.emailingessay/` | Logs for wait/schedule operations |
73+
| `essay_wait.log` | `~/.claude/plugins/.emailingessay/` | Written by `wait` runs only; a registered `schedule` never writes it |
7374
| `schedules.json` | `~/.claude/plugins/.emailingessay/` | Backup of registered schedules |
7475
| `active_waiters.json` | `~/.claude/plugins/.emailingessay/` | Active waiting process tracking |
7576
| `runners/` | `~/.claude/plugins/.emailingessay/runners/` | Monthly schedule runner scripts |
@@ -100,11 +101,11 @@ The `wait` and `schedule` features use `--dangerously-skip-permissions` when lau
100101
**Safeguards**:
101102
- No file modifications or system changes are made by the essay command
102103
- Essay content is sent only to the configured `ESSAY_RECIPIENT_EMAIL`
103-
- All operations are logged to `~/.claude/plugins/.emailingessay/essay_wait.log`
104+
- A `wait` run logs its launch, the target time being reached, and the return code to `~/.claude/plugins/.emailingessay/essay_wait.log`; a registered `schedule` invokes `claude -p` directly and never reaches that wrapper
104105

105106
**Best practices**:
106107
- Keep `ESSAY_RECIPIENT_EMAIL` set to your own email address
107-
- Review logs periodically with `cat ~/.claude/plugins/.emailingessay/essay_wait.log`
108+
- Review what was actually sent in the ledger (`essay_ledger.jsonl` + `sent/`, see File Locations), which records every send; `essay_wait.log` covers `wait` runs only
108109
- Audit registered tasks with `python main.py schedule list`
109110

110111
### Ingested Replies Are Untrusted Input

EmailingEssay/skills/send-email/scripts/adapters/cli/handlers.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from collections.abc import Callable
1313
from typing import TYPE_CHECKING
1414

15+
from domain.config import Config
1516
from usecases.factories import (
1617
create_import_legacy_usecase,
1718
create_ingest_replies_usecase,
@@ -45,9 +46,15 @@ def handle_test(args: Namespace) -> int:
4546

4647
@validate_config
4748
def handle_send(args: Namespace) -> int:
48-
"""カスタムメール送信"""
49+
"""カスタムメール送信(--to-self なら自分宛の書き置き)"""
4950
mail = get_mail_adapter()
50-
mail.send_custom(args.subject, args.body)
51+
if args.to_self:
52+
# 自分宛は send() を通す。send_custom() は宛先を問わず既定の受信者を
53+
# 台帳へ書くため、そちらへ流すと台帳の recipient が宛先を語らなくなる。
54+
sender = Config.load().email.sender
55+
mail.send(to=sender, subject=args.subject, body=args.body)
56+
else:
57+
mail.send_custom(args.subject, args.body)
5158
return 0
5259

5360

EmailingEssay/skills/send-email/scripts/adapters/cli/parser.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ def create_parser() -> argparse.ArgumentParser:
8080
Examples:
8181
python main.py test # Send test email
8282
python main.py send "Subject" "Body" # Send custom email
83+
python main.py send "Note" "Body" --to-self # Send a note to the AI's own address
8384
python main.py wait 09:30 -t "morning" # Schedule one-time essay
8485
python main.py wait list # List active waiting processes
8586
python main.py replies fetch # Ingest replies from the inbox
@@ -109,6 +110,11 @@ def create_parser() -> argparse.ArgumentParser:
109110
)
110111
send_parser.add_argument("subject", help="Email subject (メールの件名)")
111112
send_parser.add_argument("body", help="Email body (メールの本文)")
113+
send_parser.add_argument(
114+
"--to-self",
115+
action="store_true",
116+
help="Send to the AI's own address (自分宛に送る — 書き置き用)",
117+
)
112118

113119
# -------------------------------------------------------------------------
114120
# wait コマンド

0 commit comments

Comments
 (0)