wave-agent-sdk 0.0.1
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/README.md +32 -0
- package/dist/agent.d.ts +96 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +286 -0
- package/dist/hooks/executor.d.ts +56 -0
- package/dist/hooks/executor.d.ts.map +1 -0
- package/dist/hooks/executor.js +312 -0
- package/dist/hooks/index.d.ts +17 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +14 -0
- package/dist/hooks/manager.d.ts +90 -0
- package/dist/hooks/manager.d.ts.map +1 -0
- package/dist/hooks/manager.js +395 -0
- package/dist/hooks/matcher.d.ts +49 -0
- package/dist/hooks/matcher.d.ts.map +1 -0
- package/dist/hooks/matcher.js +147 -0
- package/dist/hooks/settings.d.ts +46 -0
- package/dist/hooks/settings.d.ts.map +1 -0
- package/dist/hooks/settings.js +100 -0
- package/dist/hooks/types.d.ts +80 -0
- package/dist/hooks/types.d.ts.map +1 -0
- package/dist/hooks/types.js +59 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/managers/aiManager.d.ts +61 -0
- package/dist/managers/aiManager.d.ts.map +1 -0
- package/dist/managers/aiManager.js +415 -0
- package/dist/managers/backgroundBashManager.d.ts +27 -0
- package/dist/managers/backgroundBashManager.d.ts.map +1 -0
- package/dist/managers/backgroundBashManager.js +166 -0
- package/dist/managers/bashManager.d.ts +20 -0
- package/dist/managers/bashManager.d.ts.map +1 -0
- package/dist/managers/bashManager.js +66 -0
- package/dist/managers/mcpManager.d.ts +63 -0
- package/dist/managers/mcpManager.d.ts.map +1 -0
- package/dist/managers/mcpManager.js +378 -0
- package/dist/managers/messageManager.d.ts +85 -0
- package/dist/managers/messageManager.d.ts.map +1 -0
- package/dist/managers/messageManager.js +265 -0
- package/dist/managers/skillManager.d.ts +59 -0
- package/dist/managers/skillManager.d.ts.map +1 -0
- package/dist/managers/skillManager.js +317 -0
- package/dist/managers/slashCommandManager.d.ts +77 -0
- package/dist/managers/slashCommandManager.d.ts.map +1 -0
- package/dist/managers/slashCommandManager.js +208 -0
- package/dist/managers/toolManager.d.ts +23 -0
- package/dist/managers/toolManager.d.ts.map +1 -0
- package/dist/managers/toolManager.js +79 -0
- package/dist/services/aiService.d.ts +28 -0
- package/dist/services/aiService.d.ts.map +1 -0
- package/dist/services/aiService.js +180 -0
- package/dist/services/memory.d.ts +8 -0
- package/dist/services/memory.d.ts.map +1 -0
- package/dist/services/memory.js +128 -0
- package/dist/services/session.d.ts +54 -0
- package/dist/services/session.d.ts.map +1 -0
- package/dist/services/session.js +196 -0
- package/dist/tools/bashTool.d.ts +14 -0
- package/dist/tools/bashTool.d.ts.map +1 -0
- package/dist/tools/bashTool.js +351 -0
- package/dist/tools/deleteFileTool.d.ts +6 -0
- package/dist/tools/deleteFileTool.d.ts.map +1 -0
- package/dist/tools/deleteFileTool.js +67 -0
- package/dist/tools/editTool.d.ts +6 -0
- package/dist/tools/editTool.d.ts.map +1 -0
- package/dist/tools/editTool.js +168 -0
- package/dist/tools/globTool.d.ts +6 -0
- package/dist/tools/globTool.d.ts.map +1 -0
- package/dist/tools/globTool.js +113 -0
- package/dist/tools/grepTool.d.ts +6 -0
- package/dist/tools/grepTool.d.ts.map +1 -0
- package/dist/tools/grepTool.js +268 -0
- package/dist/tools/lsTool.d.ts +6 -0
- package/dist/tools/lsTool.d.ts.map +1 -0
- package/dist/tools/lsTool.js +160 -0
- package/dist/tools/multiEditTool.d.ts +6 -0
- package/dist/tools/multiEditTool.d.ts.map +1 -0
- package/dist/tools/multiEditTool.js +222 -0
- package/dist/tools/readTool.d.ts +6 -0
- package/dist/tools/readTool.d.ts.map +1 -0
- package/dist/tools/readTool.js +136 -0
- package/dist/tools/types.d.ts +35 -0
- package/dist/tools/types.d.ts.map +1 -0
- package/dist/tools/types.js +4 -0
- package/dist/tools/writeTool.d.ts +6 -0
- package/dist/tools/writeTool.d.ts.map +1 -0
- package/dist/tools/writeTool.js +138 -0
- package/dist/types.d.ts +212 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +13 -0
- package/dist/utils/bashHistory.d.ts +46 -0
- package/dist/utils/bashHistory.d.ts.map +1 -0
- package/dist/utils/bashHistory.js +236 -0
- package/dist/utils/commandArgumentParser.d.ts +34 -0
- package/dist/utils/commandArgumentParser.d.ts.map +1 -0
- package/dist/utils/commandArgumentParser.js +123 -0
- package/dist/utils/constants.d.ts +27 -0
- package/dist/utils/constants.d.ts.map +1 -0
- package/dist/utils/constants.js +28 -0
- package/dist/utils/convertMessagesForAPI.d.ts +9 -0
- package/dist/utils/convertMessagesForAPI.d.ts.map +1 -0
- package/dist/utils/convertMessagesForAPI.js +189 -0
- package/dist/utils/customCommands.d.ts +14 -0
- package/dist/utils/customCommands.d.ts.map +1 -0
- package/dist/utils/customCommands.js +71 -0
- package/dist/utils/fileFilter.d.ts +26 -0
- package/dist/utils/fileFilter.d.ts.map +1 -0
- package/dist/utils/fileFilter.js +177 -0
- package/dist/utils/markdownParser.d.ts +27 -0
- package/dist/utils/markdownParser.d.ts.map +1 -0
- package/dist/utils/markdownParser.js +109 -0
- package/dist/utils/mcpUtils.d.ts +24 -0
- package/dist/utils/mcpUtils.d.ts.map +1 -0
- package/dist/utils/mcpUtils.js +51 -0
- package/dist/utils/messageOperations.d.ts +118 -0
- package/dist/utils/messageOperations.d.ts.map +1 -0
- package/dist/utils/messageOperations.js +334 -0
- package/dist/utils/path.d.ts +25 -0
- package/dist/utils/path.d.ts.map +1 -0
- package/dist/utils/path.js +109 -0
- package/dist/utils/skillParser.d.ts +18 -0
- package/dist/utils/skillParser.d.ts.map +1 -0
- package/dist/utils/skillParser.js +147 -0
- package/dist/utils/stringUtils.d.ts +13 -0
- package/dist/utils/stringUtils.d.ts.map +1 -0
- package/dist/utils/stringUtils.js +44 -0
- package/package.json +51 -0
- package/src/agent.ts +405 -0
- package/src/hooks/executor.ts +440 -0
- package/src/hooks/index.ts +52 -0
- package/src/hooks/manager.ts +618 -0
- package/src/hooks/matcher.ts +187 -0
- package/src/hooks/settings.ts +129 -0
- package/src/hooks/types.ts +169 -0
- package/src/index.ts +24 -0
- package/src/managers/aiManager.ts +573 -0
- package/src/managers/backgroundBashManager.ts +203 -0
- package/src/managers/bashManager.ts +97 -0
- package/src/managers/mcpManager.ts +493 -0
- package/src/managers/messageManager.ts +415 -0
- package/src/managers/skillManager.ts +404 -0
- package/src/managers/slashCommandManager.ts +293 -0
- package/src/managers/toolManager.ts +106 -0
- package/src/services/aiService.ts +252 -0
- package/src/services/memory.ts +149 -0
- package/src/services/session.ts +265 -0
- package/src/tools/bashTool.ts +402 -0
- package/src/tools/deleteFileTool.ts +81 -0
- package/src/tools/editTool.ts +192 -0
- package/src/tools/globTool.ts +135 -0
- package/src/tools/grepTool.ts +326 -0
- package/src/tools/lsTool.ts +187 -0
- package/src/tools/multiEditTool.ts +268 -0
- package/src/tools/readTool.ts +165 -0
- package/src/tools/types.ts +47 -0
- package/src/tools/writeTool.ts +163 -0
- package/src/types.ts +260 -0
- package/src/utils/bashHistory.ts +303 -0
- package/src/utils/commandArgumentParser.ts +153 -0
- package/src/utils/constants.ts +37 -0
- package/src/utils/convertMessagesForAPI.ts +236 -0
- package/src/utils/customCommands.ts +85 -0
- package/src/utils/fileFilter.ts +202 -0
- package/src/utils/markdownParser.ts +156 -0
- package/src/utils/mcpUtils.ts +81 -0
- package/src/utils/messageOperations.ts +506 -0
- package/src/utils/path.ts +118 -0
- package/src/utils/skillParser.ts +188 -0
- package/src/utils/stringUtils.ts +50 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Application constants definition
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import path from "path";
|
|
6
|
+
import os from "os";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Application data storage directory
|
|
10
|
+
* Used to store debug logs, command history and other data
|
|
11
|
+
*/
|
|
12
|
+
export const DATA_DIRECTORY = path.join(os.homedir(), ".wave");
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Bash command history file path
|
|
16
|
+
*/
|
|
17
|
+
export const BASH_HISTORY_FILE = path.join(DATA_DIRECTORY, "bash-history.json");
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Error log directory path
|
|
21
|
+
*/
|
|
22
|
+
export const ERROR_LOG_DIRECTORY = path.join(DATA_DIRECTORY, "error-logs");
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* User-level memory file path
|
|
26
|
+
*/
|
|
27
|
+
export const USER_MEMORY_FILE = path.join(DATA_DIRECTORY, "user-memory.md");
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* AI related constants
|
|
31
|
+
*/
|
|
32
|
+
export const DEFAULT_TOKEN_LIMIT = 64000; // Default token limit
|
|
33
|
+
|
|
34
|
+
export const FAST_MODEL_ID = process.env.AIGW_FAST_MODEL || "gemini-2.5-flash";
|
|
35
|
+
|
|
36
|
+
export const AGENT_MODEL_ID =
|
|
37
|
+
process.env.AIGW_MODEL || "claude-sonnet-4-20250514";
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import type { Message } from "../types.js";
|
|
2
|
+
import { convertImageToBase64 } from "./messageOperations.js";
|
|
3
|
+
import { ChatCompletionMessageToolCall } from "openai/resources";
|
|
4
|
+
import { stripAnsiColors } from "./stringUtils.js";
|
|
5
|
+
import {
|
|
6
|
+
ChatCompletionContentPart,
|
|
7
|
+
ChatCompletionMessageParam,
|
|
8
|
+
} from "openai/resources.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Safely handle tool call parameters, ensuring a legal JSON string is returned
|
|
12
|
+
* @param args Tool call parameters
|
|
13
|
+
* @returns Legal JSON string
|
|
14
|
+
*/
|
|
15
|
+
function safeToolArguments(args: string): string {
|
|
16
|
+
if (!args) {
|
|
17
|
+
return "{}";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
// Try to parse as JSON to validate format
|
|
22
|
+
JSON.parse(args);
|
|
23
|
+
return args;
|
|
24
|
+
} catch {
|
|
25
|
+
// logger.error(`Invalid tool arguments: ${args}`);
|
|
26
|
+
// If not valid JSON, return a fallback empty object
|
|
27
|
+
return "{}";
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Convert message format to API call format, stopping when a compressed message is encountered
|
|
33
|
+
* @param messages Message list
|
|
34
|
+
* @returns Converted API message format list
|
|
35
|
+
*/
|
|
36
|
+
export function convertMessagesForAPI(
|
|
37
|
+
messages: Message[],
|
|
38
|
+
): ChatCompletionMessageParam[] {
|
|
39
|
+
const recentMessages: ChatCompletionMessageParam[] = [];
|
|
40
|
+
|
|
41
|
+
const startIndex = messages.length - 1;
|
|
42
|
+
for (let i = startIndex; i >= 0; i--) {
|
|
43
|
+
const message = messages[i];
|
|
44
|
+
|
|
45
|
+
// Check if a compression block is encountered, if so, stop iteration
|
|
46
|
+
if (
|
|
47
|
+
message.role === "assistant" &&
|
|
48
|
+
message.blocks.some((block) => block.type === "compress")
|
|
49
|
+
) {
|
|
50
|
+
// Add the content of the compression block as an assistant message to the history
|
|
51
|
+
const compressBlock = message.blocks.find(
|
|
52
|
+
(block) => block.type === "compress",
|
|
53
|
+
);
|
|
54
|
+
if (compressBlock && compressBlock.type === "compress") {
|
|
55
|
+
recentMessages.unshift({
|
|
56
|
+
role: "system",
|
|
57
|
+
content: `[Compressed Message Summary] ${compressBlock.content}`,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Skip empty assistant messages
|
|
64
|
+
if (message.role === "assistant" && message.blocks.length === 0) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (message.role === "assistant") {
|
|
69
|
+
// First check if there is a tool block, if so, add the tool message first
|
|
70
|
+
// Filter out incomplete tool blocks (no result or still running)
|
|
71
|
+
const toolBlocks = message.blocks.filter(
|
|
72
|
+
(block) => block.type === "tool",
|
|
73
|
+
);
|
|
74
|
+
const completedToolIds = new Set<string>(); // Record completed tool IDs
|
|
75
|
+
|
|
76
|
+
if (toolBlocks.length > 0) {
|
|
77
|
+
toolBlocks.forEach((toolBlock) => {
|
|
78
|
+
// Only add completed tool blocks (i.e., not running)
|
|
79
|
+
if (toolBlock.id && !toolBlock.isRunning) {
|
|
80
|
+
completedToolIds.add(toolBlock.id);
|
|
81
|
+
|
|
82
|
+
// Check for image data
|
|
83
|
+
if (toolBlock.images && toolBlock.images.length > 0) {
|
|
84
|
+
// If there is an image, create a user message instead of a tool message
|
|
85
|
+
const contentParts: ChatCompletionContentPart[] = [];
|
|
86
|
+
|
|
87
|
+
// Add tool result as text
|
|
88
|
+
const toolResultText = `Tool result for ${toolBlock.name || "unknown tool"}:\n${stripAnsiColors(toolBlock.result || "")}`;
|
|
89
|
+
contentParts.push({
|
|
90
|
+
type: "text",
|
|
91
|
+
text: toolResultText,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Add image
|
|
95
|
+
toolBlock.images.forEach((image) => {
|
|
96
|
+
const imageUrl = image.data.startsWith("data:")
|
|
97
|
+
? image.data
|
|
98
|
+
: `data:${image.mediaType || "image/png"};base64,${image.data}`;
|
|
99
|
+
|
|
100
|
+
contentParts.push({
|
|
101
|
+
type: "image_url",
|
|
102
|
+
image_url: {
|
|
103
|
+
url: imageUrl,
|
|
104
|
+
detail: "auto",
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Add user message
|
|
110
|
+
recentMessages.unshift({
|
|
111
|
+
role: "user",
|
|
112
|
+
content: contentParts,
|
|
113
|
+
});
|
|
114
|
+
} else {
|
|
115
|
+
// Normal tool message
|
|
116
|
+
recentMessages.unshift({
|
|
117
|
+
tool_call_id: toolBlock.id,
|
|
118
|
+
role: "tool",
|
|
119
|
+
content: stripAnsiColors(toolBlock.result || ""),
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Construct the content of the assistant message
|
|
127
|
+
let content = "";
|
|
128
|
+
let tool_calls: ChatCompletionMessageToolCall[] | undefined = undefined;
|
|
129
|
+
|
|
130
|
+
// Construct content from text blocks
|
|
131
|
+
const textBlocks = message.blocks.filter(
|
|
132
|
+
(block) => block.type === "text",
|
|
133
|
+
);
|
|
134
|
+
if (textBlocks.length > 0) {
|
|
135
|
+
content = textBlocks.map((block) => block.content || "").join("\n");
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Construct tool calls from tool blocks
|
|
139
|
+
if (toolBlocks.length > 0) {
|
|
140
|
+
tool_calls = toolBlocks
|
|
141
|
+
.filter(
|
|
142
|
+
(toolBlock) => toolBlock.id && completedToolIds.has(toolBlock.id),
|
|
143
|
+
)
|
|
144
|
+
.map((toolBlock) => ({
|
|
145
|
+
id: toolBlock.id!,
|
|
146
|
+
type: "function",
|
|
147
|
+
function: {
|
|
148
|
+
name: toolBlock.name || "",
|
|
149
|
+
arguments: safeToolArguments(
|
|
150
|
+
String(toolBlock.parameters || "{}"),
|
|
151
|
+
),
|
|
152
|
+
},
|
|
153
|
+
}));
|
|
154
|
+
|
|
155
|
+
if (tool_calls.length === 0) {
|
|
156
|
+
tool_calls = undefined;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Construct assistant message - only add if there is content or tool calls
|
|
161
|
+
if (content || tool_calls) {
|
|
162
|
+
const assistantMessage: ChatCompletionMessageParam = {
|
|
163
|
+
role: "assistant",
|
|
164
|
+
content,
|
|
165
|
+
tool_calls,
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
recentMessages.unshift(assistantMessage);
|
|
169
|
+
}
|
|
170
|
+
} else if (message.role === "user") {
|
|
171
|
+
// User messages converted to standard format
|
|
172
|
+
const contentParts: ChatCompletionContentPart[] = [];
|
|
173
|
+
|
|
174
|
+
message.blocks.forEach((block) => {
|
|
175
|
+
// Add text content
|
|
176
|
+
if (block.type === "text" && block.content) {
|
|
177
|
+
contentParts.push({
|
|
178
|
+
type: "text",
|
|
179
|
+
text: block.content,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Handle custom command blocks - pass full content as text to AI
|
|
184
|
+
if (block.type === "custom_command" && block.content) {
|
|
185
|
+
contentParts.push({
|
|
186
|
+
type: "text",
|
|
187
|
+
text: block.content,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// If there is an image, add image content
|
|
192
|
+
if (
|
|
193
|
+
block.type === "image" &&
|
|
194
|
+
block.imageUrls &&
|
|
195
|
+
block.imageUrls.length > 0
|
|
196
|
+
) {
|
|
197
|
+
block.imageUrls.forEach((imageUrl: string) => {
|
|
198
|
+
// Check if it's already base64, convert if not
|
|
199
|
+
let finalImageUrl = imageUrl;
|
|
200
|
+
if (!imageUrl.startsWith("data:image/")) {
|
|
201
|
+
// If it's a file path, it needs to be converted to base64
|
|
202
|
+
try {
|
|
203
|
+
finalImageUrl = convertImageToBase64(imageUrl);
|
|
204
|
+
} catch (error) {
|
|
205
|
+
console.error(
|
|
206
|
+
"Failed to convert image path to base64:",
|
|
207
|
+
imageUrl,
|
|
208
|
+
error,
|
|
209
|
+
);
|
|
210
|
+
// Skip this image, do not add to content
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
contentParts.push({
|
|
216
|
+
type: "image_url",
|
|
217
|
+
image_url: {
|
|
218
|
+
url: finalImageUrl,
|
|
219
|
+
detail: "auto",
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
if (contentParts.length > 0) {
|
|
227
|
+
recentMessages.unshift({
|
|
228
|
+
role: "user",
|
|
229
|
+
content: contentParts,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return recentMessages;
|
|
236
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { existsSync, readdirSync } from "fs";
|
|
2
|
+
import { join, extname, basename } from "path";
|
|
3
|
+
import { homedir } from "os";
|
|
4
|
+
import type { CustomSlashCommand } from "../types.js";
|
|
5
|
+
import { parseMarkdownFile } from "./markdownParser.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Get the project-specific commands directory
|
|
9
|
+
*/
|
|
10
|
+
export function getProjectCommandsDir(workdir: string): string {
|
|
11
|
+
return join(workdir, ".wave", "commands");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get the user-specific commands directory
|
|
16
|
+
*/
|
|
17
|
+
export function getUserCommandsDir(): string {
|
|
18
|
+
return join(homedir(), ".wave", "commands");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Scan a directory for markdown command files
|
|
23
|
+
*/
|
|
24
|
+
function scanCommandsDirectory(dirPath: string): CustomSlashCommand[] {
|
|
25
|
+
if (!existsSync(dirPath)) {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const commands: CustomSlashCommand[] = [];
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const files = readdirSync(dirPath);
|
|
33
|
+
|
|
34
|
+
for (const file of files) {
|
|
35
|
+
if (extname(file) !== ".md") {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const filePath = join(dirPath, file);
|
|
40
|
+
const commandName = basename(file, ".md");
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
const { content, config } = parseMarkdownFile(filePath);
|
|
44
|
+
|
|
45
|
+
commands.push({
|
|
46
|
+
id: commandName,
|
|
47
|
+
name: commandName,
|
|
48
|
+
description: config?.description, // Use description from frontmatter
|
|
49
|
+
filePath,
|
|
50
|
+
content,
|
|
51
|
+
config,
|
|
52
|
+
});
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.warn(`Failed to load custom command from ${filePath}:`, error);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.warn(`Failed to scan commands directory ${dirPath}:`, error);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return commands;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Load all custom slash commands from both project and user directories
|
|
66
|
+
*/
|
|
67
|
+
export function loadCustomSlashCommands(workdir: string): CustomSlashCommand[] {
|
|
68
|
+
const projectCommands = scanCommandsDirectory(getProjectCommandsDir(workdir));
|
|
69
|
+
const userCommands = scanCommandsDirectory(getUserCommandsDir());
|
|
70
|
+
|
|
71
|
+
// Project commands take precedence over user commands with the same name
|
|
72
|
+
const commandMap = new Map<string, CustomSlashCommand>();
|
|
73
|
+
|
|
74
|
+
// Add user commands first
|
|
75
|
+
for (const command of userCommands) {
|
|
76
|
+
commandMap.set(command.id, command);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Add project commands (will overwrite user commands with same name)
|
|
80
|
+
for (const command of projectCommands) {
|
|
81
|
+
commandMap.set(command.id, command);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return Array.from(commandMap.values());
|
|
85
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Common ignore directory and file patterns
|
|
6
|
+
* Can be reused by multiple tools (glob, ripgrep, etc.)
|
|
7
|
+
*/
|
|
8
|
+
export const COMMON_IGNORE_PATTERNS = {
|
|
9
|
+
// Dependencies and build directories
|
|
10
|
+
dependencies: [
|
|
11
|
+
"node_modules/**",
|
|
12
|
+
".git/**",
|
|
13
|
+
"dist/**",
|
|
14
|
+
"build/**",
|
|
15
|
+
".next/**",
|
|
16
|
+
"coverage/**",
|
|
17
|
+
".nyc_output/**",
|
|
18
|
+
"tmp/**",
|
|
19
|
+
"temp/**",
|
|
20
|
+
],
|
|
21
|
+
|
|
22
|
+
// Cache and temporary files
|
|
23
|
+
cache: ["*.log", "*.cache", ".DS_Store", "Thumbs.db", "*~", "*.swp", "*.swo"],
|
|
24
|
+
|
|
25
|
+
// Editor and IDE files
|
|
26
|
+
editor: [".vscode/**", ".idea/**", "*.sublime-*"],
|
|
27
|
+
|
|
28
|
+
// Operating system related
|
|
29
|
+
os: [".DS_Store", "Thumbs.db", "desktop.ini"],
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Get flat array of all common ignore patterns
|
|
34
|
+
*/
|
|
35
|
+
export const getAllIgnorePatterns = (): string[] => {
|
|
36
|
+
return [
|
|
37
|
+
...COMMON_IGNORE_PATTERNS.dependencies,
|
|
38
|
+
...COMMON_IGNORE_PATTERNS.cache,
|
|
39
|
+
...COMMON_IGNORE_PATTERNS.editor,
|
|
40
|
+
...COMMON_IGNORE_PATTERNS.os,
|
|
41
|
+
];
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Recursively find all .gitignore files in directory
|
|
46
|
+
* @param dir Directory to search
|
|
47
|
+
* @param maxDepth Maximum recursion depth to prevent too deep searches
|
|
48
|
+
* @returns Array of .gitignore file paths
|
|
49
|
+
*/
|
|
50
|
+
const findAllGitignoreFiles = (dir: string, maxDepth: number = 5): string[] => {
|
|
51
|
+
const gitignoreFiles: string[] = [];
|
|
52
|
+
|
|
53
|
+
if (maxDepth <= 0) return gitignoreFiles;
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
const items = fs.readdirSync(dir, { withFileTypes: true });
|
|
57
|
+
|
|
58
|
+
for (const item of items) {
|
|
59
|
+
const fullPath = path.join(dir, item.name);
|
|
60
|
+
|
|
61
|
+
if (item.isFile() && item.name === ".gitignore") {
|
|
62
|
+
gitignoreFiles.push(fullPath);
|
|
63
|
+
} else if (item.isDirectory() && !shouldSkipDirectory(item.name)) {
|
|
64
|
+
// Recursively search subdirectories, but skip some obviously unnecessary directories
|
|
65
|
+
gitignoreFiles.push(...findAllGitignoreFiles(fullPath, maxDepth - 1));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
} catch {
|
|
69
|
+
// Ignore permission errors and other issues
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return gitignoreFiles;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Determine whether to skip searching a directory
|
|
77
|
+
*/
|
|
78
|
+
const shouldSkipDirectory = (dirName: string): boolean => {
|
|
79
|
+
const skipDirs = [
|
|
80
|
+
"node_modules",
|
|
81
|
+
".git",
|
|
82
|
+
"dist",
|
|
83
|
+
"build",
|
|
84
|
+
".next",
|
|
85
|
+
"coverage",
|
|
86
|
+
".nyc_output",
|
|
87
|
+
"tmp",
|
|
88
|
+
"temp",
|
|
89
|
+
".cache",
|
|
90
|
+
];
|
|
91
|
+
return skipDirs.includes(dirName);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Parse single .gitignore file content
|
|
96
|
+
* @param gitignorePath .gitignore file path
|
|
97
|
+
* @param basePath Base path for calculating relative paths
|
|
98
|
+
* @returns Array of parsed glob patterns
|
|
99
|
+
*/
|
|
100
|
+
const parseGitignoreFile = (
|
|
101
|
+
gitignorePath: string,
|
|
102
|
+
basePath: string,
|
|
103
|
+
): string[] => {
|
|
104
|
+
const patterns: string[] = [];
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
if (fs.existsSync(gitignorePath)) {
|
|
108
|
+
const gitignoreContent = fs.readFileSync(gitignorePath, "utf8");
|
|
109
|
+
const gitignoreDir = path.dirname(gitignorePath);
|
|
110
|
+
// Calculate relative directory relative to base path
|
|
111
|
+
const relativeDirFromBase = path.relative(basePath, gitignoreDir);
|
|
112
|
+
|
|
113
|
+
const lines = gitignoreContent
|
|
114
|
+
.split("\n")
|
|
115
|
+
.map((line) => line.trim())
|
|
116
|
+
.filter((line) => line && !line.startsWith("#"));
|
|
117
|
+
|
|
118
|
+
for (const line of lines) {
|
|
119
|
+
// Skip negation rules (starting with !)
|
|
120
|
+
if (line.startsWith("!")) {
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
let pattern = line;
|
|
125
|
+
|
|
126
|
+
// Handle patterns starting with / (relative to current .gitignore file directory)
|
|
127
|
+
if (pattern.startsWith("/")) {
|
|
128
|
+
pattern = pattern.slice(1); // Remove leading /
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// If .gitignore is in subdirectory, need to add path prefix
|
|
132
|
+
if (relativeDirFromBase && relativeDirFromBase !== ".") {
|
|
133
|
+
pattern = path.posix.join(relativeDirFromBase, pattern);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// If directory pattern (ending with /)
|
|
137
|
+
if (pattern.endsWith("/")) {
|
|
138
|
+
const dirName = pattern.slice(0, -1);
|
|
139
|
+
// For directory patterns, add both exact match and wildcard match
|
|
140
|
+
patterns.push(`${dirName}/**`); // Directory and all its sub-content
|
|
141
|
+
// If no path separators, it's a simple directory name, add global match
|
|
142
|
+
if (!dirName.includes("/") && !dirName.includes("*")) {
|
|
143
|
+
patterns.push(`**/${dirName}/**`); // Match directories of same name at any level
|
|
144
|
+
}
|
|
145
|
+
} else {
|
|
146
|
+
// File pattern
|
|
147
|
+
patterns.push(pattern);
|
|
148
|
+
// If no wildcards and no extension, also treat as directory
|
|
149
|
+
if (!pattern.includes("*") && !pattern.includes(".")) {
|
|
150
|
+
patterns.push(`${pattern}/**`);
|
|
151
|
+
// Also add global match for simple directory names
|
|
152
|
+
if (!pattern.includes("/")) {
|
|
153
|
+
patterns.push(`**/${pattern}/**`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
} catch {
|
|
160
|
+
// Ignore errors when reading .gitignore files
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return patterns;
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Parse all .gitignore files in the working directory and its subdirectories and convert to glob patterns
|
|
168
|
+
* @param workdir Working directory
|
|
169
|
+
* @returns Array of glob ignore patterns
|
|
170
|
+
*/
|
|
171
|
+
export const parseGitignoreToGlob = (workdir: string): string[] => {
|
|
172
|
+
const patterns: string[] = [];
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
// Find all .gitignore files
|
|
176
|
+
const gitignoreFiles = findAllGitignoreFiles(workdir);
|
|
177
|
+
|
|
178
|
+
// Parse each .gitignore file
|
|
179
|
+
for (const gitignoreFile of gitignoreFiles) {
|
|
180
|
+
patterns.push(...parseGitignoreFile(gitignoreFile, workdir));
|
|
181
|
+
}
|
|
182
|
+
} catch {
|
|
183
|
+
// Ignore errors during search process
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return patterns;
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Get ignore patterns for glob search
|
|
191
|
+
* @param workdir Working directory for resolving .gitignore files
|
|
192
|
+
*/
|
|
193
|
+
export const getGlobIgnorePatterns = (workdir?: string): string[] => {
|
|
194
|
+
const patterns = getAllIgnorePatterns();
|
|
195
|
+
|
|
196
|
+
// If working directory is provided, parse .gitignore files
|
|
197
|
+
if (workdir) {
|
|
198
|
+
patterns.push(...parseGitignoreToGlob(workdir));
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return patterns;
|
|
202
|
+
};
|