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 +1 -1
- package/src/cli/menu.js +80 -0
- package/src/commands/init.js +3 -3
- package/src/data/releases.json +70 -0
- package/templates/hooks/context-injector.template.js +261 -0
- package/templates/hooks/happy-mode-detector.template.js +214 -0
- package/templates/hooks/happy-title-generator.template.js +260 -0
- package/templates/hooks/token-budget-loader.template.js +234 -0
- package/templates/hooks/tool-output-cacher.template.js +219 -0
package/package.json
CHANGED
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
|
|
package/src/commands/init.js
CHANGED
|
@@ -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'
|
|
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'
|
|
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',
|
|
57
|
+
npmPackage: 'happy-coder',
|
|
58
58
|
npmInstallPrompt: 'Install Happy Coder CLI globally? (npm i -g happy-coder)',
|
|
59
59
|
},
|
|
60
60
|
{
|
package/src/data/releases.json
CHANGED
|
@@ -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
|
+
};
|