feat(cli)!: subject-based command tree (tea-style) + global --output flag
BREAKING:
- Flat oo verb-noun layout replaced with subject → verb, mirroring the
gitea `tea` CLI. Every subject is one file in cmd/oo/:
oo calendar list | events | add | delete
oo projects list | get | milestones | create | update | delete
oo tasks list | get | create | update | delete | subtask add
oo users list | self (alias: oo whoami)
oo contacts list | get | delete | info-add
oo persons list | create | delete
oo companies list | create | delete
oo opportunities list | get | create | delete | stages | member-add
oo cases list | create | delete | member-add
oo crm-tasks list | create | delete | categories
oo applications sync
- Global `--output/-o table|json` flag on the root command; every list uses
a single printTable helper that flattens nested bidCurrency → abbreviation
for readability, truncates long cells, and dumps verbatim in JSON mode.
Library additions surfaced in the CLI:
- oo users / oo whoami via SelfUserID + GetUsers.
- oo projects full CRUD via Create/Update/Delete/GetProjectByID/Milestones.
- oo crm-tasks and oo cases via ListCRMTasks/CreateCRMTask/DeleteCRMTask/
ListTaskCategories/ListCases/CreateCase/DeleteCase/AddCaseMember.
- oo contacts info-add via AddContactInfo.
- oo opportunities stages/member-add via ListDealStages/AddOpportunityMember.
Internal:
- cmd/oo/common.go — rootCmd, newOO (uses AuthenticateContext), printTable,
printObject, fmtCell, deref* helpers.
- cmd/oo/crm.go deleted; content split into contacts.go, opportunities.go,
cases.go, crm_tasks.go for locality.
Docs:
- README: new subject→verb table + migration note.
- AGENTS.md: CLI section rewritten; new rule enforcing subject→verb and
printTable/printObject routing.
- CHANGELOG: 0.5.0 entry with full old→new mapping.
Made-with: Cursor
This commit is contained in:
+120
-65
@@ -6,14 +6,30 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func cmdTaskList() *cobra.Command {
|
||||
var tasksCmd = &cobra.Command{
|
||||
Use: "tasks",
|
||||
Aliases: []string{"task"},
|
||||
Short: "Project tasks and subtasks",
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(tasksCmd)
|
||||
tasksCmd.AddCommand(taskListCmd())
|
||||
tasksCmd.AddCommand(taskGetCmd())
|
||||
tasksCmd.AddCommand(taskCreateCmd())
|
||||
tasksCmd.AddCommand(taskUpdateCmd())
|
||||
tasksCmd.AddCommand(taskDeleteCmd())
|
||||
tasksCmd.AddCommand(subtaskCmd())
|
||||
}
|
||||
|
||||
func taskListCmd() *cobra.Command {
|
||||
var project, status string
|
||||
var all, verbose bool
|
||||
cmd := &cobra.Command{
|
||||
Use: "task-list",
|
||||
Use: "list",
|
||||
Short: "List project tasks",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
c, err := newOO()
|
||||
c, err := newOO(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -26,36 +42,54 @@ func cmdTaskList() *cobra.Command {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, t := range tasks {
|
||||
fmt.Printf("[%v] %v status=%v\n", t["id"], t["title"], t["status"])
|
||||
if verbose {
|
||||
d, _ := c.GetTaskByID(cmd.Context(), fmt.Sprint(t["id"]))
|
||||
subs, _ := d["subtasks"].([]any)
|
||||
for _, s := range subs {
|
||||
sm, _ := s.(map[string]any)
|
||||
fmt.Printf(" └─ [%v] %v\n", sm["id"], sm["title"])
|
||||
if verbose {
|
||||
for i, t := range tasks {
|
||||
d, err := c.GetTaskByID(cmd.Context(), idString(t, "id"))
|
||||
if err == nil {
|
||||
tasks[i] = d
|
||||
}
|
||||
}
|
||||
}
|
||||
printTable([]string{"id", "title", "status", "priority", "responsible"}, tasks)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
cmd.Flags().StringVarP(&project, "project", "p", "", "project id")
|
||||
cmd.Flags().StringVarP(&project, "project", "p", "", "project id (default $OO_PROJECT_ID)")
|
||||
cmd.Flags().StringVarP(&status, "status", "s", "", "open|closed")
|
||||
cmd.Flags().BoolVarP(&all, "all", "a", false, "all projects (@self)")
|
||||
cmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "")
|
||||
cmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "include subtasks & full details")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func cmdTaskAdd() *cobra.Command {
|
||||
var project, desc, deadline string
|
||||
var prio string
|
||||
cmd := &cobra.Command{
|
||||
Use: "task-add TITLE",
|
||||
Short: "Add project task",
|
||||
func taskGetCmd() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "get TASK_ID",
|
||||
Short: "Show a single task (incl. subtasks)",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
c, err := newOO()
|
||||
c, err := newOO(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
out, err := c.GetTaskByID(cmd.Context(), args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(out)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func taskCreateCmd() *cobra.Command {
|
||||
var project, desc, deadline, prio string
|
||||
cmd := &cobra.Command{
|
||||
Use: "create TITLE",
|
||||
Aliases: []string{"add"},
|
||||
Short: "Create a project task",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
c, err := newOO(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -70,24 +104,79 @@ func cmdTaskAdd() *cobra.Command {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printJSON(out)
|
||||
printObject(out)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
cmd.Flags().StringVar(&project, "project", "", "")
|
||||
cmd.Flags().StringVar(&desc, "description", "", "")
|
||||
cmd.Flags().StringVar(&deadline, "deadline", "", "")
|
||||
cmd.Flags().StringVarP(&project, "project", "p", "", "project id (default $OO_PROJECT_ID)")
|
||||
cmd.Flags().StringVar(&desc, "description", "", "description")
|
||||
cmd.Flags().StringVar(&deadline, "deadline", "", "deadline YYYY-MM-DD")
|
||||
cmd.Flags().StringVar(&prio, "priority", "normal", "high|normal|low")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func cmdSubtaskAdd() *cobra.Command {
|
||||
func taskUpdateCmd() *cobra.Command {
|
||||
var status string
|
||||
cmd := &cobra.Command{
|
||||
Use: "update TASK_ID",
|
||||
Short: "Update task status",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if status == "" {
|
||||
return fmt.Errorf("--status is required (open|closed)")
|
||||
}
|
||||
c, err := newOO(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
out, err := c.UpdateTaskStatus(cmd.Context(), args[0], status)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(out)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
cmd.Flags().StringVarP(&status, "status", "s", "", "new status: open|closed")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func taskDeleteCmd() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "subtask-add PARENT_TASK_ID TITLE [TITLE...]",
|
||||
Short: "Add subtask(s)",
|
||||
Use: "delete TASK_ID [TASK_ID...]",
|
||||
Aliases: []string{"rm"},
|
||||
Short: "Delete one or more tasks",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
c, err := newOO(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, id := range args {
|
||||
out, err := c.DeleteTask(cmd.Context(), id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(out)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// subtaskCmd exposes `oo tasks subtask {add}` — keeps things tidy without a
|
||||
// dedicated subtasks top-level command.
|
||||
func subtaskCmd() *cobra.Command {
|
||||
sub := &cobra.Command{
|
||||
Use: "subtask",
|
||||
Short: "Subtasks of a parent task",
|
||||
}
|
||||
sub.AddCommand(&cobra.Command{
|
||||
Use: "add PARENT_TASK_ID TITLE [TITLE...]",
|
||||
Short: "Add one or more subtasks",
|
||||
Args: cobra.MinimumNArgs(2),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
c, err := newOO()
|
||||
c, err := newOO(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -97,44 +186,10 @@ func cmdSubtaskAdd() *cobra.Command {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printJSON(out)
|
||||
printObject(out)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func cmdTaskUpdate() *cobra.Command {
|
||||
var del bool
|
||||
cmd := &cobra.Command{
|
||||
Use: "task-update TASK_ID open|closed",
|
||||
Short: "Update task status or delete",
|
||||
Args: cobra.RangeArgs(1, 2),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
c, err := newOO()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
id := args[0]
|
||||
if del {
|
||||
out, err := c.DeleteTask(cmd.Context(), id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printJSON(out)
|
||||
return nil
|
||||
}
|
||||
if len(args) < 2 {
|
||||
return fmt.Errorf("need status or --delete")
|
||||
}
|
||||
out, err := c.UpdateTaskStatus(cmd.Context(), id, args[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printJSON(out)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
cmd.Flags().BoolVar(&del, "delete", false, "delete task")
|
||||
return cmd
|
||||
})
|
||||
return sub
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user