claude-code-handoff 1.9.0 → 2.0.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.
Files changed (3) hide show
  1. package/cli.js +116 -69
  2. package/install.sh +133 -99
  3. package/package.json +1 -1
package/cli.js CHANGED
@@ -3,24 +3,35 @@
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
5
 
6
- const GREEN = '\x1b[32m';
7
- const YELLOW = '\x1b[33m';
8
- const CYAN = '\x1b[36m';
6
+ // Colors (RGB)
7
+ const AMBER = '\x1b[38;2;245;158;11m';
8
+ const GREEN = '\x1b[38;2;16;185;129m';
9
+ const RED = '\x1b[38;2;239;68;68m';
10
+ const CYAN = '\x1b[38;2;34;211;238m';
11
+ const WHITE = '\x1b[37m';
12
+ const GRAY = '\x1b[90m';
13
+ const BOLD = '\x1b[1m';
14
+ const DIM = '\x1b[2m';
9
15
  const NC = '\x1b[0m';
10
16
 
17
+ const ok = (msg) => console.log(` ${GREEN}✓${NC} ${msg}`);
18
+ const fail = (msg) => console.log(` ${RED}✗${NC} ${msg}`);
19
+ const info = (msg) => console.log(` ${GRAY}${msg}${NC}`);
20
+ const head = (msg) => console.log(`\n ${AMBER}${BOLD}${msg}${NC}`);
21
+
11
22
  const PROJECT_DIR = process.cwd();
12
23
  const CLAUDE_DIR = path.join(PROJECT_DIR, '.claude');
13
24
  const SCRIPT_DIR = __dirname;
14
25
 
26
+ // ─── Banner ───────────────────────────────────────────
15
27
  console.log('');
16
- console.log(`${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}`);
17
- console.log(`${CYAN} claude-code-handoff — Session Continuity${NC}`);
18
- console.log(`${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}`);
19
- console.log('');
20
- console.log(` Project: ${GREEN}${PROJECT_DIR}${NC}`);
28
+ console.log(` ${AMBER}${BOLD}┌──────────────────────────────────────┐${NC}`);
29
+ console.log(` ${AMBER}${BOLD}│${NC} ${WHITE}${BOLD}claude-code-handoff${NC} ${DIM}v1.9${NC} ${AMBER}${BOLD}│${NC}`);
30
+ console.log(` ${AMBER}${BOLD}│${NC} ${GRAY}Session Continuity for Claude Code${NC} ${AMBER}${BOLD}│${NC}`);
31
+ console.log(` ${AMBER}${BOLD}└──────────────────────────────────────┘${NC}`);
21
32
  console.log('');
33
+ info(`Project: ${WHITE}${PROJECT_DIR}${NC}`);
22
34
 
23
- // Helper
24
35
  function ensureDir(dir) {
25
36
  if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
26
37
  }
@@ -30,48 +41,62 @@ function copyFile(src, dst) {
30
41
  if (fs.existsSync(srcPath)) {
31
42
  fs.copyFileSync(srcPath, dst);
32
43
  } else {
33
- console.error(` Error: ${src} not found in package`);
44
+ fail(`${src} not found in package`);
34
45
  process.exit(1);
35
46
  }
36
47
  }
37
48
 
38
- // 1. Create directories
39
- console.log(` ${YELLOW}[1/10]${NC} Creating directories...`);
49
+ // ─── Pre-flight ───────────────────────────────────────
50
+ head('Pre-flight');
51
+
52
+ const monitorPath = path.join(CLAUDE_DIR, 'hooks', 'context-monitor.sh');
53
+ let isReinstall = false;
54
+ let savedThreshold = '';
55
+ let savedMaxContext = '';
56
+ if (fs.existsSync(monitorPath)) {
57
+ isReinstall = true;
58
+ const oldContent = fs.readFileSync(monitorPath, 'utf-8');
59
+ const thresholdMatch = oldContent.match(/CLAUDE_CONTEXT_THRESHOLD:-(\d+)/);
60
+ const maxContextMatch = oldContent.match(/CLAUDE_MAX_CONTEXT:-(\d+)/);
61
+ if (thresholdMatch) savedThreshold = thresholdMatch[1];
62
+ if (maxContextMatch) savedMaxContext = maxContextMatch[1];
63
+ }
64
+
65
+ if (isReinstall) {
66
+ info('Existing installation found. Upgrading...');
67
+ } else {
68
+ ok('Fresh install');
69
+ }
70
+
71
+ // ─── Directories ──────────────────────────────────────
72
+ head('Creating directories');
73
+
40
74
  ensureDir(path.join(CLAUDE_DIR, 'commands'));
41
75
  ensureDir(path.join(CLAUDE_DIR, 'rules'));
42
76
  ensureDir(path.join(CLAUDE_DIR, 'hooks'));
43
77
  ensureDir(path.join(CLAUDE_DIR, 'handoffs', 'archive'));
78
+ ok('Directory structure created');
79
+
80
+ // ─── Commands ─────────────────────────────────────────
81
+ head('Installing commands');
44
82
 
45
- // 2. Copy commands
46
- console.log(` ${YELLOW}[2/10]${NC} Installing commands...`);
47
83
  copyFile('commands/resume.md', path.join(CLAUDE_DIR, 'commands', 'resume.md'));
48
84
  copyFile('commands/save-handoff.md', path.join(CLAUDE_DIR, 'commands', 'save-handoff.md'));
49
85
  copyFile('commands/switch-context.md', path.join(CLAUDE_DIR, 'commands', 'switch-context.md'));
50
86
  copyFile('commands/handoff.md', path.join(CLAUDE_DIR, 'commands', 'handoff.md'));
51
87
  copyFile('commands/delete-handoff.md', path.join(CLAUDE_DIR, 'commands', 'delete-handoff.md'));
52
88
  copyFile('commands/auto-handoff.md', path.join(CLAUDE_DIR, 'commands', 'auto-handoff.md'));
89
+ ok('6 slash commands installed');
90
+
91
+ // ─── Rules ────────────────────────────────────────────
92
+ head('Installing rules');
53
93
 
54
- // 3. Copy rules
55
- console.log(` ${YELLOW}[3/10]${NC} Installing rules...`);
56
94
  copyFile('rules/session-continuity.md', path.join(CLAUDE_DIR, 'rules', 'session-continuity.md'));
57
95
  copyFile('rules/auto-handoff.md', path.join(CLAUDE_DIR, 'rules', 'auto-handoff.md'));
96
+ ok('Behavioral rules installed');
58
97
 
59
- // 4. Install hooks
60
- console.log(` ${YELLOW}[4/10]${NC} Installing hooks...`);
61
-
62
- // Detect reinstall: save user's custom settings before overwriting
63
- const monitorPath = path.join(CLAUDE_DIR, 'hooks', 'context-monitor.sh');
64
- let isReinstall = false;
65
- let savedThreshold = '';
66
- let savedMaxContext = '';
67
- if (fs.existsSync(monitorPath)) {
68
- isReinstall = true;
69
- const oldContent = fs.readFileSync(monitorPath, 'utf-8');
70
- const thresholdMatch = oldContent.match(/CLAUDE_CONTEXT_THRESHOLD:-(\d+)/);
71
- const maxContextMatch = oldContent.match(/CLAUDE_MAX_CONTEXT:-(\d+)/);
72
- if (thresholdMatch) savedThreshold = thresholdMatch[1];
73
- if (maxContextMatch) savedMaxContext = maxContextMatch[1];
74
- }
98
+ // ─── Hooks ────────────────────────────────────────────
99
+ head('Installing hooks');
75
100
 
76
101
  copyFile('hooks/context-monitor.sh', monitorPath);
77
102
  copyFile('hooks/session-cleanup.sh', path.join(CLAUDE_DIR, 'hooks', 'session-cleanup.sh'));
@@ -79,26 +104,26 @@ fs.chmodSync(monitorPath, 0o755);
79
104
  fs.chmodSync(path.join(CLAUDE_DIR, 'hooks', 'session-cleanup.sh'), 0o755);
80
105
 
81
106
  if (isReinstall) {
82
- // Restore user's custom settings
83
107
  let content = fs.readFileSync(monitorPath, 'utf-8');
84
108
  if (savedThreshold && savedThreshold !== '90') {
85
109
  content = content.replace('CLAUDE_CONTEXT_THRESHOLD:-90', `CLAUDE_CONTEXT_THRESHOLD:-${savedThreshold}`);
86
- console.log(` Preserved threshold: ${CYAN}${savedThreshold}%${NC}`);
110
+ ok(`Preserved threshold: ${CYAN}${savedThreshold}%${NC}`);
87
111
  }
88
112
  if (savedMaxContext && savedMaxContext !== '200000') {
89
113
  content = content.replace('CLAUDE_MAX_CONTEXT:-200000', `CLAUDE_MAX_CONTEXT:-${savedMaxContext}`);
90
- console.log(` Preserved max context: ${CYAN}${savedMaxContext} tokens${NC}`);
114
+ ok(`Preserved max context: ${CYAN}${savedMaxContext} tokens${NC}`);
91
115
  }
92
116
  fs.writeFileSync(monitorPath, content);
93
117
  }
94
- // Auto-handoff is opt-in: disabled by default (no file = disabled)
95
- // User enables via /auto-handoff which creates .auto-handoff-enabled
96
- // Clean up legacy disabled flag if present
118
+
119
+ // Clean up legacy disabled flag
97
120
  const legacyDisabled = path.join(CLAUDE_DIR, 'hooks', '.auto-handoff-disabled');
98
121
  if (fs.existsSync(legacyDisabled)) fs.unlinkSync(legacyDisabled);
122
+ ok('Context monitor + session cleanup hooks');
123
+
124
+ // ─── Settings ─────────────────────────────────────────
125
+ head('Configuring settings.json');
99
126
 
100
- // 5. Configure hooks in settings.json
101
- console.log(` ${YELLOW}[5/10]${NC} Configuring hooks in settings.json...`);
102
127
  const settingsPath = path.join(CLAUDE_DIR, 'settings.json');
103
128
  const hooksConfig = {
104
129
  Stop: [{ hooks: [{ type: 'command', command: '"$CLAUDE_PROJECT_DIR/.claude/hooks/context-monitor.sh"', timeout: 10 }] }],
@@ -109,15 +134,20 @@ if (fs.existsSync(settingsPath)) {
109
134
  if (!JSON.stringify(settings).includes('context-monitor')) {
110
135
  settings.hooks = hooksConfig;
111
136
  fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
137
+ ok('Hooks added to existing settings.json');
138
+ } else {
139
+ ok('Hooks already configured');
112
140
  }
113
141
  } else {
114
142
  fs.writeFileSync(settingsPath, JSON.stringify({ hooks: hooksConfig }, null, 2) + '\n');
143
+ ok('settings.json created with hooks');
115
144
  }
116
145
 
117
- // 4. Create initial _active.md
146
+ // ─── Handoff ──────────────────────────────────────────
147
+ head('Setting up handoff storage');
148
+
118
149
  const activePath = path.join(CLAUDE_DIR, 'handoffs', '_active.md');
119
150
  if (!fs.existsSync(activePath)) {
120
- console.log(` ${YELLOW}[6/10]${NC} Creating initial handoff...`);
121
151
  fs.writeFileSync(activePath, `# Session Handoff
122
152
 
123
153
  > No active session yet. Use \`/handoff\` or \`/save-handoff\` to save your first session state.
@@ -143,24 +173,31 @@ if (!fs.existsSync(activePath)) {
143
173
  ## Decisions Registry
144
174
  (none)
145
175
  `);
176
+ ok('Initial handoff template created');
146
177
  } else {
147
- console.log(` ${YELLOW}[6/10]${NC} Handoff already exists, keeping it`);
178
+ ok('Existing handoff preserved');
148
179
  }
149
180
 
150
- // 7. Update .gitignore
151
- console.log(` ${YELLOW}[7/10]${NC} Updating .gitignore...`);
181
+ // ─── Gitignore ────────────────────────────────────────
182
+ head('Updating .gitignore');
183
+
152
184
  const gitignorePath = path.join(PROJECT_DIR, '.gitignore');
153
185
  if (fs.existsSync(gitignorePath)) {
154
186
  const content = fs.readFileSync(gitignorePath, 'utf-8');
155
187
  if (!content.includes('.claude/handoffs/')) {
156
188
  fs.appendFileSync(gitignorePath, '\n# claude-code-handoff (personal session state)\n.claude/handoffs/\n');
189
+ ok('Added .claude/handoffs/ to .gitignore');
190
+ } else {
191
+ ok('Already in .gitignore');
157
192
  }
158
193
  } else {
159
194
  fs.writeFileSync(gitignorePath, '# claude-code-handoff (personal session state)\n.claude/handoffs/\n');
195
+ ok('.gitignore created');
160
196
  }
161
197
 
162
- // 8. Update CLAUDE.md
163
- console.log(` ${YELLOW}[8/10]${NC} Updating CLAUDE.md...`);
198
+ // ─── CLAUDE.md ────────────────────────────────────────
199
+ head('Updating CLAUDE.md');
200
+
164
201
  const claudeMdPath = path.join(CLAUDE_DIR, 'CLAUDE.md');
165
202
  const continuityBlock = `## Session Continuity (MANDATORY)
166
203
 
@@ -179,13 +216,26 @@ if (fs.existsSync(claudeMdPath)) {
179
216
  } else {
180
217
  fs.appendFileSync(claudeMdPath, '\n' + continuityBlock + '\n');
181
218
  }
219
+ ok('Session Continuity section added');
220
+ } else {
221
+ ok('Session Continuity already present');
182
222
  }
183
223
  } else {
184
224
  fs.writeFileSync(claudeMdPath, `# Project Rules\n\n${continuityBlock}\n`);
225
+ ok('CLAUDE.md created');
185
226
  }
186
227
 
187
- // 9. Verify
188
- console.log(` ${YELLOW}[9/10]${NC} Verifying installation...`);
228
+ // ─── Legacy cleanup ──────────────────────────────────
229
+ let cleaned = 0;
230
+ for (const f of ['retomar.md', 'salvar-handoff.md', 'trocar-contexto.md', 'auto-handoff-toggle.md']) {
231
+ const fp = path.join(CLAUDE_DIR, 'commands', f);
232
+ if (fs.existsSync(fp)) { fs.unlinkSync(fp); cleaned++; }
233
+ }
234
+ if (cleaned > 0) info(`Removed ${cleaned} legacy command(s)`);
235
+
236
+ // ─── Verify ───────────────────────────────────────────
237
+ head('Verifying');
238
+
189
239
  let installed = 0;
190
240
  for (const f of ['resume.md', 'save-handoff.md', 'switch-context.md', 'handoff.md', 'delete-handoff.md', 'auto-handoff.md']) {
191
241
  if (fs.existsSync(path.join(CLAUDE_DIR, 'commands', f))) installed++;
@@ -194,31 +244,28 @@ let hooksOk = 0;
194
244
  if (fs.existsSync(path.join(CLAUDE_DIR, 'hooks', 'context-monitor.sh'))) hooksOk++;
195
245
  if (fs.existsSync(path.join(CLAUDE_DIR, 'hooks', 'session-cleanup.sh'))) hooksOk++;
196
246
 
197
- console.log('');
198
- console.log(` ${YELLOW}[10/10]${NC} Done!`);
199
- console.log('');
200
247
  if (installed === 6 && hooksOk === 2) {
201
- console.log(`${GREEN} Installed successfully! (${installed}/6 commands, ${hooksOk}/2 hooks)${NC}`);
248
+ ok(`${installed}/6 commands, ${hooksOk}/2 hooks`);
202
249
  } else {
203
- console.log(`${YELLOW} Partial install: ${installed}/6 commands, ${hooksOk}/2 hooks${NC}`);
250
+ fail(`Partial: ${installed}/6 commands, ${hooksOk}/2 hooks`);
204
251
  }
252
+
253
+ // ─── Done ─────────────────────────────────────────────
205
254
  console.log('');
206
- console.log(' Commands available:');
207
- console.log(` ${CYAN}/handoff${NC} Auto-save session (no wizard)`);
208
- console.log(` ${CYAN}/resume${NC} Resume with wizard`);
209
- console.log(` ${CYAN}/save-handoff${NC} Save session state (wizard)`);
210
- console.log(` ${CYAN}/switch-context${NC} Switch workstream`);
211
- console.log(` ${CYAN}/delete-handoff${NC} Delete handoff(s)`);
212
- console.log(` ${CYAN}/auto-handoff${NC} Toggle auto-handoff on/off`);
255
+ console.log(` ${AMBER}${BOLD}════════════════════════════════════════${NC}`);
256
+ console.log(` ${GREEN}${BOLD} Installed successfully!${NC}`);
257
+ console.log(` ${AMBER}${BOLD}════════════════════════════════════════${NC}`);
213
258
  console.log('');
214
- console.log(` Auto-handoff: ${YELLOW}(beta — disabled by default)${NC}`);
215
- console.log(` Use ${CYAN}/auto-handoff${NC} to enable and configure threshold`);
259
+ console.log(` ${WHITE}${BOLD}Commands:${NC}`);
260
+ console.log(` ${CYAN}/handoff${NC} ${GRAY}Auto-save session${NC}`);
261
+ console.log(` ${CYAN}/resume${NC} ${GRAY}Resume with wizard${NC}`);
262
+ console.log(` ${CYAN}/save-handoff${NC} ${GRAY}Save with options${NC}`);
263
+ console.log(` ${CYAN}/switch-context${NC} ${GRAY}Switch workstream${NC}`);
264
+ console.log(` ${CYAN}/delete-handoff${NC} ${GRAY}Delete handoff(s)${NC}`);
265
+ console.log(` ${CYAN}/auto-handoff${NC} ${GRAY}Toggle auto-handoff${NC}`);
216
266
  console.log('');
217
- console.log(' Files:');
218
- console.log(' .claude/commands/ 6 command files');
219
- console.log(' .claude/rules/ session-continuity.md, auto-handoff.md');
220
- console.log(' .claude/hooks/ context-monitor.sh, session-cleanup.sh');
221
- console.log(' .claude/handoffs/ session state (gitignored)');
267
+ console.log(` ${WHITE}${BOLD}Auto-handoff:${NC} ${DIM}beta — disabled by default${NC}`);
268
+ console.log(` ${GRAY}Run ${CYAN}/auto-handoff${GRAY} inside Claude Code to enable${NC}`);
222
269
  console.log('');
223
- console.log(` ${YELLOW}Start Claude Code and use /resume to begin.${NC}`);
270
+ console.log(` ${DIM}Start Claude Code and use ${WHITE}/resume${DIM} to begin.${NC}`);
224
271
  console.log('');
package/install.sh CHANGED
@@ -1,18 +1,21 @@
1
1
  #!/bin/bash
2
- # claude-code-handoff — Session continuity for Claude Code
3
- # Install: curl -fsSL https://raw.githubusercontent.com/eximIA-Ventures/claude-code-handoff/main/install.sh | bash
4
- #
5
- # Or clone and run:
6
- # git clone https://github.com/eximIA-Ventures/claude-code-handoff.git /tmp/claude-code-handoff
7
- # cd /your/project && /tmp/claude-code-handoff/install.sh
2
+ # ═══════════════════════════════════════════════════════
3
+ # claude-code-handoff Installer
4
+ # Usage: curl -fsSL https://raw.githubusercontent.com/eximIA-Ventures/claude-code-handoff/main/install.sh | bash
5
+ # ═══════════════════════════════════════════════════════
8
6
 
9
7
  set -e
10
8
 
11
- # Colors
12
- GREEN='\033[0;32m'
13
- YELLOW='\033[1;33m'
14
- CYAN='\033[0;36m'
15
- NC='\033[0m'
9
+ # Colors (RGB)
10
+ AMBER='\033[38;2;245;158;11m'
11
+ GREEN='\033[38;2;16;185;129m'
12
+ RED='\033[38;2;239;68;68m'
13
+ CYAN='\033[38;2;34;211;238m'
14
+ WHITE='\033[37m'
15
+ GRAY='\033[90m'
16
+ BOLD='\033[1m'
17
+ DIM='\033[2m'
18
+ RESET='\033[0m'
16
19
 
17
20
  REPO="eximIA-Ventures/claude-code-handoff"
18
21
  BRANCH="main"
@@ -20,13 +23,19 @@ RAW_BASE="https://raw.githubusercontent.com/$REPO/$BRANCH"
20
23
  PROJECT_DIR="$(pwd)"
21
24
  CLAUDE_DIR="$PROJECT_DIR/.claude"
22
25
 
26
+ ok() { echo -e " ${GREEN}✓${RESET} $1"; }
27
+ fail() { echo -e " ${RED}✗${RESET} $1"; }
28
+ info() { echo -e " ${GRAY}$1${RESET}"; }
29
+ head() { echo -e "\n ${AMBER}${BOLD}$1${RESET}"; }
30
+
31
+ # ─── Banner ───────────────────────────────────────────
23
32
  echo ""
24
- echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
25
- echo -e "${CYAN} claude-code-handoff — Session Continuity${NC}"
26
- echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
27
- echo ""
28
- echo -e " Project: ${GREEN}$PROJECT_DIR${NC}"
33
+ echo -e " ${AMBER}${BOLD}┌──────────────────────────────────────┐${RESET}"
34
+ echo -e " ${AMBER}${BOLD}│${RESET} ${WHITE}${BOLD}claude-code-handoff${RESET} ${DIM}v1.9${RESET} ${AMBER}${BOLD}│${RESET}"
35
+ echo -e " ${AMBER}${BOLD}│${RESET} ${GRAY}Session Continuity for Claude Code${RESET} ${AMBER}${BOLD}│${RESET}"
36
+ echo -e " ${AMBER}${BOLD}└──────────────────────────────────────┘${RESET}"
29
37
  echo ""
38
+ info "Project: ${WHITE}$PROJECT_DIR${RESET}"
30
39
 
31
40
  # Detect if running from cloned repo or via curl
32
41
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}" 2>/dev/null)" 2>/dev/null && pwd 2>/dev/null || echo "")"
@@ -34,49 +43,61 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}" 2>/dev/null)" 2>/dev/null && pwd
34
43
  download_file() {
35
44
  local src="$1"
36
45
  local dst="$2"
37
-
38
- # If running from cloned repo, copy locally
39
46
  if [ -n "$SCRIPT_DIR" ] && [ -f "$SCRIPT_DIR/$src" ]; then
40
47
  cp "$SCRIPT_DIR/$src" "$dst"
41
48
  else
42
- # Download from GitHub
43
49
  curl -fsSL "$RAW_BASE/$src" -o "$dst"
44
50
  fi
45
51
  }
46
52
 
47
- # 1. Create directories
48
- echo -e " ${YELLOW}[1/10]${NC} Creating directories..."
53
+ # ─── Pre-flight ───────────────────────────────────────
54
+ head "Pre-flight"
55
+
56
+ # Detect reinstall
57
+ IS_REINSTALL=false
58
+ SAVED_THRESHOLD=""
59
+ SAVED_MAX_CONTEXT=""
60
+ if [ -f "$CLAUDE_DIR/hooks/context-monitor.sh" ]; then
61
+ IS_REINSTALL=true
62
+ SAVED_THRESHOLD=$(grep -oP 'CLAUDE_CONTEXT_THRESHOLD:-\K[0-9]+' "$CLAUDE_DIR/hooks/context-monitor.sh" 2>/dev/null || echo "")
63
+ SAVED_MAX_CONTEXT=$(grep -oP 'CLAUDE_MAX_CONTEXT:-\K[0-9]+' "$CLAUDE_DIR/hooks/context-monitor.sh" 2>/dev/null || echo "")
64
+ fi
65
+
66
+ if [ "$IS_REINSTALL" = true ]; then
67
+ info "Existing installation found. Upgrading..."
68
+ else
69
+ ok "Fresh install"
70
+ fi
71
+
72
+ # ─── Directories ──────────────────────────────────────
73
+ head "Creating directories"
74
+
49
75
  mkdir -p "$CLAUDE_DIR/commands"
50
76
  mkdir -p "$CLAUDE_DIR/rules"
51
77
  mkdir -p "$CLAUDE_DIR/hooks"
52
78
  mkdir -p "$CLAUDE_DIR/handoffs/archive"
79
+ ok "Directory structure created"
80
+
81
+ # ─── Commands ─────────────────────────────────────────
82
+ head "Installing commands"
53
83
 
54
- # 2. Download/copy commands
55
- echo -e " ${YELLOW}[2/10]${NC} Installing commands..."
56
84
  download_file "commands/resume.md" "$CLAUDE_DIR/commands/resume.md"
57
85
  download_file "commands/save-handoff.md" "$CLAUDE_DIR/commands/save-handoff.md"
58
86
  download_file "commands/switch-context.md" "$CLAUDE_DIR/commands/switch-context.md"
59
87
  download_file "commands/handoff.md" "$CLAUDE_DIR/commands/handoff.md"
60
88
  download_file "commands/delete-handoff.md" "$CLAUDE_DIR/commands/delete-handoff.md"
61
89
  download_file "commands/auto-handoff.md" "$CLAUDE_DIR/commands/auto-handoff.md"
90
+ ok "6 slash commands installed"
91
+
92
+ # ─── Rules ────────────────────────────────────────────
93
+ head "Installing rules"
62
94
 
63
- # 3. Download/copy rules
64
- echo -e " ${YELLOW}[3/10]${NC} Installing rules..."
65
95
  download_file "rules/session-continuity.md" "$CLAUDE_DIR/rules/session-continuity.md"
66
96
  download_file "rules/auto-handoff.md" "$CLAUDE_DIR/rules/auto-handoff.md"
97
+ ok "Behavioral rules installed"
67
98
 
68
- # 4. Install hooks (auto-handoff context monitor)
69
- echo -e " ${YELLOW}[4/10]${NC} Installing hooks..."
70
-
71
- # Detect reinstall: save user's custom settings before overwriting
72
- IS_REINSTALL=false
73
- SAVED_THRESHOLD=""
74
- SAVED_MAX_CONTEXT=""
75
- if [ -f "$CLAUDE_DIR/hooks/context-monitor.sh" ]; then
76
- IS_REINSTALL=true
77
- SAVED_THRESHOLD=$(grep -oP 'CLAUDE_CONTEXT_THRESHOLD:-\K[0-9]+' "$CLAUDE_DIR/hooks/context-monitor.sh" 2>/dev/null || echo "")
78
- SAVED_MAX_CONTEXT=$(grep -oP 'CLAUDE_MAX_CONTEXT:-\K[0-9]+' "$CLAUDE_DIR/hooks/context-monitor.sh" 2>/dev/null || echo "")
79
- fi
99
+ # ─── Hooks ────────────────────────────────────────────
100
+ head "Installing hooks"
80
101
 
81
102
  download_file "hooks/context-monitor.sh" "$CLAUDE_DIR/hooks/context-monitor.sh"
82
103
  download_file "hooks/session-cleanup.sh" "$CLAUDE_DIR/hooks/session-cleanup.sh"
@@ -84,30 +105,28 @@ chmod +x "$CLAUDE_DIR/hooks/context-monitor.sh"
84
105
  chmod +x "$CLAUDE_DIR/hooks/session-cleanup.sh"
85
106
 
86
107
  if [ "$IS_REINSTALL" = true ]; then
87
- # Restore user's custom settings
88
108
  if [ -n "$SAVED_THRESHOLD" ] && [ "$SAVED_THRESHOLD" != "90" ]; then
89
109
  sed -i.bak "s/CLAUDE_CONTEXT_THRESHOLD:-90/CLAUDE_CONTEXT_THRESHOLD:-${SAVED_THRESHOLD}/" "$CLAUDE_DIR/hooks/context-monitor.sh"
90
110
  rm -f "$CLAUDE_DIR/hooks/context-monitor.sh.bak"
91
- echo -e " Preserved threshold: ${CYAN}${SAVED_THRESHOLD}%${NC}"
111
+ ok "Preserved threshold: ${CYAN}${SAVED_THRESHOLD}%${RESET}"
92
112
  fi
93
113
  if [ -n "$SAVED_MAX_CONTEXT" ] && [ "$SAVED_MAX_CONTEXT" != "200000" ]; then
94
114
  sed -i.bak "s/CLAUDE_MAX_CONTEXT:-200000/CLAUDE_MAX_CONTEXT:-${SAVED_MAX_CONTEXT}/" "$CLAUDE_DIR/hooks/context-monitor.sh"
95
115
  rm -f "$CLAUDE_DIR/hooks/context-monitor.sh.bak"
96
- echo -e " Preserved max context: ${CYAN}${SAVED_MAX_CONTEXT} tokens${NC}"
116
+ ok "Preserved max context: ${CYAN}${SAVED_MAX_CONTEXT} tokens${RESET}"
97
117
  fi
98
118
  fi
99
- # Auto-handoff is opt-in: disabled by default (no file = disabled)
100
- # User enables via /auto-handoff which creates .auto-handoff-enabled
101
- # Clean up legacy disabled flag if present
119
+
120
+ # Clean up legacy disabled flag
102
121
  rm -f "$CLAUDE_DIR/hooks/.auto-handoff-disabled"
122
+ ok "Context monitor + session cleanup hooks"
123
+
124
+ # ─── Settings ─────────────────────────────────────────
125
+ head "Configuring settings.json"
103
126
 
104
- # 5. Configure hooks in settings.json
105
- echo -e " ${YELLOW}[5/10]${NC} Configuring hooks in settings.json..."
106
127
  SETTINGS_FILE="$CLAUDE_DIR/settings.json"
107
128
  if [ -f "$SETTINGS_FILE" ]; then
108
- # Check if hooks already configured
109
129
  if ! grep -q "context-monitor" "$SETTINGS_FILE" 2>/dev/null; then
110
- # Merge hooks into existing settings.json using jq if available
111
130
  if command -v jq &>/dev/null; then
112
131
  HOOKS_JSON='{
113
132
  "hooks": {
@@ -116,22 +135,22 @@ if [ -f "$SETTINGS_FILE" ]; then
116
135
  }
117
136
  }'
118
137
  jq --argjson hooks "$(echo "$HOOKS_JSON" | jq '.hooks')" '. + {hooks: $hooks}' "$SETTINGS_FILE" > "${SETTINGS_FILE}.tmp" && mv "${SETTINGS_FILE}.tmp" "$SETTINGS_FILE"
138
+ ok "Hooks added to existing settings.json"
119
139
  else
120
- # Fallback: rewrite settings.json preserving existing keys
121
- echo -e " ${YELLOW} ⚠ jq not found. Adding hooks config manually...${NC}"
122
- # Read existing content, strip trailing brace, append hooks
123
140
  EXISTING=$(cat "$SETTINGS_FILE")
124
- # Remove trailing } and whitespace
125
141
  EXISTING=$(echo "$EXISTING" | sed '$ s/}$//')
126
- cat > "$SETTINGS_FILE" << 'SETTINGSEOF'
142
+ LANG_SETTING=$(echo "$EXISTING" | grep '"language"' | head -1 | sed 's/,$//')
143
+ if [ -n "$LANG_SETTING" ]; then
144
+ cat > "$SETTINGS_FILE" << SETTINGSEOF
127
145
  {
146
+ ${LANG_SETTING},
128
147
  "hooks": {
129
148
  "Stop": [
130
149
  {
131
150
  "hooks": [
132
151
  {
133
152
  "type": "command",
134
- "command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/context-monitor.sh\"",
153
+ "command": "\"\$CLAUDE_PROJECT_DIR/.claude/hooks/context-monitor.sh\"",
135
154
  "timeout": 10
136
155
  }
137
156
  ]
@@ -142,7 +161,7 @@ if [ -f "$SETTINGS_FILE" ]; then
142
161
  "hooks": [
143
162
  {
144
163
  "type": "command",
145
- "command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/session-cleanup.sh\"",
164
+ "command": "\"\$CLAUDE_PROJECT_DIR/.claude/hooks/session-cleanup.sh\"",
146
165
  "timeout": 5
147
166
  }
148
167
  ]
@@ -151,21 +170,17 @@ if [ -f "$SETTINGS_FILE" ]; then
151
170
  }
152
171
  }
153
172
  SETTINGSEOF
154
- # Merge with original using jq-less approach: just add hooks key
155
- # Since we can't reliably merge JSON without jq, write a complete file
156
- # preserving the language setting if it exists
157
- LANG_SETTING=$(echo "$EXISTING" | grep '"language"' | head -1 | sed 's/,$//')
158
- if [ -n "$LANG_SETTING" ]; then
159
- cat > "$SETTINGS_FILE" << SETTINGSEOF
173
+ ok "Hooks added (preserved language setting)"
174
+ else
175
+ cat > "$SETTINGS_FILE" << 'SETTINGSEOF'
160
176
  {
161
- ${LANG_SETTING},
162
177
  "hooks": {
163
178
  "Stop": [
164
179
  {
165
180
  "hooks": [
166
181
  {
167
182
  "type": "command",
168
- "command": "\"\$CLAUDE_PROJECT_DIR/.claude/hooks/context-monitor.sh\"",
183
+ "command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/context-monitor.sh\"",
169
184
  "timeout": 10
170
185
  }
171
186
  ]
@@ -176,7 +191,7 @@ SETTINGSEOF
176
191
  "hooks": [
177
192
  {
178
193
  "type": "command",
179
- "command": "\"\$CLAUDE_PROJECT_DIR/.claude/hooks/session-cleanup.sh\"",
194
+ "command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/session-cleanup.sh\"",
180
195
  "timeout": 5
181
196
  }
182
197
  ]
@@ -185,11 +200,13 @@ SETTINGSEOF
185
200
  }
186
201
  }
187
202
  SETTINGSEOF
203
+ ok "Hooks config written"
188
204
  fi
189
205
  fi
206
+ else
207
+ ok "Hooks already configured"
190
208
  fi
191
209
  else
192
- # Create new settings.json with hooks
193
210
  cat > "$SETTINGS_FILE" << 'SETTINGSEOF'
194
211
  {
195
212
  "hooks": {
@@ -218,11 +235,13 @@ else
218
235
  }
219
236
  }
220
237
  SETTINGSEOF
238
+ ok "settings.json created with hooks"
221
239
  fi
222
240
 
223
- # 6. Create initial _active.md if not exists
241
+ # ─── Handoff ──────────────────────────────────────────
242
+ head "Setting up handoff storage"
243
+
224
244
  if [ ! -f "$CLAUDE_DIR/handoffs/_active.md" ]; then
225
- echo -e " ${YELLOW}[6/10]${NC} Creating initial handoff..."
226
245
  cat > "$CLAUDE_DIR/handoffs/_active.md" << 'HANDOFF'
227
246
  # Session Handoff
228
247
 
@@ -249,33 +268,34 @@ if [ ! -f "$CLAUDE_DIR/handoffs/_active.md" ]; then
249
268
  ## Decisions Registry
250
269
  (none)
251
270
  HANDOFF
271
+ ok "Initial handoff template created"
252
272
  else
253
- echo -e " ${YELLOW}[6/10]${NC} Handoff already exists, keeping it"
273
+ ok "Existing handoff preserved"
254
274
  fi
255
275
 
256
- # 7. Add to .gitignore
257
- echo -e " ${YELLOW}[7/10]${NC} Updating .gitignore..."
276
+ # ─── Gitignore ────────────────────────────────────────
277
+ head "Updating .gitignore"
278
+
258
279
  GITIGNORE="$PROJECT_DIR/.gitignore"
259
280
  if [ -f "$GITIGNORE" ]; then
260
281
  if ! grep -q ".claude/handoffs/" "$GITIGNORE" 2>/dev/null; then
261
282
  echo "" >> "$GITIGNORE"
262
283
  echo "# claude-code-handoff (personal session state)" >> "$GITIGNORE"
263
284
  echo ".claude/handoffs/" >> "$GITIGNORE"
285
+ ok "Added .claude/handoffs/ to .gitignore"
286
+ else
287
+ ok "Already in .gitignore"
264
288
  fi
265
289
  else
266
290
  echo "# claude-code-handoff (personal session state)" > "$GITIGNORE"
267
291
  echo ".claude/handoffs/" >> "$GITIGNORE"
292
+ ok ".gitignore created"
268
293
  fi
269
294
 
270
- # 8. Add to CLAUDE.md
271
- echo -e " ${YELLOW}[8/10]${NC} Updating CLAUDE.md..."
272
- CLAUDE_MD="$CLAUDE_DIR/CLAUDE.md"
273
- CONTINUITY_BLOCK='## Session Continuity (MANDATORY)
274
-
275
- At the START of every session, read `.claude/handoffs/_active.md` to recover context from prior sessions.
276
- During work, update the handoff proactively after significant milestones.
277
- Use `/handoff` before `/clear`. Use `/resume` to pick up. Use `/switch-context <topic>` to switch workstreams.'
295
+ # ─── CLAUDE.md ────────────────────────────────────────
296
+ head "Updating CLAUDE.md"
278
297
 
298
+ CLAUDE_MD="$CLAUDE_DIR/CLAUDE.md"
279
299
  if [ -f "$CLAUDE_MD" ]; then
280
300
  if ! grep -q "Session Continuity" "$CLAUDE_MD" 2>/dev/null; then
281
301
  TEMP_FILE=$(mktemp)
@@ -295,6 +315,9 @@ if [ -f "$CLAUDE_MD" ]; then
295
315
  { print }
296
316
  ' "$CLAUDE_MD" > "$TEMP_FILE"
297
317
  mv "$TEMP_FILE" "$CLAUDE_MD"
318
+ ok "Session Continuity section added"
319
+ else
320
+ ok "Session Continuity already present"
298
321
  fi
299
322
  else
300
323
  cat > "$CLAUDE_MD" << 'CLAUDEMD'
@@ -306,10 +329,24 @@ At the START of every session, read `.claude/handoffs/_active.md` to recover con
306
329
  During work, update the handoff proactively after significant milestones.
307
330
  Use `/handoff` before `/clear`. Use `/resume` to pick up. Use `/switch-context <topic>` to switch workstreams.
308
331
  CLAUDEMD
332
+ ok "CLAUDE.md created"
309
333
  fi
310
334
 
311
- # 9. Summary
312
- echo -e " ${YELLOW}[9/10]${NC} Verifying installation..."
335
+ # ─── Legacy cleanup ──────────────────────────────────
336
+ CLEANED=0
337
+ for f in retomar.md salvar-handoff.md trocar-contexto.md auto-handoff-toggle.md; do
338
+ if [ -f "$CLAUDE_DIR/commands/$f" ]; then
339
+ rm -f "$CLAUDE_DIR/commands/$f"
340
+ CLEANED=$((CLEANED + 1))
341
+ fi
342
+ done
343
+ if [ "$CLEANED" -gt 0 ]; then
344
+ info "Removed $CLEANED legacy command(s)"
345
+ fi
346
+
347
+ # ─── Verify ───────────────────────────────────────────
348
+ head "Verifying"
349
+
313
350
  INSTALLED=0
314
351
  for f in resume.md save-handoff.md switch-context.md handoff.md delete-handoff.md auto-handoff.md; do
315
352
  [ -f "$CLAUDE_DIR/commands/$f" ] && INSTALLED=$((INSTALLED + 1))
@@ -318,31 +355,28 @@ HOOKS_OK=0
318
355
  [ -f "$CLAUDE_DIR/hooks/context-monitor.sh" ] && HOOKS_OK=$((HOOKS_OK + 1))
319
356
  [ -f "$CLAUDE_DIR/hooks/session-cleanup.sh" ] && HOOKS_OK=$((HOOKS_OK + 1))
320
357
 
321
- echo ""
322
- echo -e " ${YELLOW}[10/10]${NC} Done!"
323
- echo ""
324
358
  if [ "$INSTALLED" -eq 6 ] && [ "$HOOKS_OK" -eq 2 ]; then
325
- echo -e "${GREEN} Installed successfully! ($INSTALLED/6 commands, $HOOKS_OK/2 hooks)${NC}"
359
+ ok "${INSTALLED}/6 commands, ${HOOKS_OK}/2 hooks"
326
360
  else
327
- echo -e "${YELLOW} Partial install: $INSTALLED/6 commands, $HOOKS_OK/2 hooks${NC}"
361
+ fail "Partial: ${INSTALLED}/6 commands, ${HOOKS_OK}/2 hooks"
328
362
  fi
363
+
364
+ # ─── Done ─────────────────────────────────────────────
329
365
  echo ""
330
- echo -e " Commands available:"
331
- echo -e " ${CYAN}/handoff${NC} Auto-save session (no wizard)"
332
- echo -e " ${CYAN}/resume${NC} Resume with wizard"
333
- echo -e " ${CYAN}/save-handoff${NC} Save session state (wizard)"
334
- echo -e " ${CYAN}/switch-context${NC} Switch workstream"
335
- echo -e " ${CYAN}/delete-handoff${NC} Delete handoff(s)"
336
- echo -e " ${CYAN}/auto-handoff${NC} Toggle auto-handoff on/off"
366
+ echo -e " ${AMBER}${BOLD}════════════════════════════════════════${RESET}"
367
+ echo -e " ${GREEN}${BOLD} Installed successfully!${RESET}"
368
+ echo -e " ${AMBER}${BOLD}════════════════════════════════════════${RESET}"
337
369
  echo ""
338
- echo -e " Auto-handoff: ${YELLOW}(beta — disabled by default)${NC}"
339
- echo -e " Use ${CYAN}/auto-handoff${NC} to enable and configure threshold"
370
+ echo -e " ${WHITE}${BOLD}Commands:${RESET}"
371
+ echo -e " ${CYAN}/handoff${RESET} ${GRAY}Auto-save session${RESET}"
372
+ echo -e " ${CYAN}/resume${RESET} ${GRAY}Resume with wizard${RESET}"
373
+ echo -e " ${CYAN}/save-handoff${RESET} ${GRAY}Save with options${RESET}"
374
+ echo -e " ${CYAN}/switch-context${RESET} ${GRAY}Switch workstream${RESET}"
375
+ echo -e " ${CYAN}/delete-handoff${RESET} ${GRAY}Delete handoff(s)${RESET}"
376
+ echo -e " ${CYAN}/auto-handoff${RESET} ${GRAY}Toggle auto-handoff${RESET}"
340
377
  echo ""
341
- echo -e " Files:"
342
- echo -e " .claude/commands/ 6 command files"
343
- echo -e " .claude/rules/ session-continuity.md, auto-handoff.md"
344
- echo -e " .claude/hooks/ context-monitor.sh, session-cleanup.sh"
345
- echo -e " .claude/handoffs/ session state (gitignored)"
378
+ echo -e " ${WHITE}${BOLD}Auto-handoff:${RESET} ${DIM}beta — disabled by default${RESET}"
379
+ echo -e " ${GRAY}Run ${CYAN}/auto-handoff${GRAY} inside Claude Code to enable${RESET}"
346
380
  echo ""
347
- echo -e " ${YELLOW}Start Claude Code and use /resume to begin.${NC}"
381
+ echo -e " ${DIM}Start Claude Code and use ${WHITE}/resume${DIM} to begin.${RESET}"
348
382
  echo ""
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-handoff",
3
- "version": "1.9.0",
3
+ "version": "2.0.0",
4
4
  "description": "Session continuity for Claude Code — 6 slash commands to save, resume, delete, and switch workstreams across /clear",
5
5
  "bin": {
6
6
  "claude-code-handoff": "./cli.js"