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
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ go run ./cmd/gws # or go run .

## Current Version

**v1.38.0** - Drive `resolve-comment` / `unresolve-comment` now use `Replies.Create` with `action=resolve|reopen` (preserves the original comment; optional `--content` for a closing/reopening note). Gmail `read` and `thread` expose an `attachments` array with `filename`, `mime_type`, `size`, `attachment_id`, and `part_id` so callers can chain into `gws gmail attachment`. New `gws chat recent --since <2h|7d|RFC3339>` recaps messages across active spaces using `lastActiveTime` as a prefilter, with `--max`, `--max-per-space`, `--max-spaces`, `--resolve-senders`, and `--exclude-self`.
**v1.39.0** - Adds `--raw` and `--params` to six list/get commands for programmatic, API-shape JSON output (skips field renaming, base64 decoding, header collapsing). New noun-verb command paths: `gws chat spaces list`, `gws chat messages list`, `gws chat members list`, and `gws people get`. `gmail list` switches to `users.messages.list` shape under `--raw`. `--all` aggregates the top-level list field across pages and drops `nextPageToken`. `--params` is a JSON object whose keys map directly to Google API request parameters and override the equivalent CLI flags (params win). `contacts get` and `gmail thread` relaxed to accept the resource id via `--params resourceName`/`--params id`.

## Roadmap

Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ PKG := ./cmd/gws
BUILD_DIR := ./bin

# Version info
VERSION ?= 1.38.0
VERSION ?= 1.39.0
COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown")
BUILD_DATE := $(shell date -u +"%Y-%m-%dT%H:%M:%SZ")
LDFLAGS := -ldflags "-X github.com/omriariav/workspace-cli/cmd.Version=$(VERSION) \
Expand Down
57 changes: 51 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,9 @@ Add `--format text` for human-readable output, or `--format yaml` for YAML.

| Command | Description |
|---------|-------------|
| `gws gmail list` | List threads with `thread_id` and `message_id` (`--max`, `--query`, `--all`, `--include-labels`) |
| `gws gmail list` | List threads with `thread_id` and `message_id` (`--max`, `--query`, `--all`, `--include-labels`, `--raw`, `--params`). Under `--raw` switches to `users.messages.list` shape. |
| `gws gmail read <id>` | Read message body and headers |
| `gws gmail thread <id>` | Read full thread conversation |
| `gws gmail thread [id]` | Read full thread conversation (`--raw`, `--params`; id may be supplied via `--params id`) |
| `gws gmail send` | Send email (`--to`, `--subject`, `--body`, `--cc`, `--bcc`, `--thread-id`, `--reply-to-message-id`, `--attachment`) |
| `gws gmail reply <id>` | Reply to message (`--body`, `--cc`, `--bcc`, `--all`) |
| `gws gmail forward <id>` | Forward message (`--to`, `--body`, `--cc`, `--bcc`) |
Expand Down Expand Up @@ -347,10 +347,13 @@ Add `--format text` for human-readable output, or `--format yaml` for YAML.

| Command | Description |
|---------|-------------|
| `gws chat list` | List spaces (`--filter`, `--page-size`) |
| `gws chat list` | List spaces (`--filter`, `--page-size`, `--raw`, `--params`) |
| `gws chat spaces list` | List spaces (API-shape friendly path; same as `chat list` with `--raw` / `--params` documented examples) |
| `gws chat recent` | Recap messages across active spaces (`--since`, `--max`, `--max-per-space`, `--max-spaces`) |
| `gws chat messages <space>` | List messages (`--max`, `--filter`, `--order-by`, `--show-deleted`, `--after`, `--before`, `--resolve-senders`) |
| `gws chat members <space>` | List members with display names + emails via People API (`--max`, `--filter`, `--show-groups`, `--show-invited`) |
| `gws chat messages [space]` | List messages (`--max`, `--filter`, `--order-by`, `--show-deleted`, `--after`, `--before`, `--resolve-senders`, `--raw`, `--params`; space may be supplied via `--params parent`) |
| `gws chat messages list` | List messages by `parent` via `--params` (programmatic path) |
| `gws chat members [space]` | List members with display names + emails via People API (`--max`, `--filter`, `--show-groups`, `--show-invited`, `--raw`, `--params`; space may be supplied via `--params parent`) |
| `gws chat members list` | List members by `parent` via `--params` (programmatic path) |
| `gws chat send` | Send message (`--space`, `--text`) |
| `gws chat get <message>` | Get a single message (`--resolve-senders`) |
| `gws chat update <message>` | Update message text (`--text`) |
Expand Down Expand Up @@ -397,10 +400,16 @@ Add `--format text` for human-readable output, or `--format yaml` for YAML.
|---------|-------------|
| `gws contacts list` | List contacts (`--max`) |
| `gws contacts search <query>` | Search contacts by name/email/phone |
| `gws contacts get <resource-name>` | Get contact details |
| `gws contacts get [resource-name]` | Get contact details (`--raw`, `--params`; resource-name may be supplied via `--params resourceName`) |
| `gws contacts create` | Create contact (`--name`, `--email`, `--phone`) |
| `gws contacts delete <resource-name>` | Delete a contact |

### People (programmatic People API)

| Command | Description |
|---------|-------------|
| `gws people get [resource-name]` | Direct People.Get wrapper for programmatic use (`--raw`, `--params`, `--person-fields`). Use `--params '{"resourceName":"people/me","personFields":"emailAddresses"}'`. |

### Groups

> Requires Admin SDK API enabled and Workspace admin privileges.
Expand Down Expand Up @@ -456,6 +465,42 @@ $ gws calendar events --days 1
}
```

### Programmatic mode: `--raw` and `--params`

Several list/get commands accept two extra flags for scripting:

- `--raw` emits the unmodified Google API response JSON (no field renaming,
no base64 decoding, no header collapsing). Default ergonomic output is
untouched when the flag is not set.
- `--params <json>` accepts a JSON object whose keys map directly to the
underlying API request parameters. Keys supplied here **override** the
equivalent CLI flags (params win).

Under `--all`, raw mode concatenates the top-level list field across pages
(`messages` / `spaces` / `memberships`) and drops `nextPageToken` from the final
output.

Supported in this release:

| Command | Wraps |
|---|---|
| `gmail list` | `users.messages.list` (under `--raw`) |
| `gmail thread <id>` | `users.threads.get` |
| `chat spaces list` | `spaces.list` |
| `chat members list` | `spaces.members.list` |
| `chat messages list` | `spaces.messages.list` |
| `people get` | `people.get` |

Examples:

```bash
gws gmail list --query "in:sent" --max 5 --raw
gws gmail thread 18abc --raw
gws chat spaces list --params '{"pageSize":50}' --all --raw
gws chat messages list --params '{"parent":"spaces/AAA","pageSize":50,"filter":"createTime > \"2025-01-01T00:00:00Z\""}' --all --raw
gws people get --params '{"resourceName":"people/me","personFields":"emailAddresses"}' --raw
```

## Development

### Project Layout
Expand Down
87 changes: 73 additions & 14 deletions cmd/chat.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,14 @@ var chatListCmd = &cobra.Command{
}

var chatMessagesCmd = &cobra.Command{
Use: "messages <space-id>",
Use: "messages [space-id]",
Short: "List messages in a space",
Long: "Lists recent messages in a Chat space.",
Args: cobra.ExactArgs(1),
RunE: runChatMessages,
Long: `Lists recent messages in a Chat space.

The space id is required; pass it as a positional argument or supply it
via --params (e.g. '{"parent":"spaces/AAA"}') when using --raw.`,
Args: cobra.MaximumNArgs(1),
RunE: runChatMessages,
}

var chatRecentCmd = &cobra.Command{
Expand All @@ -66,11 +69,14 @@ Examples:
}

var chatMembersCmd = &cobra.Command{
Use: "members <space-id>",
Use: "members [space-id]",
Short: "List members of a space",
Long: "Lists all members (users and bots) in a Chat space with display names.",
Args: cobra.ExactArgs(1),
RunE: runChatMembers,
Long: `Lists all members (users and bots) in a Chat space with display names.

The space id is required; pass it as a positional argument or supply it
via --params (e.g. '{"parent":"spaces/AAA"}') when using --raw.`,
Args: cobra.MaximumNArgs(1),
RunE: runChatMembers,
}

var chatSendCmd = &cobra.Command{
Expand Down Expand Up @@ -581,6 +587,14 @@ func runChatList(cmd *cobra.Command, args []string) error {
filter, _ := cmd.Flags().GetString("filter")
pageSize, _ := cmd.Flags().GetInt64("page-size")
maxResults, _ := cmd.Flags().GetInt64("max")
fetchAll := false
if f := cmd.Flags().Lookup("all"); f != nil {
fetchAll, _ = cmd.Flags().GetBool("all")
}

if isRaw(cmd) {
return runChatListRaw(cmd, svc, filter, pageSize, maxResults, fetchAll, cmd.Flags().Changed("max"))
}

var results []map[string]interface{}
var pageToken string
Expand Down Expand Up @@ -624,8 +638,23 @@ func runChatList(cmd *cobra.Command, args []string) error {

func runChatMessages(cmd *cobra.Command, args []string) error {
p := GetPrinter()
ctx := context.Background()

// Resolve space name from positional + --params before touching auth
// so input errors surface ahead of OAuth/config errors.
spaceName := ""
if len(args) > 0 {
spaceName = ensureSpaceName(args[0])
}
if params, perr := parseParams(cmd); perr != nil {
return p.PrintError(perr)
} else if v, ok := paramString(params, "parent"); ok && v != "" {
spaceName = v
}
if spaceName == "" {
return p.PrintError(errors.New("chat messages: a space id is required (positional arg or --params parent)"))
}

ctx := context.Background()
factory, err := client.NewFactory(ctx)
if err != nil {
return p.PrintError(err)
Expand All @@ -635,17 +664,21 @@ func runChatMessages(cmd *cobra.Command, args []string) error {
if err != nil {
return p.PrintError(err)
}

spaceName := ensureSpaceName(args[0])
maxResults, _ := cmd.Flags().GetInt64("max")
filter, _ := cmd.Flags().GetString("filter")
orderBy, _ := cmd.Flags().GetString("order-by")
showDeleted, _ := cmd.Flags().GetBool("show-deleted")
after, _ := cmd.Flags().GetString("after")
before, _ := cmd.Flags().GetString("before")
resolveSenders, _ := cmd.Flags().GetBool("resolve-senders")
fetchAll := false
if f := cmd.Flags().Lookup("all"); f != nil {
fetchAll, _ = cmd.Flags().GetBool("all")
}

// Build filter from --after/--before flags, combining with --filter
// Fold --after/--before into the filter expression up-front so the
// raw path sees the same query the ergonomic path does. (Before this
// move, --after/--before were silently dropped under --raw.)
var filterParts []string
if after != "" {
filterParts = append(filterParts, fmt.Sprintf(`createTime > "%s"`, after))
Expand All @@ -660,6 +693,10 @@ func runChatMessages(cmd *cobra.Command, args []string) error {
filter = strings.Join(filterParts, " AND ")
}

if isRaw(cmd) {
return runChatMessagesRaw(cmd, svc, spaceName, maxResults, filter, orderBy, showDeleted, fetchAll, cmd.Flags().Changed("max"))
}

if maxResults <= 0 {
return p.Print(map[string]interface{}{
"messages": []map[string]interface{}{},
Expand Down Expand Up @@ -1015,8 +1052,23 @@ func runChatRecent(cmd *cobra.Command, args []string) error {

func runChatMembers(cmd *cobra.Command, args []string) error {
p := GetPrinter()
ctx := context.Background()

// Resolve space name from positional + --params before touching auth
// so input errors surface ahead of OAuth/config errors.
spaceName := ""
if len(args) > 0 {
spaceName = ensureSpaceName(args[0])
}
if params, perr := parseParams(cmd); perr != nil {
return p.PrintError(perr)
} else if v, ok := paramString(params, "parent"); ok && v != "" {
spaceName = v
}
if spaceName == "" {
return p.PrintError(errors.New("chat members: a space id is required (positional arg or --params parent)"))
}

ctx := context.Background()
factory, err := client.NewFactory(ctx)
if err != nil {
return p.PrintError(err)
Expand All @@ -1027,11 +1079,18 @@ func runChatMembers(cmd *cobra.Command, args []string) error {
return p.PrintError(err)
}

spaceName := ensureSpaceName(args[0])
maxResults, _ := cmd.Flags().GetInt64("max")
filter, _ := cmd.Flags().GetString("filter")
showGroups, _ := cmd.Flags().GetBool("show-groups")
showInvited, _ := cmd.Flags().GetBool("show-invited")
fetchAll := false
if f := cmd.Flags().Lookup("all"); f != nil {
fetchAll, _ = cmd.Flags().GetBool("all")
}

if isRaw(cmd) {
return runChatMembersRaw(cmd, svc, spaceName, maxResults, filter, showGroups, showInvited, fetchAll, cmd.Flags().Changed("max"))
}

// Page size per request (Google caps at 100)
pageSize := maxResults
Expand Down
Loading
Loading