session-planner 1.0.1 → 1.0.3

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/README.md CHANGED
@@ -3,39 +3,21 @@
3
3
  [![CI](https://github.com/MahmoudMabrok/SessionPlanner/actions/workflows/ci.yml/badge.svg)](https://github.com/MahmoudMabrok/SessionPlanner/actions)
4
4
  [![npm](https://img.shields.io/npm/v/session-planner)](https://www.npmjs.com/package/session-planner)
5
5
 
6
- > Schedule "Hello!" Claude Code sessions at times you choose opening Claude **N hours before** each target time. Works as a terminal CLI, an `npx` one-liner, or a Claude Code plugin.
7
- >
8
- > 📦 **Available on npm:** [session-planner](https://www.npmjs.com/package/session-planner)
6
+ This tool helps maximize your Claude Code session duration by opening sessions ahead of time. Starting a session earlier ensures that if you hit a usage limit, the next reset time is already much closer (or has already passed), effectively eliminating idle waiting time.
9
7
 
10
- ---
11
8
 
12
- ## 💡 Motivation
9
+ # How it works
13
10
 
14
- This tool helps maximize your Claude Code session duration by opening sessions ahead of time. Starting a session earlier ensures that if you hit a usage limit, the next reset time is already much closer (or has already passed), effectively eliminating idle waiting time.
15
-
16
- ### The "Session Hack" Explained
11
+ > Schedule "Hello!" Claude Code sessions at times you choose opening Claude **N hours before** each target time. Works as a terminal CLI, an `npx` one-liner, or a Claude Code plugin.
12
+ >
13
+ > 📦 **Available on npm:** [session-planner](https://www.npmjs.com/package/session-planner)
17
14
 
18
- ![Hacking Time Illustration](file:///Users/mahmoud.maghrabia/.gemini/antigravity/brain/4caf6a88-8f79-4afb-a4ff-4f4b38b32272/session_planner_hack_final_illustration_1775343574461.png)
19
15
 
20
- ```mermaid
21
- graph TD
22
- subgraph Standard_Way [Standard Way]
23
- A1[Start Work at 1 PM] --> B1{Hit Limit?}
24
- B1 -- Yes --> C1["'Limit will reset at 9:00 PM'"]
25
- C1 --> D1[Wait 5 Hours 😫]
26
- end
27
16
 
28
- subgraph The_Hack [The Hack]
29
- A2[Auto-Open at 9 AM] --> B2[Start Work at 1 PM]
30
- B2 --> C2{Hit Limit?}
31
- C2 -- Yes --> D2["'Limit will reset... in 5 mins'"]
32
- D2 --> E2[NO MORE WAITING 😎]
33
- end
17
+ ## The "Session Hack" Explained
18
+ ![session_planner_hack_infographic_1775344203921](https://github.com/user-attachments/assets/55c788ed-e35d-4f23-85f6-9ddb79df940c)
19
+ ![session_planner_hack_final_illustration_1775343574461](https://github.com/user-attachments/assets/b31971d5-eae6-49bf-8cbb-d938494045c6)
34
20
 
35
- style A2 fill:#4CAF50,stroke:#2E7D32,color:#fff
36
- style E2 fill:#2196F3,stroke:#1565C0,color:#fff
37
- style D1 fill:#f44336,stroke:#c62828,color:#fff
38
- ```
39
21
 
40
22
  ---
41
23
 
@@ -143,14 +125,11 @@ Logs: `~/.claude/session-planner.log`
143
125
 
144
126
  ```
145
127
  SessionPlanner/
146
- ├── .claude-plugin/plugin.json Claude Code plugin manifest
147
- ├── bin/session-planner.js ← npx / global CLI entry
148
- ├── commands/{hello,hello-list,hello-remove}.md
149
- ├── skills/hello-scheduler/SKILL.md
150
- ├── scripts/hello-scheduler.sh ← core scheduling logic
151
- ├── tests/test-parse.sh ← 18 unit tests
128
+ ├── hello-scheduler.sh core scheduling logic
129
+ ├── session-planner.js ← npx / global CLI entry
130
+ ├── {hello,hello-list,hello-remove}.md ← command definitions
152
131
  ├── package.json
153
- └── .github/workflows/ci.yml
132
+ └── README.md
154
133
  ```
155
134
 
156
135
  ---
package/hello-list.md ADDED
@@ -0,0 +1,10 @@
1
+ ---
2
+ description: List all scheduled Hello! sessions. Shows target times, session-open times, and cron expressions.
3
+ allowed-tools: Bash
4
+ ---
5
+
6
+ List all scheduled hello sessions:
7
+
8
+ !`bash ~/.claude/scripts/hello-scheduler.sh --list`
9
+
10
+ Present the output as a clean table to the user. If no jobs are found, let them know and suggest running `/hello <time>` to schedule one.
@@ -0,0 +1,15 @@
1
+ ---
2
+ description: Remove a scheduled Hello! session. Usage: /hello-remove 2pm OR /hello-remove --all
3
+ argument-hint: [time] e.g. 2pm OR --all
4
+ allowed-tools: Bash
5
+ ---
6
+
7
+ The user wants to remove a scheduled hello session. Their argument is: $ARGUMENTS
8
+
9
+ If the argument is "--all" or "all":
10
+ !`bash ~/.claude/scripts/hello-scheduler.sh --remove-all`
11
+
12
+ Otherwise treat $ARGUMENTS as a time and run:
13
+ !`bash ~/.claude/scripts/hello-scheduler.sh --remove $ARGUMENTS`
14
+
15
+ Confirm to the user what was removed.
@@ -0,0 +1,270 @@
1
+ #!/usr/bin/env bash
2
+ # scripts/hello-scheduler.sh
3
+ # Schedule "Hello!" Claude Code sessions at specific times.
4
+ # Each session opens N hours before the chosen time (default: 4h).
5
+ #
6
+ # When installed as a plugin, this script lives at:
7
+ # ~/.claude/plugins/session-planner/scripts/hello-scheduler.sh
8
+ #
9
+ # Usage:
10
+ # hello-scheduler.sh 1am
11
+ # hello-scheduler.sh 9am 2pm 6pm
12
+ # hello-scheduler.sh 14:00 --offset 2h
13
+ # hello-scheduler.sh --list
14
+ # hello-scheduler.sh --remove 2pm
15
+ # hello-scheduler.sh --remove-all
16
+ # hello-scheduler.sh --help
17
+
18
+ set -euo pipefail
19
+
20
+ # ── defaults ──────────────────────────────────────────────────────────────────
21
+ OFFSET_HOURS=4
22
+ TIMES=()
23
+ LIST_MODE=false
24
+ REMOVE_ALL=false
25
+ REMOVE_TIME=""
26
+
27
+ # ── colours (disabled when not a tty, e.g. inside cron) ──────────────────────
28
+ if [[ -t 1 ]]; then
29
+ RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
30
+ BOLD='\033[1m'; RESET='\033[0m'
31
+ else
32
+ RED=''; GREEN=''; YELLOW=''; BOLD=''; RESET=''
33
+ fi
34
+
35
+ # ── helpers ───────────────────────────────────────────────────────────────────
36
+
37
+ usage() {
38
+ cat <<EOF
39
+ ${BOLD}hello-scheduler${RESET} — Schedule "Hello!" Claude Code sessions
40
+
41
+ ${BOLD}USAGE${RESET}
42
+ hello-scheduler.sh <time> [time2 ...] [--offset Nh]
43
+ hello-scheduler.sh --list
44
+ hello-scheduler.sh --remove <time>
45
+ hello-scheduler.sh --remove-all
46
+
47
+ ${BOLD}TIME FORMATS${RESET}
48
+ 1am 2pm 12:00 9:30am 14:30
49
+
50
+ ${BOLD}OPTIONS${RESET}
51
+ --offset Nh Open session N hours before target time (default: 4)
52
+ --list Show all scheduled hello jobs
53
+ --remove <time> Remove the job for a specific target time
54
+ --remove-all Cancel all scheduled hello jobs
55
+ --help Show this help
56
+
57
+ ${BOLD}EXAMPLES${RESET}
58
+ hello-scheduler.sh 1am
59
+ hello-scheduler.sh 9am 2pm 6pm
60
+ hello-scheduler.sh 14:00 --offset 2h
61
+ hello-scheduler.sh --remove 2pm
62
+ hello-scheduler.sh --list
63
+ EOF
64
+ exit 0
65
+ }
66
+
67
+ error() { echo -e "${RED}ERROR:${RESET} $1" >&2; exit 1; }
68
+
69
+ # Cross-platform date → epoch (GNU date -d OR BSD/macOS date -j)
70
+ timestamp_for() {
71
+ local s="$1"
72
+ date -d "$s" +%s 2>/dev/null \
73
+ || date -j -f "%Y-%m-%d %H:%M:%S" "$s" +%s 2>/dev/null \
74
+ || error "Cannot parse date '$s'. Is 'date' available?"
75
+ }
76
+
77
+ # Parse any supported time format → sets PARSED_HOUR, PARSED_MIN
78
+ parse_time() {
79
+ local raw="${1,,}"
80
+ PARSED_HOUR=""; PARSED_MIN=0
81
+
82
+ # 24-hour H:MM or HH:MM
83
+ if [[ "$raw" =~ ^([0-9]{1,2}):([0-9]{2})$ ]]; then
84
+ PARSED_HOUR=$((10#${BASH_REMATCH[1]}))
85
+ PARSED_MIN=$((10#${BASH_REMATCH[2]}))
86
+
87
+ # 12-hour with minutes H:MMam / H:MMpm
88
+ elif [[ "$raw" =~ ^([0-9]{1,2}):([0-9]{2})(am|pm)$ ]]; then
89
+ local h=$((10#${BASH_REMATCH[1]})) m=$((10#${BASH_REMATCH[2]})) ap=${BASH_REMATCH[3]}
90
+ [[ $h -lt 1 || $h -gt 12 ]] && error "Hour must be 1-12 for am/pm (got: $1)"
91
+ [[ $m -gt 59 ]] && error "Minutes must be 00-59 (got: $1)"
92
+ [[ "$ap" == "am" ]] && PARSED_HOUR=$(( h==12 ? 0 : h )) || PARSED_HOUR=$(( h==12 ? 12 : h+12 ))
93
+ PARSED_MIN=$m
94
+
95
+ # 12-hour no minutes Ham / Hpm
96
+ elif [[ "$raw" =~ ^([0-9]{1,2})(am|pm)$ ]]; then
97
+ local h=$((10#${BASH_REMATCH[1]})) ap=${BASH_REMATCH[2]}
98
+ [[ $h -lt 1 || $h -gt 12 ]] && error "Hour must be 1-12 for am/pm (got: $1)"
99
+ [[ "$ap" == "am" ]] && PARSED_HOUR=$(( h==12 ? 0 : h )) || PARSED_HOUR=$(( h==12 ? 12 : h+12 ))
100
+ PARSED_MIN=0
101
+
102
+ else
103
+ error "Unrecognised time format: '$1'. Try: 1am 2:30pm 14:00"
104
+ fi
105
+
106
+ [[ $PARSED_HOUR -gt 23 ]] && error "Hour out of range 0-23 (got: $1)"
107
+ }
108
+
109
+ # 24h hour+min → pretty 12h string
110
+ pretty_time() {
111
+ local h=$1 m=$2 suffix ph
112
+ if [[ $h -eq 0 ]]; then ph=12; suffix="am"
113
+ elif [[ $h -lt 12 ]]; then ph=$h; suffix="am"
114
+ elif [[ $h -eq 12 ]]; then ph=12; suffix="pm"
115
+ else ph=$(( h-12 )); suffix="pm"
116
+ fi
117
+ printf "%d:%02d %s" $ph $m $suffix
118
+ }
119
+
120
+ # Seconds until next occurrence of HH:MM (today or tomorrow)
121
+ seconds_until() {
122
+ local th=$1 tm=$2
123
+ local now today target_ts
124
+ now=$(date +%s)
125
+ today=$(date +%Y-%m-%d)
126
+ target_ts=$(timestamp_for "$today $(printf '%02d:%02d' "$th" "$tm"):00")
127
+ [[ $target_ts -le $now ]] && target_ts=$(( target_ts + 86400 ))
128
+ echo $(( target_ts - now ))
129
+ }
130
+
131
+ # ── argument parsing ──────────────────────────────────────────────────────────
132
+
133
+ [[ $# -eq 0 ]] && usage
134
+
135
+ while [[ $# -gt 0 ]]; do
136
+ case "$1" in
137
+ --help|-h) usage ;;
138
+ --list|-l) LIST_MODE=true; shift ;;
139
+ --remove-all) REMOVE_ALL=true; shift ;;
140
+ --remove)
141
+ [[ -z "${2:-}" ]] && error "--remove requires a time (e.g. --remove 2pm)"
142
+ REMOVE_TIME="$2"; shift 2 ;;
143
+ --offset)
144
+ [[ -z "${2:-}" ]] && error "--offset requires a value (e.g. 2h or 2)"
145
+ val="${2//h/}"
146
+ [[ "$val" =~ ^[0-9]+$ ]] || error "--offset must be a number (e.g. 2h or 2)"
147
+ OFFSET_HOURS=$val; shift 2 ;;
148
+ --offset=*)
149
+ val="${1#*=}"; val="${val//h/}"
150
+ [[ "$val" =~ ^[0-9]+$ ]] || error "--offset must be a number (e.g. --offset=2h)"
151
+ OFFSET_HOURS=$val; shift ;;
152
+ -*) error "Unknown option: $1" ;;
153
+ *) TIMES+=("$1"); shift ;;
154
+ esac
155
+ done
156
+
157
+ # ── --list ────────────────────────────────────────────────────────────────────
158
+
159
+ if $LIST_MODE; then
160
+ echo -e "${BOLD}Scheduled Hello jobs:${RESET}"
161
+ if crontab -l 2>/dev/null | grep -q "# session-planner"; then
162
+ crontab -l 2>/dev/null | grep "# session-planner"
163
+ else
164
+ echo -e " ${YELLOW}No session-planner jobs found.${RESET}"
165
+ fi
166
+ exit 0
167
+ fi
168
+
169
+ # ── --remove-all ──────────────────────────────────────────────────────────────
170
+
171
+ if $REMOVE_ALL; then
172
+ tmp=$(mktemp)
173
+ crontab -l 2>/dev/null \
174
+ | grep -v "# session-planner" \
175
+ | grep -v "session-planner" \
176
+ > "$tmp" || true
177
+ crontab "$tmp"; rm -f "$tmp"
178
+ echo -e "${GREEN}✓${RESET} All session-planner jobs removed."
179
+ exit 0
180
+ fi
181
+
182
+ # ── --remove <time> ───────────────────────────────────────────────────────────
183
+
184
+ if [[ -n "$REMOVE_TIME" ]]; then
185
+ parse_time "$REMOVE_TIME"
186
+ TARGET_PRETTY=$(pretty_time "$PARSED_HOUR" "$PARSED_MIN")
187
+ tmp=$(mktemp)
188
+ crontab -l 2>/dev/null \
189
+ | grep -v "# session-planner.*${TARGET_PRETTY}" \
190
+ | grep -v "Hello! It is now ${TARGET_PRETTY}" \
191
+ > "$tmp" || true
192
+ crontab "$tmp"; rm -f "$tmp"
193
+ echo -e "${GREEN}✓${RESET} Removed session-planner job for ${BOLD}${TARGET_PRETTY}${RESET}."
194
+ exit 0
195
+ fi
196
+
197
+ # ── validate at least one time was given ─────────────────────────────────────
198
+
199
+ [[ ${#TIMES[@]} -eq 0 ]] && error "Please provide at least one time. E.g: hello-scheduler.sh 1am"
200
+
201
+ # ── ensure log dir ────────────────────────────────────────────────────────────
202
+
203
+ mkdir -p "$HOME/.claude"
204
+ LOG="$HOME/.claude/session-planner.log"
205
+
206
+ # ── load crontab, strip existing session-planner entries ─────────────────────
207
+
208
+ tmp=$(mktemp)
209
+ crontab -l 2>/dev/null \
210
+ | grep -v "# session-planner" \
211
+ | grep -v "session-planner" \
212
+ > "$tmp" || true
213
+
214
+ # ── process each time ─────────────────────────────────────────────────────────
215
+
216
+ JOBS_JSON="["
217
+ FIRST=true
218
+ SOONEST_SECS=999999999
219
+ SOONEST_TARGET=""
220
+ SOONEST_SESSION=""
221
+
222
+ for T in "${TIMES[@]}"; do
223
+ parse_time "$T"
224
+ TARGET_H=$PARSED_HOUR
225
+ TARGET_M=$PARSED_MIN
226
+
227
+ # Calculate session-open time = target − offset
228
+ TOTAL=$(( TARGET_H*60 + TARGET_M ))
229
+ SCHED=$(( (TOTAL - OFFSET_HOURS*60 + 1440) % 1440 ))
230
+ SCHED_H=$(( SCHED/60 ))
231
+ SCHED_M=$(( SCHED%60 ))
232
+
233
+ TARGET_PRETTY=$(pretty_time "$TARGET_H" "$TARGET_M")
234
+ SCHED_PRETTY=$(pretty_time "$SCHED_H" "$SCHED_M")
235
+
236
+ # cron job: claude --print fires at session-open time
237
+ MSG="Hello! It is now ${TARGET_PRETTY}. This is your scheduled greeting."
238
+ CRON_EXPR="${SCHED_M} ${SCHED_H} * * *"
239
+ CRON_LINE="${CRON_EXPR} claude --print \"${MSG}\" >> ${LOG} 2>&1"
240
+
241
+ {
242
+ echo "# session-planner: session at ${SCHED_PRETTY} → greeting at ${TARGET_PRETTY} (offset: ${OFFSET_HOURS}h)"
243
+ echo "$CRON_LINE"
244
+ } >> "$tmp"
245
+
246
+ # Seconds until session-open for /loop handoff
247
+ LOOP_SECS=$(seconds_until "$SCHED_H" "$SCHED_M")
248
+
249
+ if [[ $LOOP_SECS -lt $SOONEST_SECS ]]; then
250
+ SOONEST_SECS=$LOOP_SECS
251
+ SOONEST_TARGET="$TARGET_PRETTY"
252
+ SOONEST_SESSION="$SCHED_PRETTY"
253
+ fi
254
+
255
+ # Structured output for Claude to parse
256
+ echo "SCHEDULED: {\"target\":\"${TARGET_PRETTY}\",\"session_at\":\"${SCHED_PRETTY}\",\"cron\":\"${CRON_EXPR}\",\"loop_in_seconds\":${LOOP_SECS},\"offset_hours\":${OFFSET_HOURS}}"
257
+
258
+ $FIRST || JOBS_JSON+=","
259
+ JOBS_JSON+="{\"target\":\"${TARGET_PRETTY}\",\"session_at\":\"${SCHED_PRETTY}\",\"loop_in_seconds\":${LOOP_SECS}}"
260
+ FIRST=false
261
+ done
262
+
263
+ JOBS_JSON+="]"
264
+
265
+ # Commit updated crontab
266
+ crontab "$tmp"
267
+ rm -f "$tmp"
268
+
269
+ # Summary for Claude's confirmation message
270
+ echo "SUMMARY: {\"count\":${#TIMES[@]},\"offset_hours\":${OFFSET_HOURS},\"soonest_session_in_seconds\":${SOONEST_SECS},\"soonest_target\":\"${SOONEST_TARGET}\",\"soonest_session\":\"${SOONEST_SESSION}\",\"log\":\"${LOG}\",\"jobs\":${JOBS_JSON}}"
package/hello.md ADDED
@@ -0,0 +1,55 @@
1
+ ---
2
+ description: Schedule one or more "Hello!" greetings at specific times. Sessions open N hours before each time (default 4h). Usage: /hello <time> [time2 ...] [--offset Nh]
3
+ argument-hint: [time ...] [--offset Nh] e.g. 1am / 9am 2pm / 14:00 --offset 2h
4
+ allowed-tools: Bash
5
+ ---
6
+
7
+ The user wants to schedule hello sessions. Their arguments are: $ARGUMENTS
8
+
9
+ ## Step 1 — Run the scheduler
10
+
11
+ Execute the scheduler script:
12
+
13
+ !`bash ~/.claude/scripts/hello-scheduler.sh $ARGUMENTS`
14
+
15
+ ## Step 2 — Parse the output and report
16
+
17
+ Read the output from the script carefully. It will print one JSON block per scheduled time in the form:
18
+
19
+ ```
20
+ SCHEDULED: {"target":"<pretty time>","session_at":"<pretty time>","cron":"<cron expression>","loop_in_seconds":<number>}
21
+ ```
22
+
23
+ And a final summary line:
24
+
25
+ ```
26
+ SUMMARY: {"count":<n>,"offset_hours":<h>,"jobs":[...]}
27
+ ```
28
+
29
+ For each SCHEDULED entry, tell the user clearly:
30
+ - The **target time** they chose
31
+ - The **session opens at** time (offset hours before)
32
+ - The cron expression registered
33
+
34
+ ## Step 3 — Use /loop for in-session countdown
35
+
36
+ After reporting all scheduled jobs, use the built-in `/loop` command to set a **single-fire reminder** for the soonest upcoming session time.
37
+
38
+ Calculate which session_at time is soonest (it will be in the output). Then issue:
39
+
40
+ ```
41
+ /loop <interval> Say: "⏰ Reminder: your Hello session is starting now! Originally scheduled for <target time>."
42
+ ```
43
+
44
+ Where `<interval>` is the number of seconds until the soonest session (use the `loop_in_seconds` value from SCHEDULED output).
45
+
46
+ This uses Claude Code's built-in `/loop` to give the user an in-session ping without needing an extra tool.
47
+
48
+ ## Step 4 — Final message
49
+
50
+ Tell the user:
51
+ 1. How many jobs were scheduled
52
+ 2. The offset used (e.g. "4 hours before each target time")
53
+ 3. That persistent cron jobs have been registered (survive terminal close)
54
+ 4. That the `/loop` in-session reminder was set for the soonest session
55
+ 5. How to list or remove jobs: `crontab -l` / `~/.claude/scripts/hello-scheduler.sh --list` / `--remove-all`
package/package.json CHANGED
@@ -1,8 +1,14 @@
1
1
  {
2
2
  "name": "session-planner",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "Schedule Hello! Claude Code sessions at specific times. Opens a session N hours before your chosen time.",
5
- "keywords": ["claude", "claude-code", "scheduler", "cli", "cron"],
5
+ "keywords": [
6
+ "claude",
7
+ "claude-code",
8
+ "scheduler",
9
+ "cli",
10
+ "cron"
11
+ ],
6
12
  "author": "MahmoudMabrok",
7
13
  "license": "MIT",
8
14
  "homepage": "https://github.com/MahmoudMabrok/SessionPlanner",
@@ -23,11 +29,11 @@
23
29
  "test": "bash tests/test-parse.sh"
24
30
  },
25
31
  "files": [
26
- "bin/",
27
- "scripts/",
28
- "commands/",
29
- "skills/",
30
- ".claude-plugin/",
32
+ "hello-scheduler.sh",
33
+ "session-planner.js",
34
+ "hello.md",
35
+ "hello-list.md",
36
+ "hello-remove.md",
31
37
  "README.md",
32
38
  "LICENSE"
33
39
  ]