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:
@@ -11,7 +11,11 @@ Canonical Go client for OnlyOffice Workspace (Projects + Calendar + CRM) and the
|
|||||||
- `http.go` — transport + DRY response decoders (`ResponseArray`/`ResponseObject`/`postFormObject`/`putFormObject`/`deleteObject`).
|
- `http.go` — transport + DRY response decoders (`ResponseArray`/`ResponseObject`/`postFormObject`/`putFormObject`/`deleteObject`).
|
||||||
- `projects.go`, `tasks.go`, `users.go`, `calendar.go`, `crm.go`, `files.go` — typed / untyped domain methods.
|
- `projects.go`, `tasks.go`, `users.go`, `calendar.go`, `crm.go`, `files.go` — typed / untyped domain methods.
|
||||||
- Pure stdlib + `google/go-querystring`; no UI, no dotenv.
|
- Pure stdlib + `google/go-querystring`; no UI, no dotenv.
|
||||||
- **CLI — `cmd/oo/` as `package main`.** Cobra wrapper that loads `.env` via `godotenv` at startup. Split by domain: `main.go`, `common.go`, `calendar.go`, `tasks.go`, `crm.go`, `apps.go`. CLI-only deps (`spf13/cobra`, `joho/godotenv`) stay out of the library.
|
- **CLI — `cmd/oo/` as `package main`.** Cobra wrapper that loads `.env` via `godotenv` at startup. **Subject-based command tree** mirroring [`tea`](https://gitea.com/gitea/tea):
|
||||||
|
- `main.go` — entry point (docstring lists the command tree).
|
||||||
|
- `common.go` — `rootCmd`, `newOO`, `printTable`/`printObject`, `--output table|json` flag.
|
||||||
|
- `calendar.go`, `projects.go`, `tasks.go`, `users.go`, `contacts.go`, `opportunities.go`, `cases.go`, `crm_tasks.go`, `apps.go` — one file per subject, each registers its subject `cobra.Command` in `init()` and attaches verb subcommands (`list`/`get`/`create`/`update`/`delete`/…).
|
||||||
|
- CLI-only deps (`spf13/cobra`, `joho/godotenv`) stay out of the library.
|
||||||
- **Applications sync — `cmd/oo/applications/`.** README→CRM bridge, CV-specific; kept under `cmd/oo/` so it's clear it's internal to the binary, not a library feature.
|
- **Applications sync — `cmd/oo/applications/`.** README→CRM bridge, CV-specific; kept under `cmd/oo/` so it's clear it's internal to the binary, not a library feature.
|
||||||
|
|
||||||
## Rules
|
## Rules
|
||||||
@@ -20,6 +24,8 @@ Canonical Go client for OnlyOffice Workspace (Projects + Calendar + CRM) and the
|
|||||||
- New endpoints go into the library first; CLI commands are thin wrappers.
|
- New endpoints go into the library first; CLI commands are thin wrappers.
|
||||||
- Prefer `ResponseObject` / `postFormObject` / `putFormObject` / `deleteObject` over hand-rolled `json.Unmarshal(responseField(...))` blocks — they exist for DRY, use them.
|
- Prefer `ResponseObject` / `postFormObject` / `putFormObject` / `deleteObject` over hand-rolled `json.Unmarshal(responseField(...))` blocks — they exist for DRY, use them.
|
||||||
- Domain split is by file, **not** by subpackage. Don't introduce `internal/` or `pkg/*` subpackages inside the library — it flattens the `*Client` call surface for a reason.
|
- Domain split is by file, **not** by subpackage. Don't introduce `internal/` or `pkg/*` subpackages inside the library — it flattens the `*Client` call surface for a reason.
|
||||||
|
- CLI commands follow **subject → verb** structure (`oo <subject> <verb>`), never `oo <verb>-<subject>`. Add new commands to the existing subject file if one fits; create a new `cmd/oo/<subject>.go` for a genuinely new domain.
|
||||||
|
- Every table output goes through `printTable(headers, rows)`; every single-object through `printObject(v)`. Do not `fmt.Println` rows ad-hoc or the `--output json` flag breaks for that command.
|
||||||
- No secrets in the repo; use `.env` (gitignored). Commit `.env.example` only.
|
- No secrets in the repo; use `.env` (gitignored). Commit `.env.example` only.
|
||||||
- Follow SemVer on tags; this repo is tagged at GitHub under `git@github.com:eSlider/go-onlyoffice.git`.
|
- Follow SemVer on tags; this repo is tagged at GitHub under `git@github.com:eSlider/go-onlyoffice.git`.
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,64 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [0.5.0] - 2026-04-24
|
||||||
|
|
||||||
|
### Changed — CLI **BREAKING**
|
||||||
|
|
||||||
|
- **Subject-based command tree** (`tea`-style). The flat `oo verb-noun`
|
||||||
|
layout is replaced with `oo <subject> <verb>`:
|
||||||
|
|
||||||
|
| Old | New |
|
||||||
|
|---|---|
|
||||||
|
| `oo cal-list` | `oo calendar list` |
|
||||||
|
| `oo cal-events` | `oo calendar events` |
|
||||||
|
| `oo cal-add` / `cal-delete` | `oo calendar add` / `calendar delete` |
|
||||||
|
| `oo task-list` | `oo tasks list` |
|
||||||
|
| `oo task-add` | `oo tasks create` |
|
||||||
|
| `oo task-update` | `oo tasks update` (deletion moved to `oo tasks delete`) |
|
||||||
|
| `oo subtask-add` | `oo tasks subtask add` |
|
||||||
|
| `oo crm-contacts` | `oo contacts list` (plus filtered `oo persons list` / `oo companies list`) |
|
||||||
|
| `oo crm-add-contact --company …` | `oo companies create --name …` |
|
||||||
|
| `oo crm-add-contact --person-first …` | `oo persons create --first …` |
|
||||||
|
| `oo crm-deals` | `oo opportunities list` |
|
||||||
|
| `oo crm-deals --stages` | `oo opportunities stages` |
|
||||||
|
| `oo crm-add-deal` | `oo opportunities create` |
|
||||||
|
| `oo crm-cases` | `oo cases list` |
|
||||||
|
| `oo applications-sync` | `oo applications sync` |
|
||||||
|
|
||||||
|
- **New subjects**: `oo projects {list,get,milestones,create,update,delete}`,
|
||||||
|
`oo users {list,self}` (plus top-level `oo whoami`), `oo crm-tasks
|
||||||
|
{list,create,delete,categories}`, `oo cases {create,delete,member-add}`,
|
||||||
|
`oo contacts {get,info-add}`.
|
||||||
|
|
||||||
|
- **Global `--output/-o` flag**: all list-like commands now support
|
||||||
|
`--output table` (default; tabwriter-aligned, truncated to 80 chars
|
||||||
|
per cell) and `--output json`. Nested `bidCurrency` flattened to its
|
||||||
|
`abbreviation` in the table view.
|
||||||
|
|
||||||
|
- **Module aliases**: `oo calendar|cal`, `oo projects|prj`, `oo tasks|task`,
|
||||||
|
`oo persons|person`, `oo companies|company`, `oo opportunities|deals|deal`,
|
||||||
|
`oo cases|case`, `oo applications|apps`. `delete|rm` on every leaf that
|
||||||
|
removes things.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- `cmd/oo/common.go` — shared `printTable(headers, rows)` and
|
||||||
|
`printObject(v)` helpers that dispatch on the `--output` flag.
|
||||||
|
- `cmd/oo/users.go` — exposes `oo users list`, `oo users self`, `oo whoami`
|
||||||
|
via the library's `GetUsers` + `SelfUserID`.
|
||||||
|
- `cmd/oo/projects.go` — full CRUD for projects backed by `CreateProject`,
|
||||||
|
`UpdateProject`, `DeleteProject`, `GetProjectByID`, `GetProjectMilestones`.
|
||||||
|
- `cmd/oo/crm_tasks.go` — dedicated `oo crm-tasks` subject (distinct from
|
||||||
|
project `oo tasks`).
|
||||||
|
- `cmd/oo/contacts.go` — unified contacts/persons/companies with shared
|
||||||
|
list/filter implementation.
|
||||||
|
|
||||||
|
### Changed — library (minor)
|
||||||
|
|
||||||
|
- `newOO` in `cmd/oo/common.go` now calls `AuthenticateContext(cmd.Context())`
|
||||||
|
so CLI aborts propagate to the auth request.
|
||||||
|
|
||||||
## [0.4.0] - 2026-04-24
|
## [0.4.0] - 2026-04-24
|
||||||
|
|
||||||
### Changed — project structure
|
### Changed — project structure
|
||||||
|
|||||||
@@ -537,21 +537,53 @@ client.AddSubtask(ctx, "4242", "Prepare CV")
|
|||||||
|
|
||||||
### oo (bundled command)
|
### oo (bundled command)
|
||||||
|
|
||||||
A ready-to-use Cobra CLI wrapping the library lives under `cmd/oo`:
|
A ready-to-use [Cobra](https://github.com/spf13/cobra) CLI wrapping the
|
||||||
|
library lives under `cmd/oo`. The command tree is **subject-based**, mirroring
|
||||||
|
the [`tea`](https://gitea.com/gitea/tea) CLI:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
go install github.com/eslider/go-onlyoffice/cmd/oo@latest
|
go install github.com/eslider/go-onlyoffice/cmd/oo@latest
|
||||||
oo cal-events
|
|
||||||
oo task-list --all --verbose
|
# Global: every list-style command takes -o table|json (default: table)
|
||||||
oo subtask-add 4242 "Prepare CV"
|
oo whoami
|
||||||
oo applications-sync --path ./applications/2026 --apply
|
oo users list
|
||||||
|
oo calendar events --start 2026-04-24 --end 2026-05-01
|
||||||
|
oo projects list
|
||||||
|
oo projects get 33
|
||||||
|
oo tasks list --all --verbose
|
||||||
|
oo tasks subtask add 4242 "Prepare CV"
|
||||||
|
oo persons create --first Jane --last Doe --email jane@example.com
|
||||||
|
oo companies create --name "Acme GmbH" --website https://acme.com
|
||||||
|
oo opportunities list
|
||||||
|
oo opportunities stages
|
||||||
|
oo cases list
|
||||||
|
oo crm-tasks categories
|
||||||
|
oo applications sync --path ./applications/2026 --apply
|
||||||
```
|
```
|
||||||
|
|
||||||
The CLI reads `.env` from CWD (godotenv is a CLI-only concern — the library
|
| Subject | Verbs |
|
||||||
itself never loads dotfiles). Run `oo --help` for the command reference.
|
|---|---|
|
||||||
|
| `calendar` | `list`, `events`, `add`, `delete` |
|
||||||
|
| `projects` | `list`, `get`, `milestones`, `create`, `update`, `delete` |
|
||||||
|
| `tasks` | `list`, `get`, `create`, `update`, `delete`, `subtask add` |
|
||||||
|
| `users` | `list`, `self` (alias: `oo whoami`) |
|
||||||
|
| `contacts` | `list`, `get`, `delete`, `info-add` |
|
||||||
|
| `persons` | `list` (filtered), `create`, `delete` |
|
||||||
|
| `companies` | `list` (filtered), `create`, `delete` |
|
||||||
|
| `opportunities` | `list`, `get`, `create`, `delete`, `stages`, `member-add` |
|
||||||
|
| `cases` | `list`, `create`, `delete`, `member-add` |
|
||||||
|
| `crm-tasks` | `list`, `create`, `delete`, `categories` |
|
||||||
|
| `applications` | `sync` |
|
||||||
|
|
||||||
> **0.4.0 migration note:** the binary was previously named `oo-cli` and
|
The CLI reads `.env` from CWD (godotenv is a CLI-only concern — the library
|
||||||
> lived at `cmd/oo-cli`. The command set and flags are unchanged.
|
itself never loads dotfiles). Run `oo --help` or `oo <subject> --help` for
|
||||||
|
the full command reference.
|
||||||
|
|
||||||
|
> **0.5.0 migration note:** the command tree was flattened per-subject. Old
|
||||||
|
> flat names (`oo cal-events`, `oo task-list`, `oo crm-contacts`,
|
||||||
|
> `oo applications-sync`, …) were replaced by subject-based equivalents
|
||||||
|
> (`oo calendar events`, `oo tasks list`, `oo contacts list`,
|
||||||
|
> `oo applications sync`). Flags on leaf commands are unchanged.
|
||||||
|
|
||||||
## Environment Variables
|
## Environment Variables
|
||||||
|
|
||||||
|
|||||||
+16
-5
@@ -7,19 +7,30 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
func cmdAppsSync() *cobra.Command {
|
var applicationsCmd = &cobra.Command{
|
||||||
|
Use: "applications",
|
||||||
|
Aliases: []string{"apps"},
|
||||||
|
Short: "Job-application workflow (CV tree → CRM)",
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(applicationsCmd)
|
||||||
|
applicationsCmd.AddCommand(applicationsSyncCmd())
|
||||||
|
}
|
||||||
|
|
||||||
|
func applicationsSyncCmd() *cobra.Command {
|
||||||
var apply, verbose bool
|
var apply, verbose bool
|
||||||
var base string
|
var base string
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "applications-sync",
|
Use: "sync",
|
||||||
Short: "Sync applications/*/README.md into CRM",
|
Short: "Sync applications/*/README.md into CRM (dry-run by default)",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
c, err := newOO()
|
c, err := newOO(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if base == "" {
|
if base == "" {
|
||||||
return fmt.Errorf("use --path to applications year dir (e.g. .../applications/2026)")
|
return fmt.Errorf("--path is required (points at applications year dir, e.g. .../applications/2026)")
|
||||||
}
|
}
|
||||||
paths, err := applications.Discover(base)
|
paths, err := applications.Discover(base)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
+38
-23
@@ -6,12 +6,26 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
func cmdCalList() *cobra.Command {
|
var calendarCmd = &cobra.Command{
|
||||||
|
Use: "calendar",
|
||||||
|
Aliases: []string{"cal"},
|
||||||
|
Short: "Calendars and events",
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(calendarCmd)
|
||||||
|
calendarCmd.AddCommand(calListCmd())
|
||||||
|
calendarCmd.AddCommand(calEventsCmd())
|
||||||
|
calendarCmd.AddCommand(calAddCmd())
|
||||||
|
calendarCmd.AddCommand(calDeleteCmd())
|
||||||
|
}
|
||||||
|
|
||||||
|
func calListCmd() *cobra.Command {
|
||||||
return &cobra.Command{
|
return &cobra.Command{
|
||||||
Use: "cal-list",
|
Use: "list",
|
||||||
Short: "List calendars (default date span)",
|
Short: "List calendars",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
c, err := newOO()
|
c, err := newOO(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -19,19 +33,19 @@ func cmdCalList() *cobra.Command {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
printJSON(out)
|
printTable([]string{"objectId", "title", "textColor", "backgroundColor", "isEditable"}, out)
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func cmdCalEvents() *cobra.Command {
|
func calEventsCmd() *cobra.Command {
|
||||||
var start, end string
|
var start, end string
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "cal-events",
|
Use: "events",
|
||||||
Short: "List calendar data for period (default: next 7 days)",
|
Short: "List calendar events for a date range (default: next 7 days)",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
c, err := newOO()
|
c, err := newOO(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -43,7 +57,7 @@ func cmdCalEvents() *cobra.Command {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
printJSON(out)
|
printTable([]string{"objectId", "title", "start", "end", "allDayLong"}, out)
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -52,15 +66,15 @@ func cmdCalEvents() *cobra.Command {
|
|||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func cmdCalAdd() *cobra.Command {
|
func calAddCmd() *cobra.Command {
|
||||||
var cal, desc string
|
var cal, desc string
|
||||||
var allDay bool
|
var allDay bool
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "cal-add TITLE START END",
|
Use: "add TITLE START END",
|
||||||
Short: "Add calendar event",
|
Short: "Add a calendar event",
|
||||||
Args: cobra.ExactArgs(3),
|
Args: cobra.ExactArgs(3),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
c, err := newOO()
|
c, err := newOO(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -68,23 +82,24 @@ func cmdCalAdd() *cobra.Command {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
printJSON(ev)
|
printObject(ev)
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
cmd.Flags().StringVar(&cal, "calendar", "", "calendar id (default from env)")
|
cmd.Flags().StringVar(&cal, "calendar", "", "calendar id (default from env OO_CALENDAR_ID)")
|
||||||
cmd.Flags().StringVar(&desc, "description", "", "")
|
cmd.Flags().StringVar(&desc, "description", "", "event description")
|
||||||
cmd.Flags().BoolVar(&allDay, "all-day", false, "")
|
cmd.Flags().BoolVar(&allDay, "all-day", false, "mark as all-day event")
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func cmdCalDel() *cobra.Command {
|
func calDeleteCmd() *cobra.Command {
|
||||||
return &cobra.Command{
|
return &cobra.Command{
|
||||||
Use: "cal-delete EVENT_ID [EVENT_ID...]",
|
Use: "delete EVENT_ID [EVENT_ID...]",
|
||||||
Short: "Delete calendar events",
|
Aliases: []string{"rm"},
|
||||||
|
Short: "Delete one or more calendar events",
|
||||||
Args: cobra.MinimumNArgs(1),
|
Args: cobra.MinimumNArgs(1),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
c, err := newOO()
|
c, err := newOO(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -93,7 +108,7 @@ func cmdCalDel() *cobra.Command {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
printJSON(out)
|
printObject(out)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
|||||||
+111
@@ -0,0 +1,111 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var casesCmd = &cobra.Command{
|
||||||
|
Use: "cases",
|
||||||
|
Aliases: []string{"case"},
|
||||||
|
Short: "CRM cases",
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(casesCmd)
|
||||||
|
casesCmd.AddCommand(casesListCmd())
|
||||||
|
casesCmd.AddCommand(casesCreateCmd())
|
||||||
|
casesCmd.AddCommand(casesDeleteCmd())
|
||||||
|
casesCmd.AddCommand(casesMemberAddCmd())
|
||||||
|
}
|
||||||
|
|
||||||
|
func casesListCmd() *cobra.Command {
|
||||||
|
var count, offset int
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "list",
|
||||||
|
Short: "List CRM cases",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
c, err := newOO(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
list, total, err := c.ListCases(cmd.Context(), count, offset)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if outputFormat == "table" {
|
||||||
|
fmt.Printf("total: %d (shown: %d)\n", total, len(list))
|
||||||
|
}
|
||||||
|
printTable([]string{"id", "title", "isClosed", "created"}, list)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
cmd.Flags().IntVar(&count, "count", 50, "")
|
||||||
|
cmd.Flags().IntVar(&offset, "offset", 0, "")
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func casesCreateCmd() *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "create TITLE",
|
||||||
|
Aliases: []string{"add"},
|
||||||
|
Short: "Create a case",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
c, err := newOO(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
out, err := c.CreateCase(cmd.Context(), args[0])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
printObject(out)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func casesDeleteCmd() *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "delete CASE_ID [CASE_ID...]",
|
||||||
|
Aliases: []string{"rm"},
|
||||||
|
Short: "Delete one or more cases",
|
||||||
|
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.DeleteCase(cmd.Context(), id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
printObject(out)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func casesMemberAddCmd() *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "member-add CASE_ID CONTACT_ID",
|
||||||
|
Short: "Attach a contact to a case",
|
||||||
|
Args: cobra.ExactArgs(2),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
c, err := newOO(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
out, err := c.AddCaseMember(cmd.Context(), args[0], args[1])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
printObject(out)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
+125
-21
@@ -5,40 +5,36 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"text/tabwriter"
|
||||||
|
|
||||||
onlyoffice "github.com/eslider/go-onlyoffice"
|
onlyoffice "github.com/eslider/go-onlyoffice"
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// outputFormat is the value of the global --output/-o flag. Valid values:
|
||||||
|
// "table" (default, tabwriter-rendered), "json" (machine-readable).
|
||||||
|
var outputFormat = "table"
|
||||||
|
|
||||||
var rootCmd = &cobra.Command{
|
var rootCmd = &cobra.Command{
|
||||||
Use: "oo",
|
Use: "oo",
|
||||||
Short: "OnlyOffice Workspace CLI (Go port of cv/bin/office)",
|
Short: "OnlyOffice Workspace CLI — subject-based command tree",
|
||||||
|
Long: "oo is a thin CLI over github.com/eslider/go-onlyoffice.\nCommands are grouped by OnlyOffice subject (calendar, projects, tasks, users, persons, companies, opportunities, cases, crm-tasks, applications).",
|
||||||
|
SilenceUsage: true,
|
||||||
|
SilenceErrors: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.PersistentFlags().StringVarP(&outputFormat, "output", "o", "table", "output format: table|json")
|
||||||
}
|
}
|
||||||
|
|
||||||
// execute runs the root command. Exported only to main.go in the same package.
|
// execute runs the root command. Exported only to main.go in the same package.
|
||||||
func execute() error { return rootCmd.Execute() }
|
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.
|
// newOO loads env (incl. .env in CWD) and returns an authenticated client.
|
||||||
// godotenv is a CLI-only concern; the library itself never loads dotfiles.
|
// godotenv is a CLI-only concern; the library itself never loads dotfiles.
|
||||||
func newOO() (*onlyoffice.Client, error) {
|
func newOO(cmd *cobra.Command) (*onlyoffice.Client, error) {
|
||||||
_ = godotenv.Load()
|
_ = godotenv.Load()
|
||||||
creds := onlyoffice.GetEnvironmentCredentials()
|
creds := onlyoffice.GetEnvironmentCredentials()
|
||||||
if creds.Url == "" || creds.User == "" || creds.Password == "" {
|
if creds.Url == "" || creds.User == "" || creds.Password == "" {
|
||||||
@@ -46,20 +42,110 @@ func newOO() (*onlyoffice.Client, error) {
|
|||||||
}
|
}
|
||||||
c := onlyoffice.NewClient(creds)
|
c := onlyoffice.NewClient(creds)
|
||||||
c.SetDefaults(onlyoffice.GetEnvironmentDefaults())
|
c.SetDefaults(onlyoffice.GetEnvironmentDefaults())
|
||||||
if err := c.Authenticate(); err != nil {
|
if err := c.AuthenticateContext(cmd.Context()); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// printJSON dumps any value as indented JSON.
|
||||||
func printJSON(v any) {
|
func printJSON(v any) {
|
||||||
enc := json.NewEncoder(os.Stdout)
|
enc := json.NewEncoder(os.Stdout)
|
||||||
enc.SetIndent("", " ")
|
enc.SetIndent("", " ")
|
||||||
_ = enc.Encode(v)
|
_ = enc.Encode(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// printObject renders a single value. JSON output dumps verbatim; table output
|
||||||
|
// prints key/value pairs when v is a map, otherwise falls back to JSON.
|
||||||
|
func printObject(v any) {
|
||||||
|
if outputFormat == "json" {
|
||||||
|
printJSON(v)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m, ok := v.(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
printJSON(v)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w := tabwriter.NewWriter(os.Stdout, 0, 2, 2, ' ', 0)
|
||||||
|
for _, k := range sortedKeys(m) {
|
||||||
|
fmt.Fprintf(w, "%s\t%s\n", k, fmtCell(m[k]))
|
||||||
|
}
|
||||||
|
_ = w.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
// printTable renders a list of rows. headers select & order the columns.
|
||||||
|
// When outputFormat=="json" the raw slice is dumped as-is.
|
||||||
|
func printTable(headers []string, rows []map[string]any) {
|
||||||
|
if outputFormat == "json" {
|
||||||
|
printJSON(rows)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(rows) == 0 {
|
||||||
|
fmt.Println("(empty)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w := tabwriter.NewWriter(os.Stdout, 0, 2, 2, ' ', 0)
|
||||||
|
fmt.Fprintln(w, strings.Join(headers, "\t"))
|
||||||
|
for _, row := range rows {
|
||||||
|
cells := make([]string, len(headers))
|
||||||
|
for i, h := range headers {
|
||||||
|
cells[i] = fmtCell(row[h])
|
||||||
|
}
|
||||||
|
fmt.Fprintln(w, strings.Join(cells, "\t"))
|
||||||
|
}
|
||||||
|
_ = w.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
// fmtCell turns an arbitrary value into a compact table cell.
|
||||||
|
func fmtCell(v any) string {
|
||||||
|
switch x := v.(type) {
|
||||||
|
case nil:
|
||||||
|
return ""
|
||||||
|
case string:
|
||||||
|
return truncate(x, 80)
|
||||||
|
case bool:
|
||||||
|
if x {
|
||||||
|
return "true"
|
||||||
|
}
|
||||||
|
return "false"
|
||||||
|
case float64:
|
||||||
|
if x == float64(int64(x)) {
|
||||||
|
return strconv.FormatInt(int64(x), 10)
|
||||||
|
}
|
||||||
|
return strconv.FormatFloat(x, 'f', -1, 64)
|
||||||
|
case int, int32, int64:
|
||||||
|
return fmt.Sprintf("%d", x)
|
||||||
|
default:
|
||||||
|
b, _ := json.Marshal(x)
|
||||||
|
return truncate(string(b), 80)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func truncate(s string, n int) string {
|
||||||
|
s = strings.ReplaceAll(s, "\n", " ")
|
||||||
|
if len(s) > n {
|
||||||
|
return s[:n-1] + "…"
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func sortedKeys(m map[string]any) []string {
|
||||||
|
ks := make([]string, 0, len(m))
|
||||||
|
for k := range m {
|
||||||
|
ks = append(ks, k)
|
||||||
|
}
|
||||||
|
// Deterministic ordering; small map so O(n log n) is fine.
|
||||||
|
for i := 1; i < len(ks); i++ {
|
||||||
|
for j := i; j > 0 && ks[j-1] > ks[j]; j-- {
|
||||||
|
ks[j-1], ks[j] = ks[j], ks[j-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ks
|
||||||
|
}
|
||||||
|
|
||||||
// flexIDFloat coerces OnlyOffice numeric id fields surfaced as float64 / int
|
// flexIDFloat coerces OnlyOffice numeric id fields surfaced as float64 / int
|
||||||
// / string into a float64. CLI-only; the library's own flexInt is unexported.
|
// / string into a float64.
|
||||||
func flexIDFloat(v any) float64 {
|
func flexIDFloat(v any) float64 {
|
||||||
switch x := v.(type) {
|
switch x := v.(type) {
|
||||||
case float64:
|
case float64:
|
||||||
@@ -71,3 +157,21 @@ func flexIDFloat(v any) float64 {
|
|||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// idString returns the id field as a string (works for int/float/string).
|
||||||
|
func idString(m map[string]any, key string) string {
|
||||||
|
v, ok := m[key]
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
switch x := v.(type) {
|
||||||
|
case string:
|
||||||
|
return x
|
||||||
|
case float64:
|
||||||
|
return strconv.FormatInt(int64(x), 10)
|
||||||
|
case int:
|
||||||
|
return strconv.Itoa(x)
|
||||||
|
default:
|
||||||
|
return fmt.Sprint(x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,248 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Contacts cover both persons and companies; OnlyOffice exposes them through
|
||||||
|
// the same `crm/contact/*` endpoint family. `oo persons` and `oo companies`
|
||||||
|
// are filtered views on top of `oo contacts list`, plus dedicated create
|
||||||
|
// commands that pick the right library call.
|
||||||
|
|
||||||
|
var contactsCmd = &cobra.Command{
|
||||||
|
Use: "contacts",
|
||||||
|
Aliases: []string{"contact"},
|
||||||
|
Short: "CRM contacts (persons + companies)",
|
||||||
|
}
|
||||||
|
|
||||||
|
var personsCmd = &cobra.Command{
|
||||||
|
Use: "persons",
|
||||||
|
Aliases: []string{"person"},
|
||||||
|
Short: "CRM persons (contacts with isCompany=false)",
|
||||||
|
}
|
||||||
|
|
||||||
|
var companiesCmd = &cobra.Command{
|
||||||
|
Use: "companies",
|
||||||
|
Aliases: []string{"company"},
|
||||||
|
Short: "CRM companies (contacts with isCompany=true)",
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(contactsCmd)
|
||||||
|
rootCmd.AddCommand(personsCmd)
|
||||||
|
rootCmd.AddCommand(companiesCmd)
|
||||||
|
|
||||||
|
contactsCmd.AddCommand(contactsListCmd(nil))
|
||||||
|
contactsCmd.AddCommand(contactsGetCmd())
|
||||||
|
contactsCmd.AddCommand(contactsDeleteCmd())
|
||||||
|
contactsCmd.AddCommand(contactsInfoAddCmd())
|
||||||
|
|
||||||
|
only := true
|
||||||
|
personsCmd.AddCommand(contactsListCmd(&only)) // persons only
|
||||||
|
personsCmd.AddCommand(personsCreateCmd())
|
||||||
|
personsCmd.AddCommand(contactsDeleteCmd())
|
||||||
|
|
||||||
|
onlyCo := false
|
||||||
|
companiesCmd.AddCommand(contactsListCmd(&onlyCo)) // companies only
|
||||||
|
companiesCmd.AddCommand(companiesCreateCmd())
|
||||||
|
companiesCmd.AddCommand(contactsDeleteCmd())
|
||||||
|
}
|
||||||
|
|
||||||
|
// contactsListCmd returns a `list` subcommand.
|
||||||
|
//
|
||||||
|
// - personsOnly == nil → list all contacts
|
||||||
|
// - personsOnly == &true → keep only persons (isCompany=false)
|
||||||
|
// - personsOnly == &false → keep only companies (isCompany=true)
|
||||||
|
func contactsListCmd(personsOnly *bool) *cobra.Command {
|
||||||
|
var search string
|
||||||
|
var count, offset int
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "list",
|
||||||
|
Short: "List contacts",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
c, err := newOO(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
list, total, err := c.ListContacts(cmd.Context(), count, offset, search)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
filtered := list
|
||||||
|
if personsOnly != nil {
|
||||||
|
filtered = filtered[:0]
|
||||||
|
for _, row := range list {
|
||||||
|
isCo, _ := row["isCompany"].(bool)
|
||||||
|
if *personsOnly && isCo {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !*personsOnly && !isCo {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
filtered = append(filtered, row)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if outputFormat == "table" {
|
||||||
|
fmt.Printf("total: %d (shown: %d)\n", total, len(filtered))
|
||||||
|
}
|
||||||
|
printTable([]string{"id", "displayName", "isCompany", "email", "companyName"}, filtered)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
cmd.Flags().StringVar(&search, "search", "", "search filter")
|
||||||
|
cmd.Flags().IntVar(&count, "count", 50, "")
|
||||||
|
cmd.Flags().IntVar(&offset, "offset", 0, "")
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func contactsGetCmd() *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "get CONTACT_ID",
|
||||||
|
Short: "Show a contact by id",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
c, err := newOO(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
out, err := c.GetContact(cmd.Context(), args[0])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
printObject(out)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func contactsDeleteCmd() *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "delete CONTACT_ID [CONTACT_ID...]",
|
||||||
|
Aliases: []string{"rm"},
|
||||||
|
Short: "Delete one or more contacts",
|
||||||
|
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.DeleteContact(cmd.Context(), id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
printObject(out)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func contactsInfoAddCmd() *cobra.Command {
|
||||||
|
var infoType, value, category string
|
||||||
|
var isPrimary bool
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "info-add CONTACT_ID",
|
||||||
|
Short: "Add a contact info entry (email, phone, website, linkedin, …)",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
if infoType == "" || value == "" {
|
||||||
|
return fmt.Errorf("--type and --value are required")
|
||||||
|
}
|
||||||
|
c, err := newOO(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
out, err := c.AddContactInfo(cmd.Context(), args[0], infoType, value, category, isPrimary)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
printObject(out)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
cmd.Flags().StringVar(&infoType, "type", "", "Email|Phone|Website|LinkedIn|…")
|
||||||
|
cmd.Flags().StringVar(&value, "value", "", "value")
|
||||||
|
cmd.Flags().StringVar(&category, "category", "Work", "Work|Home|Other")
|
||||||
|
cmd.Flags().BoolVar(&isPrimary, "primary", false, "mark as primary")
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func personsCreateCmd() *cobra.Command {
|
||||||
|
var first, last, email, linkedin string
|
||||||
|
var companyID int
|
||||||
|
var jobTitle, about string
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "create",
|
||||||
|
Aliases: []string{"add"},
|
||||||
|
Short: "Create a person",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
if first == "" || last == "" {
|
||||||
|
return fmt.Errorf("--first and --last are required")
|
||||||
|
}
|
||||||
|
c, err := newOO(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
out, err := c.CreatePerson(cmd.Context(), first, last, companyID, jobTitle, about)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pid := strconv.Itoa(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)
|
||||||
|
}
|
||||||
|
printObject(out)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
cmd.Flags().StringVar(&first, "first", "", "first name")
|
||||||
|
cmd.Flags().StringVar(&last, "last", "", "last name")
|
||||||
|
cmd.Flags().IntVar(&companyID, "company-id", 0, "employer company id")
|
||||||
|
cmd.Flags().StringVar(&jobTitle, "job-title", "", "")
|
||||||
|
cmd.Flags().StringVar(&about, "about", "", "about / bio")
|
||||||
|
cmd.Flags().StringVar(&email, "email", "", "primary email (adds ContactInfo)")
|
||||||
|
cmd.Flags().StringVar(&linkedin, "linkedin", "", "linkedin url (adds ContactInfo)")
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func companiesCreateCmd() *cobra.Command {
|
||||||
|
var name, email, website string
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "create",
|
||||||
|
Aliases: []string{"add"},
|
||||||
|
Short: "Create a company",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
if name == "" {
|
||||||
|
return fmt.Errorf("--name is required")
|
||||||
|
}
|
||||||
|
c, err := newOO(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
out, err := c.CreateCompany(cmd.Context(), name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cid := strconv.Itoa(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)
|
||||||
|
}
|
||||||
|
printObject(out)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
cmd.Flags().StringVar(&name, "name", "", "company name")
|
||||||
|
cmd.Flags().StringVar(&email, "email", "", "primary email (adds ContactInfo)")
|
||||||
|
cmd.Flags().StringVar(&website, "website", "", "website url (adds ContactInfo)")
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
-181
@@ -1,181 +0,0 @@
|
|||||||
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,123 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var crmTasksCmd = &cobra.Command{
|
||||||
|
Use: "crm-tasks",
|
||||||
|
Aliases: []string{"crm-task"},
|
||||||
|
Short: "CRM tasks (standalone, not project tasks)",
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(crmTasksCmd)
|
||||||
|
crmTasksCmd.AddCommand(crmTasksListCmd())
|
||||||
|
crmTasksCmd.AddCommand(crmTasksCreateCmd())
|
||||||
|
crmTasksCmd.AddCommand(crmTasksDeleteCmd())
|
||||||
|
crmTasksCmd.AddCommand(crmTasksCategoriesCmd())
|
||||||
|
}
|
||||||
|
|
||||||
|
func crmTasksListCmd() *cobra.Command {
|
||||||
|
var count, offset int
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "list",
|
||||||
|
Short: "List CRM tasks",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
c, err := newOO(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
list, total, err := c.ListCRMTasks(cmd.Context(), count, offset)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if outputFormat == "table" {
|
||||||
|
fmt.Printf("total: %d (shown: %d)\n", total, len(list))
|
||||||
|
}
|
||||||
|
printTable([]string{"id", "title", "deadline", "isClosed", "categoryID"}, list)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
cmd.Flags().IntVar(&count, "count", 50, "")
|
||||||
|
cmd.Flags().IntVar(&offset, "offset", 0, "")
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func crmTasksCreateCmd() *cobra.Command {
|
||||||
|
var deadline, desc, entityType string
|
||||||
|
var categoryID, contactID, entityID int
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "create TITLE",
|
||||||
|
Aliases: []string{"add"},
|
||||||
|
Short: "Create a CRM task",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
c, err := newOO(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
out, err := c.CreateCRMTask(cmd.Context(), args[0], deadline, categoryID, contactID, entityType, entityID, desc)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
printObject(out)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
cmd.Flags().StringVar(&deadline, "deadline", "", "deadline (YYYY-MM-DD or ISO8601)")
|
||||||
|
cmd.Flags().IntVar(&categoryID, "category", 0, "category id (see `oo crm-tasks categories`)")
|
||||||
|
cmd.Flags().IntVar(&contactID, "contact", 0, "contact id")
|
||||||
|
cmd.Flags().StringVar(&entityType, "entity-type", "", "opportunity|case|contact")
|
||||||
|
cmd.Flags().IntVar(&entityID, "entity-id", 0, "parent entity id")
|
||||||
|
cmd.Flags().StringVar(&desc, "description", "", "description")
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func crmTasksDeleteCmd() *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "delete TASK_ID [TASK_ID...]",
|
||||||
|
Aliases: []string{"rm"},
|
||||||
|
Short: "Delete CRM 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 {
|
||||||
|
if _, err := strconv.Atoi(id); err != nil {
|
||||||
|
return fmt.Errorf("task id %q must be integer: %w", id, err)
|
||||||
|
}
|
||||||
|
out, err := c.DeleteCRMTask(cmd.Context(), id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
printObject(out)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func crmTasksCategoriesCmd() *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "categories",
|
||||||
|
Short: "List CRM task categories",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
c, err := newOO(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
out, err := c.ListTaskCategories(cmd.Context())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
printTable([]string{"id", "title", "sortOrder", "imagePath"}, out)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
+16
-6
@@ -1,14 +1,24 @@
|
|||||||
// Command oo is a thin CLI over the github.com/eslider/go-onlyoffice library.
|
// Command oo is a thin CLI over the github.com/eslider/go-onlyoffice library.
|
||||||
// It exposes calendar, CRM, tasks, subtasks, and application-sync commands.
|
//
|
||||||
|
// Command tree is subject-based (mirrors the library split and the `tea` CLI):
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
//
|
||||||
|
// Every list supports `--output/-o json|table` (table is the default).
|
||||||
//
|
//
|
||||||
// Build & install:
|
// Build & install:
|
||||||
//
|
//
|
||||||
// go install github.com/eslider/go-onlyoffice/cmd/oo@latest
|
// 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
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -0,0 +1,177 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var opportunitiesCmd = &cobra.Command{
|
||||||
|
Use: "opportunities",
|
||||||
|
Aliases: []string{"deals", "deal"},
|
||||||
|
Short: "CRM opportunities (a.k.a. deals)",
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(opportunitiesCmd)
|
||||||
|
opportunitiesCmd.AddCommand(oppListCmd())
|
||||||
|
opportunitiesCmd.AddCommand(oppGetCmd())
|
||||||
|
opportunitiesCmd.AddCommand(oppCreateCmd())
|
||||||
|
opportunitiesCmd.AddCommand(oppDeleteCmd())
|
||||||
|
opportunitiesCmd.AddCommand(oppStagesCmd())
|
||||||
|
opportunitiesCmd.AddCommand(oppMemberAddCmd())
|
||||||
|
}
|
||||||
|
|
||||||
|
func oppListCmd() *cobra.Command {
|
||||||
|
var count, offset int
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "list",
|
||||||
|
Short: "List opportunities",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
c, err := newOO(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
list, total, err := c.ListOpportunities(cmd.Context(), count, offset)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if outputFormat == "table" {
|
||||||
|
fmt.Printf("total: %d (shown: %d)\n", total, len(list))
|
||||||
|
// Flatten nested bidCurrency {abbreviation, ...} for readability.
|
||||||
|
for _, row := range list {
|
||||||
|
if cur, ok := row["bidCurrency"].(map[string]any); ok {
|
||||||
|
row["bidCurrency"] = cur["abbreviation"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
printTable([]string{"id", "title", "bidValue", "bidCurrency", "stageName"}, list)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
cmd.Flags().IntVar(&count, "count", 100, "")
|
||||||
|
cmd.Flags().IntVar(&offset, "offset", 0, "")
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func oppGetCmd() *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "get OPPORTUNITY_ID",
|
||||||
|
Short: "Show an opportunity by id",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
c, err := newOO(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
out, err := c.GetOpportunity(cmd.Context(), args[0])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
printObject(out)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func oppCreateCmd() *cobra.Command {
|
||||||
|
var stage int
|
||||||
|
var bid float64
|
||||||
|
var currency, responsible, desc string
|
||||||
|
var contacts []string
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "create TITLE",
|
||||||
|
Aliases: []string{"add"},
|
||||||
|
Short: "Create an opportunity",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
c, err := newOO(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if stage == 0 {
|
||||||
|
stage = 1
|
||||||
|
}
|
||||||
|
out, err := c.CreateOpportunity(cmd.Context(), args[0], stage, responsible, currency, desc, bid)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
did := strconv.Itoa(int(flexIDFloat(out["id"])))
|
||||||
|
for _, cid := range contacts {
|
||||||
|
_, _ = c.AddOpportunityMember(cmd.Context(), did, cid)
|
||||||
|
}
|
||||||
|
printObject(out)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
cmd.Flags().IntVar(&stage, "stage", 1, "pipeline stage id")
|
||||||
|
cmd.Flags().Float64Var(&bid, "bid", 0, "bid value")
|
||||||
|
cmd.Flags().StringVar(¤cy, "currency", "EUR", "bid currency")
|
||||||
|
cmd.Flags().StringVar(&responsible, "responsible", "", "responsible user id")
|
||||||
|
cmd.Flags().StringVar(&desc, "description", "", "description")
|
||||||
|
cmd.Flags().StringSliceVar(&contacts, "contact", nil, "contact id to add as member (repeatable)")
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func oppDeleteCmd() *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "delete OPPORTUNITY_ID [OPPORTUNITY_ID...]",
|
||||||
|
Aliases: []string{"rm"},
|
||||||
|
Short: "Delete one or more opportunities",
|
||||||
|
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.DeleteOpportunity(cmd.Context(), id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
printObject(out)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func oppStagesCmd() *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "stages",
|
||||||
|
Short: "List pipeline stages",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
c, err := newOO(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
out, err := c.ListDealStages(cmd.Context())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
printTable([]string{"id", "title", "sortOrder", "color"}, out)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func oppMemberAddCmd() *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "member-add OPPORTUNITY_ID CONTACT_ID",
|
||||||
|
Short: "Attach a contact to an opportunity",
|
||||||
|
Args: cobra.ExactArgs(2),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
c, err := newOO(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
out, err := c.AddOpportunityMember(cmd.Context(), args[0], args[1])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
printObject(out)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,231 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
onlyoffice "github.com/eslider/go-onlyoffice"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var projectsCmd = &cobra.Command{
|
||||||
|
Use: "projects",
|
||||||
|
Aliases: []string{"prj"},
|
||||||
|
Short: "OnlyOffice Projects",
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(projectsCmd)
|
||||||
|
projectsCmd.AddCommand(prjListCmd())
|
||||||
|
projectsCmd.AddCommand(prjGetCmd())
|
||||||
|
projectsCmd.AddCommand(prjMilestonesCmd())
|
||||||
|
projectsCmd.AddCommand(prjCreateCmd())
|
||||||
|
projectsCmd.AddCommand(prjUpdateCmd())
|
||||||
|
projectsCmd.AddCommand(prjDeleteCmd())
|
||||||
|
}
|
||||||
|
|
||||||
|
func prjListCmd() *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "list",
|
||||||
|
Short: "List all projects",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
c, err := newOO(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
list, err := c.GetProjects()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rows := make([]map[string]any, 0, len(list))
|
||||||
|
for _, p := range list {
|
||||||
|
row := map[string]any{
|
||||||
|
"id": derefInt(p.ID),
|
||||||
|
"title": p.String(),
|
||||||
|
"status": derefInt(p.Status),
|
||||||
|
}
|
||||||
|
if p.TaskCount != nil {
|
||||||
|
row["tasks"] = *p.TaskCount
|
||||||
|
}
|
||||||
|
if p.IsPrivate != nil {
|
||||||
|
row["private"] = *p.IsPrivate
|
||||||
|
}
|
||||||
|
rows = append(rows, row)
|
||||||
|
}
|
||||||
|
printTable([]string{"id", "title", "status", "tasks", "private"}, rows)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func prjGetCmd() *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "get [PROJECT_ID]",
|
||||||
|
Short: "Show a single project (default: $OO_PROJECT_ID)",
|
||||||
|
Args: cobra.MaximumNArgs(1),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
c, err := newOO(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
id := ""
|
||||||
|
if len(args) == 1 {
|
||||||
|
id = args[0]
|
||||||
|
}
|
||||||
|
out, err := c.GetProjectByID(cmd.Context(), id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
printObject(out)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func prjMilestonesCmd() *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "milestones PROJECT_ID",
|
||||||
|
Short: "List milestones of a project",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
c, err := newOO(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pid, err := strconv.Atoi(args[0])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("project id must be integer: %w", err)
|
||||||
|
}
|
||||||
|
ms, err := c.GetProjectMilestones(&onlyoffice.Project{ID: &pid})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rows := make([]map[string]any, 0, len(ms))
|
||||||
|
for _, m := range ms {
|
||||||
|
rows = append(rows, map[string]any{
|
||||||
|
"id": derefInt64(m.ID),
|
||||||
|
"title": derefString(m.Title),
|
||||||
|
"status": derefInt64(m.Status),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
printTable([]string{"id", "title", "status"}, rows)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func prjCreateCmd() *cobra.Command {
|
||||||
|
var desc, resp string
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "create TITLE",
|
||||||
|
Short: "Create a new project",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
c, err := newOO(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p, err := c.CreateProject(onlyoffice.NewProjectRequest{
|
||||||
|
Title: args[0],
|
||||||
|
Description: desc,
|
||||||
|
ResponsibleID: resp,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
printObject(map[string]any{
|
||||||
|
"id": derefInt(p.ID),
|
||||||
|
"title": p.String(),
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
cmd.Flags().StringVar(&desc, "description", "", "project description")
|
||||||
|
cmd.Flags().StringVar(&resp, "responsible", "", "responsible user id (default: self)")
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func prjUpdateCmd() *cobra.Command {
|
||||||
|
var title, desc, resp string
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "update PROJECT_ID",
|
||||||
|
Short: "Update project fields (only non-empty)",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
c, err := newOO(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
id, err := strconv.Atoi(args[0])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("project id must be integer: %w", err)
|
||||||
|
}
|
||||||
|
p, err := c.UpdateProject(onlyoffice.ProjectUpdateRequest{
|
||||||
|
ID: id,
|
||||||
|
Title: title,
|
||||||
|
Description: desc,
|
||||||
|
ResponsibleID: resp,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
printObject(map[string]any{
|
||||||
|
"id": derefInt(p.ID),
|
||||||
|
"title": p.String(),
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
cmd.Flags().StringVar(&title, "title", "", "new title")
|
||||||
|
cmd.Flags().StringVar(&desc, "description", "", "new description")
|
||||||
|
cmd.Flags().StringVar(&resp, "responsible", "", "new responsible user id")
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func prjDeleteCmd() *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "delete PROJECT_ID [PROJECT_ID...]",
|
||||||
|
Aliases: []string{"rm"},
|
||||||
|
Short: "Delete one or more projects",
|
||||||
|
Args: cobra.MinimumNArgs(1),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
c, err := newOO(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, raw := range args {
|
||||||
|
id, err := strconv.Atoi(raw)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("project id %q must be integer: %w", raw, err)
|
||||||
|
}
|
||||||
|
p, err := c.DeleteProject(id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
printObject(map[string]any{"id": derefInt(p.ID), "title": p.String()})
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func derefString(p *string) string {
|
||||||
|
if p == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return *p
|
||||||
|
}
|
||||||
|
|
||||||
|
func derefInt(p *int) int {
|
||||||
|
if p == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return *p
|
||||||
|
}
|
||||||
|
|
||||||
|
func derefInt64(p *int64) int64 {
|
||||||
|
if p == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return *p
|
||||||
|
}
|
||||||
+119
-64
@@ -6,14 +6,30 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"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 project, status string
|
||||||
var all, verbose bool
|
var all, verbose bool
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "task-list",
|
Use: "list",
|
||||||
Short: "List project tasks",
|
Short: "List project tasks",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
c, err := newOO()
|
c, err := newOO(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -26,36 +42,54 @@ func cmdTaskList() *cobra.Command {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, t := range tasks {
|
|
||||||
fmt.Printf("[%v] %v status=%v\n", t["id"], t["title"], t["status"])
|
|
||||||
if verbose {
|
if verbose {
|
||||||
d, _ := c.GetTaskByID(cmd.Context(), fmt.Sprint(t["id"]))
|
for i, t := range tasks {
|
||||||
subs, _ := d["subtasks"].([]any)
|
d, err := c.GetTaskByID(cmd.Context(), idString(t, "id"))
|
||||||
for _, s := range subs {
|
if err == nil {
|
||||||
sm, _ := s.(map[string]any)
|
tasks[i] = d
|
||||||
fmt.Printf(" └─ [%v] %v\n", sm["id"], sm["title"])
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
printTable([]string{"id", "title", "status", "priority", "responsible"}, tasks)
|
||||||
return nil
|
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().StringVarP(&status, "status", "s", "", "open|closed")
|
||||||
cmd.Flags().BoolVarP(&all, "all", "a", false, "all projects (@self)")
|
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
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func cmdTaskAdd() *cobra.Command {
|
func taskGetCmd() *cobra.Command {
|
||||||
var project, desc, deadline string
|
return &cobra.Command{
|
||||||
var prio string
|
Use: "get TASK_ID",
|
||||||
cmd := &cobra.Command{
|
Short: "Show a single task (incl. subtasks)",
|
||||||
Use: "task-add TITLE",
|
|
||||||
Short: "Add project task",
|
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -70,24 +104,79 @@ func cmdTaskAdd() *cobra.Command {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
printJSON(out)
|
printObject(out)
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
cmd.Flags().StringVar(&project, "project", "", "")
|
cmd.Flags().StringVarP(&project, "project", "p", "", "project id (default $OO_PROJECT_ID)")
|
||||||
cmd.Flags().StringVar(&desc, "description", "", "")
|
cmd.Flags().StringVar(&desc, "description", "", "description")
|
||||||
cmd.Flags().StringVar(&deadline, "deadline", "", "")
|
cmd.Flags().StringVar(&deadline, "deadline", "", "deadline YYYY-MM-DD")
|
||||||
cmd.Flags().StringVar(&prio, "priority", "normal", "high|normal|low")
|
cmd.Flags().StringVar(&prio, "priority", "normal", "high|normal|low")
|
||||||
return cmd
|
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{
|
return &cobra.Command{
|
||||||
Use: "subtask-add PARENT_TASK_ID TITLE [TITLE...]",
|
Use: "delete TASK_ID [TASK_ID...]",
|
||||||
Short: "Add subtask(s)",
|
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),
|
Args: cobra.MinimumNArgs(2),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
c, err := newOO()
|
c, err := newOO(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -97,44 +186,10 @@ func cmdSubtaskAdd() *cobra.Command {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
printJSON(out)
|
printObject(out)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
})
|
||||||
}
|
return sub
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var usersCmd = &cobra.Command{
|
||||||
|
Use: "users",
|
||||||
|
Short: "Portal / workspace users",
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(usersCmd)
|
||||||
|
usersCmd.AddCommand(usersListCmd())
|
||||||
|
usersCmd.AddCommand(usersSelfCmd())
|
||||||
|
rootCmd.AddCommand(whoamiCmd())
|
||||||
|
}
|
||||||
|
|
||||||
|
func usersListCmd() *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "list",
|
||||||
|
Short: "List portal users",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
c, err := newOO(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
users, err := c.GetUsers()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rows := make([]map[string]any, 0, len(users))
|
||||||
|
for _, u := range users {
|
||||||
|
rows = append(rows, map[string]any{
|
||||||
|
"id": derefString(u.ID),
|
||||||
|
"userName": derefString(u.UserName),
|
||||||
|
"displayName": derefString(u.DisplayName),
|
||||||
|
"email": derefString(u.Email),
|
||||||
|
"isAdmin": derefBool(u.IsAdmin),
|
||||||
|
"status": derefInt(u.Status),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
printTable([]string{"id", "userName", "displayName", "email", "isAdmin", "status"}, rows)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func usersSelfCmd() *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "self",
|
||||||
|
Short: "Show current user id (people/@self)",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
c, err := newOO(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
id, err := c.SelfUserID(cmd.Context())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
printObject(map[string]any{"id": id})
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// whoamiCmd is a convenience shortcut at the root level.
|
||||||
|
func whoamiCmd() *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "whoami",
|
||||||
|
Short: "Alias for `oo users self`",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
c, err := newOO(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
id, err := c.SelfUserID(cmd.Context())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
printObject(map[string]any{"id": id})
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func derefBool(p *bool) bool {
|
||||||
|
if p == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return *p
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user