mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-03-01 15:03:57 +08:00
feat: Add environment file support for CLI tools
- Introduced a new input group for environment file configuration in the dashboard CSS. - Updated hook manager to queue CLAUDE.md updates with configurable threshold and timeout. - Enhanced CLI manager view to include environment file input for built-in tools (gemini, qwen). - Implemented environment file loading mechanism in cli-executor-core, allowing custom environment variables. - Added unit tests for environment file parsing and loading functionalities. - Updated memory update queue to support dynamic configuration of threshold and timeout settings.
This commit is contained in:
@@ -13,11 +13,34 @@ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
||||
import { join, dirname, resolve } from 'path';
|
||||
import { homedir } from 'os';
|
||||
|
||||
// Configuration constants
|
||||
const QUEUE_THRESHOLD = 5;
|
||||
const QUEUE_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
|
||||
// Default configuration
|
||||
const DEFAULT_THRESHOLD = 5;
|
||||
const DEFAULT_TIMEOUT_SECONDS = 300; // 5 minutes
|
||||
const QUEUE_FILE_PATH = join(homedir(), '.claude', '.memory-queue.json');
|
||||
|
||||
/**
|
||||
* Get queue configuration (from file or defaults)
|
||||
* @returns {{ threshold: number, timeoutMs: number }}
|
||||
*/
|
||||
function getQueueConfig() {
|
||||
try {
|
||||
if (existsSync(QUEUE_FILE_PATH)) {
|
||||
const content = readFileSync(QUEUE_FILE_PATH, 'utf8');
|
||||
const data = JSON.parse(content);
|
||||
return {
|
||||
threshold: data.config?.threshold || DEFAULT_THRESHOLD,
|
||||
timeoutMs: (data.config?.timeout || DEFAULT_TIMEOUT_SECONDS) * 1000
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
// Use defaults
|
||||
}
|
||||
return {
|
||||
threshold: DEFAULT_THRESHOLD,
|
||||
timeoutMs: DEFAULT_TIMEOUT_SECONDS * 1000
|
||||
};
|
||||
}
|
||||
|
||||
// In-memory timeout reference (for cross-call persistence, we track via file timestamp)
|
||||
let scheduledTimeoutId = null;
|
||||
|
||||
@@ -33,7 +56,7 @@ function ensureDir(filePath) {
|
||||
|
||||
/**
|
||||
* Load queue from file
|
||||
* @returns {{ items: Array<{path: string, tool: string, strategy: string, addedAt: string}>, createdAt: string | null }}
|
||||
* @returns {{ items: Array<{path: string, tool: string, strategy: string, addedAt: string}>, createdAt: string | null, config?: { threshold: number, timeout: number } }}
|
||||
*/
|
||||
function loadQueue() {
|
||||
try {
|
||||
@@ -42,13 +65,14 @@ function loadQueue() {
|
||||
const data = JSON.parse(content);
|
||||
return {
|
||||
items: Array.isArray(data.items) ? data.items : [],
|
||||
createdAt: data.createdAt || null
|
||||
createdAt: data.createdAt || null,
|
||||
config: data.config || null
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[MemoryQueue] Failed to load queue:', e.message);
|
||||
}
|
||||
return { items: [], createdAt: null };
|
||||
return { items: [], createdAt: null, config: null };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -83,6 +107,7 @@ function normalizePath(p) {
|
||||
function addToQueue(path, options = {}) {
|
||||
const { tool = 'gemini', strategy = 'single-layer' } = options;
|
||||
const queue = loadQueue();
|
||||
const config = getQueueConfig();
|
||||
const normalizedPath = normalizePath(path);
|
||||
const now = new Date().toISOString();
|
||||
|
||||
@@ -101,7 +126,7 @@ function addToQueue(path, options = {}) {
|
||||
return {
|
||||
queued: false,
|
||||
queueSize: queue.items.length,
|
||||
willFlush: queue.items.length >= QUEUE_THRESHOLD,
|
||||
willFlush: queue.items.length >= config.threshold,
|
||||
message: `Path already in queue (updated): ${path}`
|
||||
};
|
||||
}
|
||||
@@ -121,7 +146,7 @@ function addToQueue(path, options = {}) {
|
||||
|
||||
saveQueue(queue);
|
||||
|
||||
const willFlush = queue.items.length >= QUEUE_THRESHOLD;
|
||||
const willFlush = queue.items.length >= config.threshold;
|
||||
|
||||
// Schedule timeout if not already scheduled
|
||||
scheduleTimeout();
|
||||
@@ -131,8 +156,8 @@ function addToQueue(path, options = {}) {
|
||||
queueSize: queue.items.length,
|
||||
willFlush,
|
||||
message: willFlush
|
||||
? `Queue threshold reached (${queue.items.length}/${QUEUE_THRESHOLD}), will flush`
|
||||
: `Added to queue (${queue.items.length}/${QUEUE_THRESHOLD})`
|
||||
? `Queue threshold reached (${queue.items.length}/${config.threshold}), will flush`
|
||||
: `Added to queue (${queue.items.length}/${config.threshold})`
|
||||
};
|
||||
}
|
||||
|
||||
@@ -142,24 +167,58 @@ function addToQueue(path, options = {}) {
|
||||
*/
|
||||
function getQueueStatus() {
|
||||
const queue = loadQueue();
|
||||
const config = getQueueConfig();
|
||||
let timeUntilTimeout = null;
|
||||
|
||||
if (queue.createdAt && queue.items.length > 0) {
|
||||
const createdTime = new Date(queue.createdAt).getTime();
|
||||
const elapsed = Date.now() - createdTime;
|
||||
timeUntilTimeout = Math.max(0, QUEUE_TIMEOUT_MS - elapsed);
|
||||
timeUntilTimeout = Math.max(0, config.timeoutMs - elapsed);
|
||||
}
|
||||
|
||||
return {
|
||||
queueSize: queue.items.length,
|
||||
threshold: QUEUE_THRESHOLD,
|
||||
threshold: config.threshold,
|
||||
items: queue.items,
|
||||
timeoutMs: QUEUE_TIMEOUT_MS,
|
||||
timeoutMs: config.timeoutMs,
|
||||
timeoutSeconds: config.timeoutMs / 1000,
|
||||
timeUntilTimeout,
|
||||
createdAt: queue.createdAt
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure queue settings
|
||||
* @param {{ threshold?: number, timeout?: number }} settings
|
||||
* @returns {{ success: boolean, config: { threshold: number, timeout: number } }}
|
||||
*/
|
||||
function configureQueue(settings) {
|
||||
const queue = loadQueue();
|
||||
const currentConfig = getQueueConfig();
|
||||
|
||||
const newConfig = {
|
||||
threshold: settings.threshold || currentConfig.threshold,
|
||||
timeout: settings.timeout || (currentConfig.timeoutMs / 1000)
|
||||
};
|
||||
|
||||
// Validate
|
||||
if (newConfig.threshold < 1 || newConfig.threshold > 20) {
|
||||
throw new Error('Threshold must be between 1 and 20');
|
||||
}
|
||||
if (newConfig.timeout < 60 || newConfig.timeout > 1800) {
|
||||
throw new Error('Timeout must be between 60 and 1800 seconds');
|
||||
}
|
||||
|
||||
queue.config = newConfig;
|
||||
saveQueue(queue);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
config: newConfig,
|
||||
message: `Queue configured: threshold=${newConfig.threshold}, timeout=${newConfig.timeout}s`
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush queue - execute batch update
|
||||
* @returns {Promise<{ success: boolean, processed: number, results: Array, errors: Array }>}
|
||||
@@ -243,6 +302,7 @@ function scheduleTimeout() {
|
||||
// We use file-based timeout tracking for persistence across process restarts
|
||||
// The actual timeout check happens on next add/status call
|
||||
const queue = loadQueue();
|
||||
const config = getQueueConfig();
|
||||
|
||||
if (!queue.createdAt || queue.items.length === 0) {
|
||||
return;
|
||||
@@ -251,7 +311,7 @@ function scheduleTimeout() {
|
||||
const createdTime = new Date(queue.createdAt).getTime();
|
||||
const elapsed = Date.now() - createdTime;
|
||||
|
||||
if (elapsed >= QUEUE_TIMEOUT_MS) {
|
||||
if (elapsed >= config.timeoutMs) {
|
||||
// Timeout already exceeded, should flush
|
||||
console.log('[MemoryQueue] Timeout exceeded, auto-flushing');
|
||||
// Don't await here to avoid blocking
|
||||
@@ -260,7 +320,7 @@ function scheduleTimeout() {
|
||||
});
|
||||
} else if (!scheduledTimeoutId) {
|
||||
// Schedule in-memory timeout for current process
|
||||
const remaining = QUEUE_TIMEOUT_MS - elapsed;
|
||||
const remaining = config.timeoutMs - elapsed;
|
||||
scheduledTimeoutId = setTimeout(() => {
|
||||
scheduledTimeoutId = null;
|
||||
const currentQueue = loadQueue();
|
||||
@@ -295,6 +355,7 @@ function clearScheduledTimeout() {
|
||||
*/
|
||||
async function checkTimeout() {
|
||||
const queue = loadQueue();
|
||||
const config = getQueueConfig();
|
||||
|
||||
if (!queue.createdAt || queue.items.length === 0) {
|
||||
return { expired: false, flushed: false };
|
||||
@@ -303,7 +364,7 @@ async function checkTimeout() {
|
||||
const createdTime = new Date(queue.createdAt).getTime();
|
||||
const elapsed = Date.now() - createdTime;
|
||||
|
||||
if (elapsed >= QUEUE_TIMEOUT_MS) {
|
||||
if (elapsed >= config.timeoutMs) {
|
||||
console.log('[MemoryQueue] Timeout expired, triggering flush');
|
||||
const result = await flushQueue();
|
||||
return { expired: true, flushed: true, result };
|
||||
@@ -318,7 +379,7 @@ async function checkTimeout() {
|
||||
* @returns {Promise<unknown>}
|
||||
*/
|
||||
async function execute(params) {
|
||||
const { action, path, tool = 'gemini', strategy = 'single-layer' } = params;
|
||||
const { action, path, tool = 'gemini', strategy = 'single-layer', threshold, timeout } = params;
|
||||
|
||||
switch (action) {
|
||||
case 'add':
|
||||
@@ -359,8 +420,11 @@ async function execute(params) {
|
||||
case 'flush':
|
||||
return await flushQueue();
|
||||
|
||||
case 'configure':
|
||||
return configureQueue({ threshold, timeout });
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown action: ${action}. Valid actions: add, status, flush`);
|
||||
throw new Error(`Unknown action: ${action}. Valid actions: add, status, flush, configure`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -372,21 +436,34 @@ export const memoryQueueTool = {
|
||||
description: `Memory update queue management. Batches CLAUDE.md updates for efficiency.
|
||||
|
||||
Actions:
|
||||
- add: Add path to queue (auto-flushes at threshold ${QUEUE_THRESHOLD} or timeout ${QUEUE_TIMEOUT_MS / 1000}s)
|
||||
- status: Get queue status
|
||||
- flush: Immediately execute all queued updates`,
|
||||
- add: Add path to queue (auto-flushes at configured threshold/timeout)
|
||||
- status: Get queue status and configuration
|
||||
- flush: Immediately execute all queued updates
|
||||
- configure: Set threshold and timeout settings`,
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
action: {
|
||||
type: 'string',
|
||||
enum: ['add', 'status', 'flush'],
|
||||
enum: ['add', 'status', 'flush', 'configure'],
|
||||
description: 'Queue action to perform'
|
||||
},
|
||||
path: {
|
||||
type: 'string',
|
||||
description: 'Module directory path (required for add action)'
|
||||
},
|
||||
threshold: {
|
||||
type: 'number',
|
||||
description: 'Number of paths to trigger flush (1-20, for configure action)',
|
||||
minimum: 1,
|
||||
maximum: 20
|
||||
},
|
||||
timeout: {
|
||||
type: 'number',
|
||||
description: 'Timeout in seconds to trigger flush (60-1800, for configure action)',
|
||||
minimum: 60,
|
||||
maximum: 1800
|
||||
},
|
||||
tool: {
|
||||
type: 'string',
|
||||
enum: ['gemini', 'qwen', 'codex'],
|
||||
@@ -412,10 +489,11 @@ export {
|
||||
addToQueue,
|
||||
getQueueStatus,
|
||||
flushQueue,
|
||||
configureQueue,
|
||||
scheduleTimeout,
|
||||
clearScheduledTimeout,
|
||||
checkTimeout,
|
||||
QUEUE_THRESHOLD,
|
||||
QUEUE_TIMEOUT_MS,
|
||||
DEFAULT_THRESHOLD,
|
||||
DEFAULT_TIMEOUT_SECONDS,
|
||||
QUEUE_FILE_PATH
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user