claude-cli-advanced-starter-pack 1.0.15 → 1.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-cli-advanced-starter-pack",
3
- "version": "1.0.15",
3
+ "version": "1.1.0",
4
4
  "description": "Advanced Claude Code CLI toolkit - agents, hooks, skills, MCP servers, phased development, and GitHub integration",
5
5
  "main": "src/index.js",
6
6
  "bin": {
package/src/cli/menu.js CHANGED
@@ -4,6 +4,8 @@
4
4
 
5
5
  import chalk from 'chalk';
6
6
  import inquirer from 'inquirer';
7
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
8
+ import { join, dirname } from 'path';
7
9
  import { runSetup } from '../commands/setup.js';
8
10
  import { runCreate } from '../commands/create.js';
9
11
  import { runList } from '../commands/list.js';
@@ -22,6 +24,55 @@ import { hasTestingConfig } from '../testing/config.js';
22
24
  import { showHelp } from '../commands/help.js';
23
25
  import { hasValidConfig, getVersion, loadTechStack, saveTechStack } from '../utils.js';
24
26
 
27
+ /**
28
+ * Get bypass permissions status from settings.json
29
+ */
30
+ function getBypassPermissionsStatus() {
31
+ const settingsPath = join(process.cwd(), '.claude', 'settings.json');
32
+ if (!existsSync(settingsPath)) {
33
+ return false;
34
+ }
35
+ try {
36
+ const settings = JSON.parse(readFileSync(settingsPath, 'utf8'));
37
+ return settings.permissions?.defaultMode === 'bypassPermissions';
38
+ } catch {
39
+ return false;
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Toggle bypass permissions in settings.json
45
+ */
46
+ function toggleBypassPermissions() {
47
+ const settingsPath = join(process.cwd(), '.claude', 'settings.json');
48
+ let settings = {};
49
+
50
+ if (existsSync(settingsPath)) {
51
+ try {
52
+ settings = JSON.parse(readFileSync(settingsPath, 'utf8'));
53
+ } catch {
54
+ settings = {};
55
+ }
56
+ }
57
+
58
+ if (!settings.permissions) {
59
+ settings.permissions = {};
60
+ }
61
+
62
+ const currentMode = settings.permissions.defaultMode;
63
+ const newMode = currentMode === 'bypassPermissions' ? 'acceptEdits' : 'bypassPermissions';
64
+ settings.permissions.defaultMode = newMode;
65
+
66
+ // Ensure directory exists
67
+ const dir = dirname(settingsPath);
68
+ if (!existsSync(dir)) {
69
+ mkdirSync(dir, { recursive: true });
70
+ }
71
+
72
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
73
+ return newMode === 'bypassPermissions';
74
+ }
75
+
25
76
  /**
26
77
  * ASCII Art Banner
27
78
  */
@@ -40,11 +91,22 @@ const BANNER = `
40
91
  * Show Project Settings submenu
41
92
  */
42
93
  export async function showProjectSettingsMenu() {
94
+ // Get current bypass status for display
95
+ const bypassEnabled = getBypassPermissionsStatus();
96
+ const bypassStatus = bypassEnabled ? chalk.green('ON') : chalk.red('OFF');
97
+ const bypassLine = ` [P] Bypass All Permissions [${bypassStatus}]`;
98
+ const bypassPadding = ' '.repeat(79 - bypassLine.replace(/\x1B\[[0-9;]*m/g, '').length - 1);
99
+
43
100
  console.log('');
44
101
  console.log(chalk.cyan('╔═══════════════════════════════════════════════════════════════════════════════╗'));
45
102
  console.log(chalk.cyan('║') + chalk.bold(' PROJECT CONFIGURATION ') + chalk.cyan('║'));
46
103
  console.log(chalk.cyan('╠═══════════════════════════════════════════════════════════════════════════════╣'));
47
104
  console.log(chalk.cyan('║') + ' ' + chalk.cyan('║'));
105
+ console.log(chalk.cyan('║') + bypassLine + bypassPadding + chalk.cyan('║'));
106
+ console.log(chalk.cyan('║') + chalk.dim(' └─ Toggle auto-approve all tool calls (use with caution) ') + chalk.cyan('║'));
107
+ console.log(chalk.cyan('║') + ' ' + chalk.cyan('║'));
108
+ console.log(chalk.cyan('╠───────────────────────────────────────────────────────────────────────────────╣'));
109
+ console.log(chalk.cyan('║') + ' ' + chalk.cyan('║'));
48
110
  console.log(chalk.cyan('║') + ' [1] GitHub Project Board ' + chalk.cyan('║'));
49
111
  console.log(chalk.cyan('║') + chalk.dim(' └─ Connect to GitHub Projects v2 for issue tracking ') + chalk.cyan('║'));
50
112
  console.log(chalk.cyan('║') + ' ' + chalk.cyan('║'));
@@ -71,6 +133,8 @@ export async function showProjectSettingsMenu() {
71
133
  name: 'settingsAction',
72
134
  message: 'Select a configuration area:',
73
135
  choices: [
136
+ { name: `P. Bypass All Permissions [${bypassEnabled ? 'ON' : 'OFF'}]`, value: 'bypass' },
137
+ new inquirer.Separator(),
74
138
  { name: '1. GitHub Project Board', value: 'github' },
75
139
  { name: '2. Deployment Platforms', value: 'deployment' },
76
140
  { name: '3. Tunnel Services', value: 'tunnel' },
@@ -86,6 +150,22 @@ export async function showProjectSettingsMenu() {
86
150
  return;
87
151
  }
88
152
 
153
+ // Handle bypass toggle
154
+ if (settingsAction === 'bypass') {
155
+ const newState = toggleBypassPermissions();
156
+ console.log('');
157
+ if (newState) {
158
+ console.log(chalk.green(' ✓ Bypass All Permissions: ON'));
159
+ console.log(chalk.yellow(' All tool calls will be auto-approved'));
160
+ } else {
161
+ console.log(chalk.green(' ✓ Bypass All Permissions: OFF'));
162
+ console.log(chalk.dim(' Using Accept Edits mode (prompts for Edit/Write/Bash)'));
163
+ }
164
+ console.log('');
165
+ await showProjectSettingsMenu();
166
+ return;
167
+ }
168
+
89
169
  // Load current tech-stack.json
90
170
  const techStack = loadTechStack();
91
171
 
@@ -42,7 +42,7 @@ const OPTIONAL_FEATURES = [
42
42
  label: 'Token Budget Management',
43
43
  description: 'Monitor and manage Claude API token usage with automatic compaction warnings, archive suggestions, and respawn thresholds. Includes hooks that track usage per session.',
44
44
  commands: ['context-audit'],
45
- hooks: ['context-guardian'], // Only include hooks with templates
45
+ hooks: ['context-guardian', 'token-budget-loader', 'tool-output-cacher'],
46
46
  default: false,
47
47
  requiresPostConfig: false,
48
48
  },
@@ -51,10 +51,10 @@ const OPTIONAL_FEATURES = [
51
51
  label: 'Happy Engineering Integration',
52
52
  description: 'Integration with Happy Coder mobile app for remote session control, checkpoint management, and mobile-optimized responses.',
53
53
  commands: ['happy-start'],
54
- hooks: ['happy-checkpoint-manager'], // Only include hooks with templates
54
+ hooks: ['happy-checkpoint-manager', 'happy-title-generator', 'happy-mode-detector', 'context-injector'],
55
55
  default: false,
56
56
  requiresPostConfig: true,
57
- npmPackage: 'happy-coder', // Optional npm package to install
57
+ npmPackage: 'happy-coder',
58
58
  npmInstallPrompt: 'Install Happy Coder CLI globally? (npm i -g happy-coder)',
59
59
  },
60
60
  {
@@ -1,5 +1,75 @@
1
1
  {
2
2
  "releases": [
3
+ {
4
+ "version": "1.1.0",
5
+ "date": "2026-01-30",
6
+ "summary": "Feature: Phase 1 of 85 Recommendations - 5 New Hook Templates",
7
+ "highlights": [
8
+ "Added tool-output-cacher.template.js - Caches large outputs, saves ~500 tokens per output",
9
+ "Added token-budget-loader.template.js - Pre-calculates daily budget, ~5K tokens/session savings",
10
+ "Added happy-title-generator.template.js - Auto-generates session titles with issue numbers",
11
+ "Added happy-mode-detector.template.js - Detects Happy daemon environment",
12
+ "Added context-injector.template.js - Injects prior session context for continuity",
13
+ "All hooks now configurable via .claude/config/hooks-config.json",
14
+ "Updated tokenManagement feature to include token-budget-loader and tool-output-cacher",
15
+ "Updated happyMode feature to include all Happy-related hooks"
16
+ ],
17
+ "newFeatures": {
18
+ "commands": [],
19
+ "agents": [],
20
+ "skills": [],
21
+ "hooks": [
22
+ {
23
+ "name": "tool-output-cacher",
24
+ "description": "Caches large tool outputs to reduce context consumption"
25
+ },
26
+ {
27
+ "name": "token-budget-loader",
28
+ "description": "Pre-calculates daily token budget at session start"
29
+ },
30
+ {
31
+ "name": "happy-title-generator",
32
+ "description": "Auto-generates session titles for Happy daemon"
33
+ },
34
+ {
35
+ "name": "happy-mode-detector",
36
+ "description": "Detects Happy daemon environment and enables mobile mode"
37
+ },
38
+ {
39
+ "name": "context-injector",
40
+ "description": "Injects prior session context for seamless resumption"
41
+ }
42
+ ],
43
+ "other": []
44
+ },
45
+ "breaking": [],
46
+ "deprecated": []
47
+ },
48
+ {
49
+ "version": "1.0.16",
50
+ "date": "2026-01-30",
51
+ "summary": "Feature: Bypass All Permissions toggle in Project Settings menu",
52
+ "highlights": [
53
+ "Quick toggle for Bypass All Permissions mode from /menu → Project Settings",
54
+ "Live status display shows current state (ON/OFF)",
55
+ "Toggles between bypassPermissions and acceptEdits modes",
56
+ "Updates .claude/settings.json directly"
57
+ ],
58
+ "newFeatures": {
59
+ "commands": [],
60
+ "agents": [],
61
+ "skills": [],
62
+ "hooks": [],
63
+ "other": [
64
+ {
65
+ "name": "bypass-permissions-toggle",
66
+ "description": "Quick toggle in Project Settings to enable/disable auto-approve mode"
67
+ }
68
+ ]
69
+ },
70
+ "breaking": [],
71
+ "deprecated": []
72
+ },
3
73
  {
4
74
  "version": "1.0.15",
5
75
  "date": "2026-01-30",
@@ -0,0 +1,261 @@
1
+ /**
2
+ * Context Injector Hook
3
+ *
4
+ * Injects prior session context for seamless resumption.
5
+ * Loads checkpoints, recent progress, and active features.
6
+ * Enables continuity across multi-day projects.
7
+ *
8
+ * Event: UserPromptSubmit (runs once per session)
9
+ *
10
+ * Configuration: Reads from .claude/config/hooks-config.json
11
+ */
12
+
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+
16
+ // Default configuration (can be overridden by hooks-config.json)
17
+ const DEFAULT_CONFIG = {
18
+ recent_completed_tasks: 3, // Number of completed tasks to show
19
+ next_pending_tasks: 2, // Number of pending tasks to show
20
+ active_features_limit: 3, // Number of active features to show
21
+ recent_agents_limit: 5, // Number of recent agents to show
22
+ max_checkpoint_age_hours: 48, // Ignore checkpoints older than this
23
+ };
24
+
25
+ // Paths
26
+ const CONFIG_PATH = path.join(process.cwd(), '.claude', 'config', 'hooks-config.json');
27
+ const CHECKPOINT_PATH = path.join(process.cwd(), '.claude', 'checkpoints', 'latest.json');
28
+ const FEATURE_TRACKING_PATH = path.join(process.cwd(), '.claude', 'config', 'feature-tracking.json');
29
+ const AGENT_LOG_PATH = path.join(process.cwd(), '.claude', 'logs', 'agent-activity.json');
30
+ const SESSION_MARKER = path.join(process.cwd(), '.claude', 'config', '.context-injected');
31
+
32
+ /**
33
+ * Load configuration with defaults
34
+ */
35
+ function loadConfig() {
36
+ try {
37
+ if (fs.existsSync(CONFIG_PATH)) {
38
+ const config = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
39
+ return { ...DEFAULT_CONFIG, ...(config.context_injector || {}) };
40
+ }
41
+ } catch (e) {
42
+ // Use defaults on error
43
+ }
44
+ return DEFAULT_CONFIG;
45
+ }
46
+
47
+ /**
48
+ * Check if we've already injected context this session
49
+ */
50
+ function hasInjectedThisSession() {
51
+ try {
52
+ if (fs.existsSync(SESSION_MARKER)) {
53
+ const content = fs.readFileSync(SESSION_MARKER, 'utf8');
54
+ const timestamp = parseInt(content, 10);
55
+ // Session valid for 4 hours
56
+ if (Date.now() - timestamp < 4 * 60 * 60 * 1000) {
57
+ return true;
58
+ }
59
+ }
60
+ } catch (e) {
61
+ // Continue with injection
62
+ }
63
+ return false;
64
+ }
65
+
66
+ /**
67
+ * Mark session as injected
68
+ */
69
+ function markSessionInjected() {
70
+ try {
71
+ const dir = path.dirname(SESSION_MARKER);
72
+ if (!fs.existsSync(dir)) {
73
+ fs.mkdirSync(dir, { recursive: true });
74
+ }
75
+ fs.writeFileSync(SESSION_MARKER, Date.now().toString(), 'utf8');
76
+ } catch (e) {
77
+ // Silent failure
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Load latest checkpoint
83
+ */
84
+ function loadCheckpoint(config) {
85
+ try {
86
+ if (!fs.existsSync(CHECKPOINT_PATH)) {
87
+ return null;
88
+ }
89
+
90
+ const checkpoint = JSON.parse(fs.readFileSync(CHECKPOINT_PATH, 'utf8'));
91
+
92
+ // Check age
93
+ if (checkpoint.created_at) {
94
+ const age = Date.now() - new Date(checkpoint.created_at).getTime();
95
+ const maxAge = config.max_checkpoint_age_hours * 60 * 60 * 1000;
96
+ if (age > maxAge) {
97
+ return null; // Checkpoint too old
98
+ }
99
+ }
100
+
101
+ return checkpoint;
102
+ } catch (e) {
103
+ return null;
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Load feature tracking
109
+ */
110
+ function loadFeatureTracking() {
111
+ try {
112
+ if (fs.existsSync(FEATURE_TRACKING_PATH)) {
113
+ return JSON.parse(fs.readFileSync(FEATURE_TRACKING_PATH, 'utf8'));
114
+ }
115
+ } catch (e) {
116
+ // No feature tracking
117
+ }
118
+ return null;
119
+ }
120
+
121
+ /**
122
+ * Load recent agent activity
123
+ */
124
+ function loadAgentActivity(config) {
125
+ try {
126
+ if (fs.existsSync(AGENT_LOG_PATH)) {
127
+ const activity = JSON.parse(fs.readFileSync(AGENT_LOG_PATH, 'utf8'));
128
+ // Return most recent agents
129
+ if (Array.isArray(activity)) {
130
+ return activity.slice(-config.recent_agents_limit);
131
+ }
132
+ }
133
+ } catch (e) {
134
+ // No agent activity
135
+ }
136
+ return [];
137
+ }
138
+
139
+ /**
140
+ * Format task list for display
141
+ */
142
+ function formatTaskList(tasks, limit, status) {
143
+ if (!tasks || !Array.isArray(tasks)) return '';
144
+
145
+ const filtered = tasks.filter(t => t.status === status).slice(0, limit);
146
+ if (filtered.length === 0) return '';
147
+
148
+ return filtered.map(t => ` - ${t.title || t.name || 'Unknown task'}`).join('\n');
149
+ }
150
+
151
+ /**
152
+ * Format feature list for display
153
+ */
154
+ function formatFeatureList(features, limit) {
155
+ if (!features || !Array.isArray(features)) return '';
156
+
157
+ const active = features.filter(f => f.status === 'in_progress').slice(0, limit);
158
+ if (active.length === 0) return '';
159
+
160
+ return active.map(f => ` - ${f.name}: ${f.progress || 0}%`).join('\n');
161
+ }
162
+
163
+ /**
164
+ * Format agent list for display
165
+ */
166
+ function formatAgentList(agents) {
167
+ if (!agents || agents.length === 0) return '';
168
+
169
+ return agents.map(a => {
170
+ const name = a.name || a.type || 'agent';
171
+ const status = a.success ? 'completed' : 'failed';
172
+ return ` - ${name}: ${status}`;
173
+ }).join('\n');
174
+ }
175
+
176
+ /**
177
+ * Build context message
178
+ */
179
+ function buildContextMessage(checkpoint, features, agents, config) {
180
+ const sections = [];
181
+
182
+ // Add checkpoint progress
183
+ if (checkpoint) {
184
+ const completed = formatTaskList(checkpoint.tasks, config.recent_completed_tasks, 'completed');
185
+ const pending = formatTaskList(checkpoint.tasks, config.next_pending_tasks, 'pending');
186
+
187
+ if (completed) {
188
+ sections.push(`**Recent Progress:**\n${completed}`);
189
+ }
190
+ if (pending) {
191
+ sections.push(`**Next Tasks:**\n${pending}`);
192
+ }
193
+ if (checkpoint.current_phase) {
194
+ sections.push(`**Current Phase:** ${checkpoint.current_phase}`);
195
+ }
196
+ }
197
+
198
+ // Add active features
199
+ if (features) {
200
+ const featureList = formatFeatureList(features.features || features, config.active_features_limit);
201
+ if (featureList) {
202
+ sections.push(`**Active Features:**\n${featureList}`);
203
+ }
204
+ }
205
+
206
+ // Add recent agents
207
+ if (agents && agents.length > 0) {
208
+ const agentList = formatAgentList(agents);
209
+ if (agentList) {
210
+ sections.push(`**Recent Agents:**\n${agentList}`);
211
+ }
212
+ }
213
+
214
+ return sections.join('\n\n');
215
+ }
216
+
217
+ /**
218
+ * Main hook handler
219
+ */
220
+ module.exports = async function contextInjector(context) {
221
+ // Always continue - never block
222
+ const approve = () => ({ continue: true });
223
+
224
+ try {
225
+ // Check if already injected this session
226
+ if (hasInjectedThisSession()) {
227
+ return approve();
228
+ }
229
+
230
+ // Mark session as injected
231
+ markSessionInjected();
232
+
233
+ const config = loadConfig();
234
+
235
+ // Load context sources
236
+ const checkpoint = loadCheckpoint(config);
237
+ const features = loadFeatureTracking();
238
+ const agents = loadAgentActivity(config);
239
+
240
+ // Check if we have any context to inject
241
+ if (!checkpoint && !features && agents.length === 0) {
242
+ console.log('[context-injector] No prior context found');
243
+ return approve();
244
+ }
245
+
246
+ // Build context message
247
+ const message = buildContextMessage(checkpoint, features, agents, config);
248
+
249
+ if (message) {
250
+ console.log('[context-injector] Session context loaded:');
251
+ console.log('---');
252
+ console.log(message);
253
+ console.log('---');
254
+ }
255
+
256
+ return approve();
257
+ } catch (error) {
258
+ console.error(`[context-injector] Error: ${error.message}`);
259
+ return approve();
260
+ }
261
+ };
@@ -0,0 +1,214 @@
1
+ /**
2
+ * Happy Mode Detector Hook
3
+ *
4
+ * Auto-detects Happy daemon environment and enables mobile-optimized mode.
5
+ * Sets appropriate verbosity and response formatting for mobile clients.
6
+ * Supports multiple detection methods: env var, daemon state, manual config.
7
+ *
8
+ * Event: UserPromptSubmit (runs once per session)
9
+ *
10
+ * Configuration: Reads from .claude/config/hooks-config.json
11
+ */
12
+
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+
16
+ // Default configuration (can be overridden by hooks-config.json)
17
+ const DEFAULT_CONFIG = {
18
+ enabled: false, // Manual override
19
+ auto_detect: true, // Attempt auto-detection
20
+ verbosity: 'condensed', // 'verbose', 'condensed', 'compact'
21
+ show_file_stats: true, // Show file operation statistics
22
+ max_grep_results: 5, // Limit grep results for mobile
23
+ checkpoint_interval_minutes: 10, // Auto-checkpoint frequency
24
+ };
25
+
26
+ // Paths
27
+ const CONFIG_PATH = path.join(process.cwd(), '.claude', 'config', 'hooks-config.json');
28
+ const HAPPY_MODE_PATH = path.join(process.cwd(), '.claude', 'config', 'happy-mode.json');
29
+ const HAPPY_STATE_PATH = path.join(process.env.HOME || process.env.USERPROFILE, '.happy', 'daemon.state.json');
30
+ const SESSION_MARKER = path.join(process.cwd(), '.claude', 'config', '.happy-detected');
31
+
32
+ /**
33
+ * Load configuration with defaults
34
+ */
35
+ function loadConfig() {
36
+ try {
37
+ if (fs.existsSync(CONFIG_PATH)) {
38
+ const config = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
39
+ return { ...DEFAULT_CONFIG, ...(config.happy_mode || {}) };
40
+ }
41
+ } catch (e) {
42
+ // Use defaults on error
43
+ }
44
+ return DEFAULT_CONFIG;
45
+ }
46
+
47
+ /**
48
+ * Check if we've already detected Happy mode this session
49
+ */
50
+ function hasDetectedThisSession() {
51
+ try {
52
+ if (fs.existsSync(SESSION_MARKER)) {
53
+ const content = fs.readFileSync(SESSION_MARKER, 'utf8');
54
+ const data = JSON.parse(content);
55
+ // Session valid for 4 hours
56
+ if (Date.now() - data.timestamp < 4 * 60 * 60 * 1000) {
57
+ return data;
58
+ }
59
+ }
60
+ } catch (e) {
61
+ // Continue with detection
62
+ }
63
+ return null;
64
+ }
65
+
66
+ /**
67
+ * Mark session as detected
68
+ */
69
+ function markSessionDetected(result) {
70
+ try {
71
+ const dir = path.dirname(SESSION_MARKER);
72
+ if (!fs.existsSync(dir)) {
73
+ fs.mkdirSync(dir, { recursive: true });
74
+ }
75
+ fs.writeFileSync(SESSION_MARKER, JSON.stringify({
76
+ timestamp: Date.now(),
77
+ ...result,
78
+ }, null, 2), 'utf8');
79
+ } catch (e) {
80
+ // Silent failure
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Detect Happy mode via environment variable
86
+ */
87
+ function detectViaEnvVar() {
88
+ return process.env.HAPPY_SESSION === 'true';
89
+ }
90
+
91
+ /**
92
+ * Detect Happy mode via daemon state file
93
+ */
94
+ function detectViaDaemonState() {
95
+ try {
96
+ if (fs.existsSync(HAPPY_STATE_PATH)) {
97
+ const state = JSON.parse(fs.readFileSync(HAPPY_STATE_PATH, 'utf8'));
98
+ // Check if daemon is running and has active session
99
+ if (state.running && state.current_session) {
100
+ return true;
101
+ }
102
+ }
103
+ } catch (e) {
104
+ // Daemon not running or state invalid
105
+ }
106
+ return false;
107
+ }
108
+
109
+ /**
110
+ * Detect Happy mode via manual configuration
111
+ */
112
+ function detectViaManualConfig() {
113
+ try {
114
+ if (fs.existsSync(HAPPY_MODE_PATH)) {
115
+ const config = JSON.parse(fs.readFileSync(HAPPY_MODE_PATH, 'utf8'));
116
+ return config.enabled === true;
117
+ }
118
+ } catch (e) {
119
+ // No manual config
120
+ }
121
+ return false;
122
+ }
123
+
124
+ /**
125
+ * Get detection method description
126
+ */
127
+ function getDetectionMethod(envVar, daemon, manual) {
128
+ if (manual) return 'manual_config';
129
+ if (envVar) return 'env_var';
130
+ if (daemon) return 'daemon_state';
131
+ return 'none';
132
+ }
133
+
134
+ /**
135
+ * Save Happy mode state for other hooks to use
136
+ */
137
+ function saveHappyModeState(result) {
138
+ try {
139
+ const dir = path.dirname(HAPPY_MODE_PATH);
140
+ if (!fs.existsSync(dir)) {
141
+ fs.mkdirSync(dir, { recursive: true });
142
+ }
143
+ fs.writeFileSync(HAPPY_MODE_PATH, JSON.stringify(result, null, 2), 'utf8');
144
+ } catch (e) {
145
+ // Silent failure
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Main hook handler
151
+ */
152
+ module.exports = async function happyModeDetector(context) {
153
+ // Always continue - never block
154
+ const approve = () => ({ continue: true });
155
+
156
+ try {
157
+ // Check if already detected this session
158
+ const cached = hasDetectedThisSession();
159
+ if (cached) {
160
+ return approve();
161
+ }
162
+
163
+ const config = loadConfig();
164
+
165
+ // Skip detection if auto-detect is disabled
166
+ if (!config.auto_detect) {
167
+ const result = {
168
+ happy_mode: config.enabled,
169
+ detection_method: config.enabled ? 'manual_override' : 'disabled',
170
+ config: config,
171
+ };
172
+ markSessionDetected(result);
173
+ return approve();
174
+ }
175
+
176
+ // Try all detection methods
177
+ const viaEnvVar = detectViaEnvVar();
178
+ const viaDaemon = detectViaDaemonState();
179
+ const viaManual = detectViaManualConfig();
180
+
181
+ const isHappyMode = viaEnvVar || viaDaemon || viaManual;
182
+ const detectionMethod = getDetectionMethod(viaEnvVar, viaDaemon, viaManual);
183
+
184
+ // Build result object
185
+ const result = {
186
+ happy_mode: isHappyMode,
187
+ detection_method: detectionMethod,
188
+ detected_at: new Date().toISOString(),
189
+ config: {
190
+ verbosity: config.verbosity,
191
+ show_file_stats: config.show_file_stats,
192
+ max_grep_results: config.max_grep_results,
193
+ checkpoint_interval_minutes: config.checkpoint_interval_minutes,
194
+ },
195
+ };
196
+
197
+ // Save state for other hooks
198
+ saveHappyModeState(result);
199
+ markSessionDetected(result);
200
+
201
+ // Log detection result
202
+ if (isHappyMode) {
203
+ console.log(`[happy-mode-detector] Happy mode ENABLED (via ${detectionMethod})`);
204
+ console.log(`[happy-mode-detector] Verbosity: ${config.verbosity}`);
205
+ } else {
206
+ console.log('[happy-mode-detector] Happy mode not detected');
207
+ }
208
+
209
+ return approve();
210
+ } catch (error) {
211
+ console.error(`[happy-mode-detector] Error: ${error.message}`);
212
+ return approve();
213
+ }
214
+ };