claude-code-recap 1.2.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.
package/install.sh ADDED
@@ -0,0 +1,249 @@
1
+ #!/bin/sh
2
+ # install.sh: install the recap skill for Claude Code.
3
+ #
4
+ # curl -fsSL https://raw.githubusercontent.com/noluyorAbi/claude-code-recap/main/install.sh | sh
5
+ #
6
+ # Copies skills/recap into $CLAUDE_CONFIG_DIR/skills/recap (default ~/.claude/skills/recap).
7
+ # Works from a git checkout and from a curl pipe, where $0 is not a usable path
8
+ # and the skill files are fetched from GitHub instead.
9
+ #
10
+ # Flags:
11
+ # --force overwrite an existing install, even if it was modified locally
12
+ # --uninstall remove the files this script installed
13
+ # --help show usage
14
+ #
15
+ # Env:
16
+ # CLAUDE_CONFIG_DIR Claude Code config dir (default: $HOME/.claude)
17
+ # RECAP_REF git ref to fetch when running from a pipe (default: main)
18
+ # RECAP_TARBALL full tarball URL, overrides REPO/REF (used by the tests)
19
+
20
+ set -eu
21
+
22
+ REPO="noluyorAbi/claude-code-recap"
23
+ REF="${RECAP_REF:-main}"
24
+ SKILL_NAME="recap"
25
+ MANIFEST_NAME=".claude-code-recap-manifest.json"
26
+ TRACKED="SKILL.md recap.py"
27
+
28
+ FORCE=0
29
+ UNINSTALL=0
30
+ TMPDIR_SELF=""
31
+
32
+ usage() {
33
+ cat <<'EOF'
34
+ install.sh: install the recap skill for Claude Code.
35
+
36
+ Usage:
37
+ ./install.sh [--force] [--uninstall]
38
+ curl -fsSL https://raw.githubusercontent.com/noluyorAbi/claude-code-recap/main/install.sh | sh
39
+
40
+ Options:
41
+ --force overwrite an existing install, even if it has local edits
42
+ --uninstall remove the files this script installed
43
+ -h, --help show this help
44
+
45
+ Environment:
46
+ CLAUDE_CONFIG_DIR Claude Code config dir (default: $HOME/.claude)
47
+ RECAP_REF git ref fetched when piped from curl (default: main)
48
+ EOF
49
+ }
50
+
51
+ die() {
52
+ printf 'install.sh: %s\n' "$1" >&2
53
+ exit 1
54
+ }
55
+
56
+ cleanup() {
57
+ if [ -n "$TMPDIR_SELF" ] && [ -d "$TMPDIR_SELF" ]; then
58
+ rm -rf "$TMPDIR_SELF"
59
+ fi
60
+ }
61
+ trap cleanup EXIT HUP INT TERM
62
+
63
+ for arg in "$@"; do
64
+ case "$arg" in
65
+ --force) FORCE=1 ;;
66
+ --uninstall) UNINSTALL=1 ;;
67
+ -h|--help) usage; exit 0 ;;
68
+ *) printf 'install.sh: unknown option: %s\n\n' "$arg" >&2; usage >&2; exit 2 ;;
69
+ esac
70
+ done
71
+
72
+ CONFIG_DIR="${CLAUDE_CONFIG_DIR:-$HOME/.claude}"
73
+ DEST="$CONFIG_DIR/skills/$SKILL_NAME"
74
+ MANIFEST="$DEST/$MANIFEST_NAME"
75
+
76
+ sha256() {
77
+ if command -v shasum >/dev/null 2>&1; then
78
+ shasum -a 256 "$1" | awk '{print $1}'
79
+ elif command -v sha256sum >/dev/null 2>&1; then
80
+ sha256sum "$1" | awk '{print $1}'
81
+ else
82
+ die "need shasum or sha256sum to verify installed files"
83
+ fi
84
+ }
85
+
86
+ # Emit "<relative-path> <sha256>" for every file recorded in the manifest.
87
+ manifest_entries() {
88
+ sed -n '/"files"[[:space:]]*:/,/}/p' "$1" |
89
+ sed -n 's/^[[:space:]]*"\([^"]*\)"[[:space:]]*:[[:space:]]*"\([0-9a-f]\{64\}\)".*/\1 \2/p'
90
+ }
91
+
92
+ # Print every tracked file whose content no longer matches the manifest.
93
+ modified_files() {
94
+ [ -f "$MANIFEST" ] || return 0
95
+ manifest_entries "$MANIFEST" | while read -r rel recorded; do
96
+ [ -f "$DEST/$rel" ] || continue
97
+ if [ "$(sha256 "$DEST/$rel")" != "$recorded" ]; then
98
+ printf '%s\n' "$rel"
99
+ fi
100
+ done
101
+ }
102
+
103
+ skill_version() {
104
+ sed -n 's/^[[:space:]]\{1,\}version:[[:space:]]*"\{0,1\}\([0-9][0-9A-Za-z.+-]*\)"\{0,1\}[[:space:]]*$/\1/p' "$1" |
105
+ head -n 1
106
+ }
107
+
108
+ # ---------- uninstall ----------
109
+ if [ "$UNINSTALL" -eq 1 ]; then
110
+ if [ ! -d "$DEST" ]; then
111
+ printf 'recap is not installed (%s does not exist).\n' "$DEST"
112
+ exit 0
113
+ fi
114
+
115
+ if [ ! -f "$MANIFEST" ]; then
116
+ if [ "$FORCE" -ne 1 ]; then
117
+ printf 'install.sh: %s exists but was not installed by this script (no manifest).\n' "$DEST" >&2
118
+ printf 'Refusing to remove it. Re-run with --force to delete it anyway.\n' >&2
119
+ exit 1
120
+ fi
121
+ rm -rf "$DEST"
122
+ printf 'Removed %s (forced, no manifest).\n' "$DEST"
123
+ exit 0
124
+ fi
125
+
126
+ changed="$(modified_files)"
127
+ if [ -n "$changed" ] && [ "$FORCE" -ne 1 ]; then
128
+ printf 'install.sh: these installed files have local edits:\n' >&2
129
+ printf '%s\n' "$changed" | sed 's/^/ /' >&2
130
+ printf 'Refusing to delete them. Back them up, then re-run with --force.\n' >&2
131
+ exit 1
132
+ fi
133
+
134
+ manifest_entries "$MANIFEST" | while read -r rel _hash; do
135
+ rm -f "$DEST/$rel"
136
+ done
137
+ rm -f "$MANIFEST"
138
+ # Prune empty directories bottom-up, including DEST itself when nothing is left.
139
+ find "$DEST" -depth -type d -empty -exec rmdir {} + 2>/dev/null || true
140
+
141
+ if [ -d "$DEST" ]; then
142
+ printf 'Removed the recap skill files from %s.\n' "$DEST"
143
+ printf 'Kept the directory: it still contains files this script did not install.\n'
144
+ else
145
+ printf 'Removed %s.\n' "$DEST"
146
+ fi
147
+ exit 0
148
+ fi
149
+
150
+ # ---------- locate the skill source ----------
151
+ SRC=""
152
+ SCRIPT_DIR=""
153
+ case "${0:-}" in
154
+ ''|sh|-sh|bash|-bash|dash|-dash|zsh|-zsh) : ;;
155
+ *)
156
+ if [ -f "$0" ]; then
157
+ SCRIPT_DIR="$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd)"
158
+ fi
159
+ ;;
160
+ esac
161
+
162
+ if [ -n "$SCRIPT_DIR" ] && [ -f "$SCRIPT_DIR/skills/$SKILL_NAME/SKILL.md" ]; then
163
+ SRC="$SCRIPT_DIR/skills/$SKILL_NAME"
164
+ else
165
+ # Curl pipe: no local checkout, fetch the tarball.
166
+ command -v curl >/dev/null 2>&1 || die "curl is required to fetch the skill"
167
+ command -v tar >/dev/null 2>&1 || die "tar is required to unpack the skill"
168
+ TMPDIR_SELF="$(mktemp -d 2>/dev/null || mktemp -d -t recap)"
169
+ TARBALL="${RECAP_TARBALL:-https://codeload.github.com/$REPO/tar.gz/$REF}"
170
+ printf 'Fetching %s@%s ...\n' "$REPO" "$REF"
171
+ curl -fsSL --retry 2 -o "$TMPDIR_SELF/src.tar.gz" "$TARBALL" ||
172
+ die "could not download $TARBALL"
173
+ tar -xzf "$TMPDIR_SELF/src.tar.gz" -C "$TMPDIR_SELF" ||
174
+ die "could not unpack the archive downloaded from $TARBALL"
175
+ SRC="$(find "$TMPDIR_SELF" -type d -path "*/skills/$SKILL_NAME" | head -n 1)"
176
+ [ -n "$SRC" ] || die "downloaded archive does not contain skills/$SKILL_NAME"
177
+ fi
178
+
179
+ [ -f "$SRC/SKILL.md" ] || die "missing $SRC/SKILL.md"
180
+ [ -f "$SRC/recap.py" ] || die "missing $SRC/recap.py"
181
+
182
+ # The Agent Skills spec requires SKILL.md `name` to equal the directory name.
183
+ DECLARED="$(sed -n 's/^name:[[:space:]]*\([a-z0-9][a-z0-9-]*\)[[:space:]]*$/\1/p' "$SRC/SKILL.md" | head -n 1)"
184
+ [ "$DECLARED" = "$SKILL_NAME" ] ||
185
+ die "SKILL.md declares name '$DECLARED' but must declare '$SKILL_NAME'"
186
+
187
+ VERSION="$(skill_version "$SRC/SKILL.md")"
188
+ [ -n "$VERSION" ] || VERSION="unknown"
189
+
190
+ # ---------- guard an existing install ----------
191
+ if [ -d "$DEST" ]; then
192
+ if [ -f "$MANIFEST" ]; then
193
+ changed="$(modified_files)"
194
+ if [ -n "$changed" ] && [ "$FORCE" -ne 1 ]; then
195
+ printf 'install.sh: these installed files have local edits:\n' >&2
196
+ printf '%s\n' "$changed" | sed 's/^/ /' >&2
197
+ printf 'Refusing to overwrite them. Back them up, then re-run with --force.\n' >&2
198
+ exit 1
199
+ fi
200
+ manifest_entries "$MANIFEST" | while read -r rel _hash; do
201
+ rm -f "$DEST/$rel"
202
+ done
203
+ rm -f "$MANIFEST"
204
+ elif [ "$FORCE" -ne 1 ]; then
205
+ printf 'install.sh: %s already exists and was not installed by this script.\n' "$DEST" >&2
206
+ printf 'Refusing to overwrite it. Move it aside, or re-run with --force.\n' >&2
207
+ exit 1
208
+ fi
209
+ fi
210
+
211
+ # ---------- install ----------
212
+ mkdir -p "$DEST"
213
+ cp "$SRC/SKILL.md" "$DEST/SKILL.md"
214
+ cp "$SRC/recap.py" "$DEST/recap.py"
215
+ chmod 0644 "$DEST/SKILL.md"
216
+ chmod 0755 "$DEST/recap.py"
217
+
218
+ SKILL_SHA="$(sha256 "$DEST/SKILL.md")"
219
+ RECAP_SHA="$(sha256 "$DEST/recap.py")"
220
+ NOW="$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
221
+
222
+ {
223
+ printf '{\n'
224
+ printf ' "tool": "claude-code-recap",\n'
225
+ printf ' "version": "%s",\n' "$VERSION"
226
+ printf ' "installedAt": "%s",\n' "$NOW"
227
+ printf ' "files": {\n'
228
+ printf ' "SKILL.md": "%s",\n' "$SKILL_SHA"
229
+ printf ' "recap.py": "%s"\n' "$RECAP_SHA"
230
+ printf ' }\n'
231
+ printf '}\n'
232
+ } >"$MANIFEST"
233
+
234
+ printf '\nInstalled the recap skill (v%s).\n' "$VERSION"
235
+ printf ' %s\n' "$DEST/SKILL.md"
236
+ printf ' %s\n' "$DEST/recap.py"
237
+ printf '\nClaude Code picks up %s/skills without a restart.\n' "$CONFIG_DIR"
238
+ printf 'Restart it only if that directory did not exist when the session started.\n'
239
+ printf '\nRun it with: /recap\n'
240
+ printf 'Or directly: python3 %s\n' "$DEST/recap.py"
241
+ printf 'Uninstall: ./install.sh --uninstall\n'
242
+
243
+ if ! command -v python3 >/dev/null 2>&1; then
244
+ printf '\nWarning: python3 was not found on PATH. The skill needs Python 3 to run.\n' >&2
245
+ fi
246
+
247
+ for rel in $TRACKED; do
248
+ [ -f "$DEST/$rel" ] || die "post-install check failed: $DEST/$rel is missing"
249
+ done
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "claude-code-recap",
3
+ "version": "1.2.0",
4
+ "description": "Installs the recap skill into your Claude Code skills directory: list recent sessions across every project and get a ready-to-paste resume command.",
5
+ "type": "module",
6
+ "bin": {
7
+ "claude-code-recap": "bin/cli.mjs"
8
+ },
9
+ "exports": {
10
+ "./package.json": "./package.json"
11
+ },
12
+ "files": [
13
+ "bin/",
14
+ "skills/",
15
+ "install.sh",
16
+ "!**/__pycache__",
17
+ "!**/*.pyc"
18
+ ],
19
+ "engines": {
20
+ "node": ">=18.17"
21
+ },
22
+ "keywords": [
23
+ "claude-code",
24
+ "claude-code-skills",
25
+ "claude-code-skill",
26
+ "claude-skill",
27
+ "agent-skills",
28
+ "agent-skill",
29
+ "skill-md",
30
+ "skills",
31
+ "anthropic",
32
+ "claude",
33
+ "session-recap",
34
+ "resume",
35
+ "developer-tools",
36
+ "cli"
37
+ ],
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "git+https://github.com/noluyorAbi/claude-code-recap.git"
41
+ },
42
+ "homepage": "https://github.com/noluyorAbi/claude-code-recap#readme",
43
+ "bugs": {
44
+ "url": "https://github.com/noluyorAbi/claude-code-recap/issues"
45
+ },
46
+ "license": "MIT",
47
+ "author": "noluyorAbi <121226886+noluyorAbi@users.noreply.github.com>",
48
+ "publishConfig": {
49
+ "access": "public"
50
+ },
51
+ "scripts": {
52
+ "check:version": "node scripts/check-version.mjs",
53
+ "test": "sh tests/smoke.sh"
54
+ }
55
+ }
@@ -0,0 +1,103 @@
1
+ ---
2
+ name: recap
3
+ description: Show recent Claude Code sessions across all projects, so the user can re-enter work after a reboot or context switch. Lists per session the absolute project path, a short summary, last activity, turn count, git branch, model, and a ready-to-paste resume command, and can re-open all of them at once in new terminal tabs. Use when the user asks "what was I working on", "which projects did I touch recently", "where did I leave off", "list my recent sessions", "how do I get back into that session", "open all my sessions again", or invokes /recap. Reads only local files; the default run is instant and offline.
4
+ license: MIT
5
+ metadata:
6
+ version: "1.2.0"
7
+ author: noluyorAbi
8
+ source: https://github.com/noluyorAbi/claude-code-recap
9
+ ---
10
+
11
+ # recap: re-entry radar for Claude Code sessions
12
+
13
+ One command, full overview of recent sessions across every project on this machine. Built for the post-reboot moment: which projects, what was each session about, and the exact command to jump back in.
14
+
15
+ ## How to run it
16
+
17
+ Personal or project install (the skill directory is on disk next to this file):
18
+
19
+ ```bash
20
+ python3 ~/.claude/skills/recap/recap.py [flags]
21
+ ```
22
+
23
+ Plugin install (Claude Code exports the plugin root):
24
+
25
+ ```bash
26
+ python3 "$CLAUDE_PLUGIN_ROOT/skills/recap/recap.py" [flags]
27
+ ```
28
+
29
+ Nothing to install beyond Python 3 (stdlib only, no third-party packages).
30
+
31
+ | Flag | Meaning |
32
+ |------|---------|
33
+ | (none) | last 15 sessions, newest first, instant, no network |
34
+ | `--since 7d` | only sessions active in the window (`30m`, `24h`, `7d`, `2w`) |
35
+ | `--project foo` | filter by substring of the absolute project path |
36
+ | `--limit N` | max rows (default 15) |
37
+ | `--json` | machine-readable output with full session ids and resume commands |
38
+ | `--smart` | real one-sentence summaries via ONE `claude -p` haiku call (network, ~10s) |
39
+ | `--pick` | choose a row interactively, then `cd` + `claude -r` into it directly |
40
+ | `--open` | open EVERY listed session in its own new terminal tab and resume it there (macOS) |
41
+ | `--claude-flags "..."` | extra flags for each resumed `claude` (e.g. `"--chrome --dangerously-skip-permissions"`); also shown in the printed resume lines and `--json` |
42
+ | `--terminal auto\|iTerm2\|Terminal` | which app `--open` drives (default: iTerm2 when installed) |
43
+ | `--yes` / `-y` | skip the `--open` confirmation prompt (required when stdin is not a TTY) |
44
+ | `--dry-run` | with `--open`: print the tabs and commands, open nothing |
45
+ | `--color` | force ANSI colors when output is piped (e.g. inside Claude Code) |
46
+ | `--plain` | no ANSI at all (also honors NO_COLOR / FORCE_COLOR env) |
47
+
48
+ ## Usage examples
49
+
50
+ ```bash
51
+ # after a reboot: what was I doing?
52
+ python3 ~/.claude/skills/recap/recap.py
53
+
54
+ # everything in one repo this week
55
+ python3 ~/.claude/skills/recap/recap.py --since 7d --project my-repo
56
+
57
+ # jump straight back into a session
58
+ python3 ~/.claude/skills/recap/recap.py --pick
59
+
60
+ # re-open yesterday's whole working set in terminal tabs, browser enabled, no permission prompts
61
+ python3 ~/.claude/skills/recap/recap.py --since 24h --limit 12 \
62
+ --open --yes --claude-flags "--chrome --dangerously-skip-permissions"
63
+
64
+ # preview first, open nothing
65
+ python3 ~/.claude/skills/recap/recap.py --open --dry-run
66
+
67
+ # feed into other tooling
68
+ python3 ~/.claude/skills/recap/recap.py --json --limit 50
69
+ ```
70
+
71
+ ## The `--open` flag
72
+
73
+ Restores a whole working set at once. It opens one new tab per listed session and types the resume command into it. Rules:
74
+
75
+ - The session recap itself runs in is skipped automatically (matched on `CLAUDE_CODE_SESSION_ID`), so it never re-opens itself.
76
+ - Sessions whose project directory no longer exists are skipped and reported, never opened.
77
+ - Scope comes from the normal filters, so `--limit` / `--since` / `--project` decide exactly which tabs appear. Check with `--dry-run` before committing.
78
+ - Without `--yes` it asks for confirmation; with piped stdin (Claude Code's Bash tool) it refuses to open unless `--yes` is passed.
79
+ - `--claude-flags` is passed through verbatim to every tab. `--dangerously-skip-permissions` disables all permission checks in each opened session; the script prints a warning before doing it.
80
+ - Tab opening drives iTerm2 or Terminal through `osascript`, so it is macOS-only. On other platforms `--open` exits with a clear message; `--open --dry-run` still prints the commands so they can be pasted anywhere.
81
+
82
+ ## When Claude runs this skill
83
+
84
+ Run the script with Bash, ALWAYS with `--color` (tool output is piped, auto-detection would strip the colors; Claude Code renders ANSI). Add `--since`/`--project` if the user narrowed the question, then relay the table. Quote the resume command (`cd <path> && claude -r <id>`) for any session the user wants to re-enter. Only add `--smart` if the user explicitly asks for better summaries; it makes a network call.
85
+
86
+ Use `--open` only when the user explicitly asks to re-open or resume the sessions. Because the Bash tool pipes stdin, `--open` needs `--yes`; treat the user's request as the confirmation, and set the scope with `--limit`/`--since` so no unwanted session gets a tab. Pass `--claude-flags` only with flags the user named; state the permission-bypass warning in your reply whenever `--dangerously-skip-permissions` is among them.
87
+
88
+ ## Data sources and guarantees
89
+
90
+ - `~/.claude/history.jsonl` is the fast index (prompt text, timestamp, project path, session id). `CLAUDE_CONFIG_DIR` is honored when set.
91
+ - `~/.claude/projects/<encoded>/<session>.jsonl` transcripts are parsed only for the displayed rows (title, branch, model, turn count). Project paths always come from the `cwd`/`project` fields, never decoded from folder names (that encoding is lossy).
92
+ - Summary preference: Claude Code's own `ai-title` line, else the first real user prompt, else `(no prompt)`.
93
+ - `--smart` privacy: the only path that leaves the machine. It shells out to the local `claude` CLI once and sends, for the listed sessions only, each session's 8-character id prefix, its title (first 150 characters), and its first user prompt (first 300 characters). No file bodies, no transcript contents, no other sessions. If `claude` is not on PATH, `--smart` is skipped with a warning and the offline summaries are used.
94
+ - Turn count groups assistant streaming chunks by message id and is labeled approximate.
95
+ - Broken or partial JSONL lines are skipped, never fatal. Times shown in the local timezone.
96
+ - Display: day-grouped timeline (Today green, Yesterday amber), 14-day activity sparkline in the header, per-project colored dot (stable hash), four-level gray hierarchy with one accent color, per-session resume line indented with spaces only (safe to copy). Falls back to plain text when piped or when NO_COLOR is set.
97
+ - Read-only: the tool never writes or deletes anything. It sees only sessions still on disk (Claude Code prunes old ones). `--open` and `--pick` are the only side-effecting paths: `--open` drives iTerm2/Terminal via `osascript`, `--pick` `exec`s `claude -r` in the chosen directory. Neither writes or touches session data.
98
+
99
+ ## Optional shell alias
100
+
101
+ ```bash
102
+ alias recap='python3 ~/.claude/skills/recap/recap.py'
103
+ ```