elliot-stack 1.0.40 → 1.0.41
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.
- package/package.json +1 -1
- package/skills/estack-migrate-claude-session-history/scripts/__pycache__/validate-migration.cpython-313.pyc +0 -0
- package/skills/estack-read-claude-session-history/SKILL.md +9 -2
- package/skills/estack-read-claude-session-history/references/modes.md +1 -0
- package/skills/estack-read-claude-session-history/scripts/__pycache__/read_transcript.cpython-313.pyc +0 -0
- package/skills/estack-read-claude-session-history/scripts/lib/__pycache__/__init__.cpython-313.pyc +0 -0
- package/skills/estack-read-claude-session-history/scripts/lib/__pycache__/parser.cpython-313.pyc +0 -0
- package/skills/estack-read-claude-session-history/scripts/lib/__pycache__/paths.cpython-313.pyc +0 -0
- package/skills/estack-read-claude-session-history/scripts/lib/__pycache__/search.cpython-313.pyc +0 -0
- package/skills/estack-read-claude-session-history/scripts/lib/__pycache__/subagents.cpython-313.pyc +0 -0
- package/skills/estack-read-claude-session-history/scripts/lib/__pycache__/tools.cpython-313.pyc +0 -0
- package/skills/estack-read-claude-session-history/scripts/read_transcript.py +28 -16
package/package.json
CHANGED
|
Binary file
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: estack-read-claude-session-history
|
|
3
|
-
version: 1.3.
|
|
3
|
+
version: 1.3.1
|
|
4
4
|
description: >-
|
|
5
5
|
(read-claude-session-history) Invoke for ANY task involving Claude Code
|
|
6
6
|
session history, transcripts, or .jsonl files - this is the only way to read,
|
|
@@ -80,6 +80,13 @@ output. If you need a different zone, pass `--tz` (IANA name like
|
|
|
80
80
|
`America/New_York`, `UTC`, or an offset like `-4`) — never convert manually.
|
|
81
81
|
`--since/--until/--date` specs are interpreted in that same display timezone.
|
|
82
82
|
|
|
83
|
+
**Report times to the user in 12-hour format unless they ask otherwise.** The
|
|
84
|
+
report modes (`session-report`, `engagement`, `timeline`) already emit each clock
|
|
85
|
+
time as `7:00pm (19:00)` — 12-hour with the 24-hour value in parens — so quoting
|
|
86
|
+
them directly satisfies this. When you paraphrase or summarize a time instead of
|
|
87
|
+
quoting raw output, keep the 12-hour form (the parenthetical 24-hour is optional
|
|
88
|
+
in prose). Switch to 24-hour only if the user requests it.
|
|
89
|
+
|
|
83
90
|
## Decision tree
|
|
84
91
|
|
|
85
92
|
```
|
|
@@ -231,7 +238,7 @@ When the user asks a natural-language "what did I do" / "review my day" / "break
|
|
|
231
238
|
- **One to two sentences per session** describing what they did and why — synthesized from the `intent` + `last` + files, not a raw dump of either. The mode hands you the inputs; you write the sentence.
|
|
232
239
|
- **Show both clocks and name the overlap.** `active` is deduped attention (parallel chats never double-counted); `ran`/elapsed is the session's own first→last span and *will* overlap others. Say so once — users work on several things at once and want the overlap reflected in the span but removed from active minutes.
|
|
233
240
|
- **Counts are clean already.** `you N msgs` is real typed prompts (tool-results and hook/skill injections excluded); `assistant N msgs` is text replies (tool-only turns excluded). Do NOT hand-count raw `type:user`/`type:assistant` entries from the .jsonl — that over-counts both (tool-result envelopes inflate "user"; multi-block turns inflate "assistant"). Trust the mode's numbers.
|
|
234
|
-
- **
|
|
241
|
+
- **12-hour time** as the report modes emit it (`7:00pm (19:00)` — 12-hour with 24-hour in parens); keep the 12-hour form in prose, and switch to 24-hour only if the user asks.
|
|
235
242
|
- **Scope to a sub-window with `--since/--until`** (omit `--date` — `--date` overrides `--since`). The metrics are windowed to the range, so "from 7pm" counts and active-time reflect only that slice.
|
|
236
243
|
|
|
237
244
|
For a normal day (16–20 sessions) `session-report` text output stays well within the read budget; prefer it over `--mode list --format json`, which dumps full per-session arrays and can overflow into a persisted-file round-trip.
|
|
@@ -18,6 +18,7 @@ Global flag notes:
|
|
|
18
18
|
- `--since <spec>` / `--until <spec>` — the time window is half-open `[since, until)`: `since` is inclusive, `until` is exclusive, so an event stamped exactly at `--until` is not included. This holds for every message-level mode (`search`, `timeline`, `engagement`, `tool-usage`). Session-level modes (`list`, `journal`, `count`) instead filter whole sessions by file mtime, so a session last written at or before `--until` is shown.
|
|
19
19
|
- `--project <name>` — case-insensitive substring filter on project directory names (encoded or decoded form). Applies to `list`, `journal`, `search`, `count`, `find`, `timeline`, `engagement`, `tool-usage`. Exit 1 when nothing matches.
|
|
20
20
|
- `--tz <spec>` — display timezone: IANA name (`America/New_York`), `UTC`, or fixed offset (`+5`, `-4`, `+05:30`, `UTC-4`). Default is system local time. All displayed timestamps AND `--since/--until/--date` interpretation use this zone.
|
|
21
|
+
- **Clock format:** the report modes (`session-report`, `engagement`, `timeline`) render every time-of-day as 12-hour with the 24-hour value in parens — `7:00pm (19:00)` — and date-prefix it (`2026-06-19 7:00pm (19:00)`) when the window spans more than one day; their headers carry a `12h (24h)` label. Forensic modes (`changelog`, `tool-calls`) keep `HH:MM:SS` for density. JSON output is always ISO-8601 (unaffected by the 12-hour rendering).
|
|
21
22
|
- `--format json` (alias `--json`) — structured output on every mode except the legacy `--list`/`--list-subagents` aliases. Shapes per mode are listed below.
|
|
22
23
|
|
|
23
24
|
Legacy flags are preserved unchanged:
|
|
Binary file
|
package/skills/estack-read-claude-session-history/scripts/lib/__pycache__/__init__.cpython-313.pyc
CHANGED
|
Binary file
|
package/skills/estack-read-claude-session-history/scripts/lib/__pycache__/parser.cpython-313.pyc
CHANGED
|
Binary file
|
package/skills/estack-read-claude-session-history/scripts/lib/__pycache__/paths.cpython-313.pyc
CHANGED
|
Binary file
|
package/skills/estack-read-claude-session-history/scripts/lib/__pycache__/search.cpython-313.pyc
CHANGED
|
Binary file
|
package/skills/estack-read-claude-session-history/scripts/lib/__pycache__/subagents.cpython-313.pyc
CHANGED
|
Binary file
|
package/skills/estack-read-claude-session-history/scripts/lib/__pycache__/tools.cpython-313.pyc
CHANGED
|
Binary file
|
|
@@ -1036,6 +1036,21 @@ def _fmt_dur(td: timedelta) -> str:
|
|
|
1036
1036
|
return f"{h}h{m:02d}m" if h else f"{m}m"
|
|
1037
1037
|
|
|
1038
1038
|
|
|
1039
|
+
def _fmt_tod(dt: datetime) -> str:
|
|
1040
|
+
"""Time-of-day as 12-hour with 24-hour in parens: '7:00pm (19:00)'.
|
|
1041
|
+
|
|
1042
|
+
Computed by hand (not strftime %-I/%#I) so it's identical on every platform.
|
|
1043
|
+
"""
|
|
1044
|
+
h12 = dt.hour % 12 or 12
|
|
1045
|
+
ampm = "am" if dt.hour < 12 else "pm"
|
|
1046
|
+
return f"{h12}:{dt.minute:02d}{ampm} ({dt.hour:02d}:{dt.minute:02d})"
|
|
1047
|
+
|
|
1048
|
+
|
|
1049
|
+
def _fmt_clock(dt: datetime, with_date: bool) -> str:
|
|
1050
|
+
"""A report clock value — `_fmt_tod`, date-prefixed when the window spans days."""
|
|
1051
|
+
return f"{dt:%Y-%m-%d} {_fmt_tod(dt)}" if with_date else _fmt_tod(dt)
|
|
1052
|
+
|
|
1053
|
+
|
|
1039
1054
|
_GAP_RE = re.compile(r"^(\d+)\s*(m|h)?$", re.IGNORECASE)
|
|
1040
1055
|
|
|
1041
1056
|
|
|
@@ -1112,10 +1127,9 @@ def render_timeline(data: dict, tz_label: str) -> str:
|
|
|
1112
1127
|
since, until = data["since"], data["until"]
|
|
1113
1128
|
blocks, sessions = data["blocks"], data["sessions"]
|
|
1114
1129
|
multi_day = (until - since) > timedelta(days=1)
|
|
1115
|
-
tfmt = "%Y-%m-%d %H:%M" if multi_day else "%H:%M"
|
|
1116
1130
|
head = (
|
|
1117
|
-
f"=== Timeline {since
|
|
1118
|
-
f"(times: {tz_label}, gap={data['gap_minutes']}m) ==="
|
|
1131
|
+
f"=== Timeline {_fmt_clock(since, True)} → {_fmt_clock(until, True)} "
|
|
1132
|
+
f"(times: {tz_label} 12h (24h), gap={data['gap_minutes']}m) ==="
|
|
1119
1133
|
)
|
|
1120
1134
|
if not blocks:
|
|
1121
1135
|
return head + "\n\n(no activity in range)"
|
|
@@ -1125,7 +1139,7 @@ def render_timeline(data: dict, tz_label: str) -> str:
|
|
|
1125
1139
|
if prev_end is not None:
|
|
1126
1140
|
out.append(f" ── idle {_fmt_dur(b['start'] - prev_end)} ──")
|
|
1127
1141
|
dur = b["end"] - b["start"]
|
|
1128
|
-
out.append(f"{b['start']
|
|
1142
|
+
out.append(f"{_fmt_clock(b['start'], multi_day)}–{_fmt_tod(b['end'])} ({_fmt_dur(dur)})")
|
|
1129
1143
|
for f, n in sorted(b["counts"].items(), key=lambda x: -x[1]):
|
|
1130
1144
|
out.append(f" · {_session_label(sessions[f])} — {n} msgs")
|
|
1131
1145
|
prev_end = b["end"]
|
|
@@ -1135,7 +1149,7 @@ def render_timeline(data: dict, tz_label: str) -> str:
|
|
|
1135
1149
|
# no claim about user attention time. For that, use --mode engagement.
|
|
1136
1150
|
out.append(
|
|
1137
1151
|
f"Total: {len(blocks)} block(s) across a {_fmt_dur(span)} span "
|
|
1138
|
-
f"({blocks[0]['start']
|
|
1152
|
+
f"({_fmt_clock(blocks[0]['start'], multi_day)}–{_fmt_tod(blocks[-1]['end'])}), "
|
|
1139
1153
|
f"{len(sessions)} session(s)"
|
|
1140
1154
|
)
|
|
1141
1155
|
return "\n".join(out)
|
|
@@ -1376,10 +1390,9 @@ def render_engagement(data: dict, tz_label: str) -> str:
|
|
|
1376
1390
|
since, until = data["since"], data["until"]
|
|
1377
1391
|
sessions = data["sessions"]
|
|
1378
1392
|
multi_day = (until - since) > timedelta(days=1)
|
|
1379
|
-
tfmt = "%Y-%m-%d %H:%M" if multi_day else "%H:%M"
|
|
1380
1393
|
head = (
|
|
1381
|
-
f"=== Engagement {since
|
|
1382
|
-
f"(times: {tz_label}, break={data['break_minutes']}m) ==="
|
|
1394
|
+
f"=== Engagement {_fmt_clock(since, True)} → {_fmt_clock(until, True)} "
|
|
1395
|
+
f"(times: {tz_label} 12h (24h), break={data['break_minutes']}m) ==="
|
|
1383
1396
|
)
|
|
1384
1397
|
if not sessions:
|
|
1385
1398
|
return head + "\n\n(no user messages in range)"
|
|
@@ -1396,7 +1409,7 @@ def render_engagement(data: dict, tz_label: str) -> str:
|
|
|
1396
1409
|
out.append(
|
|
1397
1410
|
f"{_fmt_dur(s['active']):>7} ratio {ratio} "
|
|
1398
1411
|
f"you {s['user_messages']:<3} ai {s['assistant_messages']:<4} "
|
|
1399
|
-
f"{s['first']
|
|
1412
|
+
f"{_fmt_clock(s['first'], multi_day)}–{_fmt_tod(s['last'])} "
|
|
1400
1413
|
f"{_session_label(s['summary'])}"
|
|
1401
1414
|
)
|
|
1402
1415
|
total_active = sum((s["active"] for s in sessions.values()), timedelta())
|
|
@@ -1405,13 +1418,13 @@ def render_engagement(data: dict, tz_label: str) -> str:
|
|
|
1405
1418
|
out.append("")
|
|
1406
1419
|
out.append(
|
|
1407
1420
|
f"Total: {_fmt_dur(total_active)} active across {len(sessions)} session(s), "
|
|
1408
|
-
f"{first
|
|
1421
|
+
f"{_fmt_clock(first, multi_day)}–{_fmt_tod(last)} span ({_fmt_dur(last - first)})"
|
|
1409
1422
|
)
|
|
1410
1423
|
breaks = data["breaks"]
|
|
1411
1424
|
if breaks:
|
|
1412
1425
|
shown = breaks[:6]
|
|
1413
1426
|
items = ", ".join(
|
|
1414
|
-
f"{a
|
|
1427
|
+
f"{_fmt_clock(a, multi_day)}→{_fmt_tod(b)} ({_fmt_dur(b - a)})"
|
|
1415
1428
|
for a, b in shown
|
|
1416
1429
|
)
|
|
1417
1430
|
more = f" (+{len(breaks) - len(shown)} more)" if len(breaks) > len(shown) else ""
|
|
@@ -1502,10 +1515,9 @@ def render_session_report(data: dict, tz_label: str) -> str:
|
|
|
1502
1515
|
since, until = data["since"], data["until"]
|
|
1503
1516
|
sessions = data["sessions"]
|
|
1504
1517
|
multi_day = (until - since) > timedelta(days=1)
|
|
1505
|
-
tfmt = "%Y-%m-%d %H:%M" if multi_day else "%H:%M"
|
|
1506
1518
|
head = (
|
|
1507
|
-
f"=== Session report {since
|
|
1508
|
-
f"(times: {tz_label}, break={data['break_minutes']}m) ==="
|
|
1519
|
+
f"=== Session report {_fmt_clock(since, True)} → {_fmt_clock(until, True)} "
|
|
1520
|
+
f"(times: {tz_label} 12h (24h), break={data['break_minutes']}m) ==="
|
|
1509
1521
|
)
|
|
1510
1522
|
if not sessions:
|
|
1511
1523
|
return head + "\n\n(no user activity in range)"
|
|
@@ -1518,7 +1530,7 @@ def render_session_report(data: dict, tz_label: str) -> str:
|
|
|
1518
1530
|
out.append(f"{i}. {title}")
|
|
1519
1531
|
out.append(
|
|
1520
1532
|
f" {summary['decoded_project']} · "
|
|
1521
|
-
f"{s['first']
|
|
1533
|
+
f"{_fmt_clock(s['first'], multi_day)}–{_fmt_tod(s['last'])} "
|
|
1522
1534
|
f"(ran {_fmt_dur(elapsed)} · active {_fmt_dur(s['active'])})"
|
|
1523
1535
|
)
|
|
1524
1536
|
out.append(
|
|
@@ -1535,7 +1547,7 @@ def render_session_report(data: dict, tz_label: str) -> str:
|
|
|
1535
1547
|
out.append(
|
|
1536
1548
|
f"Total: {len(sessions)} session(s) · {_fmt_dur(total_active)} active "
|
|
1537
1549
|
f"(overlap removed) across a {_fmt_dur(last - first)} span "
|
|
1538
|
-
f"({first
|
|
1550
|
+
f"({_fmt_clock(first, multi_day)}–{_fmt_tod(last)})."
|
|
1539
1551
|
)
|
|
1540
1552
|
out.append(
|
|
1541
1553
|
"(active = your attention, parallel chats never double-counted; "
|