Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions cmd/categories.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package cmd

import (
"fmt"

"github.com/example/splitwise-cli/internal/api"
"github.com/example/splitwise-cli/internal/output"
"github.com/spf13/cobra"
)

var categoriesCmd = &cobra.Command{
Use: "categories",
Short: "List available expense categories",
Run: func(cmd *cobra.Command, args []string) {
client, err := api.New()
if err != nil {
output.Die("%v", err)
}

categories, err := client.GetCategories()
if err != nil {
output.Die("%v", err)
}

if jsonOut {
output.JSON(categories)
return
}

if quiet {
for _, cat := range categories {
fmt.Printf("%d\t%s\n", cat.ID, cat.Name)
for _, sub := range cat.Subcategories {
fmt.Printf("%d\t%s\n", sub.ID, sub.Name)
}
}
return
}

var rows [][]string
for _, cat := range categories {
rows = append(rows, []string{fmt.Sprintf("%d", cat.ID), cat.Name, ""})
for _, sub := range cat.Subcategories {
rows = append(rows, []string{fmt.Sprintf("%d", sub.ID), " " + sub.Name, cat.Name})
}
}
output.Table([]string{"ID", "Name", "Parent Category"}, rows)
},
}

func init() {
rootCmd.AddCommand(categoriesCmd)
}
3 changes: 3 additions & 0 deletions cmd/expenses.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ var expensesCreateCmd = &cobra.Command{
split, _ := cmd.Flags().GetString("split")
currency, _ := cmd.Flags().GetString("currency")
paidBy, _ := cmd.Flags().GetString("paid-by")
category, _ := cmd.Flags().GetInt("category")

// Resolve defaults.
cfg, _ := config.Load()
Expand All @@ -137,6 +138,7 @@ var expensesCreateCmd = &cobra.Command{
Cost: cost,
CurrencyCode: currency,
GroupID: group.ID,
CategoryID: category,
}

if split == "" || split == "even" {
Expand Down Expand Up @@ -277,6 +279,7 @@ func init() {
expensesCreateCmd.Flags().String("split", "even", `Split type: even, or exact:Name:Amount,Name:Amount (e.g. "exact:MemberA:60,MemberB:40")`)
expensesCreateCmd.Flags().String("paid-by", "", "Who paid (name, defaults to you)")
expensesCreateCmd.Flags().StringP("currency", "c", "", "Currency code (e.g. USD)")
expensesCreateCmd.Flags().Int("category", 0, "Category ID for the expense")

expensesCmd.AddCommand(expensesListCmd)
expensesCmd.AddCommand(expensesCreateCmd)
Expand Down
24 changes: 22 additions & 2 deletions internal/api/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,8 +198,9 @@ type ExpenseShare struct {
}

type Category struct {
ID int `json:"id"`
Name string `json:"name"`
ID int `json:"id"`
Name string `json:"name"`
Subcategories []Category `json:"subcategories,omitempty"`
}

// ---------- API Methods ----------
Expand Down Expand Up @@ -264,6 +265,21 @@ func (c *Client) GetFriends() ([]Friend, error) {
return resp.Friends, nil
}

// GetCategories returns all available expense categories.
func (c *Client) GetCategories() ([]Category, error) {
data, err := c.get("/get_categories", nil)
if err != nil {
return nil, err
}
var resp struct {
Categories []Category `json:"categories"`
}
if err := json.Unmarshal(data, &resp); err != nil {
return nil, fmt.Errorf("failed to parse response: %w", err)
}
return resp.Categories, nil
}

// GetExpensesParams holds query parameters for listing expenses.
type GetExpensesParams struct {
GroupID int64
Expand Down Expand Up @@ -316,6 +332,7 @@ type CreateExpenseParams struct {
GroupID int64
SplitEqually bool
Date string
CategoryID int
// For by-shares split: user_id -> {paid_share, owed_share}
Shares []ShareParam
}
Expand All @@ -337,6 +354,9 @@ func (c *Client) CreateExpense(p CreateExpenseParams) (*Expense, error) {
if p.Date != "" {
params.Set("date", p.Date)
}
if p.CategoryID > 0 {
params.Set("category_id", fmt.Sprintf("%d", p.CategoryID))
}

if p.SplitEqually {
params.Set("group_id", fmt.Sprintf("%d", p.GroupID))
Expand Down
5 changes: 5 additions & 0 deletions skills/splitwise/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ splitwise expenses create "Dinner" 120.00 --group "Trip"

# Different currency
splitwise expenses create "Dinner on Trip" 45.00 --group "Trip" --currency EUR

# With a category
splitwise expenses create "Groceries" 87.50 --category 18
```

### Other commands
Expand All @@ -74,6 +77,7 @@ splitwise me # Current user info
splitwise groups # List all groups
splitwise group "Household" # Group details + member balances
splitwise friends # List friends
splitwise categories # List all categories with their id, name, and subcategories
splitwise settle "MemberB" # Record a settlement
splitwise expenses delete 12345 # Delete an expense by ID
```
Expand Down Expand Up @@ -114,6 +118,7 @@ Run multiple `splitwise expenses create` commands in sequence. No special syntax
- Group/friend names use case-insensitive partial matching
- A configured default group means `--group` is optional
- Amounts are USD by default (configurable via `splitwise config set default_currency`)
- You can categorize an expense using the `--category <category_id>` flag. If the user provides enough context, make an effort to find and use an appropriate category by checking `splitwise categories`.
- `--split even` is the default — expense split equally among all group members
- `--split "exact:Name:Amount,Name:Amount"` — custom per-person split (amounts must sum to total)
- The `--paid-by` flag defaults to the authenticated user