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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 49 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,9 @@ copera boards list
copera boards list --query "roadmap"
copera boards get <board-id>
copera boards create --name "Q3 Roadmap"
copera boards create --name "CRM" \
--column '{"label":"Contact","type":"CONTACT"}' \
--column '{"label":"Follow up","type":"DATE","format":"DATE_ISO"}'
copera boards update <board-id> --name "Q4 Roadmap"
copera boards delete <board-id> --force
copera boards participants add --board <board-id> --participant <user-id>
Expand All @@ -192,6 +195,11 @@ for the exact flags each write command accepts.
```bash
copera rows list --board <board-id> --table <table-id>
copera rows list --board <board-id> --table <table-id> --query "oauth"
copera rows list --board <board-id> --table <table-id> --offset 0 --limit 100
copera rows query --data @query.json
copera rows aggregate <board-id> <table-id> --data @aggregation.json
copera rows bulk <board-id> <table-id> --data @operations.json
copera rows views <board-id> <table-id>
copera rows get <row-id> --board <board-id> --table <table-id>
copera rows get --row 42 --board <board-id> --table <table-id> # look up by visible Row#
copera rows create --board <board-id> --table <table-id> --data '{"columns":[{"columnId":"<column-id>","value":"Hello"}]}'
Expand Down Expand Up @@ -252,6 +260,7 @@ copera docs update <doc-id> --content "Replacement content"
copera docs update <doc-id> --operation append --content "More content"
copera docs metadata <doc-id> --title "New title"
copera docs delete <doc-id> --force
copera docs generate --data @document.json
```

### Drive
Expand Down Expand Up @@ -304,20 +313,59 @@ copera workspace teams
copera workspace team create --name "Design" --participant <user-id>
```

### Bookings and Meeting Notes
### Tempo, Agenda, Inbox, Automations, and Bookings

```bash
copera tempo calendars list # `time` is an alias
copera tempo calendars create --field name="Focus"
copera tempo shelf create --data '{"title":"Prepare review"}'
copera tempo shelf schedule <item-id> --data @schedule.json

copera agenda events list --query-param from=2026-09-01T00:00:00Z
copera agenda events create --data @event.json
copera agenda events respond <event-id> --field status=ACCEPTED

copera inbox list
copera inbox threads list <inbox-id> --query-param page=1
copera inbox send --data @message.json
copera inbox attachments upload <inbox-id> --file ./contract.pdf
copera inbox delete <inbox-id> --data @email-ids.json --force

copera automations list
copera automations run <automation-id> --data @trigger.json
copera automations retry <run-id>

copera bookings list
copera bookings list --status CONFIRMED --from 2026-01-01T00:00:00Z
copera bookings get <booking-id>
copera bookings cancel <booking-id> --field reason="Customer request" --force
copera booking-types list
copera booking-types create --data @booking-type.json
copera booking-host profile get
copera booking-host schedules create --data @schedule.json
copera booking-host links create <booking-type-id> --field maxUses=1

copera meeting-notes list
copera meeting-notes list --query "standup"
copera meeting-notes get <note-id>
copera meeting-notes get <note-id> --transcript
```

Newer structured commands accept `--data '{...}'`, `--data @file.json`, or
`--data -` for stdin. Repeatable `--field key=value` flags override top-level
JSON fields, and `--query-param key=value` adds URL query parameters. Destructive
commands require interactive confirmation or `--force`.

Inbox requires a Personal Access Token with `access_inbox`; it is deliberately
available through REST and the CLI, not through hosted MCP.

### Exports

```bash
copera exports job <job-id>
copera exports result-set --data '{"resultSetId":"<id>","format":"CSV"}'
```

### Search and Notifications

```bash
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.15.0
0.16.0
46 changes: 38 additions & 8 deletions commands/boards.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package commands

import (
"context"
"encoding/json"
"fmt"
"os"
"strings"
Expand Down Expand Up @@ -415,6 +416,8 @@ func parseColumnOptions(raw []string) []api.ColumnOptionInput {

func newBoardsCreateCmd(cli *CLI) *cobra.Command {
var flagName, flagDescription, flagTableName string
var flagData string
var flagColumns []string

cmd := &cobra.Command{
Use: "create",
Expand All @@ -430,23 +433,48 @@ Example:
return err
}

if flagName == "" {
payload, err := readObjectInput(cli, flagData, false)
if err != nil {
return usageError(cli, err)
}
if payload == nil {
payload = map[string]any{}
}
if cmd.Flags().Changed("name") {
payload["name"] = flagName
}
if cmd.Flags().Changed("description") {
payload["description"] = flagDescription
}
if cmd.Flags().Changed("table-name") {
payload["tableName"] = flagTableName
}
if len(flagColumns) > 0 {
columns := make([]any, 0, len(flagColumns))
for _, raw := range flagColumns {
var column map[string]any
if err := json.Unmarshal([]byte(raw), &column); err != nil {
return usageError(cli, fmt.Errorf("invalid --column JSON: %w", err))
}
columns = append(columns, column)
}
payload["columns"] = columns
}
name, _ := payload["name"].(string)
if name == "" {
cli.Printer.PrintError("missing_input", "board name is required",
"Use --name <board-name>", false)
"Use --name <board-name> or provide name in --data", false)
return exitcodes.Newf(exitcodes.Usage, "board name is required")
}

board, err := client.BoardCreate(context.Background(), &api.CreateBoardInput{
Name: flagName,
Description: flagDescription,
TableName: flagTableName,
})
var board api.CreatedBoard
err = client.DoJSON(context.Background(), "POST", "/board", payload, &board)
if err != nil {
return apiError(cli, err)
}

if cli.Printer.IsJSON() {
return cli.Printer.PrintJSON(board)
return cli.Printer.PrintJSON(&board)
}

cli.Printer.PrintLine(fmt.Sprintf("ID: %s", board.ID))
Expand All @@ -463,6 +491,8 @@ Example:
cmd.Flags().StringVar(&flagName, "name", "", "Board name (required)")
cmd.Flags().StringVar(&flagDescription, "description", "", "Board description")
cmd.Flags().StringVar(&flagTableName, "table-name", "", "Name for the initial table (default: Table)")
cmd.Flags().StringVar(&flagData, "data", "", "Full board JSON inline, @file, or - for stdin")
cmd.Flags().StringArrayVar(&flagColumns, "column", nil, `Initial column JSON, e.g. {"label":"Due","type":"DATE","format":"DATE_ISO"} (repeatable)`)
return cmd
}

Expand Down
2 changes: 2 additions & 0 deletions commands/bookings.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ func newBookingsCmd(cli *CLI) *cobra.Command {
newBookingsListCmd(cli),
newBookingsGetCmd(cli),
)
cmd.AddCommand(bookingLifecycleCommands(cli)...)
return cmd
}

Expand Down Expand Up @@ -146,6 +147,7 @@ func newBookingTypesCmd(cli *CLI) *cobra.Command {
cmd.AddCommand(
newBookingTypesListCmd(cli),
)
cmd.AddCommand(bookingTypeManagementCommands(cli)...)
return cmd
}

Expand Down
2 changes: 1 addition & 1 deletion commands/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ func newDocsCmd(cli *CLI) *cobra.Command {
newDocsMetadataCmd(cli),
newDocsCreateCmd(cli),
newDocsDeleteCmd(cli),
newEndpointCmd(cli, endpointSpec{Use: "generate", Short: "Generate a PDF, spreadsheet, CSV, Markdown, or HTML document", Method: "POST", Path: "/documents/generate", Body: true, BodyRequired: true}),
)
return cmd
}
Expand Down Expand Up @@ -502,4 +503,3 @@ func readStdinContent(cli *CLI) (string, error) {
}
return content, nil
}

Loading
Loading