Skip to content

Commit 8ba3ad1

Browse files
author
Ada
committed
feat: Implement file explorer and file operations (Issue #6)
- Add file_operations.go with file browser and action handlers - Implement /claude-files command to browse project files - Implement /claude-new-file command to create files - Add file action menu (view, edit, delete) - View file content with syntax highlighting - Delete files with confirmation - Extend BridgeClient with file operation methods - Register HTTP endpoints for file actions - Update help command with file operation docs Bridge server endpoints already implemented in Issue #3. Full interactive editing deferred to Issue #5.
1 parent 600934d commit 8ba3ad1

4 files changed

Lines changed: 673 additions & 2 deletions

File tree

server/bridge_client.go

Lines changed: 139 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -335,8 +335,8 @@ func (bc *BridgeClient) ModifyChange(sessionID, changeID, instructions string) e
335335
return nil
336336
}
337337

338-
// GetFileContent retrieves the full content of a file from the session's project
339-
func (bc *BridgeClient) GetFileContent(sessionID, filename string) (string, error) {
338+
// GetFileContentByName retrieves the full content of a file from the session's project by filename
339+
func (bc *BridgeClient) GetFileContentByName(sessionID, filename string) (string, error) {
340340
reqBody := map[string]string{
341341
"filename": filename,
342342
}
@@ -370,3 +370,140 @@ func (bc *BridgeClient) GetFileContent(sessionID, filename string) (string, erro
370370

371371
return result.Content, nil
372372
}
373+
374+
// ListFiles retrieves the file tree for a session
375+
func (bc *BridgeClient) ListFiles(sessionID string) ([]FileNode, error) {
376+
resp, err := bc.httpClient.Get(fmt.Sprintf("%s/api/sessions/%s/files", bc.baseURL, sessionID))
377+
if err != nil {
378+
return nil, fmt.Errorf("failed to list files: %w", err)
379+
}
380+
defer resp.Body.Close()
381+
382+
if resp.StatusCode != http.StatusOK {
383+
body, _ := io.ReadAll(resp.Body)
384+
return nil, fmt.Errorf("bridge server returned status %d: %s", resp.StatusCode, string(body))
385+
}
386+
387+
var result struct {
388+
Files []FileNode `json:"files"`
389+
}
390+
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
391+
return nil, fmt.Errorf("failed to decode response: %w", err)
392+
}
393+
394+
return result.Files, nil
395+
}
396+
397+
// GetFileContent retrieves the content of a file
398+
func (bc *BridgeClient) GetFileContent(sessionID, filePath string) (string, error) {
399+
resp, err := bc.httpClient.Get(fmt.Sprintf("%s/api/sessions/%s/files/%s", bc.baseURL, sessionID, filePath))
400+
if err != nil {
401+
return "", fmt.Errorf("failed to get file content: %w", err)
402+
}
403+
defer resp.Body.Close()
404+
405+
if resp.StatusCode != http.StatusOK {
406+
body, _ := io.ReadAll(resp.Body)
407+
return "", fmt.Errorf("bridge server returned status %d: %s", resp.StatusCode, string(body))
408+
}
409+
410+
var result struct {
411+
Path string `json:"path"`
412+
Content string `json:"content"`
413+
}
414+
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
415+
return "", fmt.Errorf("failed to decode response: %w", err)
416+
}
417+
418+
return result.Content, nil
419+
}
420+
421+
// CreateFile creates a new file in the project
422+
func (bc *BridgeClient) CreateFile(sessionID, filePath, content string) error {
423+
reqBody := map[string]string{
424+
"path": filePath,
425+
"content": content,
426+
}
427+
428+
jsonData, err := json.Marshal(reqBody)
429+
if err != nil {
430+
return fmt.Errorf("failed to marshal request: %w", err)
431+
}
432+
433+
resp, err := bc.httpClient.Post(
434+
fmt.Sprintf("%s/api/sessions/%s/files", bc.baseURL, sessionID),
435+
"application/json",
436+
bytes.NewBuffer(jsonData),
437+
)
438+
if err != nil {
439+
return fmt.Errorf("failed to create file: %w", err)
440+
}
441+
defer resp.Body.Close()
442+
443+
if resp.StatusCode != http.StatusCreated {
444+
body, _ := io.ReadAll(resp.Body)
445+
return fmt.Errorf("bridge server returned status %d: %s", resp.StatusCode, string(body))
446+
}
447+
448+
return nil
449+
}
450+
451+
// UpdateFile updates the content of an existing file
452+
func (bc *BridgeClient) UpdateFile(sessionID, filePath, content string) error {
453+
reqBody := map[string]string{
454+
"content": content,
455+
}
456+
457+
jsonData, err := json.Marshal(reqBody)
458+
if err != nil {
459+
return fmt.Errorf("failed to marshal request: %w", err)
460+
}
461+
462+
req, err := http.NewRequest(
463+
http.MethodPut,
464+
fmt.Sprintf("%s/api/sessions/%s/files/%s", bc.baseURL, sessionID, filePath),
465+
bytes.NewBuffer(jsonData),
466+
)
467+
if err != nil {
468+
return fmt.Errorf("failed to create request: %w", err)
469+
}
470+
req.Header.Set("Content-Type", "application/json")
471+
472+
resp, err := bc.httpClient.Do(req)
473+
if err != nil {
474+
return fmt.Errorf("failed to update file: %w", err)
475+
}
476+
defer resp.Body.Close()
477+
478+
if resp.StatusCode != http.StatusOK {
479+
body, _ := io.ReadAll(resp.Body)
480+
return fmt.Errorf("bridge server returned status %d: %s", resp.StatusCode, string(body))
481+
}
482+
483+
return nil
484+
}
485+
486+
// DeleteFile deletes a file from the project
487+
func (bc *BridgeClient) DeleteFile(sessionID, filePath string) error {
488+
req, err := http.NewRequest(
489+
http.MethodDelete,
490+
fmt.Sprintf("%s/api/sessions/%s/files/%s", bc.baseURL, sessionID, filePath),
491+
nil,
492+
)
493+
if err != nil {
494+
return fmt.Errorf("failed to create request: %w", err)
495+
}
496+
497+
resp, err := bc.httpClient.Do(req)
498+
if err != nil {
499+
return fmt.Errorf("failed to delete file: %w", err)
500+
}
501+
defer resp.Body.Close()
502+
503+
if resp.StatusCode != http.StatusOK {
504+
body, _ := io.ReadAll(resp.Body)
505+
return fmt.Errorf("bridge server returned status %d: %s", resp.StatusCode, string(body))
506+
}
507+
508+
return nil
509+
}

server/commands.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ const (
1515
commandTriggerClaudeStop = "claude-stop"
1616
commandTriggerClaudeStatus = "claude-status"
1717
commandTriggerClaudeThread = "claude-thread"
18+
commandTriggerClaudeFiles = "claude-files"
19+
commandTriggerClaudeNewFile = "claude-new-file"
1820
commandTriggerClaudeHelp = "claude-help"
1921
)
2022

@@ -77,6 +79,29 @@ func (p *Plugin) registerCommands() error {
7779
return err
7880
}
7981

82+
// Register /claude-files command
83+
if err := p.API.RegisterCommand(&model.Command{
84+
Trigger: commandTriggerClaudeFiles,
85+
AutoComplete: true,
86+
AutoCompleteDesc: "Browse project files",
87+
DisplayName: "Browse Files",
88+
Description: "Open file browser for the current Claude session",
89+
}); err != nil {
90+
return err
91+
}
92+
93+
// Register /claude-new-file command
94+
if err := p.API.RegisterCommand(&model.Command{
95+
Trigger: commandTriggerClaudeNewFile,
96+
AutoComplete: true,
97+
AutoCompleteDesc: "Create a new file",
98+
AutoCompleteHint: "[file-path]",
99+
DisplayName: "Create New File",
100+
Description: "Create a new file in the project",
101+
}); err != nil {
102+
return err
103+
}
104+
80105
// Register /claude-help command
81106
if err := p.API.RegisterCommand(&model.Command{
82107
Trigger: commandTriggerClaudeHelp,
@@ -112,6 +137,10 @@ func (p *Plugin) ExecuteCommand(c *plugin.Context, args *model.CommandArgs) (*mo
112137
return p.executeClaudeStatus(args), nil
113138
case commandTriggerClaudeThread:
114139
return p.executeClaudeThread(args, commandArgs), nil
140+
case commandTriggerClaudeFiles:
141+
return p.executeClaudeFiles(args), nil
142+
case commandTriggerClaudeNewFile:
143+
return p.executeClaudeNewFile(args, commandArgs), nil
115144
case commandTriggerClaudeHelp:
116145
return p.executeClaudeHelp(), nil
117146
default:
@@ -398,6 +427,8 @@ func (p *Plugin) executeClaudeHelp() *model.CommandResponse {
398427
"- `/claude-stop` - Stop the current session\n" +
399428
"- `/claude-status` - Show current session status\n" +
400429
"- `/claude-thread [action]` - Add thread context to Claude (run in a thread)\n" +
430+
"- `/claude-files` - Browse project files\n" +
431+
"- `/claude-new-file <path>` - Create a new file\n" +
401432
"- `/claude-help` - Show this help message\n\n" +
402433
"**Getting Started:**\n" +
403434
"1. Start a session with `/claude-start /path/to/your/project`\n" +
@@ -413,6 +444,10 @@ func (p *Plugin) executeClaudeHelp() *model.CommandResponse {
413444
"- `/claude-thread summarize` - Add context and ask Claude to summarize\n" +
414445
"- `/claude-thread implement` - Add context and ask Claude to implement\n" +
415446
"- `/claude-thread review` - Add context and ask Claude to review\n\n" +
447+
"**File Operations:**\n" +
448+
"- `/claude-files` - Browse and manage project files\n" +
449+
"- `/claude-new-file src/example.ts` - Create a new file\n" +
450+
"- Click file actions to view, edit, or delete files\n\n" +
416451
"**Configuration:**\n" +
417452
"Go to **System Console > Plugins > Claude Code** to configure settings.\n\n" +
418453
"For more information, visit: https://github.com/appsome/claude-code-mattermost-plugin"

0 commit comments

Comments
 (0)