Skip to content

Commit 9a243c3

Browse files
authored
Expose client visibility at create time (Fixes #457) (#554)
* Document SDK gap blocking create-time client visibility (#457) Basecamp (bc3 master) accepts a top-level visible_to_clients param at create time, but the Go SDK's create-request types don't carry it, so the CLI can't pass it without an out-of-lane SDK change. Record the required SDK change (Smithy create inputs -> generated -> wrapper -> mapping) and the CLI wiring that follows in SDK-GAP-457.md. * Correct SDK gap handoff: flat message wire, tri-state *bool, defer docs/uploads Address review of SDK-GAP-457.md: - Fix message create contract: POST /message_boards/:board_id/messages.json with a flat body; the visible_to_clients key is top-level. - Require *bool tri-state (nil=inherit, true, false) end-to-end instead of bool,omitempty, which drops explicit false; cite the AllDay precedent. - Defer docs/uploads: they accept arbitrary folder IDs and nested vaults inherit folder visibility (silent no-op), so gate or defer before wiring. - Scope the verified gap to SDK v0.8.0; correct the lane-policy wording. - Link SDK tracking issue basecamp/basecamp-sdk#395 as the unblocker. * Refine SDK gap handoff: de-reference PR number, stable identifiers, link follow-ups - Reference 'this PR' instead of a hard-coded PR number (drift-prone). - Point at function/struct identifiers instead of line numbers (files.go, schedules.go). - Link deferred docs/uploads to CLI follow-up #556; note the SDK should still expose those inputs for completeness (#395 covers all six) while the CLI withholds only the flag. - Clarify #457 closure sequencing (four-together vs messages-first). * Make SDK gap internally consistent: all six inputs, correct wrappers, locked #457 boundary - Requested SDK change now targets all six inputs in the implementation steps (message, todolist, question, schedule entry, document, upload), matching the surrounding completeness note; no more four-now/two-later contradiction. - Gap evidence lists the six actual target wrappers (CreateMessageRequest, CreateTodolistRequest, CreateQuestionRequest, CreateScheduleEntryRequest, CreateDocumentRequest, CreateUploadRequest), not the unrelated card/todo/ comment structs. - Lock #457 completion boundary to the four initial commands; messages-first stays Refs #457 until all four are wired. * Bump SDK to e2c1abea for create-time visible_to_clients Advances the basecamp-sdk pin from v0.8.0 to v0.8.1-0.20260724184307-e2c1abea4aea (basecamp-sdk#401), which adds a create-time visible_to_clients field to the six create-request inputs and repins bc3 provenance. The same bundle reshaped SearchMetadata: the Projects list was replaced by RecordingSearchTypes / FileSearchTypes filter options. Adapt runSearchMetadata's empty-check and summary to the new shape. * feat(clients): add --visible-to-clients to create commands Wire the SDK's create-time visible_to_clients field into the four create commands where Basecamp accepts a top-level client-visibility param: messages, todolists, check-in questions, and schedule entries. The flag is tri-state — set req.VisibleToClients only when --visible-to-clients was provided (gate on Flags().Changed, matching --subscribe), so omitting it preserves the server's default and an explicit --visible-to-clients=false reaches the wire. Single atomic create, no follow-up toggle. Parent-inherited types (cards, todos, comments) are excluded — they ignore the create param and 403 on the toggle. Docs/uploads are deferred to #556 (nested-vault folder inheritance needs gating). Tests assert the create body for each command carries visible_to_clients for the unset/true/false cases. Regenerate .surface and document the flag in SKILL.md. Fixes #457 * Correct client-visibility help and search metadata text Address review: - Client-visibility help was unsafe for client-authenticated callers. The four flag descriptions, inline comments, and SKILL.md claimed omission means team-only, but the server forces client visibility for client callers (even an explicit false is overridden). Document the context-dependent rule instead: team-only when posting as a team member; client callers always client-visible. - The SDK-bump adaptation left 'search metadata' describing 'available projects' though it now returns recording/file filter types. Fix the command Short/Long and add focused runSearchMetadata tests (filter-type summary; empty-response usage error), which the search tests previously never exercised. * Refine schedule visibility wiring and search readability Address review: - schedule create: thread visibleToClients as a parameter into runScheduleCreate (matching the existing allDay pattern) instead of re-reading the flag via GetBool and dropping its error. Consistent with the other three create commands. - search metadata: parenthesize the nil-or-both-empty guard for clarity. - search_test: the transport's body field is a string, not a pointer — say 'empty body', not 'nil body'.
1 parent 763a5a1 commit 9a243c3

15 files changed

Lines changed: 482 additions & 17 deletions

.surface

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3504,6 +3504,7 @@ FLAG basecamp checkin question create --styled type=bool
35043504
FLAG basecamp checkin question create --time type=string
35053505
FLAG basecamp checkin question create --todolist type=string
35063506
FLAG basecamp checkin question create --verbose type=count
3507+
FLAG basecamp checkin question create --visible-to-clients type=bool
35073508
FLAG basecamp checkin question show --account type=string
35083509
FLAG basecamp checkin question show --agent type=bool
35093510
FLAG basecamp checkin question show --all-comments type=bool
@@ -3773,6 +3774,7 @@ FLAG basecamp checkins question create --styled type=bool
37733774
FLAG basecamp checkins question create --time type=string
37743775
FLAG basecamp checkins question create --todolist type=string
37753776
FLAG basecamp checkins question create --verbose type=count
3777+
FLAG basecamp checkins question create --visible-to-clients type=bool
37763778
FLAG basecamp checkins question show --account type=string
37773779
FLAG basecamp checkins question show --agent type=bool
37783780
FLAG basecamp checkins question show --all-comments type=bool
@@ -9568,6 +9570,7 @@ FLAG basecamp messages create --styled type=bool
95689570
FLAG basecamp messages create --subscribe type=string
95699571
FLAG basecamp messages create --todolist type=string
95709572
FLAG basecamp messages create --verbose type=count
9573+
FLAG basecamp messages create --visible-to-clients type=bool
95719574
FLAG basecamp messages list --account type=string
95729575
FLAG basecamp messages list --agent type=bool
95739576
FLAG basecamp messages list --all type=bool
@@ -9978,6 +9981,7 @@ FLAG basecamp msgs create --styled type=bool
99789981
FLAG basecamp msgs create --subscribe type=string
99799982
FLAG basecamp msgs create --todolist type=string
99809983
FLAG basecamp msgs create --verbose type=count
9984+
FLAG basecamp msgs create --visible-to-clients type=bool
99819985
FLAG basecamp msgs list --account type=string
99829986
FLAG basecamp msgs list --agent type=bool
99839987
FLAG basecamp msgs list --all type=bool
@@ -11206,6 +11210,7 @@ FLAG basecamp schedule create --summary type=string
1120611210
FLAG basecamp schedule create --title type=string
1120711211
FLAG basecamp schedule create --todolist type=string
1120811212
FLAG basecamp schedule create --verbose type=count
11213+
FLAG basecamp schedule create --visible-to-clients type=bool
1120911214
FLAG basecamp schedule entries --account type=string
1121011215
FLAG basecamp schedule entries --agent type=bool
1121111216
FLAG basecamp schedule entries --all type=bool
@@ -12448,6 +12453,7 @@ FLAG basecamp todolist create --styled type=bool
1244812453
FLAG basecamp todolist create --todolist type=string
1244912454
FLAG basecamp todolist create --todoset type=string
1245012455
FLAG basecamp todolist create --verbose type=count
12456+
FLAG basecamp todolist create --visible-to-clients type=bool
1245112457
FLAG basecamp todolist list --account type=string
1245212458
FLAG basecamp todolist list --agent type=bool
1245312459
FLAG basecamp todolist list --all type=bool
@@ -12990,6 +12996,7 @@ FLAG basecamp todolists create --styled type=bool
1299012996
FLAG basecamp todolists create --todolist type=string
1299112997
FLAG basecamp todolists create --todoset type=string
1299212998
FLAG basecamp todolists create --verbose type=count
12999+
FLAG basecamp todolists create --visible-to-clients type=bool
1299313000
FLAG basecamp todolists list --account type=string
1299413001
FLAG basecamp todolists list --agent type=bool
1299513002
FLAG basecamp todolists list --all type=bool

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ require (
66
charm.land/bubbles/v2 v2.1.1
77
charm.land/bubbletea/v2 v2.0.8
88
charm.land/lipgloss/v2 v2.0.5
9-
github.com/basecamp/basecamp-sdk/go v0.8.0
9+
github.com/basecamp/basecamp-sdk/go v0.8.1-0.20260724184307-e2c1abea4aea
1010
github.com/basecamp/cli v0.2.1
1111
github.com/charmbracelet/bubbles v1.0.0
1212
github.com/charmbracelet/glamour v1.0.0

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ github.com/aymanbagabas/go-udiff v0.4.1 h1:OEIrQ8maEeDBXQDoGCbbTTXYJMYRCRO1fnodZ
2323
github.com/aymanbagabas/go-udiff v0.4.1/go.mod h1:0L9PGwj20lrtmEMeyw4WKJ/TMyDtvAoK9bf2u/mNo3w=
2424
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
2525
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
26-
github.com/basecamp/basecamp-sdk/go v0.8.0 h1:ZxrNTyGTMcGocGE582ZMmpjyodvODD2+fZMjISokTYY=
27-
github.com/basecamp/basecamp-sdk/go v0.8.0/go.mod h1:eX5mEKCdtxSfEL4P/n5AwOl21JVA/K+gRPic/Hd8W/Y=
26+
github.com/basecamp/basecamp-sdk/go v0.8.1-0.20260724184307-e2c1abea4aea h1:dRwhvhnzbyXxitGbb3uGFaUIJHazztD9UgWC0XzM870=
27+
github.com/basecamp/basecamp-sdk/go v0.8.1-0.20260724184307-e2c1abea4aea/go.mod h1:eX5mEKCdtxSfEL4P/n5AwOl21JVA/K+gRPic/Hd8W/Y=
2828
github.com/basecamp/cli v0.2.1 h1:8GyehPVtsTXla0oOPu4QgXRjwwzJ99prlByvyi+0HRQ=
2929
github.com/basecamp/cli v0.2.1/go.mod h1:p8tt/DatJ2LAzWO6N6tNfV8x3gF5T3IxDTo+U8FfWPo=
3030
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=

internal/commands/checkins.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,7 @@ func newCheckinsQuestionCreateCmd(project *string) *cobra.Command {
268268
var frequency string
269269
var timeOfDay string
270270
var days string
271+
var visibleToClients bool
271272

272273
cmd := &cobra.Command{
273274
Use: "create <title>",
@@ -366,6 +367,14 @@ Days format: comma-separated (0=Sun, 1=Mon, 2=Tue, 3=Wed, 4=Thu, 5=Fri, 6=Sat)`,
366367
},
367368
}
368369

370+
// Set client visibility only when the flag was provided. Omitting it
371+
// uses the server's default: team-only when posting as a team member,
372+
// but a client-authenticated caller always creates client-visible
373+
// records (an explicit false is overridden server-side).
374+
if cmd.Flags().Changed("visible-to-clients") {
375+
req.VisibleToClients = &visibleToClients
376+
}
377+
369378
question, err := app.Account().Checkins().CreateQuestion(cmd.Context(), qID, req)
370379
if err != nil {
371380
return convertSDKError(err)
@@ -393,6 +402,7 @@ Days format: comma-separated (0=Sun, 1=Mon, 2=Tue, 3=Wed, 4=Thu, 5=Fri, 6=Sat)`,
393402
cmd.Flags().StringVarP(&frequency, "frequency", "f", "", "Schedule frequency (default: every_day)")
394403
cmd.Flags().StringVar(&timeOfDay, "time", "", "Time to ask (default: 5:00pm)")
395404
cmd.Flags().StringVarP(&days, "days", "d", "", "Days to ask, comma-separated (default: 1,2,3,4,5)")
405+
cmd.Flags().BoolVar(&visibleToClients, "visible-to-clients", false, "Make the question visible to clients on the project (omit for the server default; client-authenticated callers always post client-visible)")
396406

397407
return cmd
398408
}

internal/commands/checkins_test.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,95 @@ func (m *mockCheckinsAnswerCreateTransport) RoundTrip(req *http.Request) (*http.
146146
}
147147
}
148148

149+
// mockCheckinsQuestionCreateTransport resolves the questionnaire via the project
150+
// dock and captures the POST body sent to create a question.
151+
type mockCheckinsQuestionCreateTransport struct {
152+
recordedBody map[string]any
153+
}
154+
155+
func (m *mockCheckinsQuestionCreateTransport) RoundTrip(req *http.Request) (*http.Response, error) {
156+
header := make(http.Header)
157+
header.Set("Content-Type", "application/json")
158+
159+
switch {
160+
case req.Method == "GET" && strings.Contains(req.URL.Path, "/projects.json"):
161+
return &http.Response{
162+
StatusCode: 200,
163+
Body: io.NopCloser(strings.NewReader(`[{"id":123,"name":"Test Project"}]`)),
164+
Header: header,
165+
}, nil
166+
case req.Method == "GET" && strings.Contains(req.URL.Path, "/projects/"):
167+
return &http.Response{
168+
StatusCode: 200,
169+
Body: io.NopCloser(strings.NewReader(`{"id":123,"dock":[{"name":"questionnaire","id":555,"enabled":true}]}`)),
170+
Header: header,
171+
}, nil
172+
case req.Method == "POST" && strings.Contains(req.URL.Path, "/questions.json"):
173+
if req.Body != nil {
174+
defer req.Body.Close()
175+
}
176+
body, err := io.ReadAll(req.Body)
177+
if err != nil {
178+
return nil, err
179+
}
180+
if err := json.Unmarshal(body, &m.recordedBody); err != nil {
181+
return nil, err
182+
}
183+
return &http.Response{
184+
StatusCode: 201,
185+
Body: io.NopCloser(strings.NewReader(`{"id":789,"title":"How are you?","type":"Question"}`)),
186+
Header: header,
187+
}, nil
188+
default:
189+
return &http.Response{
190+
StatusCode: 404,
191+
Body: io.NopCloser(strings.NewReader(`{"error":"Not Found"}`)),
192+
Header: header,
193+
}, nil
194+
}
195+
}
196+
197+
func runCheckinsQuestionCreate(t *testing.T, args ...string) *mockCheckinsQuestionCreateTransport {
198+
t.Helper()
199+
transport := &mockCheckinsQuestionCreateTransport{}
200+
app, _ := newTestAppWithTransport(t, transport)
201+
app.Config.ProjectID = "123"
202+
203+
project := ""
204+
cmd := newCheckinsQuestionCreateCmd(&project)
205+
206+
err := executeCommand(cmd, app, args...)
207+
require.NoError(t, err)
208+
require.NotNil(t, transport.recordedBody, "expected request body to be captured")
209+
return transport
210+
}
211+
212+
func TestCheckinsQuestionCreateHasVisibleToClientsFlag(t *testing.T) {
213+
project := ""
214+
cmd := newCheckinsQuestionCreateCmd(&project)
215+
216+
flag := cmd.Flags().Lookup("visible-to-clients")
217+
require.NotNil(t, flag, "expected --visible-to-clients flag on check-in question create")
218+
}
219+
220+
func TestCheckinsQuestionCreateDefaultOmitsVisibleToClients(t *testing.T) {
221+
transport := runCheckinsQuestionCreate(t, "How are you?")
222+
_, ok := transport.recordedBody["visible_to_clients"]
223+
assert.False(t, ok, "expected visible_to_clients to be omitted when flag is not set")
224+
}
225+
226+
func TestCheckinsQuestionCreateVisibleToClientsTrue(t *testing.T) {
227+
transport := runCheckinsQuestionCreate(t, "How are you?", "--visible-to-clients")
228+
assert.Equal(t, true, transport.recordedBody["visible_to_clients"])
229+
}
230+
231+
func TestCheckinsQuestionCreateVisibleToClientsFalse(t *testing.T) {
232+
transport := runCheckinsQuestionCreate(t, "How are you?", "--visible-to-clients=false")
233+
val, ok := transport.recordedBody["visible_to_clients"]
234+
require.True(t, ok, "expected visible_to_clients present for explicit --visible-to-clients=false")
235+
assert.Equal(t, false, val)
236+
}
237+
149238
func TestCheckinsAnswerCreateDefaultsDateToToday(t *testing.T) {
150239
originalNow := checkinsNow
151240
checkinsNow = func() time.Time {

internal/commands/messages.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,7 @@ func newMessagesCreateCmd(project *string, messageBoard *string) *cobra.Command
263263
var subscribe string
264264
var noSubscribe bool
265265
var attachFiles []string
266+
var visibleToClients bool
266267

267268
cmd := &cobra.Command{
268269
Use: "create <title> [body]",
@@ -384,6 +385,14 @@ func newMessagesCreateCmd(project *string, messageBoard *string) *cobra.Command
384385
req.Status = "active"
385386
}
386387

388+
// Set client visibility only when the flag was provided. Omitting it
389+
// uses the server's default: team-only when posting as a team member,
390+
// but a client-authenticated caller always creates client-visible
391+
// records (an explicit false is overridden server-side).
392+
if cmd.Flags().Changed("visible-to-clients") {
393+
req.VisibleToClients = &visibleToClients
394+
}
395+
387396
message, err := app.Account().Messages().Create(cmd.Context(), boardID, req)
388397
if err != nil {
389398
return convertSDKError(err)
@@ -417,6 +426,7 @@ func newMessagesCreateCmd(project *string, messageBoard *string) *cobra.Command
417426
cmd.Flags().StringVar(&subscribe, "subscribe", "", "Subscribe specific people (comma-separated names, emails, IDs, or \"me\")")
418427
cmd.Flags().BoolVar(&noSubscribe, "no-subscribe", false, "Don't subscribe anyone else (silent, no notifications)")
419428
cmd.Flags().StringArrayVar(&attachFiles, "attach", nil, "Attach file (repeatable)")
429+
cmd.Flags().BoolVar(&visibleToClients, "visible-to-clients", false, "Make the message visible to clients on the project (omit for the server default; client-authenticated callers always post client-visible)")
420430

421431
return cmd
422432
}

internal/commands/messages_test.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,72 @@ func TestMessagesCreateSubscribeEmptyIsError(t *testing.T) {
301301
assert.Contains(t, e.Message, "at least one person")
302302
}
303303

304+
// TestMessagesCreateHasVisibleToClientsFlag tests that messages create has the --visible-to-clients flag.
305+
func TestMessagesCreateHasVisibleToClientsFlag(t *testing.T) {
306+
cmd := NewMessagesCmd()
307+
createCmd, _, err := cmd.Find([]string{"create"})
308+
require.NoError(t, err)
309+
310+
flag := createCmd.Flags().Lookup("visible-to-clients")
311+
require.NotNil(t, flag, "expected --visible-to-clients flag on messages create")
312+
}
313+
314+
// TestMessagesCreateDefaultOmitsVisibleToClients verifies that without the flag,
315+
// visible_to_clients is omitted so the server applies its own default.
316+
func TestMessagesCreateDefaultOmitsVisibleToClients(t *testing.T) {
317+
transport := &mockMessageCreateTransport{}
318+
app, _ := setupMessagesMockApp(t, transport)
319+
320+
cmd := NewMessagesCmd()
321+
322+
err := executeMessagesCommand(cmd, app, "create", "Normal post")
323+
require.NoError(t, err)
324+
require.NotEmpty(t, transport.capturedBody)
325+
326+
var body map[string]any
327+
require.NoError(t, json.Unmarshal(transport.capturedBody, &body))
328+
329+
_, ok := body["visible_to_clients"]
330+
assert.False(t, ok, "expected visible_to_clients to be omitted when flag is not set")
331+
}
332+
333+
// TestMessagesCreateVisibleToClientsTrue verifies --visible-to-clients sends true.
334+
func TestMessagesCreateVisibleToClientsTrue(t *testing.T) {
335+
transport := &mockMessageCreateTransport{}
336+
app, _ := setupMessagesMockApp(t, transport)
337+
338+
cmd := NewMessagesCmd()
339+
340+
err := executeMessagesCommand(cmd, app, "create", "Client post", "--visible-to-clients")
341+
require.NoError(t, err)
342+
require.NotEmpty(t, transport.capturedBody)
343+
344+
var body map[string]any
345+
require.NoError(t, json.Unmarshal(transport.capturedBody, &body))
346+
347+
assert.Equal(t, true, body["visible_to_clients"])
348+
}
349+
350+
// TestMessagesCreateVisibleToClientsFalse verifies --visible-to-clients=false
351+
// sends an explicit false rather than dropping the field.
352+
func TestMessagesCreateVisibleToClientsFalse(t *testing.T) {
353+
transport := &mockMessageCreateTransport{}
354+
app, _ := setupMessagesMockApp(t, transport)
355+
356+
cmd := NewMessagesCmd()
357+
358+
err := executeMessagesCommand(cmd, app, "create", "Team post", "--visible-to-clients=false")
359+
require.NoError(t, err)
360+
require.NotEmpty(t, transport.capturedBody)
361+
362+
var body map[string]any
363+
require.NoError(t, json.Unmarshal(transport.capturedBody, &body))
364+
365+
val, ok := body["visible_to_clients"]
366+
require.True(t, ok, "expected visible_to_clients present for explicit --visible-to-clients=false")
367+
assert.Equal(t, false, val)
368+
}
369+
304370
// mockMessageUpdateTransport handles PUT requests and captures the body.
305371
type mockMessageUpdateTransport struct {
306372
capturedBody []byte

internal/commands/schedule.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,7 @@ func newScheduleCreateCmd(project, scheduleID *string) *cobra.Command {
409409
var subscribe string
410410
var noSubscribe bool
411411
var attachFiles []string
412+
var visibleToClients bool
412413

413414
cmd := &cobra.Command{
414415
Use: "create <summary>",
@@ -438,7 +439,7 @@ func newScheduleCreateCmd(project, scheduleID *string) *cobra.Command {
438439
return output.ErrUsage("--ends-at required (ISO 8601 datetime)")
439440
}
440441

441-
return runScheduleCreate(cmd, app, *project, *scheduleID, entrySummary, startsAt, endsAt, description, allDay, notify, participants, subscribe, noSubscribe, attachFiles)
442+
return runScheduleCreate(cmd, app, *project, *scheduleID, entrySummary, startsAt, endsAt, description, allDay, notify, visibleToClients, participants, subscribe, noSubscribe, attachFiles)
442443
},
443444
}
444445

@@ -457,11 +458,12 @@ func newScheduleCreateCmd(project, scheduleID *string) *cobra.Command {
457458
cmd.Flags().StringVar(&subscribe, "subscribe", "", "Subscribe specific people (comma-separated names, emails, IDs, or \"me\")")
458459
cmd.Flags().BoolVar(&noSubscribe, "no-subscribe", false, "Don't subscribe anyone else (silent, no notifications)")
459460
cmd.Flags().StringArrayVar(&attachFiles, "attach", nil, "Attach file (repeatable)")
461+
cmd.Flags().BoolVar(&visibleToClients, "visible-to-clients", false, "Make the schedule entry visible to clients on the project (omit for the server default; client-authenticated callers always post client-visible)")
460462

461463
return cmd
462464
}
463465

464-
func runScheduleCreate(cmd *cobra.Command, app *appctx.App, project, scheduleID, summary, startsAt, endsAt, description string, allDay, notify bool, participants, subscribe string, noSubscribe bool, attachFiles []string) error {
466+
func runScheduleCreate(cmd *cobra.Command, app *appctx.App, project, scheduleID, summary, startsAt, endsAt, description string, allDay, notify, visibleToClients bool, participants, subscribe string, noSubscribe bool, attachFiles []string) error {
465467
// Resolve subscription flags early (fail fast on bad input)
466468
subs, err := applySubscribeFlags(cmd.Context(), app.Names, subscribe, cmd.Flags().Changed("subscribe"), noSubscribe)
467469
if err != nil {
@@ -534,6 +536,14 @@ func runScheduleCreate(cmd *cobra.Command, app *appctx.App, project, scheduleID,
534536
Subscriptions: subs,
535537
}
536538

539+
// Set client visibility only when the flag was provided. Omitting it uses the
540+
// server's default: team-only when posting as a team member, but a
541+
// client-authenticated caller always creates client-visible records (an
542+
// explicit false is overridden server-side).
543+
if cmd.Flags().Changed("visible-to-clients") {
544+
req.VisibleToClients = &visibleToClients
545+
}
546+
537547
if participants != "" {
538548
var ids []int64
539549
for idStr := range strings.SplitSeq(participants, ",") {

0 commit comments

Comments
 (0)