claude-code-workflow 6.2.7 → 6.3.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/.claude/CLAUDE.md +16 -1
- package/.claude/workflows/cli-templates/protocols/analysis-protocol.md +11 -4
- package/.claude/workflows/cli-templates/protocols/write-protocol.md +10 -75
- package/.claude/workflows/cli-tools-usage.md +14 -24
- package/.codex/AGENTS.md +51 -1
- package/.codex/prompts/compact.md +378 -0
- package/.gemini/GEMINI.md +57 -20
- package/ccw/dist/cli.d.ts.map +1 -1
- package/ccw/dist/cli.js +21 -8
- package/ccw/dist/cli.js.map +1 -1
- package/ccw/dist/commands/cli.d.ts +2 -0
- package/ccw/dist/commands/cli.d.ts.map +1 -1
- package/ccw/dist/commands/cli.js +129 -8
- package/ccw/dist/commands/cli.js.map +1 -1
- package/ccw/dist/commands/hook.d.ts.map +1 -1
- package/ccw/dist/commands/hook.js +3 -2
- package/ccw/dist/commands/hook.js.map +1 -1
- package/ccw/dist/config/litellm-api-config-manager.d.ts +180 -0
- package/ccw/dist/config/litellm-api-config-manager.d.ts.map +1 -0
- package/ccw/dist/config/litellm-api-config-manager.js +770 -0
- package/ccw/dist/config/litellm-api-config-manager.js.map +1 -0
- package/ccw/dist/config/provider-models.d.ts +73 -0
- package/ccw/dist/config/provider-models.d.ts.map +1 -0
- package/ccw/dist/config/provider-models.js +172 -0
- package/ccw/dist/config/provider-models.js.map +1 -0
- package/ccw/dist/core/cache-manager.d.ts.map +1 -1
- package/ccw/dist/core/cache-manager.js +3 -5
- package/ccw/dist/core/cache-manager.js.map +1 -1
- package/ccw/dist/core/dashboard-generator.d.ts.map +1 -1
- package/ccw/dist/core/dashboard-generator.js +3 -1
- package/ccw/dist/core/dashboard-generator.js.map +1 -1
- package/ccw/dist/core/routes/cli-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/cli-routes.js +169 -0
- package/ccw/dist/core/routes/cli-routes.js.map +1 -1
- package/ccw/dist/core/routes/codexlens-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/codexlens-routes.js +234 -18
- package/ccw/dist/core/routes/codexlens-routes.js.map +1 -1
- package/ccw/dist/core/routes/hooks-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/hooks-routes.js +30 -32
- package/ccw/dist/core/routes/hooks-routes.js.map +1 -1
- package/ccw/dist/core/routes/litellm-api-routes.d.ts +21 -0
- package/ccw/dist/core/routes/litellm-api-routes.d.ts.map +1 -0
- package/ccw/dist/core/routes/litellm-api-routes.js +780 -0
- package/ccw/dist/core/routes/litellm-api-routes.js.map +1 -0
- package/ccw/dist/core/routes/litellm-routes.d.ts +20 -0
- package/ccw/dist/core/routes/litellm-routes.d.ts.map +1 -0
- package/ccw/dist/core/routes/litellm-routes.js +85 -0
- package/ccw/dist/core/routes/litellm-routes.js.map +1 -0
- package/ccw/dist/core/routes/mcp-routes.js +2 -2
- package/ccw/dist/core/routes/mcp-routes.js.map +1 -1
- package/ccw/dist/core/routes/status-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/status-routes.js +39 -0
- package/ccw/dist/core/routes/status-routes.js.map +1 -1
- package/ccw/dist/core/routes/system-routes.js +1 -1
- package/ccw/dist/core/routes/system-routes.js.map +1 -1
- package/ccw/dist/core/server.d.ts.map +1 -1
- package/ccw/dist/core/server.js +15 -1
- package/ccw/dist/core/server.js.map +1 -1
- package/ccw/dist/mcp-server/index.js +1 -1
- package/ccw/dist/mcp-server/index.js.map +1 -1
- package/ccw/dist/tools/claude-cli-tools.d.ts +82 -0
- package/ccw/dist/tools/claude-cli-tools.d.ts.map +1 -0
- package/ccw/dist/tools/claude-cli-tools.js +216 -0
- package/ccw/dist/tools/claude-cli-tools.js.map +1 -0
- package/ccw/dist/tools/cli-executor.d.ts.map +1 -1
- package/ccw/dist/tools/cli-executor.js +76 -14
- package/ccw/dist/tools/cli-executor.js.map +1 -1
- package/ccw/dist/tools/codex-lens.d.ts +9 -2
- package/ccw/dist/tools/codex-lens.d.ts.map +1 -1
- package/ccw/dist/tools/codex-lens.js +114 -9
- package/ccw/dist/tools/codex-lens.js.map +1 -1
- package/ccw/dist/tools/context-cache-store.d.ts +136 -0
- package/ccw/dist/tools/context-cache-store.d.ts.map +1 -0
- package/ccw/dist/tools/context-cache-store.js +256 -0
- package/ccw/dist/tools/context-cache-store.js.map +1 -0
- package/ccw/dist/tools/context-cache.d.ts +56 -0
- package/ccw/dist/tools/context-cache.d.ts.map +1 -0
- package/ccw/dist/tools/context-cache.js +294 -0
- package/ccw/dist/tools/context-cache.js.map +1 -0
- package/ccw/dist/tools/core-memory.d.ts.map +1 -1
- package/ccw/dist/tools/core-memory.js +33 -19
- package/ccw/dist/tools/core-memory.js.map +1 -1
- package/ccw/dist/tools/index.d.ts.map +1 -1
- package/ccw/dist/tools/index.js +2 -0
- package/ccw/dist/tools/index.js.map +1 -1
- package/ccw/dist/tools/litellm-client.d.ts +85 -0
- package/ccw/dist/tools/litellm-client.d.ts.map +1 -0
- package/ccw/dist/tools/litellm-client.js +188 -0
- package/ccw/dist/tools/litellm-client.js.map +1 -0
- package/ccw/dist/tools/litellm-executor.d.ts +34 -0
- package/ccw/dist/tools/litellm-executor.d.ts.map +1 -0
- package/ccw/dist/tools/litellm-executor.js +192 -0
- package/ccw/dist/tools/litellm-executor.js.map +1 -0
- package/ccw/dist/tools/pattern-parser.d.ts +55 -0
- package/ccw/dist/tools/pattern-parser.d.ts.map +1 -0
- package/ccw/dist/tools/pattern-parser.js +237 -0
- package/ccw/dist/tools/pattern-parser.js.map +1 -0
- package/ccw/dist/tools/smart-search.d.ts +1 -0
- package/ccw/dist/tools/smart-search.d.ts.map +1 -1
- package/ccw/dist/tools/smart-search.js +117 -41
- package/ccw/dist/tools/smart-search.js.map +1 -1
- package/ccw/dist/types/litellm-api-config.d.ts +294 -0
- package/ccw/dist/types/litellm-api-config.d.ts.map +1 -0
- package/ccw/dist/types/litellm-api-config.js +8 -0
- package/ccw/dist/types/litellm-api-config.js.map +1 -0
- package/ccw/src/cli.ts +258 -244
- package/ccw/src/commands/cli.ts +153 -9
- package/ccw/src/commands/hook.ts +3 -2
- package/ccw/src/config/.litellm-api-config-manager.ts.2025-12-23T11-57-43-727Z.bak +441 -0
- package/ccw/src/config/litellm-api-config-manager.ts +1012 -0
- package/ccw/src/config/provider-models.ts +222 -0
- package/ccw/src/core/cache-manager.ts +292 -294
- package/ccw/src/core/dashboard-generator.ts +3 -1
- package/ccw/src/core/routes/cli-routes.ts +192 -0
- package/ccw/src/core/routes/codexlens-routes.ts +241 -19
- package/ccw/src/core/routes/hooks-routes.ts +399 -405
- package/ccw/src/core/routes/litellm-api-routes.ts +930 -0
- package/ccw/src/core/routes/litellm-routes.ts +107 -0
- package/ccw/src/core/routes/mcp-routes.ts +1271 -1271
- package/ccw/src/core/routes/status-routes.ts +51 -0
- package/ccw/src/core/routes/system-routes.ts +1 -1
- package/ccw/src/core/server.ts +15 -1
- package/ccw/src/mcp-server/index.ts +1 -1
- package/ccw/src/templates/dashboard-css/12-cli-legacy.css +44 -0
- package/ccw/src/templates/dashboard-css/31-api-settings.css +2265 -0
- package/ccw/src/templates/dashboard-js/components/cli-history.js +15 -8
- package/ccw/src/templates/dashboard-js/components/cli-status.js +323 -9
- package/ccw/src/templates/dashboard-js/components/navigation.js +329 -313
- package/ccw/src/templates/dashboard-js/i18n.js +583 -1
- package/ccw/src/templates/dashboard-js/views/api-settings.js +3362 -0
- package/ccw/src/templates/dashboard-js/views/cli-manager.js +199 -24
- package/ccw/src/templates/dashboard-js/views/codexlens-manager.js +1265 -27
- package/ccw/src/templates/dashboard.html +840 -831
- package/ccw/src/tools/claude-cli-tools.ts +300 -0
- package/ccw/src/tools/cli-executor.ts +83 -14
- package/ccw/src/tools/codex-lens.ts +146 -9
- package/ccw/src/tools/context-cache-store.ts +368 -0
- package/ccw/src/tools/context-cache.ts +393 -0
- package/ccw/src/tools/core-memory.ts +33 -19
- package/ccw/src/tools/index.ts +2 -0
- package/ccw/src/tools/litellm-client.ts +246 -0
- package/ccw/src/tools/litellm-executor.ts +241 -0
- package/ccw/src/tools/pattern-parser.ts +329 -0
- package/ccw/src/tools/smart-search.ts +142 -41
- package/ccw/src/types/litellm-api-config.ts +402 -0
- package/ccw-litellm/README.md +180 -0
- package/ccw-litellm/pyproject.toml +35 -0
- package/ccw-litellm/src/ccw_litellm/__init__.py +47 -0
- package/ccw-litellm/src/ccw_litellm/__pycache__/__init__.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/__pycache__/cli.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/cli.py +108 -0
- package/ccw-litellm/src/ccw_litellm/clients/__init__.py +12 -0
- package/ccw-litellm/src/ccw_litellm/clients/__pycache__/__init__.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/clients/__pycache__/litellm_embedder.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/clients/__pycache__/litellm_llm.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/clients/litellm_embedder.py +251 -0
- package/ccw-litellm/src/ccw_litellm/clients/litellm_llm.py +165 -0
- package/ccw-litellm/src/ccw_litellm/config/__init__.py +22 -0
- package/ccw-litellm/src/ccw_litellm/config/__pycache__/__init__.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/config/__pycache__/loader.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/config/__pycache__/models.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/config/loader.py +316 -0
- package/ccw-litellm/src/ccw_litellm/config/models.py +130 -0
- package/ccw-litellm/src/ccw_litellm/interfaces/__init__.py +14 -0
- package/ccw-litellm/src/ccw_litellm/interfaces/__pycache__/__init__.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/interfaces/__pycache__/embedder.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/interfaces/__pycache__/llm.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/interfaces/embedder.py +52 -0
- package/ccw-litellm/src/ccw_litellm/interfaces/llm.py +45 -0
- package/codex-lens/src/codexlens/__pycache__/config.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/cli/__pycache__/commands.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/cli/__pycache__/embedding_manager.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/cli/__pycache__/model_manager.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/cli/__pycache__/output.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/cli/commands.py +378 -23
- package/codex-lens/src/codexlens/cli/embedding_manager.py +660 -56
- package/codex-lens/src/codexlens/cli/model_manager.py +31 -18
- package/codex-lens/src/codexlens/cli/output.py +12 -1
- package/codex-lens/src/codexlens/config.py +93 -0
- package/codex-lens/src/codexlens/search/__pycache__/chain_search.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/search/__pycache__/hybrid_search.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/search/__pycache__/ranking.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/search/chain_search.py +6 -2
- package/codex-lens/src/codexlens/search/hybrid_search.py +44 -21
- package/codex-lens/src/codexlens/search/ranking.py +1 -1
- package/codex-lens/src/codexlens/semantic/__init__.py +42 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/__init__.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/base.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/chunker.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/embedder.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/factory.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/gpu_support.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/litellm_embedder.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/vector_store.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/base.py +61 -0
- package/codex-lens/src/codexlens/semantic/chunker.py +43 -20
- package/codex-lens/src/codexlens/semantic/embedder.py +60 -13
- package/codex-lens/src/codexlens/semantic/factory.py +98 -0
- package/codex-lens/src/codexlens/semantic/gpu_support.py +225 -3
- package/codex-lens/src/codexlens/semantic/litellm_embedder.py +144 -0
- package/codex-lens/src/codexlens/semantic/rotational_embedder.py +434 -0
- package/codex-lens/src/codexlens/semantic/vector_store.py +33 -8
- package/codex-lens/src/codexlens/storage/__pycache__/path_mapper.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/storage/migrations/__pycache__/migration_004_dual_fts.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/storage/path_mapper.py +27 -1
- package/package.json +15 -5
- package/.codex/prompts.zip +0 -0
- package/ccw/package.json +0 -65
|
@@ -1,405 +1,399 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
|
-
/**
|
|
3
|
-
* Hooks Routes Module
|
|
4
|
-
* Handles all hooks-related API endpoints
|
|
5
|
-
*/
|
|
6
|
-
import type { IncomingMessage, ServerResponse } from 'http';
|
|
7
|
-
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
8
|
-
import { join, dirname } from 'path';
|
|
9
|
-
import { homedir } from 'os';
|
|
10
|
-
|
|
11
|
-
export interface RouteContext {
|
|
12
|
-
pathname: string;
|
|
13
|
-
url: URL;
|
|
14
|
-
req: IncomingMessage;
|
|
15
|
-
res: ServerResponse;
|
|
16
|
-
initialPath: string;
|
|
17
|
-
handlePostRequest: (req: IncomingMessage, res: ServerResponse, handler: (body: unknown) => Promise<any>) => void;
|
|
18
|
-
broadcastToClients: (data: unknown) => void;
|
|
19
|
-
extractSessionIdFromPath: (filePath: string) => string | null;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// ========================================
|
|
23
|
-
// Helper Functions
|
|
24
|
-
// ========================================
|
|
25
|
-
|
|
26
|
-
const GLOBAL_SETTINGS_PATH = join(homedir(), '.claude', 'settings.json');
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Get project settings path
|
|
30
|
-
* @param {string} projectPath
|
|
31
|
-
* @returns {string}
|
|
32
|
-
*/
|
|
33
|
-
function getProjectSettingsPath(projectPath) {
|
|
34
|
-
|
|
35
|
-
return join(
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Read settings file safely
|
|
40
|
-
* @param {string} filePath
|
|
41
|
-
* @returns {Object}
|
|
42
|
-
*/
|
|
43
|
-
function readSettingsFile(filePath) {
|
|
44
|
-
try {
|
|
45
|
-
if (!existsSync(filePath)) {
|
|
46
|
-
return {};
|
|
47
|
-
}
|
|
48
|
-
const content = readFileSync(filePath, 'utf8');
|
|
49
|
-
return JSON.parse(content);
|
|
50
|
-
} catch (error: unknown) {
|
|
51
|
-
console.error(`Error reading settings file ${filePath}:`, error);
|
|
52
|
-
return {};
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Get hooks configuration from global and project settings
|
|
58
|
-
* @param {string} projectPath
|
|
59
|
-
* @returns {Object}
|
|
60
|
-
*/
|
|
61
|
-
function getHooksConfig(projectPath) {
|
|
62
|
-
const globalSettings = readSettingsFile(GLOBAL_SETTINGS_PATH);
|
|
63
|
-
const projectSettingsPath = projectPath ? getProjectSettingsPath(projectPath) : null;
|
|
64
|
-
const projectSettings = projectSettingsPath ? readSettingsFile(projectSettingsPath) : {};
|
|
65
|
-
|
|
66
|
-
return {
|
|
67
|
-
global: {
|
|
68
|
-
path: GLOBAL_SETTINGS_PATH,
|
|
69
|
-
hooks: globalSettings.hooks || {}
|
|
70
|
-
},
|
|
71
|
-
project: {
|
|
72
|
-
path: projectSettingsPath,
|
|
73
|
-
hooks: projectSettings.hooks || {}
|
|
74
|
-
}
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Save a hook to settings file
|
|
80
|
-
* @param {string} projectPath
|
|
81
|
-
* @param {string} scope - 'global' or 'project'
|
|
82
|
-
* @param {string} event - Hook event type
|
|
83
|
-
* @param {Object} hookData - Hook configuration
|
|
84
|
-
* @returns {Object}
|
|
85
|
-
*/
|
|
86
|
-
function saveHookToSettings(projectPath, scope, event, hookData) {
|
|
87
|
-
try {
|
|
88
|
-
const filePath = scope === 'global' ? GLOBAL_SETTINGS_PATH : getProjectSettingsPath(projectPath);
|
|
89
|
-
const settings = readSettingsFile(filePath);
|
|
90
|
-
|
|
91
|
-
// Ensure hooks object exists
|
|
92
|
-
if (!settings.hooks) {
|
|
93
|
-
settings.hooks = {};
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Ensure the event array exists
|
|
97
|
-
if (!settings.hooks[event]) {
|
|
98
|
-
settings.hooks[event] = [];
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Ensure it's an array
|
|
102
|
-
if (!Array.isArray(settings.hooks[event])) {
|
|
103
|
-
settings.hooks[event] = [settings.hooks[event]];
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Check if we're replacing an existing hook
|
|
107
|
-
if (hookData.replaceIndex !== undefined) {
|
|
108
|
-
const index = hookData.replaceIndex;
|
|
109
|
-
delete hookData.replaceIndex;
|
|
110
|
-
if (index >= 0 && index < settings.hooks[event].length) {
|
|
111
|
-
settings.hooks[event][index] = hookData;
|
|
112
|
-
}
|
|
113
|
-
} else {
|
|
114
|
-
// Add new hook
|
|
115
|
-
settings.hooks[event].push(hookData);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Ensure directory exists and write file
|
|
119
|
-
const dirPath = dirname(filePath);
|
|
120
|
-
if (!existsSync(dirPath)) {
|
|
121
|
-
mkdirSync(dirPath, { recursive: true });
|
|
122
|
-
}
|
|
123
|
-
writeFileSync(filePath, JSON.stringify(settings, null, 2), 'utf8');
|
|
124
|
-
|
|
125
|
-
return {
|
|
126
|
-
success: true,
|
|
127
|
-
event,
|
|
128
|
-
hookData
|
|
129
|
-
};
|
|
130
|
-
} catch (error: unknown) {
|
|
131
|
-
console.error('Error saving hook:', error);
|
|
132
|
-
return { error: (error as Error).message };
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Delete a hook from settings file
|
|
138
|
-
* @param {string} projectPath
|
|
139
|
-
* @param {string} scope - 'global' or 'project'
|
|
140
|
-
* @param {string} event - Hook event type
|
|
141
|
-
* @param {number} hookIndex - Index of hook to delete
|
|
142
|
-
* @returns {Object}
|
|
143
|
-
*/
|
|
144
|
-
function deleteHookFromSettings(projectPath, scope, event, hookIndex) {
|
|
145
|
-
try {
|
|
146
|
-
const filePath = scope === 'global' ? GLOBAL_SETTINGS_PATH : getProjectSettingsPath(projectPath);
|
|
147
|
-
const settings = readSettingsFile(filePath);
|
|
148
|
-
|
|
149
|
-
if (!settings.hooks || !settings.hooks[event]) {
|
|
150
|
-
return { error: 'Hook not found' };
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// Ensure it's an array
|
|
154
|
-
if (!Array.isArray(settings.hooks[event])) {
|
|
155
|
-
settings.hooks[event] = [settings.hooks[event]];
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
if (hookIndex < 0 || hookIndex >= settings.hooks[event].length) {
|
|
159
|
-
return { error: 'Invalid hook index' };
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// Remove the hook
|
|
163
|
-
settings.hooks[event].splice(hookIndex, 1);
|
|
164
|
-
|
|
165
|
-
// Remove empty event arrays
|
|
166
|
-
if (settings.hooks[event].length === 0) {
|
|
167
|
-
delete settings.hooks[event];
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
writeFileSync(filePath, JSON.stringify(settings, null, 2), 'utf8');
|
|
171
|
-
|
|
172
|
-
return {
|
|
173
|
-
success: true,
|
|
174
|
-
event,
|
|
175
|
-
hookIndex
|
|
176
|
-
};
|
|
177
|
-
} catch (error: unknown) {
|
|
178
|
-
console.error('Error deleting hook:', error);
|
|
179
|
-
return { error: (error as Error).message };
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// ========================================
|
|
184
|
-
// Session State Tracking
|
|
185
|
-
// ========================================
|
|
186
|
-
|
|
187
|
-
//
|
|
188
|
-
//
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
//
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
};
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
//
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
if (
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
content
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
contextType
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
res
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
});
|
|
401
|
-
return true;
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
return false;
|
|
405
|
-
}
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* Hooks Routes Module
|
|
4
|
+
* Handles all hooks-related API endpoints
|
|
5
|
+
*/
|
|
6
|
+
import type { IncomingMessage, ServerResponse } from 'http';
|
|
7
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
8
|
+
import { join, dirname } from 'path';
|
|
9
|
+
import { homedir } from 'os';
|
|
10
|
+
|
|
11
|
+
export interface RouteContext {
|
|
12
|
+
pathname: string;
|
|
13
|
+
url: URL;
|
|
14
|
+
req: IncomingMessage;
|
|
15
|
+
res: ServerResponse;
|
|
16
|
+
initialPath: string;
|
|
17
|
+
handlePostRequest: (req: IncomingMessage, res: ServerResponse, handler: (body: unknown) => Promise<any>) => void;
|
|
18
|
+
broadcastToClients: (data: unknown) => void;
|
|
19
|
+
extractSessionIdFromPath: (filePath: string) => string | null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// ========================================
|
|
23
|
+
// Helper Functions
|
|
24
|
+
// ========================================
|
|
25
|
+
|
|
26
|
+
const GLOBAL_SETTINGS_PATH = join(homedir(), '.claude', 'settings.json');
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Get project settings path
|
|
30
|
+
* @param {string} projectPath
|
|
31
|
+
* @returns {string}
|
|
32
|
+
*/
|
|
33
|
+
function getProjectSettingsPath(projectPath) {
|
|
34
|
+
// path.join automatically handles cross-platform path separators
|
|
35
|
+
return join(projectPath, '.claude', 'settings.json');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Read settings file safely
|
|
40
|
+
* @param {string} filePath
|
|
41
|
+
* @returns {Object}
|
|
42
|
+
*/
|
|
43
|
+
function readSettingsFile(filePath) {
|
|
44
|
+
try {
|
|
45
|
+
if (!existsSync(filePath)) {
|
|
46
|
+
return {};
|
|
47
|
+
}
|
|
48
|
+
const content = readFileSync(filePath, 'utf8');
|
|
49
|
+
return JSON.parse(content);
|
|
50
|
+
} catch (error: unknown) {
|
|
51
|
+
console.error(`Error reading settings file ${filePath}:`, error);
|
|
52
|
+
return {};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Get hooks configuration from global and project settings
|
|
58
|
+
* @param {string} projectPath
|
|
59
|
+
* @returns {Object}
|
|
60
|
+
*/
|
|
61
|
+
function getHooksConfig(projectPath) {
|
|
62
|
+
const globalSettings = readSettingsFile(GLOBAL_SETTINGS_PATH);
|
|
63
|
+
const projectSettingsPath = projectPath ? getProjectSettingsPath(projectPath) : null;
|
|
64
|
+
const projectSettings = projectSettingsPath ? readSettingsFile(projectSettingsPath) : {};
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
global: {
|
|
68
|
+
path: GLOBAL_SETTINGS_PATH,
|
|
69
|
+
hooks: globalSettings.hooks || {}
|
|
70
|
+
},
|
|
71
|
+
project: {
|
|
72
|
+
path: projectSettingsPath,
|
|
73
|
+
hooks: projectSettings.hooks || {}
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Save a hook to settings file
|
|
80
|
+
* @param {string} projectPath
|
|
81
|
+
* @param {string} scope - 'global' or 'project'
|
|
82
|
+
* @param {string} event - Hook event type
|
|
83
|
+
* @param {Object} hookData - Hook configuration
|
|
84
|
+
* @returns {Object}
|
|
85
|
+
*/
|
|
86
|
+
function saveHookToSettings(projectPath, scope, event, hookData) {
|
|
87
|
+
try {
|
|
88
|
+
const filePath = scope === 'global' ? GLOBAL_SETTINGS_PATH : getProjectSettingsPath(projectPath);
|
|
89
|
+
const settings = readSettingsFile(filePath);
|
|
90
|
+
|
|
91
|
+
// Ensure hooks object exists
|
|
92
|
+
if (!settings.hooks) {
|
|
93
|
+
settings.hooks = {};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Ensure the event array exists
|
|
97
|
+
if (!settings.hooks[event]) {
|
|
98
|
+
settings.hooks[event] = [];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Ensure it's an array
|
|
102
|
+
if (!Array.isArray(settings.hooks[event])) {
|
|
103
|
+
settings.hooks[event] = [settings.hooks[event]];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Check if we're replacing an existing hook
|
|
107
|
+
if (hookData.replaceIndex !== undefined) {
|
|
108
|
+
const index = hookData.replaceIndex;
|
|
109
|
+
delete hookData.replaceIndex;
|
|
110
|
+
if (index >= 0 && index < settings.hooks[event].length) {
|
|
111
|
+
settings.hooks[event][index] = hookData;
|
|
112
|
+
}
|
|
113
|
+
} else {
|
|
114
|
+
// Add new hook
|
|
115
|
+
settings.hooks[event].push(hookData);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Ensure directory exists and write file
|
|
119
|
+
const dirPath = dirname(filePath);
|
|
120
|
+
if (!existsSync(dirPath)) {
|
|
121
|
+
mkdirSync(dirPath, { recursive: true });
|
|
122
|
+
}
|
|
123
|
+
writeFileSync(filePath, JSON.stringify(settings, null, 2), 'utf8');
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
success: true,
|
|
127
|
+
event,
|
|
128
|
+
hookData
|
|
129
|
+
};
|
|
130
|
+
} catch (error: unknown) {
|
|
131
|
+
console.error('Error saving hook:', error);
|
|
132
|
+
return { error: (error as Error).message };
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Delete a hook from settings file
|
|
138
|
+
* @param {string} projectPath
|
|
139
|
+
* @param {string} scope - 'global' or 'project'
|
|
140
|
+
* @param {string} event - Hook event type
|
|
141
|
+
* @param {number} hookIndex - Index of hook to delete
|
|
142
|
+
* @returns {Object}
|
|
143
|
+
*/
|
|
144
|
+
function deleteHookFromSettings(projectPath, scope, event, hookIndex) {
|
|
145
|
+
try {
|
|
146
|
+
const filePath = scope === 'global' ? GLOBAL_SETTINGS_PATH : getProjectSettingsPath(projectPath);
|
|
147
|
+
const settings = readSettingsFile(filePath);
|
|
148
|
+
|
|
149
|
+
if (!settings.hooks || !settings.hooks[event]) {
|
|
150
|
+
return { error: 'Hook not found' };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Ensure it's an array
|
|
154
|
+
if (!Array.isArray(settings.hooks[event])) {
|
|
155
|
+
settings.hooks[event] = [settings.hooks[event]];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (hookIndex < 0 || hookIndex >= settings.hooks[event].length) {
|
|
159
|
+
return { error: 'Invalid hook index' };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Remove the hook
|
|
163
|
+
settings.hooks[event].splice(hookIndex, 1);
|
|
164
|
+
|
|
165
|
+
// Remove empty event arrays
|
|
166
|
+
if (settings.hooks[event].length === 0) {
|
|
167
|
+
delete settings.hooks[event];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
writeFileSync(filePath, JSON.stringify(settings, null, 2), 'utf8');
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
success: true,
|
|
174
|
+
event,
|
|
175
|
+
hookIndex
|
|
176
|
+
};
|
|
177
|
+
} catch (error: unknown) {
|
|
178
|
+
console.error('Error deleting hook:', error);
|
|
179
|
+
return { error: (error as Error).message };
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ========================================
|
|
184
|
+
// Session State Tracking
|
|
185
|
+
// ========================================
|
|
186
|
+
// NOTE: Session state is managed by the CLI command (src/commands/hook.ts)
|
|
187
|
+
// using file-based persistence (~/.claude/.ccw-sessions/).
|
|
188
|
+
// This ensures consistent state tracking across all invocation methods.
|
|
189
|
+
// The /api/hook endpoint delegates to SessionClusteringService without
|
|
190
|
+
// managing its own state, as the authoritative state lives in the CLI layer.
|
|
191
|
+
|
|
192
|
+
// ========================================
|
|
193
|
+
// Route Handler
|
|
194
|
+
// ========================================
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Handle hooks routes
|
|
198
|
+
* @returns true if route was handled, false otherwise
|
|
199
|
+
*/
|
|
200
|
+
export async function handleHooksRoutes(ctx: RouteContext): Promise<boolean> {
|
|
201
|
+
const { pathname, url, req, res, initialPath, handlePostRequest, broadcastToClients, extractSessionIdFromPath } = ctx;
|
|
202
|
+
|
|
203
|
+
// API: Hook endpoint for Claude Code notifications
|
|
204
|
+
if (pathname === '/api/hook' && req.method === 'POST') {
|
|
205
|
+
handlePostRequest(req, res, async (body) => {
|
|
206
|
+
const { type, filePath, sessionId, ...extraData } = body;
|
|
207
|
+
|
|
208
|
+
// Determine session ID from file path if not provided
|
|
209
|
+
let resolvedSessionId = sessionId;
|
|
210
|
+
if (!resolvedSessionId && filePath) {
|
|
211
|
+
resolvedSessionId = extractSessionIdFromPath(filePath);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Handle context hooks (session-start, context)
|
|
215
|
+
if (type === 'session-start' || type === 'context') {
|
|
216
|
+
try {
|
|
217
|
+
const projectPath = url.searchParams.get('path') || initialPath;
|
|
218
|
+
const { SessionClusteringService } = await import('../session-clustering-service.js');
|
|
219
|
+
const clusteringService = new SessionClusteringService(projectPath);
|
|
220
|
+
|
|
221
|
+
const format = url.searchParams.get('format') || 'markdown';
|
|
222
|
+
|
|
223
|
+
// Pass type and prompt to getProgressiveIndex
|
|
224
|
+
// session-start: returns recent sessions by time
|
|
225
|
+
// context: returns intent-matched sessions based on prompt
|
|
226
|
+
const index = await clusteringService.getProgressiveIndex({
|
|
227
|
+
type: type as 'session-start' | 'context',
|
|
228
|
+
sessionId: resolvedSessionId,
|
|
229
|
+
prompt: extraData.prompt // Pass user prompt for intent matching
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
// Return context directly
|
|
233
|
+
return {
|
|
234
|
+
success: true,
|
|
235
|
+
type: 'context',
|
|
236
|
+
format,
|
|
237
|
+
content: index,
|
|
238
|
+
sessionId: resolvedSessionId
|
|
239
|
+
};
|
|
240
|
+
} catch (error) {
|
|
241
|
+
console.error('[Hooks] Failed to generate context:', error);
|
|
242
|
+
// Return empty content on failure (fail silently)
|
|
243
|
+
return {
|
|
244
|
+
success: true,
|
|
245
|
+
type: 'context',
|
|
246
|
+
format: 'markdown',
|
|
247
|
+
content: '',
|
|
248
|
+
sessionId: resolvedSessionId,
|
|
249
|
+
error: (error as Error).message
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Broadcast to all connected WebSocket clients
|
|
255
|
+
const notification = {
|
|
256
|
+
type: type || 'session_updated',
|
|
257
|
+
payload: {
|
|
258
|
+
sessionId: resolvedSessionId,
|
|
259
|
+
filePath: filePath,
|
|
260
|
+
timestamp: new Date().toISOString(),
|
|
261
|
+
...extraData // Pass through toolName, status, result, params, error, etc.
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
broadcastToClients(notification);
|
|
266
|
+
|
|
267
|
+
return { success: true, notification };
|
|
268
|
+
});
|
|
269
|
+
return true;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// API: Unified Session Context endpoint (Progressive Disclosure)
|
|
273
|
+
// DEPRECATED: Use CLI command `ccw hook session-context --stdin` instead.
|
|
274
|
+
// This endpoint now uses file-based state (shared with CLI) for consistency.
|
|
275
|
+
// - First prompt: returns cluster-based session overview
|
|
276
|
+
// - Subsequent prompts: returns intent-matched sessions based on prompt
|
|
277
|
+
if (pathname === '/api/hook/session-context' && req.method === 'POST') {
|
|
278
|
+
handlePostRequest(req, res, async (body) => {
|
|
279
|
+
const { sessionId, prompt } = body as { sessionId?: string; prompt?: string };
|
|
280
|
+
|
|
281
|
+
if (!sessionId) {
|
|
282
|
+
return {
|
|
283
|
+
success: true,
|
|
284
|
+
content: '',
|
|
285
|
+
error: 'sessionId is required'
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
try {
|
|
290
|
+
const projectPath = url.searchParams.get('path') || initialPath;
|
|
291
|
+
const { SessionClusteringService } = await import('../session-clustering-service.js');
|
|
292
|
+
const clusteringService = new SessionClusteringService(projectPath);
|
|
293
|
+
|
|
294
|
+
// Use file-based session state (shared with CLI hook.ts)
|
|
295
|
+
const sessionStateDir = join(homedir(), '.claude', '.ccw-sessions');
|
|
296
|
+
const sessionStateFile = join(sessionStateDir, `session-${sessionId}.json`);
|
|
297
|
+
|
|
298
|
+
let existingState: { firstLoad: string; loadCount: number; lastPrompt?: string } | null = null;
|
|
299
|
+
if (existsSync(sessionStateFile)) {
|
|
300
|
+
try {
|
|
301
|
+
existingState = JSON.parse(readFileSync(sessionStateFile, 'utf-8'));
|
|
302
|
+
} catch {
|
|
303
|
+
existingState = null;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const isFirstPrompt = !existingState;
|
|
308
|
+
|
|
309
|
+
// Update session state (file-based)
|
|
310
|
+
const newState = isFirstPrompt
|
|
311
|
+
? { firstLoad: new Date().toISOString(), loadCount: 1, lastPrompt: prompt }
|
|
312
|
+
: { ...existingState!, loadCount: existingState!.loadCount + 1, lastPrompt: prompt };
|
|
313
|
+
|
|
314
|
+
if (!existsSync(sessionStateDir)) {
|
|
315
|
+
mkdirSync(sessionStateDir, { recursive: true });
|
|
316
|
+
}
|
|
317
|
+
writeFileSync(sessionStateFile, JSON.stringify(newState, null, 2));
|
|
318
|
+
|
|
319
|
+
// Determine which type of context to return
|
|
320
|
+
let contextType: 'session-start' | 'context';
|
|
321
|
+
let content: string;
|
|
322
|
+
|
|
323
|
+
if (isFirstPrompt) {
|
|
324
|
+
// First prompt: return session overview with clusters
|
|
325
|
+
contextType = 'session-start';
|
|
326
|
+
content = await clusteringService.getProgressiveIndex({
|
|
327
|
+
type: 'session-start',
|
|
328
|
+
sessionId
|
|
329
|
+
});
|
|
330
|
+
} else if (prompt && prompt.trim().length > 0) {
|
|
331
|
+
// Subsequent prompts with content: return intent-matched sessions
|
|
332
|
+
contextType = 'context';
|
|
333
|
+
content = await clusteringService.getProgressiveIndex({
|
|
334
|
+
type: 'context',
|
|
335
|
+
sessionId,
|
|
336
|
+
prompt
|
|
337
|
+
});
|
|
338
|
+
} else {
|
|
339
|
+
// Subsequent prompts without content: return minimal context
|
|
340
|
+
contextType = 'context';
|
|
341
|
+
content = ''; // No context needed for empty prompts
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return {
|
|
345
|
+
success: true,
|
|
346
|
+
type: contextType,
|
|
347
|
+
isFirstPrompt,
|
|
348
|
+
loadCount: newState.loadCount,
|
|
349
|
+
content,
|
|
350
|
+
sessionId
|
|
351
|
+
};
|
|
352
|
+
} catch (error) {
|
|
353
|
+
console.error('[Hooks] Failed to generate session context:', error);
|
|
354
|
+
return {
|
|
355
|
+
success: true,
|
|
356
|
+
content: '',
|
|
357
|
+
sessionId,
|
|
358
|
+
error: (error as Error).message
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
return true;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// API: Get hooks configuration
|
|
366
|
+
if (pathname === '/api/hooks' && req.method === 'GET') {
|
|
367
|
+
const projectPathParam = url.searchParams.get('path');
|
|
368
|
+
const hooksData = getHooksConfig(projectPathParam);
|
|
369
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
370
|
+
res.end(JSON.stringify(hooksData));
|
|
371
|
+
return true;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// API: Save hook
|
|
375
|
+
if (pathname === '/api/hooks' && req.method === 'POST') {
|
|
376
|
+
handlePostRequest(req, res, async (body) => {
|
|
377
|
+
const { projectPath, scope, event, hookData } = body;
|
|
378
|
+
if (!scope || !event || !hookData) {
|
|
379
|
+
return { error: 'scope, event, and hookData are required', status: 400 };
|
|
380
|
+
}
|
|
381
|
+
return saveHookToSettings(projectPath, scope, event, hookData);
|
|
382
|
+
});
|
|
383
|
+
return true;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// API: Delete hook
|
|
387
|
+
if (pathname === '/api/hooks' && req.method === 'DELETE') {
|
|
388
|
+
handlePostRequest(req, res, async (body) => {
|
|
389
|
+
const { projectPath, scope, event, hookIndex } = body;
|
|
390
|
+
if (!scope || !event || hookIndex === undefined) {
|
|
391
|
+
return { error: 'scope, event, and hookIndex are required', status: 400 };
|
|
392
|
+
}
|
|
393
|
+
return deleteHookFromSettings(projectPath, scope, event, hookIndex);
|
|
394
|
+
});
|
|
395
|
+
return true;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
return false;
|
|
399
|
+
}
|