thatgfsj-code 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +136 -0
- package/ROADMAP.md +61 -0
- package/dist/app/agent.d.ts +31 -0
- package/dist/app/agent.d.ts.map +1 -0
- package/dist/app/agent.js +106 -0
- package/dist/app/agent.js.map +1 -0
- package/dist/app/index.d.ts +39 -0
- package/dist/app/index.d.ts.map +1 -0
- package/dist/app/index.js +80 -0
- package/dist/app/index.js.map +1 -0
- package/dist/cmd/index.d.ts +12 -0
- package/dist/cmd/index.d.ts.map +1 -0
- package/dist/cmd/index.js +85 -0
- package/dist/cmd/index.js.map +1 -0
- package/dist/config/index.d.ts +39 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +110 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/providers.d.ts +23 -0
- package/dist/config/providers.d.ts.map +1 -0
- package/dist/config/providers.js +152 -0
- package/dist/config/providers.js.map +1 -0
- package/dist/config/types.d.ts +33 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +5 -0
- package/dist/config/types.js.map +1 -0
- package/dist/hooks/index.d.ts +31 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +71 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/llm/anthropic.d.ts +17 -0
- package/dist/llm/anthropic.d.ts.map +1 -0
- package/dist/llm/anthropic.js +126 -0
- package/dist/llm/anthropic.js.map +1 -0
- package/dist/llm/gemini.d.ts +16 -0
- package/dist/llm/gemini.d.ts.map +1 -0
- package/dist/llm/gemini.js +107 -0
- package/dist/llm/gemini.js.map +1 -0
- package/dist/llm/index.d.ts +48 -0
- package/dist/llm/index.d.ts.map +1 -0
- package/dist/llm/index.js +174 -0
- package/dist/llm/index.js.map +1 -0
- package/dist/llm/openai.d.ts +17 -0
- package/dist/llm/openai.d.ts.map +1 -0
- package/dist/llm/openai.js +113 -0
- package/dist/llm/openai.js.map +1 -0
- package/dist/llm/provider.d.ts +33 -0
- package/dist/llm/provider.d.ts.map +1 -0
- package/dist/llm/provider.js +6 -0
- package/dist/llm/provider.js.map +1 -0
- package/dist/mcp/client.d.ts +83 -0
- package/dist/mcp/client.d.ts.map +1 -0
- package/dist/mcp/client.js +267 -0
- package/dist/mcp/client.js.map +1 -0
- package/dist/prompts/index.d.ts +27 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +102 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/session/compactor.d.ts +48 -0
- package/dist/session/compactor.d.ts.map +1 -0
- package/dist/session/compactor.js +77 -0
- package/dist/session/compactor.js.map +1 -0
- package/dist/session/index.d.ts +48 -0
- package/dist/session/index.d.ts.map +1 -0
- package/dist/session/index.js +69 -0
- package/dist/session/index.js.map +1 -0
- package/dist/session/message.d.ts +26 -0
- package/dist/session/message.d.ts.map +1 -0
- package/dist/session/message.js +34 -0
- package/dist/session/message.js.map +1 -0
- package/dist/tools/file.d.ts +46 -0
- package/dist/tools/file.d.ts.map +1 -0
- package/dist/tools/file.js +100 -0
- package/dist/tools/file.js.map +1 -0
- package/dist/tools/git.d.ts +52 -0
- package/dist/tools/git.d.ts.map +1 -0
- package/dist/tools/git.js +112 -0
- package/dist/tools/git.js.map +1 -0
- package/dist/tools/index.d.ts +42 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +92 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/search.d.ts +36 -0
- package/dist/tools/search.d.ts.map +1 -0
- package/dist/tools/search.js +223 -0
- package/dist/tools/search.js.map +1 -0
- package/dist/tools/shell.d.ts +50 -0
- package/dist/tools/shell.d.ts.map +1 -0
- package/dist/tools/shell.js +117 -0
- package/dist/tools/shell.js.map +1 -0
- package/dist/tools/types.d.ts +68 -0
- package/dist/tools/types.d.ts.map +1 -0
- package/dist/tools/types.js +57 -0
- package/dist/tools/types.js.map +1 -0
- package/dist/tui/index.d.ts +8 -0
- package/dist/tui/index.d.ts.map +1 -0
- package/dist/tui/index.js +8 -0
- package/dist/tui/index.js.map +1 -0
- package/dist/tui/input.d.ts +14 -0
- package/dist/tui/input.d.ts.map +1 -0
- package/dist/tui/input.js +50 -0
- package/dist/tui/input.js.map +1 -0
- package/dist/tui/output.d.ts +20 -0
- package/dist/tui/output.d.ts.map +1 -0
- package/dist/tui/output.js +70 -0
- package/dist/tui/output.js.map +1 -0
- package/dist/tui/repl.d.ts +25 -0
- package/dist/tui/repl.d.ts.map +1 -0
- package/dist/tui/repl.js +114 -0
- package/dist/tui/repl.js.map +1 -0
- package/dist/tui/welcome.d.ts +15 -0
- package/dist/tui/welcome.d.ts.map +1 -0
- package/dist/tui/welcome.js +86 -0
- package/dist/tui/welcome.js.map +1 -0
- package/dist/types.d.ts +38 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/diff.d.ts +22 -0
- package/dist/utils/diff.d.ts.map +1 -0
- package/dist/utils/diff.js +60 -0
- package/dist/utils/diff.js.map +1 -0
- package/dist/utils/project.d.ts +32 -0
- package/dist/utils/project.d.ts.map +1 -0
- package/dist/utils/project.js +89 -0
- package/dist/utils/project.js.map +1 -0
- package/docs/API_KEY_GUIDE.md +236 -0
- package/docs/FAQ.md +182 -0
- package/install.bat +63 -0
- package/install.ps1 +238 -0
- package/install.sh +113 -0
- package/package.json +36 -0
- package/src/app/agent.ts +140 -0
- package/src/app/index.ts +101 -0
- package/src/cmd/index.ts +96 -0
- package/src/config/index.ts +130 -0
- package/src/config/providers.ts +160 -0
- package/src/config/types.ts +46 -0
- package/src/hooks/index.ts +111 -0
- package/src/llm/anthropic.ts +146 -0
- package/src/llm/gemini.ts +127 -0
- package/src/llm/index.ts +209 -0
- package/src/llm/openai.ts +132 -0
- package/src/llm/provider.ts +38 -0
- package/src/mcp/client.ts +330 -0
- package/src/prompts/index.ts +123 -0
- package/src/session/compactor.ts +103 -0
- package/src/session/index.ts +81 -0
- package/src/session/message.ts +42 -0
- package/src/tools/file.ts +117 -0
- package/src/tools/git.ts +132 -0
- package/src/tools/index.ts +108 -0
- package/src/tools/search.ts +263 -0
- package/src/tools/shell.ts +136 -0
- package/src/tools/types.ts +122 -0
- package/src/tui/index.ts +8 -0
- package/src/tui/input.ts +56 -0
- package/src/tui/output.ts +83 -0
- package/src/tui/repl.ts +131 -0
- package/src/tui/welcome.ts +101 -0
- package/src/types.ts +42 -0
- package/src/utils/diff.ts +71 -0
- package/src/utils/project.ts +99 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* System Prompt Builder - Dynamic prompt construction
|
|
3
|
+
* Migrated from old src/core/system-prompt.ts
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { readFileSync, existsSync } from 'fs';
|
|
7
|
+
import { join } from 'path';
|
|
8
|
+
import type { Tool } from '../tools/types.js';
|
|
9
|
+
|
|
10
|
+
export interface SystemPromptConfig {
|
|
11
|
+
cwd?: string;
|
|
12
|
+
tools?: Tool[];
|
|
13
|
+
includeProjectMd?: boolean;
|
|
14
|
+
permissionMode?: 'accept' | 'deny' | 'ask';
|
|
15
|
+
date?: Date;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class SystemPromptBuilder {
|
|
19
|
+
private config: Required<SystemPromptConfig>;
|
|
20
|
+
|
|
21
|
+
constructor(config: SystemPromptConfig = {}) {
|
|
22
|
+
this.config = {
|
|
23
|
+
cwd: config.cwd ?? process.cwd(),
|
|
24
|
+
tools: config.tools ?? [],
|
|
25
|
+
includeProjectMd: config.includeProjectMd ?? true,
|
|
26
|
+
permissionMode: config.permissionMode ?? 'ask',
|
|
27
|
+
date: config.date ?? new Date(),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
build(): string {
|
|
32
|
+
const fragments: string[] = [
|
|
33
|
+
this.buildIdentity(),
|
|
34
|
+
this.buildToolInstructions(),
|
|
35
|
+
this.buildEnvironment(),
|
|
36
|
+
this.buildPermissionMode(),
|
|
37
|
+
this.buildProjectInstructions(),
|
|
38
|
+
this.buildDateInfo(),
|
|
39
|
+
];
|
|
40
|
+
return fragments.filter(Boolean).join('\n\n');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private buildIdentity(): string {
|
|
44
|
+
return [
|
|
45
|
+
'You are Thatgfsj Code, an interactive coding agent running in the user\'s terminal.',
|
|
46
|
+
'',
|
|
47
|
+
'You have access to tools (file operations, shell commands, git, search) that let you',
|
|
48
|
+
'read, write, and modify files and run commands to help with coding tasks.',
|
|
49
|
+
'',
|
|
50
|
+
'When working on a task:',
|
|
51
|
+
'1. First understand what files are involved',
|
|
52
|
+
'2. Read necessary files to understand the codebase',
|
|
53
|
+
'3. Make changes using appropriate tools',
|
|
54
|
+
'4. Verify the changes work',
|
|
55
|
+
'',
|
|
56
|
+
'Be concise but thorough. Show your reasoning.',
|
|
57
|
+
'',
|
|
58
|
+
'When using tools:',
|
|
59
|
+
'- Prefer small, targeted operations over large batch changes',
|
|
60
|
+
'- Confirm destructive operations (rm, git push --force, etc.)',
|
|
61
|
+
'- Explain what you are going to do before doing it',
|
|
62
|
+
].join('\n');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private buildToolInstructions(): string {
|
|
66
|
+
const tools = this.config.tools;
|
|
67
|
+
if (tools.length === 0) {
|
|
68
|
+
return '## Tools\n\nNo tools are currently registered.';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const toolDescs = tools.map(t => {
|
|
72
|
+
const params = t.parameters
|
|
73
|
+
.map(p => ` - ${p.name} (${p.type}${p.required ? ', required' : ''}): ${p.description}`)
|
|
74
|
+
.join('\n');
|
|
75
|
+
return `### ${t.name}\n${t.description}\n\nParameters:\n${params}`;
|
|
76
|
+
}).join('\n\n');
|
|
77
|
+
|
|
78
|
+
return `## Tools\n\nYou have access to the following tools:\n\n${toolDescs}\n\nTo use a tool, respond with a tool call. Each tool call must include the tool name and parameters as a JSON object.`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private buildEnvironment(): string {
|
|
82
|
+
return `## Environment\n\nWorking directory: ${this.config.cwd}`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private buildPermissionMode(): string {
|
|
86
|
+
const mode = this.config.permissionMode;
|
|
87
|
+
const explanations: Record<string, string> = {
|
|
88
|
+
accept: 'All tool calls are automatically allowed without confirmation.',
|
|
89
|
+
deny: 'All tool calls are blocked. You may only read and discuss.',
|
|
90
|
+
ask: 'Dangerous or destructive commands require user confirmation before execution.',
|
|
91
|
+
};
|
|
92
|
+
return `## Permission Mode\n\nCurrent mode: ${mode}\n\n${explanations[mode] || ''}`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
private buildProjectInstructions(): string {
|
|
96
|
+
if (!this.config.includeProjectMd) return '';
|
|
97
|
+
|
|
98
|
+
const cwd = this.config.cwd;
|
|
99
|
+
const paths = [join(cwd, 'CLAUDE.md'), join(cwd, '.claude.md'), join(cwd, 'AGENTS.md')];
|
|
100
|
+
|
|
101
|
+
for (const path of paths) {
|
|
102
|
+
if (existsSync(path)) {
|
|
103
|
+
try {
|
|
104
|
+
const content = readFileSync(path, 'utf-8');
|
|
105
|
+
return `## Project Instructions (from ${path.split(/[/\\]/).pop()})\n\n${content}`;
|
|
106
|
+
} catch {
|
|
107
|
+
// Ignore
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return '';
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private buildDateInfo(): string {
|
|
115
|
+
const now = this.config.date;
|
|
116
|
+
const iso = now.toISOString().replace('T', ' ').split('.')[0];
|
|
117
|
+
return `## Current Time\n\n${iso}`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
setTools(tools: Tool[]): this { this.config.tools = tools; return this; }
|
|
121
|
+
setCwd(cwd: string): this { this.config.cwd = cwd; return this; }
|
|
122
|
+
setPermissionMode(mode: 'accept' | 'deny' | 'ask'): this { this.config.permissionMode = mode; return this; }
|
|
123
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context Compactor - Progressive compression for conversation history
|
|
3
|
+
* Migrated from old src/core/context-compactor.ts
|
|
4
|
+
*
|
|
5
|
+
* Strategy:
|
|
6
|
+
* 1. Within limit: no compression
|
|
7
|
+
* 2. Beyond limit: keep system + recent N turns + summarize middle
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { ChatMessage } from '../types.js';
|
|
11
|
+
|
|
12
|
+
export interface CompactorConfig {
|
|
13
|
+
/** Max messages before compression kicks in */
|
|
14
|
+
maxMessages?: number;
|
|
15
|
+
/** How many recent turns to always preserve */
|
|
16
|
+
preserveRecent?: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface CompressionResult {
|
|
20
|
+
originalCount: number;
|
|
21
|
+
compactedCount: number;
|
|
22
|
+
removedCount: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class ContextCompactor {
|
|
26
|
+
private maxMessages: number;
|
|
27
|
+
private preserveRecent: number;
|
|
28
|
+
|
|
29
|
+
constructor(config: CompactorConfig = {}) {
|
|
30
|
+
this.maxMessages = config.maxMessages ?? 50;
|
|
31
|
+
this.preserveRecent = config.preserveRecent ?? 10;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Compact messages if needed
|
|
36
|
+
*/
|
|
37
|
+
compact(messages: ChatMessage[]): { compacted: ChatMessage[]; result: CompressionResult } {
|
|
38
|
+
if (messages.length <= this.maxMessages) {
|
|
39
|
+
return {
|
|
40
|
+
compacted: messages,
|
|
41
|
+
result: { originalCount: messages.length, compactedCount: messages.length, removedCount: 0 },
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const systemMsg = messages.find(m => m.role === 'system');
|
|
46
|
+
const others = messages.filter(m => m.role !== 'system');
|
|
47
|
+
|
|
48
|
+
if (others.length <= this.preserveRecent) {
|
|
49
|
+
return {
|
|
50
|
+
compacted: messages,
|
|
51
|
+
result: { originalCount: messages.length, compactedCount: messages.length, removedCount: 0 },
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Keep recent N turns
|
|
56
|
+
const recent = others.slice(-this.preserveRecent);
|
|
57
|
+
|
|
58
|
+
// Summarize the middle portion
|
|
59
|
+
const middle = others.slice(0, -this.preserveRecent);
|
|
60
|
+
const summary: ChatMessage = {
|
|
61
|
+
role: 'system',
|
|
62
|
+
content: `[Earlier conversation summary: ${middle.length} messages covering ${this.extractTopics(middle)}]`,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const compacted = systemMsg
|
|
66
|
+
? [systemMsg, summary, ...recent]
|
|
67
|
+
: [summary, ...recent];
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
compacted,
|
|
71
|
+
result: {
|
|
72
|
+
originalCount: messages.length,
|
|
73
|
+
compactedCount: compacted.length,
|
|
74
|
+
removedCount: messages.length - compacted.length,
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Auto-compact if messages exceed limit
|
|
81
|
+
*/
|
|
82
|
+
autoCompact(messages: ChatMessage[]): { compacted: ChatMessage[]; result: CompressionResult } {
|
|
83
|
+
return this.compact(messages);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Estimate token count (rough: ~4 chars per token)
|
|
88
|
+
*/
|
|
89
|
+
estimateTokens(messages: ChatMessage[]): number {
|
|
90
|
+
return messages.reduce((sum, m) => sum + Math.ceil(m.content.length / 4) + 10, 0);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Extract topic summary from messages
|
|
95
|
+
*/
|
|
96
|
+
private extractTopics(msgs: ChatMessage[]): string {
|
|
97
|
+
const topics = msgs
|
|
98
|
+
.filter(m => m.role === 'user')
|
|
99
|
+
.map(m => m.content.slice(0, 30))
|
|
100
|
+
.slice(0, 3);
|
|
101
|
+
return topics.join(', ') || 'various topics';
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Manager - Manages conversation history
|
|
3
|
+
* Migrated from old src/core/session.ts
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ChatMessage } from '../types.js';
|
|
7
|
+
import { ContextCompactor } from './compactor.js';
|
|
8
|
+
|
|
9
|
+
export class SessionManager {
|
|
10
|
+
private messages: ChatMessage[] = [];
|
|
11
|
+
private sessionId: string;
|
|
12
|
+
private createdAt: Date;
|
|
13
|
+
private compactor: ContextCompactor;
|
|
14
|
+
|
|
15
|
+
constructor(maxMessages = 50) {
|
|
16
|
+
this.sessionId = `session_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
17
|
+
this.createdAt = new Date();
|
|
18
|
+
this.compactor = new ContextCompactor({ maxMessages });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Add a message to the session
|
|
23
|
+
*/
|
|
24
|
+
addMessage(role: ChatMessage['role'], content: string, extras?: Partial<ChatMessage>): void {
|
|
25
|
+
this.messages.push({ role, content, ...extras });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Get all messages (copy)
|
|
30
|
+
*/
|
|
31
|
+
getMessages(): ChatMessage[] {
|
|
32
|
+
return [...this.messages];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Get message count
|
|
37
|
+
*/
|
|
38
|
+
getMessageCount(): number {
|
|
39
|
+
return this.messages.length;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Clear session history
|
|
44
|
+
*/
|
|
45
|
+
clear(): void {
|
|
46
|
+
this.messages = [];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Truncate messages to fit context window
|
|
51
|
+
*/
|
|
52
|
+
truncate(maxMessages?: number): void {
|
|
53
|
+
if (maxMessages) {
|
|
54
|
+
this.compactor = new ContextCompactor({ maxMessages });
|
|
55
|
+
}
|
|
56
|
+
const { compacted } = this.compactor.autoCompact(this.messages);
|
|
57
|
+
this.messages = compacted;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Get session ID
|
|
62
|
+
*/
|
|
63
|
+
getId(): string {
|
|
64
|
+
return this.sessionId;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Get session info
|
|
69
|
+
*/
|
|
70
|
+
getInfo(): { id: string; messageCount: number; createdAt: Date } {
|
|
71
|
+
return {
|
|
72
|
+
id: this.sessionId,
|
|
73
|
+
messageCount: this.messages.length,
|
|
74
|
+
createdAt: this.createdAt,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export { ContextCompactor } from './compactor.js';
|
|
80
|
+
export type { CompactorConfig, CompressionResult } from './compactor.js';
|
|
81
|
+
export * from './message.js';
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Message types and utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { ChatMessage, Role } from '../types.js';
|
|
6
|
+
|
|
7
|
+
export { ChatMessage, Role };
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Create a message
|
|
11
|
+
*/
|
|
12
|
+
export function createMessage(role: Role, content: string, extras?: Partial<ChatMessage>): ChatMessage {
|
|
13
|
+
return { role, content, ...extras };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Create a system message
|
|
18
|
+
*/
|
|
19
|
+
export function systemMessage(content: string): ChatMessage {
|
|
20
|
+
return { role: 'system', content };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Create a user message
|
|
25
|
+
*/
|
|
26
|
+
export function userMessage(content: string): ChatMessage {
|
|
27
|
+
return { role: 'user', content };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Create an assistant message
|
|
32
|
+
*/
|
|
33
|
+
export function assistantMessage(content: string): ChatMessage {
|
|
34
|
+
return { role: 'assistant', content };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Create a tool result message
|
|
39
|
+
*/
|
|
40
|
+
export function toolMessage(content: string, toolCallId: string, name: string): ChatMessage {
|
|
41
|
+
return { role: 'tool', content, tool_call_id: toolCallId, name };
|
|
42
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File Tool - File operations
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { Tool, ToolResult } from './types.js';
|
|
6
|
+
import { readFileSync, writeFileSync, existsSync, readdirSync, statSync, mkdirSync, unlinkSync } from 'fs';
|
|
7
|
+
import { join, dirname, basename, extname } from 'path';
|
|
8
|
+
|
|
9
|
+
export class FileTool implements Tool {
|
|
10
|
+
name = 'file';
|
|
11
|
+
description = 'Perform file operations: read, write, list, delete, etc.';
|
|
12
|
+
|
|
13
|
+
inputSchema = {
|
|
14
|
+
type: 'object' as const,
|
|
15
|
+
properties: {
|
|
16
|
+
action: { type: 'string', description: 'Action: read, write, list, delete, exists, mkdir' },
|
|
17
|
+
path: { type: 'string', description: 'File or directory path' },
|
|
18
|
+
content: { type: 'string', description: 'Content to write (for write action)' }
|
|
19
|
+
},
|
|
20
|
+
required: ['action', 'path']
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
metadata = {
|
|
24
|
+
permissions: ['read', 'write'] as ('read' | 'write' | 'execute' | 'network')[],
|
|
25
|
+
tags: ['file', 'filesystem'],
|
|
26
|
+
maxDuration: 30000,
|
|
27
|
+
version: '1.0.0'
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
parameters = [
|
|
31
|
+
{ name: 'action', type: 'string', description: 'Action to perform: read, write, list, delete, exists', required: true },
|
|
32
|
+
{ name: 'path', type: 'string', description: 'File or directory path', required: true },
|
|
33
|
+
{ name: 'content', type: 'string', description: 'Content to write (for write action)', required: false }
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
async execute(params: Record<string, any>): Promise<ToolResult> {
|
|
37
|
+
const { action, path, content } = params;
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
switch (action) {
|
|
41
|
+
case 'read':
|
|
42
|
+
return this.readFile(path);
|
|
43
|
+
case 'write':
|
|
44
|
+
return this.writeFile(path, content || '');
|
|
45
|
+
case 'list':
|
|
46
|
+
return this.listDir(path);
|
|
47
|
+
case 'delete':
|
|
48
|
+
return this.deleteFile(path);
|
|
49
|
+
case 'exists':
|
|
50
|
+
return this.checkExists(path);
|
|
51
|
+
case 'mkdir':
|
|
52
|
+
return this.mkdir(path);
|
|
53
|
+
default:
|
|
54
|
+
return { success: false, error: `Unknown action: ${action}` };
|
|
55
|
+
}
|
|
56
|
+
} catch (error: any) {
|
|
57
|
+
return { success: false, error: error.message };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private readFile(path: string): ToolResult {
|
|
62
|
+
if (!existsSync(path)) {
|
|
63
|
+
return { success: false, error: `File not found: ${path}` };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const content = readFileSync(path, 'utf-8');
|
|
67
|
+
return { success: true, output: content };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private writeFile(path: string, content: string): ToolResult {
|
|
71
|
+
const dir = dirname(path);
|
|
72
|
+
if (!existsSync(dir)) {
|
|
73
|
+
mkdirSync(dir, { recursive: true });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
writeFileSync(path, content, 'utf-8');
|
|
77
|
+
return { success: true, output: `File written: ${path}` };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private listDir(path: string): ToolResult {
|
|
81
|
+
if (!existsSync(path)) {
|
|
82
|
+
return { success: false, error: `Directory not found: ${path}` };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const files = readdirSync(path);
|
|
86
|
+
const items = files.map(f => {
|
|
87
|
+
const fullPath = join(path, f);
|
|
88
|
+
const stat = statSync(fullPath);
|
|
89
|
+
return {
|
|
90
|
+
name: f,
|
|
91
|
+
type: stat.isDirectory() ? 'dir' : 'file',
|
|
92
|
+
size: stat.size,
|
|
93
|
+
modified: stat.mtime.toISOString()
|
|
94
|
+
};
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
return { success: true, output: JSON.stringify(items, null, 2) };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private deleteFile(path: string): ToolResult {
|
|
101
|
+
if (!existsSync(path)) {
|
|
102
|
+
return { success: false, error: `Path not found: ${path}` };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
unlinkSync(path);
|
|
106
|
+
return { success: true, output: `Deleted: ${path}` };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private checkExists(path: string): ToolResult {
|
|
110
|
+
return { success: true, output: existsSync(path) ? 'true' : 'false' };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private mkdir(path: string): ToolResult {
|
|
114
|
+
mkdirSync(path, { recursive: true });
|
|
115
|
+
return { success: true, output: `Created: ${path}` };
|
|
116
|
+
}
|
|
117
|
+
}
|
package/src/tools/git.ts
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git Tool - Git operations
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { Tool, ToolResult } from './types.js';
|
|
6
|
+
import { exec } from 'child_process';
|
|
7
|
+
import { promisify } from 'util';
|
|
8
|
+
import { existsSync } from 'fs';
|
|
9
|
+
|
|
10
|
+
const execAsync = promisify(exec);
|
|
11
|
+
|
|
12
|
+
export class GitTool implements Tool {
|
|
13
|
+
name = 'git';
|
|
14
|
+
description = 'Git operations: status, log, diff, commit, branch, etc.';
|
|
15
|
+
|
|
16
|
+
inputSchema = {
|
|
17
|
+
type: 'object' as const,
|
|
18
|
+
properties: {
|
|
19
|
+
action: { type: 'string', description: 'Git action: status, log, diff, commit, branch, checkout, pull, push' },
|
|
20
|
+
args: { type: 'string', description: 'Additional arguments' },
|
|
21
|
+
message: { type: 'string', description: 'Commit message' },
|
|
22
|
+
cwd: { type: 'string', description: 'Working directory' }
|
|
23
|
+
},
|
|
24
|
+
required: ['action']
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
metadata = {
|
|
28
|
+
permissions: ['execute'] as ('read' | 'write' | 'execute' | 'network')[],
|
|
29
|
+
tags: ['git', 'vcs'],
|
|
30
|
+
version: '1.0.0'
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
parameters = [
|
|
34
|
+
{ name: 'action', type: 'string', description: 'Git action: status, log, diff, commit, branch, checkout, pull, push', required: true },
|
|
35
|
+
{ name: 'args', type: 'string', description: 'Additional arguments', required: false },
|
|
36
|
+
{ name: 'message', type: 'string', description: 'Commit message (for commit action)', required: false },
|
|
37
|
+
{ name: 'cwd', type: 'string', description: 'Working directory', required: false }
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
async execute(params: Record<string, any>): Promise<ToolResult> {
|
|
41
|
+
const { action, args, message, cwd } = params;
|
|
42
|
+
const workDir = cwd || process.cwd();
|
|
43
|
+
|
|
44
|
+
// Check if git repo exists
|
|
45
|
+
if (!existsSync(`${workDir}/.git`)) {
|
|
46
|
+
return { success: false, error: 'Not a git repository' };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
switch (action) {
|
|
51
|
+
case 'status':
|
|
52
|
+
return await this.status(workDir);
|
|
53
|
+
case 'log':
|
|
54
|
+
return await this.log(workDir, args || '10');
|
|
55
|
+
case 'diff':
|
|
56
|
+
return await this.diff(workDir, args || '');
|
|
57
|
+
case 'commit':
|
|
58
|
+
return await this.commit(workDir, message || args);
|
|
59
|
+
case 'branch':
|
|
60
|
+
return await this.branch(workDir);
|
|
61
|
+
case 'checkout':
|
|
62
|
+
return await this.checkout(workDir, args);
|
|
63
|
+
case 'pull':
|
|
64
|
+
return await this.pull(workDir);
|
|
65
|
+
case 'push':
|
|
66
|
+
return await this.push(workDir);
|
|
67
|
+
case 'add':
|
|
68
|
+
return await this.add(workDir, args || '.');
|
|
69
|
+
default:
|
|
70
|
+
return { success: false, error: `Unknown action: ${action}` };
|
|
71
|
+
}
|
|
72
|
+
} catch (error: any) {
|
|
73
|
+
return { success: false, error: error.message };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private async status(cwd: string): Promise<ToolResult> {
|
|
78
|
+
const { stdout } = await execAsync('git status --short', { cwd });
|
|
79
|
+
return { success: true, output: stdout || '(clean)' };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private async log(cwd: string, limit: string): Promise<ToolResult> {
|
|
83
|
+
const { stdout } = await execAsync(`git log --oneline -n ${limit}`, { cwd });
|
|
84
|
+
return { success: true, output: stdout || 'No commits yet' };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private async diff(cwd: string, args: string): Promise<ToolResult> {
|
|
88
|
+
const { stdout } = await execAsync(`git diff ${args}`, { cwd });
|
|
89
|
+
return { success: true, output: stdout || 'No changes' };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private async commit(cwd: string, message: string): Promise<ToolResult> {
|
|
93
|
+
if (!message) {
|
|
94
|
+
return { success: false, error: 'Commit message required' };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Stage all changes
|
|
98
|
+
await execAsync('git add -A', { cwd });
|
|
99
|
+
|
|
100
|
+
const { stdout } = await execAsync(`git commit -m "${message}"`, { cwd });
|
|
101
|
+
return { success: true, output: stdout || 'Committed successfully' };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private async branch(cwd: string): Promise<ToolResult> {
|
|
105
|
+
const { stdout } = await execAsync('git branch -a', { cwd });
|
|
106
|
+
return { success: true, output: stdout };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private async checkout(cwd: string, branch: string): Promise<ToolResult> {
|
|
110
|
+
if (!branch) {
|
|
111
|
+
return { success: false, error: 'Branch name required' };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const { stdout } = await execAsync(`git checkout ${branch}`, { cwd });
|
|
115
|
+
return { success: true, output: stdout };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private async pull(cwd: string): Promise<ToolResult> {
|
|
119
|
+
const { stdout } = await execAsync('git pull', { cwd });
|
|
120
|
+
return { success: true, output: stdout };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
private async push(cwd: string): Promise<ToolResult> {
|
|
124
|
+
const { stdout } = await execAsync('git push', { cwd });
|
|
125
|
+
return { success: true, output: stdout };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private async add(cwd: string, files: string): Promise<ToolResult> {
|
|
129
|
+
const { stdout } = await execAsync(`git add ${files}`, { cwd });
|
|
130
|
+
return { success: true, output: stdout || 'Added successfully' };
|
|
131
|
+
}
|
|
132
|
+
}
|