commandmate 0.3.1 → 0.3.3
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/.next/BUILD_ID +1 -1
- package/.next/app-build-manifest.json +11 -11
- package/.next/app-path-routes-manifest.json +1 -1
- package/.next/build-manifest.json +4 -4
- package/.next/cache/.tsbuildinfo +1 -1
- package/.next/cache/config.json +3 -3
- package/.next/cache/webpack/client-production/0.pack +0 -0
- package/.next/cache/webpack/client-production/1.pack +0 -0
- package/.next/cache/webpack/client-production/2.pack +0 -0
- package/.next/cache/webpack/client-production/index.pack +0 -0
- package/.next/cache/webpack/client-production/index.pack.old +0 -0
- package/.next/cache/webpack/edge-server-production/0.pack +0 -0
- package/.next/cache/webpack/edge-server-production/index.pack +0 -0
- package/.next/cache/webpack/server-production/0.pack +0 -0
- package/.next/cache/webpack/server-production/index.pack +0 -0
- package/.next/next-server.js.nft.json +1 -1
- package/.next/prerender-manifest.json +1 -1
- package/.next/required-server-files.json +1 -1
- package/.next/routes-manifest.json +1 -1
- package/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/api/app/update-check/route.js +1 -1
- package/.next/server/app/api/external-apps/[id]/health/route.js +1 -1
- package/.next/server/app/api/external-apps/[id]/route.js +1 -1
- package/.next/server/app/api/external-apps/route.js +1 -1
- package/.next/server/app/api/hooks/claude-done/route.js +1 -1
- package/.next/server/app/api/ollama/models/route.js +1 -0
- package/.next/server/app/api/ollama/models/route.js.nft.json +1 -0
- package/.next/server/app/api/ollama/models.body +1 -0
- package/.next/server/app/api/ollama/models.meta +1 -0
- package/.next/server/app/api/repositories/clone/[jobId]/route.js +1 -1
- package/.next/server/app/api/repositories/clone/route.js +1 -1
- package/.next/server/app/api/repositories/excluded/route.js +7 -7
- package/.next/server/app/api/repositories/restore/route.js +3 -3
- package/.next/server/app/api/repositories/route.js +13 -11
- package/.next/server/app/api/repositories/route.js.nft.json +1 -1
- package/.next/server/app/api/repositories/scan/route.js +1 -1
- package/.next/server/app/api/repositories/sync/route.js +3 -3
- package/.next/server/app/api/worktrees/[id]/auto-yes/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/auto-yes/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/cli-tool/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/current-output/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/current-output/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/execution-logs/[logId]/route.js +1 -0
- package/.next/server/app/api/worktrees/[id]/execution-logs/[logId]/route.js.nft.json +1 -0
- package/.next/server/app/api/worktrees/[id]/execution-logs/route.js +9 -0
- package/.next/server/app/api/worktrees/[id]/execution-logs/route.js.nft.json +1 -0
- package/.next/server/app/api/worktrees/[id]/files/[...path]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/interrupt/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/interrupt/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/kill-session/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/kill-session/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/logs/[filename]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/logs/route.js +2 -2
- package/.next/server/app/api/worktrees/[id]/memos/[memoId]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/memos/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/messages/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/prompt-response/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/prompt-response/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/respond/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/respond/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/schedules/[scheduleId]/route.js +1 -0
- package/.next/server/app/api/worktrees/[id]/schedules/[scheduleId]/route.js.nft.json +1 -0
- package/.next/server/app/api/worktrees/[id]/schedules/route.js +4 -0
- package/.next/server/app/api/worktrees/[id]/schedules/route.js.nft.json +1 -0
- package/.next/server/app/api/worktrees/[id]/search/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/send/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/send/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/slash-commands/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/start-polling/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/start-polling/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/tree/[...path]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/tree/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/upload/[...path]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/viewed/route.js +1 -1
- package/.next/server/app/api/worktrees/route.js +1 -1
- package/.next/server/app/api/worktrees/route.js.nft.json +1 -1
- package/.next/server/app/login/page.js.nft.json +1 -1
- package/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/.next/server/app/page.js +1 -1
- package/.next/server/app/page.js.nft.json +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/proxy/[...path]/route.js +1 -1
- package/.next/server/app/worktrees/[id]/files/[...path]/page.js.nft.json +1 -1
- package/.next/server/app/worktrees/[id]/files/[...path]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/worktrees/[id]/page.js +8 -3
- package/.next/server/app/worktrees/[id]/page.js.nft.json +1 -1
- package/.next/server/app/worktrees/[id]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/worktrees/[id]/terminal/page.js.nft.json +1 -1
- package/.next/server/app/worktrees/[id]/terminal/page_client-reference-manifest.js +1 -1
- package/.next/server/app-paths-manifest.json +10 -5
- package/.next/server/chunks/2314.js +1 -0
- package/.next/server/chunks/3860.js +1 -1
- package/.next/server/chunks/4559.js +1 -1
- package/.next/server/chunks/539.js +10 -10
- package/.next/server/chunks/5853.js +1 -1
- package/.next/server/chunks/6228.js +1 -0
- package/.next/server/chunks/7425.js +112 -37
- package/.next/server/chunks/7566.js +1 -1
- package/.next/server/chunks/8693.js +1 -1
- package/.next/server/chunks/9446.js +1 -0
- package/.next/server/functions-config-manifest.json +1 -1
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/middleware-manifest.json +5 -5
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/chunks/8091-274bc0716106e7fc.js +1 -0
- package/.next/static/chunks/app/page-060057e02b841125.js +1 -0
- package/.next/static/chunks/app/worktrees/[id]/page-78580947c201d698.js +1 -0
- package/.next/static/chunks/{main-db79434ee4a6c931.js → main-2feda12a4d321111.js} +1 -1
- package/.next/static/css/e85de230ef5ddc40.css +3 -0
- package/.next/trace +5 -5
- package/.next/types/app/api/ollama/models/route.ts +343 -0
- package/.next/types/app/api/worktrees/[id]/execution-logs/[logId]/route.ts +343 -0
- package/.next/types/app/api/worktrees/[id]/execution-logs/route.ts +343 -0
- package/.next/types/app/api/worktrees/[id]/schedules/[scheduleId]/route.ts +343 -0
- package/.next/types/app/api/worktrees/[id]/schedules/route.ts +343 -0
- package/README.md +74 -76
- package/dist/cli/utils/docs-reader.d.ts.map +1 -1
- package/dist/cli/utils/docs-reader.js +1 -0
- package/dist/server/server.js +5 -0
- package/dist/server/src/config/cmate-constants.js +79 -0
- package/dist/server/src/config/schedule-config.js +60 -0
- package/dist/server/src/lib/auto-yes-manager.js +2 -2
- package/dist/server/src/lib/claude-executor.js +158 -0
- package/dist/server/src/lib/cli-patterns.js +73 -9
- package/dist/server/src/lib/cli-tools/gemini.js +81 -22
- package/dist/server/src/lib/cli-tools/manager.js +4 -2
- package/dist/server/src/lib/cli-tools/types.js +64 -2
- package/dist/server/src/lib/cli-tools/vibe-local.js +163 -0
- package/dist/server/src/lib/cmate-parser.js +262 -0
- package/dist/server/src/lib/db-instance.js +3 -0
- package/dist/server/src/lib/db-migrations.js +145 -2
- package/dist/server/src/lib/db.js +51 -1
- package/dist/server/src/lib/env-sanitizer.js +57 -0
- package/dist/server/src/lib/prompt-detector.js +4 -3
- package/dist/server/src/lib/response-poller.js +22 -11
- package/dist/server/src/lib/schedule-manager.js +401 -0
- package/dist/server/src/lib/selected-agents-validator.js +99 -0
- package/dist/server/src/types/cmate.js +6 -0
- package/dist/server/src/types/sidebar.js +9 -4
- package/package.json +2 -1
- package/.next/server/chunks/7536.js +0 -1
- package/.next/static/chunks/8091-925542bdfc843dce.js +0 -1
- package/.next/static/chunks/app/page-238b5a70d8c101e9.js +0 -1
- package/.next/static/chunks/app/worktrees/[id]/page-a556551ce5c69dec.js +0 -1
- package/.next/static/css/b9ea6a4fad17dc32.css +0 -3
- /package/.next/static/{hmAjbCPjxX_C0Os7rphI1 → O7EDFfAYQNe_HRbORxQAC}/_buildManifest.js +0 -0
- /package/.next/static/{hmAjbCPjxX_C0Os7rphI1 → O7EDFfAYQNe_HRbORxQAC}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Schedule Execution Configuration Constants
|
|
4
|
+
* Issue #294: Centralized constants for schedule-related API routes
|
|
5
|
+
*
|
|
6
|
+
* Eliminates duplication of validation constants and UUID validation
|
|
7
|
+
* across schedules/route.ts, schedules/[scheduleId]/route.ts,
|
|
8
|
+
* and execution-logs/[logId]/route.ts.
|
|
9
|
+
*
|
|
10
|
+
* [S4-014] UUID v4 format validation
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.UUID_V4_PATTERN = exports.DEFAULT_PERMISSIONS = exports.VIBE_LOCAL_PERMISSIONS = exports.GEMINI_PERMISSIONS = exports.CODEX_SANDBOXES = exports.CLAUDE_PERMISSIONS = exports.MAX_SCHEDULE_CRON_LENGTH = exports.MAX_SCHEDULE_MESSAGE_LENGTH = exports.MAX_SCHEDULE_NAME_LENGTH = void 0;
|
|
14
|
+
exports.isValidUuidV4 = isValidUuidV4;
|
|
15
|
+
// =============================================================================
|
|
16
|
+
// Validation Constants
|
|
17
|
+
// =============================================================================
|
|
18
|
+
/** Maximum schedule name length */
|
|
19
|
+
exports.MAX_SCHEDULE_NAME_LENGTH = 100;
|
|
20
|
+
/** Maximum message length for schedule execution */
|
|
21
|
+
exports.MAX_SCHEDULE_MESSAGE_LENGTH = 10000;
|
|
22
|
+
/** Maximum cron expression length */
|
|
23
|
+
exports.MAX_SCHEDULE_CRON_LENGTH = 100;
|
|
24
|
+
// =============================================================================
|
|
25
|
+
// Permission Constants
|
|
26
|
+
// =============================================================================
|
|
27
|
+
/** Allowed permission values for claude CLI (--permission-mode) */
|
|
28
|
+
exports.CLAUDE_PERMISSIONS = ['default', 'acceptEdits', 'plan', 'dontAsk', 'bypassPermissions'];
|
|
29
|
+
/** Allowed sandbox values for codex CLI (--sandbox) */
|
|
30
|
+
exports.CODEX_SANDBOXES = ['read-only', 'workspace-write', 'danger-full-access'];
|
|
31
|
+
/** Allowed permission values for gemini CLI (no permission flags) */
|
|
32
|
+
exports.GEMINI_PERMISSIONS = [];
|
|
33
|
+
/** Allowed permission values for vibe-local CLI (no permission flags) */
|
|
34
|
+
exports.VIBE_LOCAL_PERMISSIONS = [];
|
|
35
|
+
/** Default permission per CLI tool */
|
|
36
|
+
exports.DEFAULT_PERMISSIONS = {
|
|
37
|
+
claude: 'acceptEdits',
|
|
38
|
+
codex: 'workspace-write',
|
|
39
|
+
gemini: '',
|
|
40
|
+
'vibe-local': '',
|
|
41
|
+
};
|
|
42
|
+
// =============================================================================
|
|
43
|
+
// UUID Validation
|
|
44
|
+
// =============================================================================
|
|
45
|
+
/**
|
|
46
|
+
* UUID v4 validation pattern.
|
|
47
|
+
* Matches standard UUID v4 format: xxxxxxxx-xxxx-4xxx-[89ab]xxx-xxxxxxxxxxxx
|
|
48
|
+
*
|
|
49
|
+
* [S4-014] Used to validate schedule IDs and execution log IDs
|
|
50
|
+
*/
|
|
51
|
+
exports.UUID_V4_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
52
|
+
/**
|
|
53
|
+
* Validate that a string is a valid UUID v4 format.
|
|
54
|
+
*
|
|
55
|
+
* @param id - String to validate
|
|
56
|
+
* @returns true if the string matches UUID v4 format
|
|
57
|
+
*/
|
|
58
|
+
function isValidUuidV4(id) {
|
|
59
|
+
return exports.UUID_V4_PATTERN.test(id);
|
|
60
|
+
}
|
|
@@ -403,7 +403,7 @@ async function captureAndCleanOutput(worktreeId, cliToolId) {
|
|
|
403
403
|
// captureSessionOutput() default is 1000 lines, but tmux buffer capture
|
|
404
404
|
// requires 5000 to avoid truncating long outputs.
|
|
405
405
|
const output = await (0, cli_session_1.captureSessionOutput)(worktreeId, cliToolId, 5000);
|
|
406
|
-
return (0, cli_patterns_1.stripAnsi)(output);
|
|
406
|
+
return (0, cli_patterns_1.stripBoxDrawing)((0, cli_patterns_1.stripAnsi)(output));
|
|
407
407
|
}
|
|
408
408
|
/**
|
|
409
409
|
* Process stop condition check using delta-based approach.
|
|
@@ -465,7 +465,7 @@ async function detectAndRespondToPrompt(worktreeId, pollerState, cliToolId, clea
|
|
|
465
465
|
try {
|
|
466
466
|
// 1. Detect prompt
|
|
467
467
|
const promptOptions = (0, cli_patterns_1.buildDetectPromptOptions)(cliToolId);
|
|
468
|
-
const promptDetection = (0, prompt_detector_1.detectPrompt)(cleanOutput, promptOptions);
|
|
468
|
+
const promptDetection = (0, prompt_detector_1.detectPrompt)((0, cli_patterns_1.stripBoxDrawing)(cleanOutput), promptOptions);
|
|
469
469
|
if (!promptDetection.isPrompt || !promptDetection.promptData) {
|
|
470
470
|
// No prompt detected - reset lastAnsweredPromptKey (Issue #306)
|
|
471
471
|
pollerState.lastAnsweredPromptKey = null;
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Claude CLI Executor
|
|
4
|
+
* Issue #294: Executes claude -p commands for scheduled executions
|
|
5
|
+
*
|
|
6
|
+
* Security:
|
|
7
|
+
* - Uses execFile (not exec) to prevent shell injection
|
|
8
|
+
* - Sanitizes environment variables via env-sanitizer.ts
|
|
9
|
+
* - Limits output size to prevent memory exhaustion
|
|
10
|
+
* - Enforces execution timeout
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.ALLOWED_CLI_TOOLS = exports.MAX_MESSAGE_LENGTH = exports.EXECUTION_TIMEOUT_MS = exports.MAX_STORED_OUTPUT_SIZE = exports.MAX_OUTPUT_SIZE = void 0;
|
|
14
|
+
exports.truncateOutput = truncateOutput;
|
|
15
|
+
exports.buildCliArgs = buildCliArgs;
|
|
16
|
+
exports.executeClaudeCommand = executeClaudeCommand;
|
|
17
|
+
exports.getActiveProcesses = getActiveProcesses;
|
|
18
|
+
const child_process_1 = require("child_process");
|
|
19
|
+
const env_sanitizer_1 = require("./env-sanitizer");
|
|
20
|
+
const cli_patterns_1 = require("./cli-patterns");
|
|
21
|
+
// =============================================================================
|
|
22
|
+
// Constants
|
|
23
|
+
// =============================================================================
|
|
24
|
+
/** Maximum output buffer size for execFile (1MB) */
|
|
25
|
+
exports.MAX_OUTPUT_SIZE = 1 * 1024 * 1024;
|
|
26
|
+
/** Maximum output size stored in DB (100KB) */
|
|
27
|
+
exports.MAX_STORED_OUTPUT_SIZE = 100 * 1024;
|
|
28
|
+
/** Execution timeout in milliseconds (5 minutes) */
|
|
29
|
+
exports.EXECUTION_TIMEOUT_MS = 5 * 60 * 1000;
|
|
30
|
+
/** Maximum message length sent to claude -p */
|
|
31
|
+
exports.MAX_MESSAGE_LENGTH = 10000;
|
|
32
|
+
/** Allowed CLI tool identifiers for scheduled execution */
|
|
33
|
+
exports.ALLOWED_CLI_TOOLS = new Set(['claude', 'codex', 'gemini', 'vibe-local']);
|
|
34
|
+
// =============================================================================
|
|
35
|
+
// Executor
|
|
36
|
+
// =============================================================================
|
|
37
|
+
/**
|
|
38
|
+
* Truncate output to MAX_STORED_OUTPUT_SIZE bytes.
|
|
39
|
+
* Appends a truncation notice if truncated.
|
|
40
|
+
*
|
|
41
|
+
* @param output - Raw output string
|
|
42
|
+
* @returns Truncated output string
|
|
43
|
+
*/
|
|
44
|
+
function truncateOutput(output) {
|
|
45
|
+
if (Buffer.byteLength(output, 'utf-8') <= exports.MAX_STORED_OUTPUT_SIZE) {
|
|
46
|
+
return output;
|
|
47
|
+
}
|
|
48
|
+
// Truncate to MAX_STORED_OUTPUT_SIZE bytes
|
|
49
|
+
const buffer = Buffer.from(output, 'utf-8');
|
|
50
|
+
const truncated = buffer.subarray(0, exports.MAX_STORED_OUTPUT_SIZE).toString('utf-8');
|
|
51
|
+
return truncated + '\n\n--- Output truncated (exceeded 100KB limit) ---';
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Build CLI arguments for non-interactive execution based on CLI tool type.
|
|
55
|
+
*
|
|
56
|
+
* - claude: -p <message> --output-format text --permission-mode <permission>
|
|
57
|
+
* - codex: exec <message> --sandbox <permission>
|
|
58
|
+
* - gemini: -p <message>
|
|
59
|
+
* - vibe-local: [-p <message> -y] or [--model <model> -p <message> -y]
|
|
60
|
+
* - others: -p <message> (fallback)
|
|
61
|
+
*
|
|
62
|
+
* @param message - Prompt message
|
|
63
|
+
* @param cliToolId - CLI tool identifier
|
|
64
|
+
* @param permission - Permission mode (claude: --permission-mode, codex: --sandbox)
|
|
65
|
+
* @param options - Additional options (e.g., model for vibe-local)
|
|
66
|
+
* @returns Array of CLI arguments
|
|
67
|
+
*/
|
|
68
|
+
function buildCliArgs(message, cliToolId, permission, options) {
|
|
69
|
+
switch (cliToolId) {
|
|
70
|
+
case 'codex':
|
|
71
|
+
return ['exec', message, '--sandbox', permission ?? 'workspace-write'];
|
|
72
|
+
case 'gemini':
|
|
73
|
+
return ['-p', message];
|
|
74
|
+
case 'vibe-local':
|
|
75
|
+
if (options?.model) {
|
|
76
|
+
return ['--model', options.model, '-p', message, '-y'];
|
|
77
|
+
}
|
|
78
|
+
return ['-p', message, '-y'];
|
|
79
|
+
case 'claude':
|
|
80
|
+
default:
|
|
81
|
+
return ['-p', message, '--output-format', 'text', '--permission-mode', permission ?? 'acceptEdits'];
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Execute a CLI command in a worktree directory.
|
|
86
|
+
*
|
|
87
|
+
* @param message - Prompt message to send
|
|
88
|
+
* @param cwd - Working directory (worktree path from DB)
|
|
89
|
+
* @param cliToolId - CLI tool to use (default: 'claude')
|
|
90
|
+
* @param permission - Permission mode (claude: --permission-mode, codex: --sandbox)
|
|
91
|
+
* @param options - Additional options (e.g., model for vibe-local)
|
|
92
|
+
* @returns Execution result with output and status
|
|
93
|
+
*/
|
|
94
|
+
async function executeClaudeCommand(message, cwd, cliToolId = 'claude', permission, options) {
|
|
95
|
+
// Validate cliToolId against whitelist [SEC-001]
|
|
96
|
+
if (!exports.ALLOWED_CLI_TOOLS.has(cliToolId)) {
|
|
97
|
+
return {
|
|
98
|
+
output: '',
|
|
99
|
+
exitCode: null,
|
|
100
|
+
status: 'failed',
|
|
101
|
+
error: `Invalid CLI tool: ${cliToolId}`,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
// Validate message length
|
|
105
|
+
const truncatedMessage = message.length > exports.MAX_MESSAGE_LENGTH
|
|
106
|
+
? message.substring(0, exports.MAX_MESSAGE_LENGTH)
|
|
107
|
+
: message;
|
|
108
|
+
const args = buildCliArgs(truncatedMessage, cliToolId, permission, options);
|
|
109
|
+
return new Promise((resolve) => {
|
|
110
|
+
const child = (0, child_process_1.execFile)(cliToolId, args, {
|
|
111
|
+
cwd,
|
|
112
|
+
env: (0, env_sanitizer_1.sanitizeEnvForChildProcess)(),
|
|
113
|
+
maxBuffer: exports.MAX_OUTPUT_SIZE,
|
|
114
|
+
timeout: exports.EXECUTION_TIMEOUT_MS,
|
|
115
|
+
}, (error, stdout, stderr) => {
|
|
116
|
+
if (error) {
|
|
117
|
+
const isTimeout = error.killed || error.code === 'ETIMEDOUT';
|
|
118
|
+
const rawOutput = (0, cli_patterns_1.stripAnsi)(stdout || stderr || error.message);
|
|
119
|
+
const output = truncateOutput(rawOutput);
|
|
120
|
+
resolve({
|
|
121
|
+
output,
|
|
122
|
+
exitCode: error.code ? parseInt(String(error.code), 10) || null : null,
|
|
123
|
+
status: isTimeout ? 'timeout' : 'failed',
|
|
124
|
+
error: error.message,
|
|
125
|
+
});
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const rawOutput = (0, cli_patterns_1.stripAnsi)(stdout || '');
|
|
129
|
+
const output = truncateOutput(rawOutput);
|
|
130
|
+
resolve({
|
|
131
|
+
output,
|
|
132
|
+
exitCode: 0,
|
|
133
|
+
status: 'completed',
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
// Close stdin immediately to prevent hanging on yes/no prompts
|
|
137
|
+
child.stdin?.end();
|
|
138
|
+
// Return the child process PID for tracking
|
|
139
|
+
if (child.pid) {
|
|
140
|
+
// Store PID in global active processes for cleanup on shutdown
|
|
141
|
+
const activeProcesses = getActiveProcesses();
|
|
142
|
+
activeProcesses.set(child.pid, child);
|
|
143
|
+
child.on('exit', () => {
|
|
144
|
+
activeProcesses.delete(child.pid);
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Get the global active processes map.
|
|
151
|
+
* Uses globalThis for hot reload persistence.
|
|
152
|
+
*/
|
|
153
|
+
function getActiveProcesses() {
|
|
154
|
+
if (!globalThis.__scheduleActiveProcesses) {
|
|
155
|
+
globalThis.__scheduleActiveProcesses = new Map();
|
|
156
|
+
}
|
|
157
|
+
return globalThis.__scheduleActiveProcesses;
|
|
158
|
+
}
|
|
@@ -4,10 +4,11 @@
|
|
|
4
4
|
* Shared between response-poller.ts and API routes
|
|
5
5
|
*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.CLAUDE_SESSION_ERROR_REGEX_PATTERNS = exports.CLAUDE_SESSION_ERROR_PATTERNS = exports.GEMINI_PROMPT_PATTERN = exports.MAX_PASTED_TEXT_RETRIES = exports.PASTED_TEXT_DETECT_DELAY = exports.PASTED_TEXT_PATTERN = exports.CODEX_SEPARATOR_PATTERN = exports.CODEX_PROMPT_PATTERN = exports.CLAUDE_TRUST_DIALOG_PATTERN = exports.CLAUDE_SEPARATOR_PATTERN = exports.CLAUDE_PROMPT_PATTERN = exports.CODEX_THINKING_PATTERN = exports.CLAUDE_THINKING_PATTERN = exports.CLAUDE_SPINNER_CHARS = void 0;
|
|
7
|
+
exports.CLAUDE_SESSION_ERROR_REGEX_PATTERNS = exports.CLAUDE_SESSION_ERROR_PATTERNS = exports.VIBE_LOCAL_THINKING_PATTERN = exports.VIBE_LOCAL_PROMPT_PATTERN = exports.GEMINI_THINKING_PATTERN = exports.GEMINI_PROMPT_PATTERN = exports.MAX_PASTED_TEXT_RETRIES = exports.PASTED_TEXT_DETECT_DELAY = exports.PASTED_TEXT_PATTERN = exports.CODEX_SEPARATOR_PATTERN = exports.CODEX_PROMPT_PATTERN = exports.CLAUDE_TRUST_DIALOG_PATTERN = exports.CLAUDE_SEPARATOR_PATTERN = exports.CLAUDE_PROMPT_PATTERN = exports.CODEX_THINKING_PATTERN = exports.CLAUDE_THINKING_PATTERN = exports.CLAUDE_SPINNER_CHARS = void 0;
|
|
8
8
|
exports.detectThinking = detectThinking;
|
|
9
9
|
exports.getCliToolPatterns = getCliToolPatterns;
|
|
10
10
|
exports.stripAnsi = stripAnsi;
|
|
11
|
+
exports.stripBoxDrawing = stripBoxDrawing;
|
|
11
12
|
exports.buildDetectPromptOptions = buildDetectPromptOptions;
|
|
12
13
|
const logger_1 = require("./logger");
|
|
13
14
|
const logger = (0, logger_1.createLogger)('cli-patterns');
|
|
@@ -113,9 +114,29 @@ exports.PASTED_TEXT_DETECT_DELAY = 500;
|
|
|
113
114
|
*/
|
|
114
115
|
exports.MAX_PASTED_TEXT_RETRIES = 3;
|
|
115
116
|
/**
|
|
116
|
-
* Gemini
|
|
117
|
+
* Gemini interactive REPL prompt pattern
|
|
118
|
+
* Gemini CLI shows a `>` or `❯` prompt when waiting for user input in interactive mode.
|
|
119
|
+
* Also matches shell prompts as fallback for session initialization phase.
|
|
117
120
|
*/
|
|
118
|
-
exports.GEMINI_PROMPT_PATTERN = /^
|
|
121
|
+
exports.GEMINI_PROMPT_PATTERN = /^[>❯]\s*$/m;
|
|
122
|
+
/**
|
|
123
|
+
* Gemini thinking/processing pattern
|
|
124
|
+
* Gemini CLI shows braille spinner characters and status text while processing.
|
|
125
|
+
*/
|
|
126
|
+
exports.GEMINI_THINKING_PATTERN = /[\u2800-\u28FF]|Thinking\.\.\./;
|
|
127
|
+
/**
|
|
128
|
+
* Vibe Local prompt pattern
|
|
129
|
+
* vibe-local (vibe-coder) shows `ctx:N% ❯` prompt when waiting for user input.
|
|
130
|
+
* The prompt line includes a context usage percentage prefix.
|
|
131
|
+
* Examples: "ctx:9% ❯", "ctx:30% ❯", "ctx:9% ❯ /model"
|
|
132
|
+
*/
|
|
133
|
+
exports.VIBE_LOCAL_PROMPT_PATTERN = /ctx:\d+%\s*[>❯]/m;
|
|
134
|
+
/**
|
|
135
|
+
* Vibe Local thinking/processing pattern
|
|
136
|
+
* vibe-local shows spinner characters and status text while processing.
|
|
137
|
+
* Matches braille spinners, "Thinking", and tool execution indicators.
|
|
138
|
+
*/
|
|
139
|
+
exports.VIBE_LOCAL_THINKING_PATTERN = /[\u2800-\u28FF]|Thinking|⠋|⠙|⠹|⠸|⠼|⠴|⠦|⠧|⠇|⠏|Running|Executing/;
|
|
119
140
|
/**
|
|
120
141
|
* Detect if CLI tool is showing "thinking" indicator
|
|
121
142
|
*/
|
|
@@ -131,8 +152,10 @@ function detectThinking(cliToolId, content) {
|
|
|
131
152
|
result = exports.CODEX_THINKING_PATTERN.test(content);
|
|
132
153
|
break;
|
|
133
154
|
case 'gemini':
|
|
134
|
-
|
|
135
|
-
|
|
155
|
+
result = exports.GEMINI_THINKING_PATTERN.test(content);
|
|
156
|
+
break;
|
|
157
|
+
case 'vibe-local':
|
|
158
|
+
result = exports.VIBE_LOCAL_THINKING_PATTERN.test(content);
|
|
136
159
|
break;
|
|
137
160
|
default:
|
|
138
161
|
result = exports.CLAUDE_THINKING_PATTERN.test(content);
|
|
@@ -186,12 +209,36 @@ function getCliToolPatterns(cliToolId) {
|
|
|
186
209
|
case 'gemini':
|
|
187
210
|
return {
|
|
188
211
|
promptPattern: exports.GEMINI_PROMPT_PATTERN,
|
|
189
|
-
separatorPattern: /^
|
|
190
|
-
thinkingPattern:
|
|
212
|
+
separatorPattern: /^[─━]{3,}$/m,
|
|
213
|
+
thinkingPattern: exports.GEMINI_THINKING_PATTERN,
|
|
214
|
+
skipPatterns: [
|
|
215
|
+
/^[>❯]\s*$/, // Prompt line
|
|
216
|
+
exports.GEMINI_THINKING_PATTERN, // Thinking indicators
|
|
217
|
+
/^\s*$/, // Empty lines
|
|
218
|
+
/Gemini\s+\d+\.\d+/, // Version line
|
|
219
|
+
exports.PASTED_TEXT_PATTERN, // [Pasted text #N +XX lines]
|
|
220
|
+
],
|
|
221
|
+
};
|
|
222
|
+
case 'vibe-local':
|
|
223
|
+
return {
|
|
224
|
+
promptPattern: exports.VIBE_LOCAL_PROMPT_PATTERN,
|
|
225
|
+
separatorPattern: /^[·]{10,}$/m, // vibe-local uses middle dot separators
|
|
226
|
+
thinkingPattern: exports.VIBE_LOCAL_THINKING_PATTERN,
|
|
191
227
|
skipPatterns: [
|
|
192
|
-
|
|
193
|
-
exports.
|
|
228
|
+
exports.VIBE_LOCAL_PROMPT_PATTERN, // Prompt line (ctx:N% ❯)
|
|
229
|
+
exports.VIBE_LOCAL_THINKING_PATTERN, // Thinking indicators
|
|
194
230
|
/^\s*$/, // Empty lines
|
|
231
|
+
/vibe-local|vibe-coder/, // Version/banner lines
|
|
232
|
+
/ctx:\s*\d+%/, // Context usage indicator
|
|
233
|
+
/Model\s+\w/, // Model info line
|
|
234
|
+
/Engine\s+\w/, // Engine info line
|
|
235
|
+
/Mode\s+/, // Mode info line
|
|
236
|
+
/RAM\s+/, // RAM info line
|
|
237
|
+
/CWD\s+/, // Working directory line
|
|
238
|
+
/^[·]{10,}$/, // Middle dot separator lines
|
|
239
|
+
/✦\s*Ready/, // Status bar "Ready" indicator
|
|
240
|
+
/ESC:\s*stop/, // Status bar "ESC: stop" hint
|
|
241
|
+
exports.PASTED_TEXT_PATTERN, // [Pasted text #N +XX lines]
|
|
195
242
|
],
|
|
196
243
|
};
|
|
197
244
|
default:
|
|
@@ -222,6 +269,23 @@ const ANSI_PATTERN = /\x1b\[[0-9;]*[a-zA-Z]|\x1b\][^\x07]*\x07|\[[0-9;]*m/g;
|
|
|
222
269
|
function stripAnsi(str) {
|
|
223
270
|
return str.replace(ANSI_PATTERN, '');
|
|
224
271
|
}
|
|
272
|
+
/**
|
|
273
|
+
* Strip box-drawing border characters from CLI output.
|
|
274
|
+
* Gemini CLI wraps Action Required prompts in ╭─╮│╰─╯ borders.
|
|
275
|
+
* Removes │ (U+2502) prefix/suffix and border-only lines (╭╮╰╯─).
|
|
276
|
+
*
|
|
277
|
+
* @param str - Input string (typically after stripAnsi())
|
|
278
|
+
* @returns String with box-drawing borders removed
|
|
279
|
+
*/
|
|
280
|
+
function stripBoxDrawing(str) {
|
|
281
|
+
return str.split('\n').map(line => {
|
|
282
|
+
// Remove border-only lines (╭──╮, ╰──╯, │ only, etc.)
|
|
283
|
+
if (/^[\u2502\u256D\u256E\u256F\u2570\u2500\s]+$/.test(line))
|
|
284
|
+
return '';
|
|
285
|
+
// Strip leading │ + optional space, trailing space + │
|
|
286
|
+
return line.replace(/^\u2502\s?/, '').replace(/\s*\u2502$/, '');
|
|
287
|
+
}).join('\n');
|
|
288
|
+
}
|
|
225
289
|
/**
|
|
226
290
|
* Build DetectPromptOptions for a given CLI tool.
|
|
227
291
|
* Centralizes cliToolId-to-options mapping logic (DRY - MF-001).
|
|
@@ -1,18 +1,33 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
3
|
* Gemini CLI tool implementation
|
|
4
|
-
* Provides integration with Google's Gemini CLI
|
|
4
|
+
* Provides integration with Google's Gemini CLI in interactive mode
|
|
5
|
+
*
|
|
6
|
+
* @remarks Issue #368: Rewritten from non-interactive pipe mode to interactive REPL mode.
|
|
7
|
+
* Previous implementation used `echo 'msg' | gemini` which caused the process to exit
|
|
8
|
+
* immediately, making response polling impossible. Now launches `gemini` in interactive
|
|
9
|
+
* mode within tmux (same approach as Claude/Codex).
|
|
5
10
|
*/
|
|
6
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
12
|
exports.GeminiTool = void 0;
|
|
8
13
|
const base_1 = require("./base");
|
|
9
14
|
const tmux_1 = require("../tmux");
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
15
|
+
const pasted_text_helper_1 = require("../pasted-text-helper");
|
|
16
|
+
/**
|
|
17
|
+
* Extract error message from unknown error type (DRY)
|
|
18
|
+
*/
|
|
19
|
+
function getErrorMessage(error) {
|
|
20
|
+
return error instanceof Error ? error.message : String(error);
|
|
21
|
+
}
|
|
22
|
+
/** Wait for Gemini CLI to initialize after launch (banner + auth + dialog) */
|
|
23
|
+
const GEMINI_INIT_WAIT_MS = 6000;
|
|
24
|
+
/** Interval for polling trust dialog detection */
|
|
25
|
+
const TRUST_DIALOG_POLL_INTERVAL_MS = 1000;
|
|
26
|
+
/** Max attempts to detect trust dialog (10 * 1000ms = 10s polling window) */
|
|
27
|
+
const TRUST_DIALOG_MAX_ATTEMPTS = 10;
|
|
13
28
|
/**
|
|
14
29
|
* Gemini CLI tool implementation
|
|
15
|
-
* Manages Gemini sessions using tmux
|
|
30
|
+
* Manages Gemini interactive sessions using tmux
|
|
16
31
|
*/
|
|
17
32
|
class GeminiTool extends base_1.BaseCLITool {
|
|
18
33
|
id = 'gemini';
|
|
@@ -30,8 +45,7 @@ class GeminiTool extends base_1.BaseCLITool {
|
|
|
30
45
|
}
|
|
31
46
|
/**
|
|
32
47
|
* Start a new Gemini session for a worktree
|
|
33
|
-
*
|
|
34
|
-
* for running one-shot commands
|
|
48
|
+
* Launches `gemini` in interactive REPL mode within tmux
|
|
35
49
|
*
|
|
36
50
|
* @param worktreeId - Worktree ID
|
|
37
51
|
* @param worktreePath - Worktree path
|
|
@@ -50,22 +64,59 @@ class GeminiTool extends base_1.BaseCLITool {
|
|
|
50
64
|
return;
|
|
51
65
|
}
|
|
52
66
|
try {
|
|
53
|
-
// Create tmux session
|
|
67
|
+
// Create tmux session with large history buffer
|
|
54
68
|
await (0, tmux_1.createSession)({
|
|
55
69
|
sessionName,
|
|
56
70
|
workingDirectory: worktreePath,
|
|
57
71
|
historyLimit: 50000,
|
|
58
72
|
});
|
|
73
|
+
// Wait a moment for the session to be created
|
|
74
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
75
|
+
// Start Gemini CLI in interactive mode (no flags = interactive REPL)
|
|
76
|
+
await (0, tmux_1.sendKeys)(sessionName, 'gemini', true);
|
|
77
|
+
// Wait for Gemini to initialize
|
|
78
|
+
await new Promise((resolve) => setTimeout(resolve, GEMINI_INIT_WAIT_MS));
|
|
79
|
+
// Auto-handle "Do you trust this folder?" dialog on first run
|
|
80
|
+
await this.handleTrustDialog(sessionName);
|
|
59
81
|
console.log(`✓ Started Gemini session: ${sessionName}`);
|
|
60
82
|
}
|
|
61
83
|
catch (error) {
|
|
62
|
-
const errorMessage =
|
|
84
|
+
const errorMessage = getErrorMessage(error);
|
|
63
85
|
throw new Error(`Failed to start Gemini session: ${errorMessage}`);
|
|
64
86
|
}
|
|
65
87
|
}
|
|
66
88
|
/**
|
|
67
|
-
*
|
|
68
|
-
*
|
|
89
|
+
* Handle Gemini "Do you trust this folder?" dialog
|
|
90
|
+
* On first run in a new directory, Gemini shows a trust confirmation.
|
|
91
|
+
* Auto-selects "1. Trust folder" to allow execution.
|
|
92
|
+
*/
|
|
93
|
+
async handleTrustDialog(sessionName) {
|
|
94
|
+
for (let i = 0; i < TRUST_DIALOG_MAX_ATTEMPTS; i++) {
|
|
95
|
+
try {
|
|
96
|
+
const output = await (0, tmux_1.capturePane)(sessionName, 50);
|
|
97
|
+
if (output.includes('Do you trust this folder?')) {
|
|
98
|
+
// Option 1 "Trust folder" is pre-selected (● marker).
|
|
99
|
+
// Send Enter to confirm the selection.
|
|
100
|
+
await (0, tmux_1.sendSpecialKey)(sessionName, 'Enter');
|
|
101
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
102
|
+
console.log('✓ Auto-trusted folder for Gemini session');
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
// Check if Gemini interactive prompt is already showing (no dialog needed)
|
|
106
|
+
if (output.match(/^[>❯]\s*$/m)) {
|
|
107
|
+
console.log('✓ Gemini prompt detected - no trust dialog needed');
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
// Capture may fail during initialization - continue polling
|
|
113
|
+
}
|
|
114
|
+
await new Promise((resolve) => setTimeout(resolve, TRUST_DIALOG_POLL_INTERVAL_MS));
|
|
115
|
+
}
|
|
116
|
+
console.log('⚠ Trust dialog detection timed out - proceeding anyway');
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Send a message to Gemini interactive session
|
|
69
120
|
*
|
|
70
121
|
* @param worktreeId - Worktree ID
|
|
71
122
|
* @param message - Message to send
|
|
@@ -78,15 +129,22 @@ class GeminiTool extends base_1.BaseCLITool {
|
|
|
78
129
|
throw new Error(`Gemini session ${sessionName} does not exist. Start the session first.`);
|
|
79
130
|
}
|
|
80
131
|
try {
|
|
81
|
-
//
|
|
82
|
-
|
|
83
|
-
//
|
|
84
|
-
|
|
85
|
-
|
|
132
|
+
// Send message to Gemini (without Enter)
|
|
133
|
+
await (0, tmux_1.sendKeys)(sessionName, message, false);
|
|
134
|
+
// Wait a moment for the text to be typed
|
|
135
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
136
|
+
// Send Enter key separately
|
|
137
|
+
await (0, tmux_1.sendSpecialKey)(sessionName, 'C-m');
|
|
138
|
+
// Wait a moment for the message to be processed
|
|
139
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
140
|
+
// Detect [Pasted text] and resend Enter for multi-line messages
|
|
141
|
+
if (message.includes('\n')) {
|
|
142
|
+
await (0, pasted_text_helper_1.detectAndResendIfPastedText)(sessionName);
|
|
143
|
+
}
|
|
86
144
|
console.log(`✓ Sent message to Gemini session: ${sessionName}`);
|
|
87
145
|
}
|
|
88
146
|
catch (error) {
|
|
89
|
-
const errorMessage =
|
|
147
|
+
const errorMessage = getErrorMessage(error);
|
|
90
148
|
throw new Error(`Failed to send message to Gemini: ${errorMessage}`);
|
|
91
149
|
}
|
|
92
150
|
}
|
|
@@ -98,12 +156,13 @@ class GeminiTool extends base_1.BaseCLITool {
|
|
|
98
156
|
async killSession(worktreeId) {
|
|
99
157
|
const sessionName = this.getSessionName(worktreeId);
|
|
100
158
|
try {
|
|
101
|
-
// Send Ctrl+D to exit Gemini gracefully
|
|
102
159
|
const exists = await (0, tmux_1.hasSession)(sessionName);
|
|
103
160
|
if (exists) {
|
|
104
|
-
// Send Ctrl+
|
|
105
|
-
await
|
|
106
|
-
|
|
161
|
+
// Send Ctrl+C to interrupt any running operation
|
|
162
|
+
await (0, tmux_1.sendSpecialKey)(sessionName, 'C-c');
|
|
163
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
164
|
+
// Send /quit to exit Gemini gracefully
|
|
165
|
+
await (0, tmux_1.sendKeys)(sessionName, '/quit', true);
|
|
107
166
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
108
167
|
}
|
|
109
168
|
// Kill the tmux session
|
|
@@ -113,7 +172,7 @@ class GeminiTool extends base_1.BaseCLITool {
|
|
|
113
172
|
}
|
|
114
173
|
}
|
|
115
174
|
catch (error) {
|
|
116
|
-
const errorMessage =
|
|
175
|
+
const errorMessage = getErrorMessage(error);
|
|
117
176
|
console.error(`Error stopping Gemini session: ${errorMessage}`);
|
|
118
177
|
throw error;
|
|
119
178
|
}
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
3
|
* CLI Tool Manager
|
|
4
|
-
* Singleton class to manage multiple CLI tools (Claude, Codex, Gemini)
|
|
4
|
+
* Singleton class to manage multiple CLI tools (Claude, Codex, Gemini, Vibe Local)
|
|
5
5
|
*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
7
|
exports.CLIToolManager = void 0;
|
|
8
8
|
const claude_1 = require("./claude");
|
|
9
9
|
const codex_1 = require("./codex");
|
|
10
10
|
const gemini_1 = require("./gemini");
|
|
11
|
+
const vibe_local_1 = require("./vibe-local");
|
|
11
12
|
const response_poller_1 = require("../response-poller");
|
|
12
13
|
/**
|
|
13
14
|
* CLI Tool Manager (Singleton)
|
|
14
|
-
* Provides centralized access to all CLI tools
|
|
15
|
+
* Provides centralized access to all CLI tools (Issue #368: includes Vibe Local)
|
|
15
16
|
*/
|
|
16
17
|
class CLIToolManager {
|
|
17
18
|
static instance;
|
|
@@ -25,6 +26,7 @@ class CLIToolManager {
|
|
|
25
26
|
this.tools.set('claude', new claude_1.ClaudeTool());
|
|
26
27
|
this.tools.set('codex', new codex_1.CodexTool());
|
|
27
28
|
this.tools.set('gemini', new gemini_1.GeminiTool());
|
|
29
|
+
this.tools.set('vibe-local', new vibe_local_1.VibeLocalTool());
|
|
28
30
|
}
|
|
29
31
|
/**
|
|
30
32
|
* Get singleton instance
|
|
@@ -3,10 +3,72 @@
|
|
|
3
3
|
* Type definitions and interfaces for CLI tools
|
|
4
4
|
*/
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.CLI_TOOL_IDS = void 0;
|
|
6
|
+
exports.OLLAMA_MODEL_PATTERN = exports.CLI_TOOL_DISPLAY_NAMES = exports.CLI_TOOL_IDS = void 0;
|
|
7
|
+
exports.isCliToolType = isCliToolType;
|
|
8
|
+
exports.getCliToolDisplayName = getCliToolDisplayName;
|
|
9
|
+
exports.getCliToolDisplayNameSafe = getCliToolDisplayNameSafe;
|
|
7
10
|
/**
|
|
8
11
|
* CLI Tool IDs constant array
|
|
9
12
|
* T2.1: Single source of truth for CLI tool IDs
|
|
10
13
|
* CLIToolType is derived from this constant (DRY principle)
|
|
11
14
|
*/
|
|
12
|
-
exports.CLI_TOOL_IDS = ['claude', 'codex', 'gemini'];
|
|
15
|
+
exports.CLI_TOOL_IDS = ['claude', 'codex', 'gemini', 'vibe-local'];
|
|
16
|
+
/**
|
|
17
|
+
* CLI tool display names for UI rendering
|
|
18
|
+
* Issue #368: Centralized display name mapping
|
|
19
|
+
*
|
|
20
|
+
* Usage: UI display (tab headers, message lists, settings).
|
|
21
|
+
* For internal logs/debug, use tool.name (BaseCLITool.name) instead.
|
|
22
|
+
*/
|
|
23
|
+
exports.CLI_TOOL_DISPLAY_NAMES = {
|
|
24
|
+
claude: 'Claude',
|
|
25
|
+
codex: 'Codex',
|
|
26
|
+
gemini: 'Gemini',
|
|
27
|
+
'vibe-local': 'Vibe Local',
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Check if a string is a valid CLIToolType
|
|
31
|
+
* Issue #368: Type guard for safe casting of untrusted CLI tool ID strings
|
|
32
|
+
*
|
|
33
|
+
* @param value - String to check
|
|
34
|
+
* @returns True if value is a valid CLIToolType
|
|
35
|
+
*/
|
|
36
|
+
function isCliToolType(value) {
|
|
37
|
+
return exports.CLI_TOOL_IDS.includes(value);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Get the display name for a CLI tool ID
|
|
41
|
+
* Issue #368: Centralized display name function for DRY compliance
|
|
42
|
+
*
|
|
43
|
+
* @param id - CLI tool type identifier
|
|
44
|
+
* @returns Human-readable display name
|
|
45
|
+
*/
|
|
46
|
+
function getCliToolDisplayName(id) {
|
|
47
|
+
return exports.CLI_TOOL_DISPLAY_NAMES[id] ?? id;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Get the display name for a CLI tool ID string, with fallback for unknown IDs
|
|
51
|
+
* Issue #368: Safe wrapper for UI components receiving untyped cliToolId strings
|
|
52
|
+
*
|
|
53
|
+
* Unlike getCliToolDisplayName(), this accepts optional/untyped strings and
|
|
54
|
+
* returns a fallback value ('Assistant') for null, undefined, or unknown IDs.
|
|
55
|
+
*
|
|
56
|
+
* @param cliToolId - Optional CLI tool ID string (may be untyped)
|
|
57
|
+
* @param fallback - Fallback display name for missing/unknown IDs (default: 'Assistant')
|
|
58
|
+
* @returns Human-readable display name or fallback
|
|
59
|
+
*/
|
|
60
|
+
function getCliToolDisplayNameSafe(cliToolId, fallback = 'Assistant') {
|
|
61
|
+
if (!cliToolId)
|
|
62
|
+
return fallback;
|
|
63
|
+
if (isCliToolType(cliToolId))
|
|
64
|
+
return getCliToolDisplayName(cliToolId);
|
|
65
|
+
return fallback;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Ollama model name validation pattern.
|
|
69
|
+
* Allows: alphanumeric start, followed by alphanumeric, dots, underscores, colons, slashes, hyphens.
|
|
70
|
+
* Max 100 characters. Used for defense-in-depth validation at point of use.
|
|
71
|
+
*
|
|
72
|
+
* [SEC-001] Shared between API route validation and CLI command construction
|
|
73
|
+
*/
|
|
74
|
+
exports.OLLAMA_MODEL_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9._:/-]*$/;
|