claude-code-workflow 6.3.24 → 6.3.26
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/commands/issue/discover-by-prompt.md +764 -0
- package/.claude/skills/text-formatter/SKILL.md +196 -0
- package/.claude/skills/text-formatter/phases/01-input-collection.md +111 -0
- package/.claude/skills/text-formatter/phases/02-content-analysis.md +248 -0
- package/.claude/skills/text-formatter/phases/03-format-transform.md +245 -0
- package/.claude/skills/text-formatter/phases/04-output-preview.md +183 -0
- package/.claude/skills/text-formatter/specs/callout-types.md +293 -0
- package/.claude/skills/text-formatter/specs/element-mapping.md +226 -0
- package/.claude/skills/text-formatter/specs/format-rules.md +273 -0
- package/.claude/skills/text-formatter/templates/bbcode-template.md +350 -0
- package/ccw/dist/core/routes/help-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/help-routes.js +43 -7
- package/ccw/dist/core/routes/help-routes.js.map +1 -1
- package/ccw/dist/core/routes/litellm-api-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/litellm-api-routes.js +31 -5
- package/ccw/dist/core/routes/litellm-api-routes.js.map +1 -1
- package/ccw/dist/core/routes/memory-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/memory-routes.js +73 -0
- package/ccw/dist/core/routes/memory-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 +36 -4
- package/ccw/dist/core/routes/status-routes.js.map +1 -1
- package/ccw/dist/core/server.d.ts.map +1 -1
- package/ccw/dist/core/server.js +58 -0
- package/ccw/dist/core/server.js.map +1 -1
- package/ccw/dist/core/services/api-key-tester.d.ts.map +1 -1
- package/ccw/dist/core/services/api-key-tester.js +8 -3
- package/ccw/dist/core/services/api-key-tester.js.map +1 -1
- package/ccw/dist/tools/claude-cli-tools.d.ts +7 -0
- package/ccw/dist/tools/claude-cli-tools.d.ts.map +1 -1
- package/ccw/dist/tools/claude-cli-tools.js +11 -1
- package/ccw/dist/tools/claude-cli-tools.js.map +1 -1
- package/ccw/dist/tools/cli-executor-core.d.ts +11 -0
- package/ccw/dist/tools/cli-executor-core.d.ts.map +1 -1
- package/ccw/dist/tools/cli-executor-core.js +89 -2
- package/ccw/dist/tools/cli-executor-core.js.map +1 -1
- package/ccw/dist/tools/codex-lens.d.ts +2 -1
- package/ccw/dist/tools/codex-lens.d.ts.map +1 -1
- package/ccw/dist/tools/codex-lens.js +51 -8
- package/ccw/dist/tools/codex-lens.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 +6 -0
- package/ccw/dist/tools/litellm-client.d.ts.map +1 -1
- package/ccw/dist/tools/litellm-client.js +22 -1
- package/ccw/dist/tools/litellm-client.js.map +1 -1
- package/ccw/dist/tools/litellm-executor.js +2 -2
- package/ccw/dist/tools/litellm-executor.js.map +1 -1
- package/ccw/dist/tools/memory-update-queue.d.ts +172 -0
- package/ccw/dist/tools/memory-update-queue.d.ts.map +1 -0
- package/ccw/dist/tools/memory-update-queue.js +431 -0
- package/ccw/dist/tools/memory-update-queue.js.map +1 -0
- package/ccw/src/core/routes/help-routes.ts +46 -7
- package/ccw/src/core/routes/litellm-api-routes.ts +35 -4
- package/ccw/src/core/routes/memory-routes.ts +84 -0
- package/ccw/src/core/routes/status-routes.ts +39 -4
- package/ccw/src/core/server.ts +62 -0
- package/ccw/src/core/services/api-key-tester.ts +9 -3
- package/ccw/src/templates/dashboard-css/21-cli-toolmgmt.css +45 -0
- package/ccw/src/templates/dashboard-js/components/cli-status.js +36 -5
- package/ccw/src/templates/dashboard-js/components/hook-manager.js +42 -81
- package/ccw/src/templates/dashboard-js/components/mcp-manager.js +170 -28
- package/ccw/src/templates/dashboard-js/components/notifications.js +14 -4
- package/ccw/src/templates/dashboard-js/i18n.js +26 -0
- package/ccw/src/templates/dashboard-js/views/cli-manager.js +72 -2
- package/ccw/src/templates/dashboard-js/views/codexlens-manager.js +11 -1
- package/ccw/src/tools/claude-cli-tools.ts +17 -1
- package/ccw/src/tools/cli-executor-core.ts +103 -2
- package/ccw/src/tools/codex-lens.ts +63 -8
- package/ccw/src/tools/index.ts +2 -0
- package/ccw/src/tools/litellm-client.ts +25 -3
- package/ccw/src/tools/litellm-executor.ts +2 -2
- package/ccw/src/tools/memory-update-queue.js +499 -0
- package/package.json +91 -91
|
@@ -9,6 +9,15 @@ import { getCliToolsStatus } from '../../tools/cli-executor.js';
|
|
|
9
9
|
import { checkVenvStatus, checkSemanticStatus } from '../../tools/codex-lens.js';
|
|
10
10
|
import type { RouteContext } from './types.js';
|
|
11
11
|
|
|
12
|
+
// Performance logging helper
|
|
13
|
+
const PERF_LOG_ENABLED = process.env.CCW_PERF_LOG === '1' || true; // Enable by default for debugging
|
|
14
|
+
function perfLog(label: string, startTime: number, extra?: Record<string, unknown>): void {
|
|
15
|
+
if (!PERF_LOG_ENABLED) return;
|
|
16
|
+
const duration = Date.now() - startTime;
|
|
17
|
+
const extraStr = extra ? ` | ${JSON.stringify(extra)}` : '';
|
|
18
|
+
console.log(`[PERF][Status] ${label}: ${duration}ms${extraStr}`);
|
|
19
|
+
}
|
|
20
|
+
|
|
12
21
|
/**
|
|
13
22
|
* Check CCW installation status
|
|
14
23
|
* Verifies that required workflow files are installed in user's home directory
|
|
@@ -62,16 +71,39 @@ export async function handleStatusRoutes(ctx: RouteContext): Promise<boolean> {
|
|
|
62
71
|
|
|
63
72
|
// API: Aggregated Status (all statuses in one call)
|
|
64
73
|
if (pathname === '/api/status/all') {
|
|
74
|
+
const totalStart = Date.now();
|
|
75
|
+
console.log('[PERF][Status] === /api/status/all START ===');
|
|
76
|
+
|
|
65
77
|
try {
|
|
66
78
|
// Check CCW installation status (sync, fast)
|
|
79
|
+
const ccwStart = Date.now();
|
|
67
80
|
const ccwInstallStatus = checkCcwInstallStatus();
|
|
81
|
+
perfLog('checkCcwInstallStatus', ccwStart);
|
|
82
|
+
|
|
83
|
+
// Execute all status checks in parallel with individual timing
|
|
84
|
+
const cliStart = Date.now();
|
|
85
|
+
const codexStart = Date.now();
|
|
86
|
+
const semanticStart = Date.now();
|
|
68
87
|
|
|
69
|
-
// Execute all status checks in parallel
|
|
70
88
|
const [cliStatus, codexLensStatus, semanticStatus] = await Promise.all([
|
|
71
|
-
getCliToolsStatus()
|
|
72
|
-
|
|
89
|
+
getCliToolsStatus().then(result => {
|
|
90
|
+
perfLog('getCliToolsStatus', cliStart, { toolCount: Object.keys(result).length });
|
|
91
|
+
return result;
|
|
92
|
+
}),
|
|
93
|
+
checkVenvStatus().then(result => {
|
|
94
|
+
perfLog('checkVenvStatus', codexStart, { ready: result.ready });
|
|
95
|
+
return result;
|
|
96
|
+
}),
|
|
73
97
|
// Always check semantic status (will return available: false if CodexLens not ready)
|
|
74
|
-
checkSemanticStatus()
|
|
98
|
+
checkSemanticStatus()
|
|
99
|
+
.then(result => {
|
|
100
|
+
perfLog('checkSemanticStatus', semanticStart, { available: result.available });
|
|
101
|
+
return result;
|
|
102
|
+
})
|
|
103
|
+
.catch(() => {
|
|
104
|
+
perfLog('checkSemanticStatus (error)', semanticStart);
|
|
105
|
+
return { available: false, backend: null };
|
|
106
|
+
})
|
|
75
107
|
]);
|
|
76
108
|
|
|
77
109
|
const response = {
|
|
@@ -82,10 +114,13 @@ export async function handleStatusRoutes(ctx: RouteContext): Promise<boolean> {
|
|
|
82
114
|
timestamp: new Date().toISOString()
|
|
83
115
|
};
|
|
84
116
|
|
|
117
|
+
perfLog('=== /api/status/all TOTAL ===', totalStart);
|
|
118
|
+
|
|
85
119
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
86
120
|
res.end(JSON.stringify(response));
|
|
87
121
|
return true;
|
|
88
122
|
} catch (error) {
|
|
123
|
+
perfLog('=== /api/status/all ERROR ===', totalStart);
|
|
89
124
|
console.error('[Status Routes] Error fetching aggregated status:', error);
|
|
90
125
|
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
91
126
|
res.end(JSON.stringify({ error: (error as Error).message }));
|
package/ccw/src/core/server.ts
CHANGED
|
@@ -42,6 +42,10 @@ import { randomBytes } from 'crypto';
|
|
|
42
42
|
// Import health check service
|
|
43
43
|
import { getHealthCheckService } from './services/health-check-service.js';
|
|
44
44
|
|
|
45
|
+
// Import status check functions for warmup
|
|
46
|
+
import { checkSemanticStatus, checkVenvStatus } from '../tools/codex-lens.js';
|
|
47
|
+
import { getCliToolsStatus } from '../tools/cli-executor.js';
|
|
48
|
+
|
|
45
49
|
import type { ServerConfig } from '../types/config.js';
|
|
46
50
|
import type { PostRequestHandler } from './routes/types.js';
|
|
47
51
|
|
|
@@ -290,6 +294,56 @@ function setCsrfCookie(res: http.ServerResponse, token: string, maxAgeSeconds: n
|
|
|
290
294
|
appendSetCookie(res, attributes.join('; '));
|
|
291
295
|
}
|
|
292
296
|
|
|
297
|
+
/**
|
|
298
|
+
* Warmup function to pre-populate caches on server startup
|
|
299
|
+
* This runs asynchronously and non-blocking after the server starts
|
|
300
|
+
*/
|
|
301
|
+
async function warmupCaches(initialPath: string): Promise<void> {
|
|
302
|
+
console.log('[WARMUP] Starting cache warmup...');
|
|
303
|
+
const startTime = Date.now();
|
|
304
|
+
|
|
305
|
+
// Run all warmup tasks in parallel for faster startup
|
|
306
|
+
const warmupTasks = [
|
|
307
|
+
// Warmup semantic status cache (Python process startup - can be slow first time)
|
|
308
|
+
(async () => {
|
|
309
|
+
const taskStart = Date.now();
|
|
310
|
+
try {
|
|
311
|
+
const semanticStatus = await checkSemanticStatus();
|
|
312
|
+
console.log(`[WARMUP] Semantic status: ${semanticStatus.available ? 'available' : 'not available'} (${Date.now() - taskStart}ms)`);
|
|
313
|
+
} catch (err) {
|
|
314
|
+
console.warn(`[WARMUP] Semantic status check failed: ${(err as Error).message}`);
|
|
315
|
+
}
|
|
316
|
+
})(),
|
|
317
|
+
|
|
318
|
+
// Warmup venv status cache
|
|
319
|
+
(async () => {
|
|
320
|
+
const taskStart = Date.now();
|
|
321
|
+
try {
|
|
322
|
+
const venvStatus = await checkVenvStatus();
|
|
323
|
+
console.log(`[WARMUP] Venv status: ${venvStatus.ready ? 'ready' : 'not ready'} (${Date.now() - taskStart}ms)`);
|
|
324
|
+
} catch (err) {
|
|
325
|
+
console.warn(`[WARMUP] Venv status check failed: ${(err as Error).message}`);
|
|
326
|
+
}
|
|
327
|
+
})(),
|
|
328
|
+
|
|
329
|
+
// Warmup CLI tools status cache
|
|
330
|
+
(async () => {
|
|
331
|
+
const taskStart = Date.now();
|
|
332
|
+
try {
|
|
333
|
+
const cliStatus = await getCliToolsStatus();
|
|
334
|
+
const availableCount = Object.values(cliStatus).filter(s => s.available).length;
|
|
335
|
+
const totalCount = Object.keys(cliStatus).length;
|
|
336
|
+
console.log(`[WARMUP] CLI tools status: ${availableCount}/${totalCount} available (${Date.now() - taskStart}ms)`);
|
|
337
|
+
} catch (err) {
|
|
338
|
+
console.warn(`[WARMUP] CLI tools status check failed: ${(err as Error).message}`);
|
|
339
|
+
}
|
|
340
|
+
})()
|
|
341
|
+
];
|
|
342
|
+
|
|
343
|
+
await Promise.allSettled(warmupTasks);
|
|
344
|
+
console.log(`[WARMUP] Cache warmup complete (${Date.now() - startTime}ms total)`);
|
|
345
|
+
}
|
|
346
|
+
|
|
293
347
|
/**
|
|
294
348
|
* Generate dashboard HTML with embedded CSS and JS
|
|
295
349
|
*/
|
|
@@ -650,6 +704,14 @@ export async function startServer(options: ServerOptions = {}): Promise<http.Ser
|
|
|
650
704
|
console.warn('[Server] Failed to start health check service:', err);
|
|
651
705
|
}
|
|
652
706
|
|
|
707
|
+
// Start cache warmup asynchronously (non-blocking)
|
|
708
|
+
// Uses setImmediate to not delay server startup response
|
|
709
|
+
setImmediate(() => {
|
|
710
|
+
warmupCaches(initialPath).catch((err) => {
|
|
711
|
+
console.warn('[WARMUP] Cache warmup failed:', err);
|
|
712
|
+
});
|
|
713
|
+
});
|
|
714
|
+
|
|
653
715
|
resolve(server);
|
|
654
716
|
});
|
|
655
717
|
server.on('error', reject);
|
|
@@ -72,6 +72,10 @@ export async function testApiKeyConnection(
|
|
|
72
72
|
return { valid: false, error: urlValidation.error };
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
// Normalize apiBase: remove trailing slashes to prevent URL construction issues
|
|
76
|
+
// e.g., "https://api.openai.com/v1/" -> "https://api.openai.com/v1"
|
|
77
|
+
const normalizedApiBase = apiBase.replace(/\/+$/, '');
|
|
78
|
+
|
|
75
79
|
const controller = new AbortController();
|
|
76
80
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
77
81
|
const startTime = Date.now();
|
|
@@ -80,7 +84,7 @@ export async function testApiKeyConnection(
|
|
|
80
84
|
if (providerType === 'anthropic') {
|
|
81
85
|
// Anthropic format: Use /v1/models endpoint (no cost, no model dependency)
|
|
82
86
|
// This validates the API key without making a billable request
|
|
83
|
-
const response = await fetch(`${
|
|
87
|
+
const response = await fetch(`${normalizedApiBase}/models`, {
|
|
84
88
|
method: 'GET',
|
|
85
89
|
headers: {
|
|
86
90
|
'x-api-key': apiKey,
|
|
@@ -114,8 +118,10 @@ export async function testApiKeyConnection(
|
|
|
114
118
|
|
|
115
119
|
return { valid: false, error: errorMessage };
|
|
116
120
|
} else {
|
|
117
|
-
// OpenAI-compatible format: GET /
|
|
118
|
-
|
|
121
|
+
// OpenAI-compatible format: GET /v{N}/models
|
|
122
|
+
// Detect if URL already ends with a version pattern like /v1, /v2, /v4, etc.
|
|
123
|
+
const hasVersionSuffix = /\/v\d+$/.test(normalizedApiBase);
|
|
124
|
+
const modelsUrl = hasVersionSuffix ? `${normalizedApiBase}/models` : `${normalizedApiBase}/v1/models`;
|
|
119
125
|
const response = await fetch(modelsUrl, {
|
|
120
126
|
method: 'GET',
|
|
121
127
|
headers: {
|
|
@@ -304,6 +304,51 @@
|
|
|
304
304
|
margin-top: 0;
|
|
305
305
|
}
|
|
306
306
|
|
|
307
|
+
/* Environment File Input Group */
|
|
308
|
+
.env-file-input-group {
|
|
309
|
+
display: flex;
|
|
310
|
+
flex-direction: column;
|
|
311
|
+
gap: 0.5rem;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.env-file-input-row {
|
|
315
|
+
display: flex;
|
|
316
|
+
gap: 0.5rem;
|
|
317
|
+
align-items: center;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.env-file-input-row .tool-config-input {
|
|
321
|
+
flex: 1;
|
|
322
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
323
|
+
font-size: 0.8125rem;
|
|
324
|
+
margin-top: 0;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
.env-file-input-row .btn-sm {
|
|
328
|
+
flex-shrink: 0;
|
|
329
|
+
display: inline-flex;
|
|
330
|
+
align-items: center;
|
|
331
|
+
gap: 0.25rem;
|
|
332
|
+
padding: 0.5rem 0.75rem;
|
|
333
|
+
font-size: 0.8125rem;
|
|
334
|
+
white-space: nowrap;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
.env-file-hint {
|
|
338
|
+
display: flex;
|
|
339
|
+
align-items: center;
|
|
340
|
+
gap: 0.375rem;
|
|
341
|
+
font-size: 0.75rem;
|
|
342
|
+
color: hsl(var(--muted-foreground));
|
|
343
|
+
margin: 0;
|
|
344
|
+
padding: 0;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
.env-file-hint i {
|
|
348
|
+
flex-shrink: 0;
|
|
349
|
+
opacity: 0.7;
|
|
350
|
+
}
|
|
351
|
+
|
|
307
352
|
.btn-ghost.text-destructive:hover {
|
|
308
353
|
background: hsl(var(--destructive) / 0.1);
|
|
309
354
|
}
|
|
@@ -33,11 +33,14 @@ function initCliStatus() {
|
|
|
33
33
|
* Load all statuses using aggregated endpoint (single API call)
|
|
34
34
|
*/
|
|
35
35
|
async function loadAllStatuses() {
|
|
36
|
+
const totalStart = performance.now();
|
|
37
|
+
console.log('[PERF][Frontend] loadAllStatuses START');
|
|
38
|
+
|
|
36
39
|
// 1. 尝试从缓存获取(预加载的数据)
|
|
37
40
|
if (window.cacheManager) {
|
|
38
41
|
const cached = window.cacheManager.get('all-status');
|
|
39
42
|
if (cached) {
|
|
40
|
-
console.log(
|
|
43
|
+
console.log(`[PERF][Frontend] Cache hit: ${(performance.now() - totalStart).toFixed(1)}ms`);
|
|
41
44
|
// 应用缓存数据
|
|
42
45
|
cliToolStatus = cached.cli || {};
|
|
43
46
|
codexLensStatus = cached.codexLens || { ready: false };
|
|
@@ -45,25 +48,32 @@ async function loadAllStatuses() {
|
|
|
45
48
|
ccwInstallStatus = cached.ccwInstall || { installed: true, workflowsInstalled: true, missingFiles: [], installPath: '' };
|
|
46
49
|
|
|
47
50
|
// Load CLI tools config, API endpoints, and CLI Settings(这些有自己的缓存)
|
|
51
|
+
const configStart = performance.now();
|
|
48
52
|
await Promise.all([
|
|
49
53
|
loadCliToolsConfig(),
|
|
50
54
|
loadApiEndpoints(),
|
|
51
55
|
loadCliSettingsEndpoints()
|
|
52
56
|
]);
|
|
57
|
+
console.log(`[PERF][Frontend] Config/Endpoints load: ${(performance.now() - configStart).toFixed(1)}ms`);
|
|
53
58
|
|
|
54
59
|
// Update badges
|
|
55
60
|
updateCliBadge();
|
|
56
61
|
updateCodexLensBadge();
|
|
57
62
|
updateCcwInstallBadge();
|
|
63
|
+
|
|
64
|
+
console.log(`[PERF][Frontend] loadAllStatuses TOTAL (cached): ${(performance.now() - totalStart).toFixed(1)}ms`);
|
|
58
65
|
return cached;
|
|
59
66
|
}
|
|
60
67
|
}
|
|
61
68
|
|
|
62
69
|
// 2. 缓存未命中,从服务器获取
|
|
63
70
|
try {
|
|
71
|
+
const fetchStart = performance.now();
|
|
72
|
+
console.log('[PERF][Frontend] Fetching /api/status/all...');
|
|
64
73
|
const response = await fetch('/api/status/all');
|
|
65
74
|
if (!response.ok) throw new Error('Failed to load status');
|
|
66
75
|
const data = await response.json();
|
|
76
|
+
console.log(`[PERF][Frontend] /api/status/all fetch: ${(performance.now() - fetchStart).toFixed(1)}ms`);
|
|
67
77
|
|
|
68
78
|
// 存入缓存
|
|
69
79
|
if (window.cacheManager) {
|
|
@@ -77,10 +87,11 @@ async function loadAllStatuses() {
|
|
|
77
87
|
ccwInstallStatus = data.ccwInstall || { installed: true, workflowsInstalled: true, missingFiles: [], installPath: '' };
|
|
78
88
|
|
|
79
89
|
// Load CLI tools config, API endpoints, and CLI Settings
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
90
|
+
const configStart = performance.now();
|
|
91
|
+
const [configResult, endpointsResult, settingsResult] = await Promise.all([
|
|
92
|
+
loadCliToolsConfig().then(r => { console.log(`[PERF][Frontend] loadCliToolsConfig: ${(performance.now() - configStart).toFixed(1)}ms`); return r; }),
|
|
93
|
+
loadApiEndpoints().then(r => { console.log(`[PERF][Frontend] loadApiEndpoints: ${(performance.now() - configStart).toFixed(1)}ms`); return r; }),
|
|
94
|
+
loadCliSettingsEndpoints().then(r => { console.log(`[PERF][Frontend] loadCliSettingsEndpoints: ${(performance.now() - configStart).toFixed(1)}ms`); return r; })
|
|
84
95
|
]);
|
|
85
96
|
|
|
86
97
|
// Update badges
|
|
@@ -88,9 +99,11 @@ async function loadAllStatuses() {
|
|
|
88
99
|
updateCodexLensBadge();
|
|
89
100
|
updateCcwInstallBadge();
|
|
90
101
|
|
|
102
|
+
console.log(`[PERF][Frontend] loadAllStatuses TOTAL: ${(performance.now() - totalStart).toFixed(1)}ms`);
|
|
91
103
|
return data;
|
|
92
104
|
} catch (err) {
|
|
93
105
|
console.error('Failed to load aggregated status:', err);
|
|
106
|
+
console.log(`[PERF][Frontend] loadAllStatuses ERROR after: ${(performance.now() - totalStart).toFixed(1)}ms`);
|
|
94
107
|
// Fallback to individual calls if aggregated endpoint fails
|
|
95
108
|
return await loadAllStatusesFallback();
|
|
96
109
|
}
|
|
@@ -1034,6 +1047,15 @@ async function startCodexLensInstall() {
|
|
|
1034
1047
|
progressBar.style.width = '100%';
|
|
1035
1048
|
statusText.textContent = 'Installation complete!';
|
|
1036
1049
|
|
|
1050
|
+
// 清理缓存以确保刷新后获取最新状态
|
|
1051
|
+
if (window.cacheManager) {
|
|
1052
|
+
window.cacheManager.invalidate('all-status');
|
|
1053
|
+
window.cacheManager.invalidate('dashboard-init');
|
|
1054
|
+
}
|
|
1055
|
+
if (typeof window.invalidateCodexLensCache === 'function') {
|
|
1056
|
+
window.invalidateCodexLensCache();
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1037
1059
|
setTimeout(() => {
|
|
1038
1060
|
closeCodexLensInstallWizard();
|
|
1039
1061
|
showRefreshToast('CodexLens installed successfully!', 'success');
|
|
@@ -1184,6 +1206,15 @@ async function startCodexLensUninstall() {
|
|
|
1184
1206
|
progressBar.style.width = '100%';
|
|
1185
1207
|
statusText.textContent = 'Uninstallation complete!';
|
|
1186
1208
|
|
|
1209
|
+
// 清理缓存以确保刷新后获取最新状态
|
|
1210
|
+
if (window.cacheManager) {
|
|
1211
|
+
window.cacheManager.invalidate('all-status');
|
|
1212
|
+
window.cacheManager.invalidate('dashboard-init');
|
|
1213
|
+
}
|
|
1214
|
+
if (typeof window.invalidateCodexLensCache === 'function') {
|
|
1215
|
+
window.invalidateCodexLensCache();
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1187
1218
|
setTimeout(() => {
|
|
1188
1219
|
closeCodexLensUninstallWizard();
|
|
1189
1220
|
showRefreshToast('CodexLens uninstalled successfully!', 'success');
|
|
@@ -49,43 +49,17 @@ const HOOK_TEMPLATES = {
|
|
|
49
49
|
description: 'Auto-update code index when files are written or edited',
|
|
50
50
|
category: 'indexing'
|
|
51
51
|
},
|
|
52
|
-
'memory-update-
|
|
52
|
+
'memory-update-queue': {
|
|
53
53
|
event: 'Stop',
|
|
54
54
|
matcher: '',
|
|
55
55
|
command: 'bash',
|
|
56
|
-
args: ['-c', 'ccw tool exec
|
|
57
|
-
description: '
|
|
56
|
+
args: ['-c', 'ccw tool exec memory_queue "{\\"action\\":\\"add\\",\\"path\\":\\"$CLAUDE_PROJECT_DIR\\"}"'],
|
|
57
|
+
description: 'Queue CLAUDE.md update when session ends (batched by threshold/timeout)',
|
|
58
58
|
category: 'memory',
|
|
59
59
|
configurable: true,
|
|
60
60
|
config: {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
},
|
|
65
|
-
'memory-update-periodic': {
|
|
66
|
-
event: 'PostToolUse',
|
|
67
|
-
matcher: 'Write|Edit',
|
|
68
|
-
command: 'bash',
|
|
69
|
-
args: ['-c', 'INTERVAL=300; LAST_FILE="$HOME/.claude/.last_memory_update"; mkdir -p "$HOME/.claude"; NOW=$(date +%s); LAST=0; [ -f "$LAST_FILE" ] && LAST=$(cat "$LAST_FILE" 2>/dev/null || echo 0); if [ $((NOW - LAST)) -ge $INTERVAL ]; then echo $NOW > "$LAST_FILE"; ccw tool exec update_module_claude \'{"strategy":"related","tool":"gemini"}\' & fi'],
|
|
70
|
-
description: 'Periodically update CLAUDE.md (default: 5 min interval)',
|
|
71
|
-
category: 'memory',
|
|
72
|
-
configurable: true,
|
|
73
|
-
config: {
|
|
74
|
-
tool: { type: 'select', options: ['gemini', 'qwen', 'codex'], default: 'gemini', label: 'CLI Tool' },
|
|
75
|
-
interval: { type: 'number', default: 300, min: 60, max: 3600, label: 'Interval (seconds)', step: 60 }
|
|
76
|
-
}
|
|
77
|
-
},
|
|
78
|
-
'memory-update-count-based': {
|
|
79
|
-
event: 'PostToolUse',
|
|
80
|
-
matcher: 'Write|Edit',
|
|
81
|
-
command: 'bash',
|
|
82
|
-
args: ['-c', 'THRESHOLD=10; COUNT_FILE="$HOME/.claude/.memory_update_count"; mkdir -p "$HOME/.claude"; INPUT=$(cat); FILE_PATH=$(echo "$INPUT" | jq -r ".tool_input.file_path // .tool_input.path // empty" 2>/dev/null); [ -z "$FILE_PATH" ] && exit 0; COUNT=0; [ -f "$COUNT_FILE" ] && COUNT=$(cat "$COUNT_FILE" 2>/dev/null || echo 0); COUNT=$((COUNT + 1)); echo $COUNT > "$COUNT_FILE"; if [ $COUNT -ge $THRESHOLD ]; then echo 0 > "$COUNT_FILE"; ccw tool exec update_module_claude \'{"strategy":"related","tool":"gemini"}\' & fi'],
|
|
83
|
-
description: 'Update CLAUDE.md when file changes reach threshold (default: 10 files)',
|
|
84
|
-
category: 'memory',
|
|
85
|
-
configurable: true,
|
|
86
|
-
config: {
|
|
87
|
-
tool: { type: 'select', options: ['gemini', 'qwen', 'codex'], default: 'gemini', label: 'CLI Tool' },
|
|
88
|
-
threshold: { type: 'number', default: 10, min: 3, max: 50, label: 'File count threshold', step: 1 }
|
|
61
|
+
threshold: { type: 'number', default: 5, min: 1, max: 20, label: 'Threshold (paths)', step: 1 },
|
|
62
|
+
timeout: { type: 'number', default: 300, min: 60, max: 1800, label: 'Timeout (seconds)', step: 60 }
|
|
89
63
|
}
|
|
90
64
|
},
|
|
91
65
|
// SKILL Context Loader templates
|
|
@@ -210,33 +184,19 @@ const HOOK_TEMPLATES = {
|
|
|
210
184
|
const WIZARD_TEMPLATES = {
|
|
211
185
|
'memory-update': {
|
|
212
186
|
name: 'Memory Update Hook',
|
|
213
|
-
description: '
|
|
187
|
+
description: 'Queue-based CLAUDE.md updates with configurable threshold and timeout',
|
|
214
188
|
icon: 'brain',
|
|
215
189
|
options: [
|
|
216
190
|
{
|
|
217
|
-
id: '
|
|
218
|
-
name: '
|
|
219
|
-
description: '
|
|
220
|
-
templateId: 'memory-update-
|
|
221
|
-
},
|
|
222
|
-
{
|
|
223
|
-
id: 'periodic',
|
|
224
|
-
name: 'Periodic Update',
|
|
225
|
-
description: 'Update documentation at regular intervals during session',
|
|
226
|
-
templateId: 'memory-update-periodic'
|
|
227
|
-
},
|
|
228
|
-
{
|
|
229
|
-
id: 'count-based',
|
|
230
|
-
name: 'Count-Based Update',
|
|
231
|
-
description: 'Update documentation when file changes reach threshold',
|
|
232
|
-
templateId: 'memory-update-count-based'
|
|
191
|
+
id: 'queue',
|
|
192
|
+
name: 'Queue-Based Update',
|
|
193
|
+
description: 'Batch updates when threshold reached or timeout expires',
|
|
194
|
+
templateId: 'memory-update-queue'
|
|
233
195
|
}
|
|
234
196
|
],
|
|
235
197
|
configFields: [
|
|
236
|
-
{ key: '
|
|
237
|
-
{ key: '
|
|
238
|
-
{ key: 'threshold', type: 'number', label: 'File Count Threshold', default: 10, min: 3, max: 50, step: 1, showFor: ['count-based'], description: 'Number of file changes to trigger update' },
|
|
239
|
-
{ key: 'strategy', type: 'select', label: 'Update Strategy', options: ['related', 'single-layer'], default: 'related', description: 'Related: changed modules, Single-layer: current directory' }
|
|
198
|
+
{ key: 'threshold', type: 'number', label: 'Threshold (paths)', default: 5, min: 1, max: 20, step: 1, description: 'Number of paths to trigger batch update' },
|
|
199
|
+
{ key: 'timeout', type: 'number', label: 'Timeout (seconds)', default: 300, min: 60, max: 1800, step: 60, description: 'Auto-flush queue after this time' }
|
|
240
200
|
]
|
|
241
201
|
},
|
|
242
202
|
'skill-context': {
|
|
@@ -730,9 +690,7 @@ function renderWizardModalContent() {
|
|
|
730
690
|
// Helper to get translated option names
|
|
731
691
|
const getOptionName = (optId) => {
|
|
732
692
|
if (wizardId === 'memory-update') {
|
|
733
|
-
if (optId === '
|
|
734
|
-
if (optId === 'periodic') return t('hook.wizard.periodicUpdate');
|
|
735
|
-
if (optId === 'count-based') return t('hook.wizard.countBasedUpdate');
|
|
693
|
+
if (optId === 'queue') return t('hook.wizard.queueBasedUpdate') || 'Queue-Based Update';
|
|
736
694
|
}
|
|
737
695
|
if (wizardId === 'memory-setup') {
|
|
738
696
|
if (optId === 'file-read') return t('hook.wizard.fileReadTracker');
|
|
@@ -748,9 +706,7 @@ function renderWizardModalContent() {
|
|
|
748
706
|
|
|
749
707
|
const getOptionDesc = (optId) => {
|
|
750
708
|
if (wizardId === 'memory-update') {
|
|
751
|
-
if (optId === '
|
|
752
|
-
if (optId === 'periodic') return t('hook.wizard.periodicUpdateDesc');
|
|
753
|
-
if (optId === 'count-based') return t('hook.wizard.countBasedUpdateDesc');
|
|
709
|
+
if (optId === 'queue') return t('hook.wizard.queueBasedUpdateDesc') || 'Batch updates when threshold reached or timeout expires';
|
|
754
710
|
}
|
|
755
711
|
if (wizardId === 'memory-setup') {
|
|
756
712
|
if (optId === 'file-read') return t('hook.wizard.fileReadTrackerDesc');
|
|
@@ -767,20 +723,16 @@ function renderWizardModalContent() {
|
|
|
767
723
|
// Helper to get translated field labels
|
|
768
724
|
const getFieldLabel = (fieldKey) => {
|
|
769
725
|
const labels = {
|
|
770
|
-
'
|
|
771
|
-
'
|
|
772
|
-
'threshold': t('hook.wizard.fileCountThreshold'),
|
|
773
|
-
'strategy': t('hook.wizard.updateStrategy')
|
|
726
|
+
'threshold': t('hook.wizard.thresholdPaths') || 'Threshold (paths)',
|
|
727
|
+
'timeout': t('hook.wizard.timeoutSeconds') || 'Timeout (seconds)'
|
|
774
728
|
};
|
|
775
729
|
return labels[fieldKey] || wizard.configFields.find(f => f.key === fieldKey)?.label || fieldKey;
|
|
776
730
|
};
|
|
777
731
|
|
|
778
732
|
const getFieldDesc = (fieldKey) => {
|
|
779
733
|
const descs = {
|
|
780
|
-
'
|
|
781
|
-
'
|
|
782
|
-
'threshold': t('hook.wizard.fileCountThresholdDesc'),
|
|
783
|
-
'strategy': t('hook.wizard.relatedStrategy')
|
|
734
|
+
'threshold': t('hook.wizard.thresholdPathsDesc') || 'Number of paths to trigger batch update',
|
|
735
|
+
'timeout': t('hook.wizard.timeoutSecondsDesc') || 'Auto-flush queue after this time'
|
|
784
736
|
};
|
|
785
737
|
return descs[fieldKey] || wizard.configFields.find(f => f.key === fieldKey)?.description || '';
|
|
786
738
|
};
|
|
@@ -1154,21 +1106,10 @@ function generateWizardCommand() {
|
|
|
1154
1106
|
}
|
|
1155
1107
|
|
|
1156
1108
|
// Handle memory-update wizard (default)
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
const
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
// Build the ccw tool command based on configuration
|
|
1163
|
-
const params = JSON.stringify({ strategy, tool });
|
|
1164
|
-
|
|
1165
|
-
if (triggerType === 'periodic') {
|
|
1166
|
-
return `INTERVAL=${interval}; LAST_FILE="$HOME/.claude/.last_memory_update"; mkdir -p "$HOME/.claude"; NOW=$(date +%s); LAST=0; [ -f "$LAST_FILE" ] && LAST=$(cat "$LAST_FILE" 2>/dev/null || echo 0); if [ $((NOW - LAST)) -ge $INTERVAL ]; then echo $NOW > "$LAST_FILE"; ccw tool exec update_module_claude '${params}' & fi`;
|
|
1167
|
-
} else if (triggerType === 'count-based') {
|
|
1168
|
-
return `THRESHOLD=${threshold}; COUNT_FILE="$HOME/.claude/.memory_update_count"; mkdir -p "$HOME/.claude"; INPUT=$(cat); FILE_PATH=$(echo "$INPUT" | jq -r ".tool_input.file_path // .tool_input.path // empty" 2>/dev/null); [ -z "$FILE_PATH" ] && exit 0; COUNT=0; [ -f "$COUNT_FILE" ] && COUNT=$(cat "$COUNT_FILE" 2>/dev/null || echo 0); COUNT=$((COUNT + 1)); echo $COUNT > "$COUNT_FILE"; if [ $COUNT -ge $THRESHOLD ]; then echo 0 > "$COUNT_FILE"; ccw tool exec update_module_claude '${params}' & fi`;
|
|
1169
|
-
} else {
|
|
1170
|
-
return `ccw tool exec update_module_claude '${params}'`;
|
|
1171
|
-
}
|
|
1109
|
+
// Now uses memory_queue for batched updates with configurable threshold/timeout
|
|
1110
|
+
// The command adds to queue, configuration is applied separately via submitHookWizard
|
|
1111
|
+
const params = `"{\\"action\\":\\"add\\",\\"path\\":\\"$CLAUDE_PROJECT_DIR\\"}"`;
|
|
1112
|
+
return `ccw tool exec memory_queue ${params}`;
|
|
1172
1113
|
}
|
|
1173
1114
|
|
|
1174
1115
|
async function submitHookWizard() {
|
|
@@ -1263,6 +1204,26 @@ async function submitHookWizard() {
|
|
|
1263
1204
|
}
|
|
1264
1205
|
|
|
1265
1206
|
await saveHook(scope, baseTemplate.event, hookData);
|
|
1207
|
+
|
|
1208
|
+
// For memory-update wizard, also configure queue settings
|
|
1209
|
+
if (wizard.id === 'memory-update') {
|
|
1210
|
+
const threshold = wizardConfig.threshold || 5;
|
|
1211
|
+
const timeout = wizardConfig.timeout || 300;
|
|
1212
|
+
try {
|
|
1213
|
+
const configParams = JSON.stringify({ action: 'configure', threshold, timeout });
|
|
1214
|
+
const response = await fetch('/api/tools/execute', {
|
|
1215
|
+
method: 'POST',
|
|
1216
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1217
|
+
body: JSON.stringify({ tool: 'memory_queue', params: configParams })
|
|
1218
|
+
});
|
|
1219
|
+
if (response.ok) {
|
|
1220
|
+
showRefreshToast(`Queue configured: threshold=${threshold}, timeout=${timeout}s`, 'success');
|
|
1221
|
+
}
|
|
1222
|
+
} catch (e) {
|
|
1223
|
+
console.warn('Failed to configure memory queue:', e);
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1266
1227
|
closeHookWizardModal();
|
|
1267
1228
|
}
|
|
1268
1229
|
|