refactor!: rename oo-cli → oo, split library by domain, relocate applications
BREAKING:
- Binary renamed oo-cli → oo; install path is now
github.com/eslider/go-onlyoffice/cmd/oo.
- Former internal/cli tree removed; cobra commands live in cmd/oo/ as
package main, split by domain (calendar.go, crm.go, tasks.go, apps.go,
common.go, main.go).
- internal/applications moved to cmd/oo/applications/ (CV-specific workflow;
not a library feature).
- examples/applications removed (it depended on an internal package).
Library split (mechanical, zero API surface change):
- client.go — Client, Credentials, Defaults, env helpers, NewClient.
- request.go — Request, Query, Time, Token, MetaResponse, Permissions.
- auth.go — Authenticate/AuthenticateContext/InvalidateToken.
- http.go — transport + ResponseArray/ResponseObject/postFormObject/
putFormObject/deleteObject/unmarshalResponseObject (renamed
from httpx.go).
- projects.go, tasks.go, users.go, calendar.go, crm.go, files.go — typed
/ untyped domain methods. tasks_extra.go merged into tasks.go.
- onlyoffice.go deleted (content redistributed).
AGENTS.md, CHANGELOG.md, README.md updated accordingly.
Made-with: Cursor
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func cmdCalList() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "cal-list",
|
||||
Short: "List calendars (default date span)",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
c, err := newOO()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
out, err := c.ListCalendars(cmd.Context(), "", "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printJSON(out)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func cmdCalEvents() *cobra.Command {
|
||||
var start, end string
|
||||
cmd := &cobra.Command{
|
||||
Use: "cal-events",
|
||||
Short: "List calendar data for period (default: next 7 days)",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
c, err := newOO()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if start == "" || end == "" {
|
||||
start = time.Now().Format("2006-01-02")
|
||||
end = time.Now().AddDate(0, 0, 7).Format("2006-01-02")
|
||||
}
|
||||
out, err := c.ListEvents(cmd.Context(), start, end)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printJSON(out)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
cmd.Flags().StringVar(&start, "start", "", "start date YYYY-MM-DD")
|
||||
cmd.Flags().StringVar(&end, "end", "", "end date YYYY-MM-DD")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func cmdCalAdd() *cobra.Command {
|
||||
var cal, desc string
|
||||
var allDay bool
|
||||
cmd := &cobra.Command{
|
||||
Use: "cal-add TITLE START END",
|
||||
Short: "Add calendar event",
|
||||
Args: cobra.ExactArgs(3),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
c, err := newOO()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ev, err := c.AddEvent(cmd.Context(), cal, args[0], args[1], args[2], desc, allDay)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printJSON(ev)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
cmd.Flags().StringVar(&cal, "calendar", "", "calendar id (default from env)")
|
||||
cmd.Flags().StringVar(&desc, "description", "", "")
|
||||
cmd.Flags().BoolVar(&allDay, "all-day", false, "")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func cmdCalDel() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "cal-delete EVENT_ID [EVENT_ID...]",
|
||||
Short: "Delete calendar events",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
c, err := newOO()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, id := range args {
|
||||
out, err := c.DeleteEvent(cmd.Context(), id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printJSON(out)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
onlyoffice "github.com/eslider/go-onlyoffice"
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "oo",
|
||||
Short: "OnlyOffice Workspace CLI (Go port of cv/bin/office)",
|
||||
}
|
||||
|
||||
// execute runs the root command. Exported only to main.go in the same package.
|
||||
func execute() error { return rootCmd.Execute() }
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(cmdCalList())
|
||||
rootCmd.AddCommand(cmdCalEvents())
|
||||
rootCmd.AddCommand(cmdCalAdd())
|
||||
rootCmd.AddCommand(cmdCalDel())
|
||||
rootCmd.AddCommand(cmdTaskList())
|
||||
rootCmd.AddCommand(cmdTaskAdd())
|
||||
rootCmd.AddCommand(cmdSubtaskAdd())
|
||||
rootCmd.AddCommand(cmdTaskUpdate())
|
||||
rootCmd.AddCommand(cmdCRMContacts())
|
||||
rootCmd.AddCommand(cmdCRMAddContact())
|
||||
rootCmd.AddCommand(cmdCRMDeals())
|
||||
rootCmd.AddCommand(cmdCRMAddDeal())
|
||||
rootCmd.AddCommand(cmdCRMCases())
|
||||
rootCmd.AddCommand(cmdAppsSync())
|
||||
}
|
||||
|
||||
// newOO loads env (incl. .env in CWD) and returns an authenticated client.
|
||||
// godotenv is a CLI-only concern; the library itself never loads dotfiles.
|
||||
func newOO() (*onlyoffice.Client, error) {
|
||||
_ = godotenv.Load()
|
||||
creds := onlyoffice.GetEnvironmentCredentials()
|
||||
if creds.Url == "" || creds.User == "" || creds.Password == "" {
|
||||
return nil, fmt.Errorf("need ONLYOFFICE_URL (or ONLYOFFICE_HOST), user (ONLYOFFICE_USER or ONLYOFFICE_NAME), password (ONLYOFFICE_PASS or ONLYOFFICE_PASSWORD)")
|
||||
}
|
||||
c := onlyoffice.NewClient(creds)
|
||||
c.SetDefaults(onlyoffice.GetEnvironmentDefaults())
|
||||
if err := c.Authenticate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func printJSON(v any) {
|
||||
enc := json.NewEncoder(os.Stdout)
|
||||
enc.SetIndent("", " ")
|
||||
_ = enc.Encode(v)
|
||||
}
|
||||
|
||||
// flexIDFloat coerces OnlyOffice numeric id fields surfaced as float64 / int
|
||||
// / string into a float64. CLI-only; the library's own flexInt is unexported.
|
||||
func flexIDFloat(v any) float64 {
|
||||
switch x := v.(type) {
|
||||
case float64:
|
||||
return x
|
||||
case int:
|
||||
return float64(x)
|
||||
default:
|
||||
f, _ := strconv.ParseFloat(fmt.Sprint(x), 64)
|
||||
return f
|
||||
}
|
||||
}
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func cmdCRMContacts() *cobra.Command {
|
||||
var companies, persons bool
|
||||
var search string
|
||||
cmd := &cobra.Command{
|
||||
Use: "crm-contacts",
|
||||
Short: "List CRM contacts",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
c, err := newOO()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
list, _, err := c.ListContacts(cmd.Context(), 50, 0, search)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, row := range list {
|
||||
isCo, _ := row["isCompany"].(bool)
|
||||
if companies && !isCo {
|
||||
continue
|
||||
}
|
||||
if persons && isCo {
|
||||
continue
|
||||
}
|
||||
printJSON(row)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
cmd.Flags().BoolVar(&companies, "companies", false, "")
|
||||
cmd.Flags().BoolVar(&persons, "persons", false, "")
|
||||
cmd.Flags().StringVar(&search, "search", "", "")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func cmdCRMAddContact() *cobra.Command {
|
||||
var company, email, website, linkedin string
|
||||
var personFirst, personLast string
|
||||
var companyID int
|
||||
cmd := &cobra.Command{
|
||||
Use: "crm-add-contact",
|
||||
Short: "Add company or person",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
c, err := newOO()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if company != "" {
|
||||
out, err := c.CreateCompany(cmd.Context(), company)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cid := fmt.Sprint(int(flexIDFloat(out["id"])))
|
||||
if email != "" {
|
||||
_, _ = c.AddContactInfo(cmd.Context(), cid, "Email", email, "Work", true)
|
||||
}
|
||||
if website != "" {
|
||||
_, _ = c.AddContactInfo(cmd.Context(), cid, "Website", website, "Work", false)
|
||||
}
|
||||
printJSON(out)
|
||||
return nil
|
||||
}
|
||||
if personFirst != "" && personLast != "" {
|
||||
out, err := c.CreatePerson(cmd.Context(), personFirst, personLast, companyID, "", "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pid := fmt.Sprint(int(flexIDFloat(out["id"])))
|
||||
if email != "" {
|
||||
_, _ = c.AddContactInfo(cmd.Context(), pid, "Email", email, "Work", true)
|
||||
}
|
||||
if linkedin != "" {
|
||||
_, _ = c.AddContactInfo(cmd.Context(), pid, "LinkedIn", linkedin, "Work", false)
|
||||
}
|
||||
printJSON(out)
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("set --company or --person-first and --person-last")
|
||||
},
|
||||
}
|
||||
cmd.Flags().StringVar(&company, "company", "", "")
|
||||
cmd.Flags().StringVar(&personFirst, "person-first", "", "")
|
||||
cmd.Flags().StringVar(&personLast, "person-last", "", "")
|
||||
cmd.Flags().IntVar(&companyID, "company-id", 0, "")
|
||||
cmd.Flags().StringVar(&email, "email", "", "")
|
||||
cmd.Flags().StringVar(&website, "website", "", "")
|
||||
cmd.Flags().StringVar(&linkedin, "linkedin", "", "")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func cmdCRMDeals() *cobra.Command {
|
||||
var stages bool
|
||||
cmd := &cobra.Command{
|
||||
Use: "crm-deals",
|
||||
Short: "List opportunities / stages",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
c, err := newOO()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if stages {
|
||||
out, err := c.ListDealStages(cmd.Context())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printJSON(out)
|
||||
return nil
|
||||
}
|
||||
list, total, err := c.ListOpportunities(cmd.Context(), 100, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("total:", total)
|
||||
printJSON(list)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
cmd.Flags().BoolVar(&stages, "stages", false, "")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func cmdCRMAddDeal() *cobra.Command {
|
||||
var stage int
|
||||
var bid float64
|
||||
var contacts []string
|
||||
cmd := &cobra.Command{
|
||||
Use: "crm-add-deal TITLE",
|
||||
Short: "Create opportunity",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
c, err := newOO()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if stage == 0 {
|
||||
stage = 1
|
||||
}
|
||||
out, err := c.CreateOpportunity(cmd.Context(), args[0], stage, "", "EUR", "", bid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
did := fmt.Sprint(int(flexIDFloat(out["id"])))
|
||||
for _, cid := range contacts {
|
||||
_, _ = c.AddOpportunityMember(cmd.Context(), did, cid)
|
||||
}
|
||||
printJSON(out)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
cmd.Flags().IntVar(&stage, "stage", 1, "")
|
||||
cmd.Flags().Float64Var(&bid, "bid", 0, "")
|
||||
cmd.Flags().StringSliceVar(&contacts, "contact", nil, "contact id (repeatable)")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func cmdCRMCases() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "crm-cases",
|
||||
Short: "List CRM cases",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
c, err := newOO()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
list, total, err := c.ListCases(cmd.Context(), 50, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("total:", total)
|
||||
printJSON(list)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Command oo is a thin CLI over the github.com/eslider/go-onlyoffice library.
|
||||
// It exposes calendar, CRM, tasks, subtasks, and application-sync commands.
|
||||
//
|
||||
// Build & install:
|
||||
//
|
||||
// go install github.com/eslider/go-onlyoffice/cmd/oo@latest
|
||||
//
|
||||
// Rationale: previous versions shipped as `oo-cli` with the cobra tree living
|
||||
// in `internal/cli`. Since the CLI is a consumer of the library — not a part
|
||||
// of its public surface — cobra lives right here in package main, split by
|
||||
// domain (calendar.go, crm.go, tasks.go, apps.go).
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if err := execute(); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func cmdTaskList() *cobra.Command {
|
||||
var project, status string
|
||||
var all, verbose bool
|
||||
cmd := &cobra.Command{
|
||||
Use: "task-list",
|
||||
Short: "List project tasks",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
c, err := newOO()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var tasks []map[string]any
|
||||
if all {
|
||||
tasks, err = c.ListAllTasks(cmd.Context(), status)
|
||||
} else {
|
||||
tasks, err = c.ListTasks(cmd.Context(), project, status)
|
||||
}
|
||||
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"])
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
cmd.Flags().StringVarP(&project, "project", "p", "", "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, "")
|
||||
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",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
c, err := newOO()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p := 0
|
||||
switch prio {
|
||||
case "high":
|
||||
p = 1
|
||||
case "low":
|
||||
p = -1
|
||||
}
|
||||
out, err := c.AddTask(cmd.Context(), project, args[0], desc, p, deadline)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printJSON(out)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
cmd.Flags().StringVar(&project, "project", "", "")
|
||||
cmd.Flags().StringVar(&desc, "description", "", "")
|
||||
cmd.Flags().StringVar(&deadline, "deadline", "", "")
|
||||
cmd.Flags().StringVar(&prio, "priority", "normal", "high|normal|low")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func cmdSubtaskAdd() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "subtask-add PARENT_TASK_ID TITLE [TITLE...]",
|
||||
Short: "Add subtask(s)",
|
||||
Args: cobra.MinimumNArgs(2),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
c, err := newOO()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
parent := args[0]
|
||||
for _, title := range args[1:] {
|
||||
out, err := c.AddSubtask(cmd.Context(), parent, title)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printJSON(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
|
||||
}
|
||||
Reference in New Issue
Block a user