Skip to content

Commit 3bdb6eb

Browse files
authored
feat(forms): add publish command
Add `gog forms publish` for publishing/unpublishing existing forms, including JSON/text output, generated docs, safety-profile deny entries, and changelog coverage. Closes #564 Co-authored-by: Anton Bogdanovich <27antonb@gmail.com>
1 parent 5b05a45 commit 3bdb6eb

14 files changed

Lines changed: 458 additions & 6 deletions

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## 0.16.0 - Unreleased
44

5+
### Added
6+
7+
- Forms: add `forms publish` to publish/unpublish existing forms and return the responder URL for automated form creation flows. (#565 / #564) — thanks @bogdanovich.
8+
59
### Fixed
610

711
- Auth: list one row per OAuth client when the same account is authorized under multiple clients, and let `auth list --client` filter that token bucket. (#563) — thanks @UnPractical91.

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ gog sheets banding set <spreadsheetId> 'Sheet1!A1:D100'
177177
```bash
178178
gog slides create-from-markdown "Weekly update" --content-file slides.md
179179
gog slides insert-text <presentationId> <objectId> "New text"
180+
gog forms publish <formId>
180181
gog forms responses list <formId> --json
181182
gog forms raw <formId> --pretty
182183
```

docs/commands.generated.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,7 @@ Generated from `gog schema --json`.
268268
- [`gog forms (form) delete-question (delete-q,dq,rm-q) <formId> <index>`](commands/gog-forms-delete-question.md) - Delete a question by index
269269
- [`gog forms (form) get (info,show) <formId>`](commands/gog-forms-get.md) - Get a form
270270
- [`gog forms (form) move-question (move-q,mq) <formId> <oldIndex> <newIndex>`](commands/gog-forms-move-question.md) - Move a question to a new position
271+
- [`gog forms (form) publish <formId> [flags]`](commands/gog-forms-publish.md) - Publish or unpublish a form
271272
- [`gog forms (form) raw <formId> [flags]`](commands/gog-forms-raw.md) - Dump raw Google Forms API response as JSON (Forms.Get; lossless; for scripting and LLM consumption)
272273
- [`gog forms (form) responses <command>`](commands/gog-forms-responses.md) - Form responses
273274
- [`gog forms (form) responses get (info,show) <formId> <responseId>`](commands/gog-forms-responses-get.md) - Get a form response

docs/commands/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
Every `gog` command has a generated docs page. The source of truth is the live CLI schema; run `make docs-commands` after changing command names, flags, help text, aliases, or arguments.
44

5-
Generated pages: 470.
5+
Generated pages: 471.
66

77
## Top-level Commands
88

@@ -311,6 +311,7 @@ Generated pages: 470.
311311
- [gog forms delete-question](gog-forms-delete-question.md) - Delete a question by index
312312
- [gog forms get](gog-forms-get.md) - Get a form
313313
- [gog forms move-question](gog-forms-move-question.md) - Move a question to a new position
314+
- [gog forms publish](gog-forms-publish.md) - Publish or unpublish a form
314315
- [gog forms raw](gog-forms-raw.md) - Dump raw Google Forms API response as JSON (Forms.Get; lossless; for scripting and LLM consumption)
315316
- [gog forms responses](gog-forms-responses.md) - Form responses
316317
- [gog forms responses get](gog-forms-responses-get.md) - Get a form response

docs/commands/gog-forms-publish.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# `gog forms publish`
2+
3+
> Generated from `gog schema --json`. Do not edit this page by hand; run `make docs-commands`.
4+
5+
Publish or unpublish a form
6+
7+
## Usage
8+
9+
```bash
10+
gog forms (form) publish <formId> [flags]
11+
```
12+
13+
## Parent
14+
15+
- [gog forms](gog-forms.md)
16+
17+
## Flags
18+
19+
| Flag | Type | Default | Help |
20+
| --- | --- | --- | --- |
21+
| `--accepting-responses` | `bool` | true | Whether a published form accepts responses |
22+
| `--access-token` | `string` | | Use provided access token directly (bypasses stored refresh tokens; token expires in ~1h) |
23+
| `-a`<br>`--account`<br>`--acct` | `string` | | Account email for API commands (gmail/calendar/chat/classroom/drive/docs/slides/contacts/tasks/people/sheets/forms/appscript/ads) |
24+
| `--client` | `string` | | OAuth client name (selects stored credentials + token bucket) |
25+
| `--color` | `string` | auto | Color output: auto\|always\|never |
26+
| `--disable-commands` | `string` | | Comma-separated list of disabled commands; dot paths allowed |
27+
| `-n`<br>`--dry-run`<br>`--dryrun`<br>`--noop`<br>`--preview` | `bool` | | Do not make changes; print intended actions and exit successfully |
28+
| `--enable-commands` | `string` | | Comma-separated list of enabled commands; dot paths allowed (restricts CLI) |
29+
| `-y`<br>`--force`<br>`--assume-yes`<br>`--yes` | `bool` | | Skip confirmations for destructive commands |
30+
| `--gmail-no-send` | `bool` | false | Block Gmail send operations (agent safety) |
31+
| `-h`<br>`--help` | `kong.helpFlag` | | Show context-sensitive help. |
32+
| `-j`<br>`--json`<br>`--machine` | `bool` | false | Output JSON to stdout (best for scripting) |
33+
| `--no-input`<br>`--non-interactive`<br>`--noninteractive` | `bool` | | Never prompt; fail instead (useful for CI) |
34+
| `-p`<br>`--plain`<br>`--tsv` | `bool` | false | Output stable, parseable text to stdout (TSV; no colors) |
35+
| `--results-only` | `bool` | | In JSON mode, emit only the primary result (drops envelope fields like nextPageToken) |
36+
| `--select`<br>`--pick`<br>`--project` | `string` | | In JSON mode, select comma-separated fields (best-effort; supports dot paths). Desire path: use --fields for most commands. |
37+
| `--unpublish` | `bool` | | Unpublish the form instead of publishing it |
38+
| `-v`<br>`--verbose` | `bool` | | Enable verbose logging |
39+
| `--version` | `kong.VersionFlag` | | Print version and exit |
40+
41+
## See Also
42+
43+
- [gog forms](gog-forms.md)
44+
- [Command index](README.md)

docs/commands/gog-forms.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ gog forms (form) <command> [flags]
2121
- [gog forms delete-question](gog-forms-delete-question.md) - Delete a question by index
2222
- [gog forms get](gog-forms-get.md) - Get a form
2323
- [gog forms move-question](gog-forms-move-question.md) - Move a question to a new position
24+
- [gog forms publish](gog-forms-publish.md) - Publish or unpublish a form
2425
- [gog forms raw](gog-forms-raw.md) - Dump raw Google Forms API response as JSON (Forms.Get; lossless; for scripting and LLM consumption)
2526
- [gog forms responses](gog-forms-responses.md) - Form responses
2627
- [gog forms update](gog-forms-update.md) - Update form title, description, or settings

internal/cmd/auth_keyring.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ func (c *AuthKeyringCmd) Run(ctx context.Context, flags *RootFlags) error {
6262
return usagef("too many args: %q %q", c.Backend, c.Backend2)
6363
}
6464

65-
if backend == "default" {
65+
if backend == literalDefault {
6666
backend = literalAuto
6767
}
6868

internal/cmd/auth_list_helpers.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ func authListServiceAccountKey(email string) string {
142142
func authListCanonicalClient(client string) string {
143143
client = strings.TrimSpace(client)
144144
if client == "" {
145-
return "default"
145+
return literalDefault
146146
}
147147
return client
148148
}

internal/cmd/forms.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ type FormsCmd struct {
1919
Get FormsGetCmd `cmd:"" name:"get" aliases:"info,show" help:"Get a form"`
2020
Create FormsCreateCmd `cmd:"" name:"create" aliases:"new" help:"Create a form"`
2121
Update FormsUpdateCmd `cmd:"" name:"update" aliases:"edit" help:"Update form title, description, or settings"`
22+
Publish FormsPublishCmd `cmd:"" name:"publish" help:"Publish or unpublish a form"`
2223
AddQuestion FormsAddQuestionCmd `cmd:"" name:"add-question" aliases:"add-q,aq" help:"Add a question to a form"`
2324
DeleteQuestion FormsDeleteQuestionCmd `cmd:"" name:"delete-question" aliases:"delete-q,dq,rm-q" help:"Delete a question by index"`
2425
MoveQuestion FormsMoveQuestionCmd `cmd:"" name:"move-question" aliases:"move-q,mq" help:"Move a question to a new position"`

internal/cmd/forms_publish.go

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package cmd
2+
3+
import (
4+
"context"
5+
"os"
6+
"strings"
7+
8+
formsapi "google.golang.org/api/forms/v1"
9+
10+
"github.com/steipete/gogcli/internal/outfmt"
11+
"github.com/steipete/gogcli/internal/ui"
12+
)
13+
14+
// FormsPublishCmd publishes a form via forms.setPublishSettings.
15+
type FormsPublishCmd struct {
16+
FormID string `arg:"" name:"formId" help:"Form ID"`
17+
Unpublish bool `name:"unpublish" help:"Unpublish the form instead of publishing it"`
18+
AcceptingResponses bool `name:"accepting-responses" help:"Whether a published form accepts responses" default:"true"`
19+
}
20+
21+
func (c *FormsPublishCmd) Run(ctx context.Context, flags *RootFlags) error {
22+
published := !c.Unpublish
23+
acceptingResponses := c.AcceptingResponses
24+
if !published {
25+
acceptingResponses = false
26+
}
27+
operation := "forms.publish"
28+
if !published {
29+
operation = "forms.unpublish"
30+
}
31+
32+
return setFormPublishState(ctx, flags, formPublishStateRequest{
33+
FormID: c.FormID,
34+
Published: published,
35+
AcceptingResponses: acceptingResponses,
36+
Operation: operation,
37+
})
38+
}
39+
40+
type formPublishStateRequest struct {
41+
FormID string
42+
Published bool
43+
AcceptingResponses bool
44+
Operation string
45+
}
46+
47+
func setFormPublishState(ctx context.Context, flags *RootFlags, publishReq formPublishStateRequest) error {
48+
account, err := requireAccount(flags)
49+
if err != nil {
50+
return err
51+
}
52+
formID := strings.TrimSpace(normalizeGoogleID(publishReq.FormID))
53+
if formID == "" {
54+
return usage("empty formId")
55+
}
56+
57+
if dryRunErr := dryRunExit(ctx, flags, publishReq.Operation, map[string]any{
58+
"form_id": formID,
59+
"published": publishReq.Published,
60+
"accepting_responses": publishReq.AcceptingResponses,
61+
}); dryRunErr != nil {
62+
return dryRunErr
63+
}
64+
65+
svc, err := newFormsService(ctx, account)
66+
if err != nil {
67+
return err
68+
}
69+
70+
req := &formsapi.SetPublishSettingsRequest{
71+
UpdateMask: "publish_state",
72+
PublishSettings: &formsapi.PublishSettings{
73+
PublishState: &formsapi.PublishState{
74+
IsPublished: publishReq.Published,
75+
IsAcceptingResponses: publishReq.AcceptingResponses,
76+
ForceSendFields: []string{"IsPublished", "IsAcceptingResponses"},
77+
},
78+
},
79+
}
80+
resp, err := svc.Forms.SetPublishSettings(formID, req).Context(ctx).Do()
81+
if err != nil {
82+
return err
83+
}
84+
85+
form, err := svc.Forms.Get(formID).Context(ctx).Do()
86+
if err != nil {
87+
return err
88+
}
89+
90+
responderURI := strings.TrimSpace(form.ResponderUri)
91+
if outfmt.IsJSON(ctx) {
92+
return outfmt.WriteJSON(ctx, os.Stdout, map[string]any{
93+
"published": publishReq.Published,
94+
"accepting_responses": publishReq.AcceptingResponses,
95+
"form_id": formID,
96+
"responder_uri": responderURI,
97+
"edit_url": formEditURL(formID),
98+
"publish_settings": resp.PublishSettings,
99+
"form": form,
100+
})
101+
}
102+
103+
u := ui.FromContext(ctx)
104+
u.Out().Printf("published\t%t", publishReq.Published)
105+
u.Out().Printf("accepting_responses\t%t", publishReq.AcceptingResponses)
106+
u.Out().Printf("form_id\t%s", formID)
107+
if responderURI != "" {
108+
u.Out().Printf("responder_uri\t%s", responderURI)
109+
}
110+
u.Out().Printf("edit_url\t%s", formEditURL(formID))
111+
return nil
112+
}

0 commit comments

Comments
 (0)