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.
- package/cli.js +116 -69
- package/install.sh +133 -99
- 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
|
-
|
|
7
|
-
const
|
|
8
|
-
const
|
|
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(
|
|
17
|
-
console.log(
|
|
18
|
-
console.log(
|
|
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
|
-
|
|
44
|
+
fail(`${src} not found in package`);
|
|
34
45
|
process.exit(1);
|
|
35
46
|
}
|
|
36
47
|
}
|
|
37
48
|
|
|
38
|
-
//
|
|
39
|
-
|
|
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
|
-
//
|
|
60
|
-
|
|
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
|
-
|
|
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
|
-
|
|
114
|
+
ok(`Preserved max context: ${CYAN}${savedMaxContext} tokens${NC}`);
|
|
91
115
|
}
|
|
92
116
|
fs.writeFileSync(monitorPath, content);
|
|
93
117
|
}
|
|
94
|
-
|
|
95
|
-
//
|
|
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
|
-
//
|
|
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
|
-
|
|
178
|
+
ok('Existing handoff preserved');
|
|
148
179
|
}
|
|
149
180
|
|
|
150
|
-
//
|
|
151
|
-
|
|
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
|
-
//
|
|
163
|
-
|
|
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
|
-
//
|
|
188
|
-
|
|
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
|
-
|
|
248
|
+
ok(`${installed}/6 commands, ${hooksOk}/2 hooks`);
|
|
202
249
|
} else {
|
|
203
|
-
|
|
250
|
+
fail(`Partial: ${installed}/6 commands, ${hooksOk}/2 hooks`);
|
|
204
251
|
}
|
|
252
|
+
|
|
253
|
+
// ─── Done ─────────────────────────────────────────────
|
|
205
254
|
console.log('');
|
|
206
|
-
console.log(
|
|
207
|
-
console.log(`
|
|
208
|
-
console.log(`
|
|
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(`
|
|
215
|
-
console.log(`
|
|
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(
|
|
218
|
-
console.log(
|
|
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(` ${
|
|
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
|
-
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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 "${
|
|
25
|
-
echo -e "${
|
|
26
|
-
echo -e "${
|
|
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
|
-
#
|
|
48
|
-
|
|
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
|
-
#
|
|
69
|
-
|
|
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
|
-
|
|
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
|
-
|
|
116
|
+
ok "Preserved max context: ${CYAN}${SAVED_MAX_CONTEXT} tokens${RESET}"
|
|
97
117
|
fi
|
|
98
118
|
fi
|
|
99
|
-
|
|
100
|
-
#
|
|
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
|
-
|
|
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": "\"
|
|
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": "\"
|
|
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
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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": "\"
|
|
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": "\"
|
|
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
|
-
#
|
|
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
|
-
|
|
273
|
+
ok "Existing handoff preserved"
|
|
254
274
|
fi
|
|
255
275
|
|
|
256
|
-
#
|
|
257
|
-
|
|
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
|
-
#
|
|
271
|
-
|
|
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
|
-
#
|
|
312
|
-
|
|
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
|
-
|
|
359
|
+
ok "${INSTALLED}/6 commands, ${HOOKS_OK}/2 hooks"
|
|
326
360
|
else
|
|
327
|
-
|
|
361
|
+
fail "Partial: ${INSTALLED}/6 commands, ${HOOKS_OK}/2 hooks"
|
|
328
362
|
fi
|
|
363
|
+
|
|
364
|
+
# ─── Done ─────────────────────────────────────────────
|
|
329
365
|
echo ""
|
|
330
|
-
echo -e "
|
|
331
|
-
echo -e "
|
|
332
|
-
echo -e "
|
|
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 "
|
|
339
|
-
echo -e "
|
|
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 "
|
|
342
|
-
echo -e "
|
|
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 " ${
|
|
381
|
+
echo -e " ${DIM}Start Claude Code and use ${WHITE}/resume${DIM} to begin.${RESET}"
|
|
348
382
|
echo ""
|
package/package.json
CHANGED