From 1923c0a41849ff2a99b5066bbfa53063a28f3aea Mon Sep 17 00:00:00 2001 From: hi-lee-mon Date: Thu, 18 Sep 2025 23:10:44 +0900 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20=E2=9C=A8sparkles:=20=E7=8F=BE?= =?UTF-8?q?=E5=9C=A8=E3=81=AE=E3=83=A6=E3=83=BC=E3=82=B6=E3=83=BC=E3=81=8C?= =?UTF-8?q?=E4=BD=9C=E6=88=90=E3=81=97=E3=81=9FIssue=E3=82=92=E8=A1=A8?= =?UTF-8?q?=E7=A4=BA=E3=81=99=E3=82=8B=E3=82=AA=E3=83=97=E3=82=B7=E3=83=A7?= =?UTF-8?q?=E3=83=B3=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `issues` コマンドに `--mine` フラグを追加し、現在のユーザーが作成したIssueのみをフィルタリングして表示できるようにしました。また、ユーザー情報を取得するための新しいAPI呼び出しを実装しました。READMEを更新し、インストール手順を整理しました。 --- CLAUDE.md | 76 ++++++++++++++++++++++++++++++++++++++++++++++ README.md | 1 + client/client.go | 7 ++--- cmd/issues.go | 1 + cmd/issues_list.go | 12 ++++++++ 5 files changed, 93 insertions(+), 4 deletions(-) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..cfc8d55 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,76 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Build and Development Commands + +```bash +# Build the project +go build -o redmine + +# Install dependencies and clean up +go mod tidy + +# Test the built binary +./redmine --help + +# Run all tests (currently no tests exist) +go test -v ./... +``` + +## Project Architecture + +This is a Redmine CLI tool written in Go using the Cobra framework. The application follows a standard CLI architecture with clear separation of concerns: + +### Core Components + +- **main.go**: Entry point that delegates to cmd.Execute() +- **cmd/**: Contains all CLI command definitions using Cobra framework + - **root.go**: Root command setup with global profile flag + - **issues.go**: Issue management commands (list, show) + - **profile.go**: Profile management commands (add, list, use, remove, show) + - **auth.go**: Authentication commands (legacy, prefer profile commands) +- **config/**: Configuration management with YAML persistence + - **config.go**: Profile-based configuration with ~/.redminecli/config storage +- **client/**: Redmine API client with HTTP communication + - **client.go**: HTTP client with complete Redmine API models (Issue, Project, User, etc.) + +### Configuration System + +The application uses a profile-based configuration system: +- Configuration stored in `~/.redminecli/config` as YAML +- Each profile contains: name, Redmine URL, and API key +- Supports default profile and per-command profile override via `--profile` flag +- Profile management through `profile` commands (add, remove, use, list, show) + +### API Client Architecture + +The HTTP client (`client/client.go`) provides: +- Structured Redmine API models (Issue, Journal, Project, User, etc.) +- Authentication via X-Redmine-API-Key header +- JSON response parsing with proper error handling +- Support for pagination and filtering parameters +- Include parameters for fetching related data (e.g., journals for comments) + +### Command Structure + +Commands follow a hierarchical structure: +- `redmine issues list` - List issues with filtering options +- `redmine issues show ` - Show issue details with optional comments +- `redmine profile add/list/use/remove/show` - Profile management +- Global `--profile` flag for per-command profile selection + +### Dependencies + +- **github.com/spf13/cobra**: CLI framework for command structure +- **gopkg.in/yaml.v3**: YAML configuration file handling +- Standard library for HTTP client and JSON processing +- Uses mise.toml for Go version management (latest) + +### Development Notes + +- No test suite currently exists +- Binary excluded from git via .gitignore (redmine, redmine-*) +- Configuration files (.yaml/.yml) excluded from git for security +- API keys are masked in profile display output +- 30-second HTTP timeout for API requests \ No newline at end of file diff --git a/README.md b/README.md index fc0b782..2bc1f8e 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,7 @@ go build -o redmine - `--offset`: オフセット (デフォルト: 0) - `--project`: プロジェクトIDでフィルタ - `--status`: ステータスIDでフィルタ +- `--mine`: 現在のユーザーが作成したIssueのみ表示 例: diff --git a/client/client.go b/client/client.go index 669532c..401e6f0 100644 --- a/client/client.go +++ b/client/client.go @@ -138,6 +138,9 @@ type UpdateIssueData struct { DoneRatio *int `json:"done_ratio,omitempty"` ParentIssueID *int `json:"parent_issue_id,omitempty"` } +type UserResponse struct { + User User `json:"user"` +} func NewClient(baseURL, apiKey string) *Client { return &Client{ @@ -309,10 +312,6 @@ type UsersResponse struct { Users []User `json:"users"` } -type UserResponse struct { - User User `json:"user"` -} - type TrackersResponse struct { Trackers []Tracker `json:"trackers"` } diff --git a/cmd/issues.go b/cmd/issues.go index aa5f96d..117f8d5 100644 --- a/cmd/issues.go +++ b/cmd/issues.go @@ -30,6 +30,7 @@ func init() { listIssuesCmd.Flags().String("offset", "0", "Offset for pagination") listIssuesCmd.Flags().String("project", "", "Project ID to filter by") listIssuesCmd.Flags().String("status", "", "Status ID to filter by") + listIssuesCmd.Flags().Bool("mine", false, "Filter issues authored by current user") // Add flags to show command showIssueCmd.Flags().BoolP("comments", "c", false, "Include comments (journals) in the output") diff --git a/cmd/issues_list.go b/cmd/issues_list.go index 68d92bb..0823cf2 100644 --- a/cmd/issues_list.go +++ b/cmd/issues_list.go @@ -63,6 +63,18 @@ var listIssuesCmd = &cobra.Command{ params["status_id"] = status } + // Check if --mine flag is set to filter by current user + mine, _ := cmd.Flags().GetBool("mine") + if mine { + // Get current user ID + userResp, err := c.GetCurrentUser() + if err != nil { + fmt.Printf("Error getting current user: %v\n", err) + return + } + params["author_id"] = fmt.Sprintf("%d", userResp.User.ID) + } + response, err := c.GetIssues(params) if err != nil { fmt.Printf("Error getting issues: %v\n", err) From 685d770aef2180a0a4e835be0cfb1572bddf5a85 Mon Sep 17 00:00:00 2001 From: hi-lee-mon Date: Sun, 21 Sep 2025 11:35:55 +0900 Subject: [PATCH 2/4] =?UTF-8?q?feat:=20:sparkles:=20`--mine`=20=E3=83=95?= =?UTF-8?q?=E3=83=A9=E3=82=B0=E3=82=92=20`--me`=20=E3=81=AB=E5=A4=89?= =?UTF-8?q?=E6=9B=B4=E3=81=97=E3=80=81=E7=8F=BE=E5=9C=A8=E3=81=AE=E3=83=A6?= =?UTF-8?q?=E3=83=BC=E3=82=B6=E3=83=BC=E3=81=8C=E4=BD=9C=E6=88=90=E3=81=97?= =?UTF-8?q?=E3=81=9FIssue=E3=82=92=E3=83=95=E3=82=A3=E3=83=AB=E3=82=BF?= =?UTF-8?q?=E3=83=AA=E3=83=B3=E3=82=B0=E3=81=99=E3=82=8B=E6=A9=9F=E8=83=BD?= =?UTF-8?q?=E3=82=92=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit READMEとコード内のフラグ名を変更し、一貫性を持たせました。 --- README.md | 9 ++++++++- cmd/issues.go | 2 +- cmd/issues_list.go | 4 ++-- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 2bc1f8e..e4a8fa9 100644 --- a/README.md +++ b/README.md @@ -69,12 +69,19 @@ go build -o redmine - `--offset`: オフセット (デフォルト: 0) - `--project`: プロジェクトIDでフィルタ - `--status`: ステータスIDでフィルタ -- `--mine`: 現在のユーザーが作成したIssueのみ表示 +- `--me`: 現在のユーザーが作成したIssueのみ表示 例: ```bash +# 基本的な使用例 ./redmine issues list --limit 50 --project 1 --status 1 + +# 自分が作成したIssueのみ表示 +./redmine issues list --me + +# 自分が作成したIssueをプロジェクトでフィルタ +./redmine issues list --me --project 1 ``` #### Issue詳細の表示 diff --git a/cmd/issues.go b/cmd/issues.go index 117f8d5..d228403 100644 --- a/cmd/issues.go +++ b/cmd/issues.go @@ -30,7 +30,7 @@ func init() { listIssuesCmd.Flags().String("offset", "0", "Offset for pagination") listIssuesCmd.Flags().String("project", "", "Project ID to filter by") listIssuesCmd.Flags().String("status", "", "Status ID to filter by") - listIssuesCmd.Flags().Bool("mine", false, "Filter issues authored by current user") + listIssuesCmd.Flags().Bool("me", false, "Filter issues authored by current user") // Add flags to show command showIssueCmd.Flags().BoolP("comments", "c", false, "Include comments (journals) in the output") diff --git a/cmd/issues_list.go b/cmd/issues_list.go index 0823cf2..d6c5abd 100644 --- a/cmd/issues_list.go +++ b/cmd/issues_list.go @@ -64,8 +64,8 @@ var listIssuesCmd = &cobra.Command{ } // Check if --mine flag is set to filter by current user - mine, _ := cmd.Flags().GetBool("mine") - if mine { + me, _ := cmd.Flags().GetBool("me") + if me { // Get current user ID userResp, err := c.GetCurrentUser() if err != nil { From 16d7d36d435955b9e76cd6b25f7e1dd4a8ba1a22 Mon Sep 17 00:00:00 2001 From: UNILORN Date: Thu, 25 Sep 2025 10:18:04 +0900 Subject: [PATCH 3/4] =?UTF-8?q?fix:=20:bug:=20issue=20list=20=E3=82=B3?= =?UTF-8?q?=E3=83=9E=E3=83=B3=E3=83=89=E3=81=A7=E3=83=A6=E3=83=BC=E3=82=B6?= =?UTF-8?q?=E3=83=BC=E3=83=95=E3=82=A3=E3=83=AB=E3=82=BF=E3=83=AA=E3=83=B3?= =?UTF-8?q?=E3=82=B0=E3=81=AE=E3=83=91=E3=83=A9=E3=83=A1=E3=83=BC=E3=82=BF?= =?UTF-8?q?=E3=82=92=E4=BF=AE=E6=AD=A3=20#1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit author_id から assigned_to_id に変更してアサインされたissueを正しく取得できるように修正 --- cmd/issues_list.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/issues_list.go b/cmd/issues_list.go index d6c5abd..a64e794 100644 --- a/cmd/issues_list.go +++ b/cmd/issues_list.go @@ -72,7 +72,7 @@ var listIssuesCmd = &cobra.Command{ fmt.Printf("Error getting current user: %v\n", err) return } - params["author_id"] = fmt.Sprintf("%d", userResp.User.ID) + params["assigned_to_id"] = fmt.Sprintf("%d", userResp.User.ID) } response, err := c.GetIssues(params) From 9672c21e244531209892e811e833a1b941c578b7 Mon Sep 17 00:00:00 2001 From: UNILORN Date: Thu, 25 Sep 2025 10:45:50 +0900 Subject: [PATCH 4/4] =?UTF-8?q?feat:=20issue=E4=B8=80=E8=A6=A7=E8=A1=A8?= =?UTF-8?q?=E7=A4=BA=E3=81=AE=E3=83=86=E3=83=BC=E3=83=96=E3=83=AB=E5=BD=A2?= =?UTF-8?q?=E5=BC=8F=E3=82=92=E6=94=B9=E5=96=84=20#1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit カラム幅を固定し、ヘッダーとセパレーターを追加してテーブル形式を整理。 開始日と期日の表示を追加し、長いステータス名は省略表示に対応。 --- cmd/issues_list.go | 60 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 52 insertions(+), 8 deletions(-) diff --git a/cmd/issues_list.go b/cmd/issues_list.go index a64e794..62ce3d9 100644 --- a/cmd/issues_list.go +++ b/cmd/issues_list.go @@ -86,8 +86,35 @@ var listIssuesCmd = &cobra.Command{ return } + // Column widths + const ( + idWidth = 6 + statusWidth = 12 + assigneeWidth = 10 + dateWidth = 12 + ) + fmt.Printf("Issues (Total: %d)\n", response.TotalCount) - fmt.Println(strings.Repeat("-", 100)) + + // Header + fmt.Printf("%-*s | %-*s | %-*s | %-*s | %-*s | %-*s | %s\n", + idWidth, "ID", + statusWidth, "Status", + assigneeWidth, "Assignee", + dateWidth, "StartDate", + dateWidth, "DueDate", + dateWidth, "UpdatedAt", + "Subject") + + // Separator + fmt.Printf("%s-|-%s-|-%s-|-%s-|-%s-|-%s-|-%s\n", + strings.Repeat("-", idWidth), + strings.Repeat("-", statusWidth), + strings.Repeat("-", assigneeWidth), + strings.Repeat("-", dateWidth), + strings.Repeat("-", dateWidth), + strings.Repeat("-", dateWidth), + strings.Repeat("-", 7)) for _, issue := range response.Issues { assignedTo := "Not assigned" @@ -95,13 +122,30 @@ var listIssuesCmd = &cobra.Command{ assignedTo = issue.AssignedTo.Name } - fmt.Printf("#%d | %s | %s | %s | %s | %s\n", - issue.ID, - truncateString(issue.Subject, 40), - issue.Status.Name, - issue.Priority.Name, - assignedTo, - issue.UpdatedOn.Format("2006-01-02")) + startDate := "-" + if issue.StartDate != nil { + startDate = *issue.StartDate + } + + dueDate := "-" + if issue.DueDate != nil { + dueDate = *issue.DueDate + } + + // Truncate long fields to fit column widths + status := issue.Status.Name + if len(status) > statusWidth { + status = status[:statusWidth-3] + "..." + } + + fmt.Printf("#%-*d | %-*s | %-*s | %-*s | %-*s | %-*s | %s\n", + idWidth-1, issue.ID, // -1 for the # prefix + statusWidth, status, + assigneeWidth, assignedTo, + dateWidth, startDate, + dateWidth, dueDate, + dateWidth, issue.UpdatedOn.Format("2006-01-02"), + issue.Subject) } }, }