Skip to content

Commit 9370476

Browse files
2witstudiosclaude
andcommitted
feat(macos): Remote control toggle + human-readable labels
- Add Remote Control toggle to Claude agent settings (--remote-control) - Replace "Use Defaults" with inverted "Customize Launch Settings" - Add human-readable descriptions to all permission modes - Add subtitle text to all Claude settings fields - Replace raw CLI flag label in Point Guard settings Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent fee462b commit 9370476

2 files changed

Lines changed: 175 additions & 83 deletions

File tree

apps/purepoint-macos/purepoint-macos/Views/Settings/SettingsAgentsView.swift

Lines changed: 168 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,17 @@ enum ClaudePermissionMode: String, CaseIterable {
1616
}
1717
}
1818

19+
var description: String {
20+
switch self {
21+
case .default: "Ask before each action"
22+
case .acceptEdits: "Auto-approve file edits, ask for other actions"
23+
case .plan: "Read-only mode, no file changes allowed"
24+
case .bypassPermissions: "Auto-approve everything (fastest, no interruptions)"
25+
case .dontAsk: "Auto-approve everything (alternative mode)"
26+
case .auto: "Let Claude decide when to ask"
27+
}
28+
}
29+
1930
var cliValue: String { rawValue }
2031
}
2132

@@ -82,6 +93,7 @@ struct ClaudeConfig {
8293
var effort: ClaudeEffort? = nil
8394
var systemPrompt: String = ""
8495
var verbose: Bool = false
96+
var remoteControl: Bool = false
8597
}
8698

8799
struct CodexConfig {
@@ -130,6 +142,8 @@ func parseClaudeLaunchArgs(_ args: [String]) -> ClaudeConfig {
130142
}
131143
case "--verbose":
132144
config.verbose = true
145+
case "--remote-control", "--rc":
146+
config.remoteControl = true
133147
default:
134148
break
135149
}
@@ -159,6 +173,9 @@ func composeClaudeLaunchArgs(_ config: ClaudeConfig) -> [String] {
159173
if config.verbose {
160174
args.append("--verbose")
161175
}
176+
if config.remoteControl {
177+
args.append("--remote-control")
178+
}
162179
return args
163180
}
164181

@@ -326,91 +343,143 @@ struct ClaudeAgentGroupBox: View {
326343
var body: some View {
327344
GroupBox {
328345
VStack(alignment: .leading, spacing: 10) {
329-
Toggle("Use Defaults", isOn: $useDefaults)
330-
.toggleStyle(.switch)
331-
.controlSize(.small)
332-
.onChange(of: useDefaults) {
333-
if useDefaults {
334-
Task {
335-
await state.updateLaunchArgs(
336-
projectRoot: projectRoot, agentName: "claude", launchArgs: nil)
346+
Toggle(
347+
"Customize Launch Settings",
348+
isOn: Binding(
349+
get: { !useDefaults },
350+
set: { useDefaults = !$0 }
351+
)
352+
)
353+
.toggleStyle(.switch)
354+
.controlSize(.small)
355+
.onChange(of: useDefaults) {
356+
if useDefaults {
357+
Task {
358+
await state.updateLaunchArgs(
359+
projectRoot: projectRoot, agentName: "claude", launchArgs: nil)
360+
}
361+
} else {
362+
commitChanges()
363+
}
364+
}
365+
366+
if useDefaults {
367+
Text("Using recommended settings: auto-approve everything, no interruptions")
368+
.font(.system(size: 11))
369+
.foregroundStyle(.tertiary)
370+
}
371+
372+
VStack(alignment: .leading, spacing: 2) {
373+
Toggle("Remote Control", isOn: $config.remoteControl)
374+
.toggleStyle(.switch)
375+
.controlSize(.small)
376+
.onChange(of: config.remoteControl) {
377+
if config.remoteControl && useDefaults {
378+
useDefaults = false
337379
}
338-
} else {
339380
commitChanges()
340381
}
341-
}
382+
Text("Continue this session from your phone or browser via claude.ai/code")
383+
.font(.system(size: 11))
384+
.foregroundStyle(.tertiary)
385+
}
342386

343387
if useDefaults {
344388
resolvedArgsView(payload.resolvedLaunchArgs)
345389
} else {
346-
LabeledContent("Permission Mode") {
347-
Picker("", selection: $config.permissionMode) {
348-
ForEach(ClaudePermissionMode.allCases, id: \.self) { mode in
349-
Text(mode.displayName).tag(mode)
390+
VStack(alignment: .leading, spacing: 2) {
391+
LabeledContent("Permission Mode") {
392+
Picker("", selection: $config.permissionMode) {
393+
ForEach(ClaudePermissionMode.allCases, id: \.self) { mode in
394+
Text(mode.displayName).tag(mode)
395+
}
350396
}
397+
.labelsHidden()
398+
.frame(width: 180)
399+
.onChange(of: config.permissionMode) { commitChanges() }
351400
}
352-
.labelsHidden()
353-
.frame(width: 180)
354-
.onChange(of: config.permissionMode) { commitChanges() }
401+
Text(config.permissionMode.description)
402+
.font(.system(size: 11))
403+
.foregroundStyle(.tertiary)
355404
}
356405

357-
LabeledContent("Model") {
358-
HStack(spacing: 8) {
406+
VStack(alignment: .leading, spacing: 2) {
407+
LabeledContent("Model") {
408+
HStack(spacing: 8) {
409+
Picker(
410+
"",
411+
selection: Binding(
412+
get: { config.model ?? .sonnet },
413+
set: {
414+
config.model = $0; config.customModel = ""
415+
}
416+
)
417+
) {
418+
ForEach(ClaudeModel.allCases, id: \.self) { m in
419+
Text(m.displayName).tag(m)
420+
}
421+
}
422+
.labelsHidden()
423+
.frame(width: 100)
424+
.onChange(of: config.model) { commitChanges() }
425+
426+
TextField("Custom", text: $config.customModel)
427+
.textFieldStyle(.roundedBorder)
428+
.frame(width: 120)
429+
.onSubmit {
430+
if !config.customModel.isEmpty { config.model = nil }
431+
commitChanges()
432+
}
433+
}
434+
}
435+
Text("Which AI model to use")
436+
.font(.system(size: 11))
437+
.foregroundStyle(.tertiary)
438+
}
439+
440+
VStack(alignment: .leading, spacing: 2) {
441+
LabeledContent("Effort") {
359442
Picker(
360443
"",
361444
selection: Binding(
362-
get: { config.model ?? .sonnet },
363-
set: {
364-
config.model = $0; config.customModel = ""
365-
}
445+
get: { config.effort ?? .high },
446+
set: { config.effort = $0 }
366447
)
367448
) {
368-
ForEach(ClaudeModel.allCases, id: \.self) { m in
369-
Text(m.displayName).tag(m)
449+
ForEach(ClaudeEffort.allCases, id: \.self) { e in
450+
Text(e.displayName).tag(e)
370451
}
371452
}
372-
.labelsHidden()
373-
.frame(width: 100)
374-
.onChange(of: config.model) { commitChanges() }
375-
376-
TextField("Custom", text: $config.customModel)
377-
.textFieldStyle(.roundedBorder)
378-
.frame(width: 120)
379-
.onSubmit {
380-
if !config.customModel.isEmpty { config.model = nil }
381-
commitChanges()
382-
}
453+
.pickerStyle(.segmented)
454+
.frame(width: 200)
455+
.onChange(of: config.effort) { commitChanges() }
383456
}
457+
Text("How much compute the model uses per response")
458+
.font(.system(size: 11))
459+
.foregroundStyle(.tertiary)
384460
}
385461

386-
LabeledContent("Effort") {
387-
Picker(
388-
"",
389-
selection: Binding(
390-
get: { config.effort ?? .high },
391-
set: { config.effort = $0 }
392-
)
393-
) {
394-
ForEach(ClaudeEffort.allCases, id: \.self) { e in
395-
Text(e.displayName).tag(e)
396-
}
462+
VStack(alignment: .leading, spacing: 2) {
463+
LabeledContent("System Prompt") {
464+
TextField("Optional", text: $config.systemPrompt)
465+
.textFieldStyle(.roundedBorder)
466+
.onSubmit { commitChanges() }
397467
}
398-
.pickerStyle(.segmented)
399-
.frame(width: 200)
400-
.onChange(of: config.effort) { commitChanges() }
468+
Text("Additional instructions appended to the agent's system prompt")
469+
.font(.system(size: 11))
470+
.foregroundStyle(.tertiary)
401471
}
402472

403-
LabeledContent("System Prompt") {
404-
TextField("Optional", text: $config.systemPrompt)
405-
.textFieldStyle(.roundedBorder)
406-
.onSubmit { commitChanges() }
473+
VStack(alignment: .leading, spacing: 2) {
474+
Toggle("Verbose", isOn: $config.verbose)
475+
.toggleStyle(.switch)
476+
.controlSize(.small)
477+
.onChange(of: config.verbose) { commitChanges() }
478+
Text("Show detailed logging output in the terminal")
479+
.font(.system(size: 11))
480+
.foregroundStyle(.tertiary)
407481
}
408482

409-
Toggle("Verbose", isOn: $config.verbose)
410-
.toggleStyle(.switch)
411-
.controlSize(.small)
412-
.onChange(of: config.verbose) { commitChanges() }
413-
414483
resolvedArgsView(composeClaudeLaunchArgs(config))
415484
}
416485
}
@@ -450,21 +519,30 @@ struct CodexAgentGroupBox: View {
450519
var body: some View {
451520
GroupBox {
452521
VStack(alignment: .leading, spacing: 10) {
453-
Toggle("Use Defaults", isOn: $useDefaults)
454-
.toggleStyle(.switch)
455-
.controlSize(.small)
456-
.onChange(of: useDefaults) {
457-
if useDefaults {
458-
Task {
459-
await state.updateLaunchArgs(
460-
projectRoot: projectRoot, agentName: "codex", launchArgs: nil)
461-
}
462-
} else {
463-
commitChanges()
522+
Toggle(
523+
"Customize Launch Settings",
524+
isOn: Binding(
525+
get: { !useDefaults },
526+
set: { useDefaults = !$0 }
527+
)
528+
)
529+
.toggleStyle(.switch)
530+
.controlSize(.small)
531+
.onChange(of: useDefaults) {
532+
if useDefaults {
533+
Task {
534+
await state.updateLaunchArgs(
535+
projectRoot: projectRoot, agentName: "codex", launchArgs: nil)
464536
}
537+
} else {
538+
commitChanges()
465539
}
540+
}
466541

467542
if useDefaults {
543+
Text("Using recommended settings: full-auto mode with workspace sandbox")
544+
.font(.system(size: 11))
545+
.foregroundStyle(.tertiary)
468546
resolvedArgsView(payload.resolvedLaunchArgs)
469547
} else {
470548
LabeledContent("Approval Mode") {
@@ -539,21 +617,30 @@ struct OpenCodeAgentGroupBox: View {
539617
var body: some View {
540618
GroupBox {
541619
VStack(alignment: .leading, spacing: 10) {
542-
Toggle("Use Defaults", isOn: $useDefaults)
543-
.toggleStyle(.switch)
544-
.controlSize(.small)
545-
.onChange(of: useDefaults) {
546-
if useDefaults {
547-
Task {
548-
await state.updateLaunchArgs(
549-
projectRoot: projectRoot, agentName: "opencode", launchArgs: nil)
550-
}
551-
} else {
552-
commitChanges()
620+
Toggle(
621+
"Customize Launch Settings",
622+
isOn: Binding(
623+
get: { !useDefaults },
624+
set: { useDefaults = !$0 }
625+
)
626+
)
627+
.toggleStyle(.switch)
628+
.controlSize(.small)
629+
.onChange(of: useDefaults) {
630+
if useDefaults {
631+
Task {
632+
await state.updateLaunchArgs(
633+
projectRoot: projectRoot, agentName: "opencode", launchArgs: nil)
553634
}
635+
} else {
636+
commitChanges()
554637
}
638+
}
555639

556640
if useDefaults {
641+
Text("Using recommended settings: default configuration")
642+
.font(.system(size: 11))
643+
.foregroundStyle(.tertiary)
557644
resolvedArgsView(payload.resolvedLaunchArgs)
558645
} else {
559646
LabeledContent("Model") {

apps/purepoint-macos/purepoint-macos/Views/Settings/SettingsPointGuardView.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,13 @@ struct SettingsPointGuardView: View {
2323

2424
Divider()
2525

26-
Toggle("Skip permissions (--dangerously-skip-permissions)", isOn: $settings.pointGuardSkipPermissions)
27-
.padding(.vertical, 8)
26+
VStack(alignment: .leading, spacing: 2) {
27+
Toggle("Auto-approve all actions", isOn: $settings.pointGuardSkipPermissions)
28+
Text("Skip permission prompts so the agent runs without interruptions")
29+
.font(.system(size: 11))
30+
.foregroundStyle(.tertiary)
31+
}
32+
.padding(.vertical, 8)
2833
}
2934
.groupBoxStyle(SettingsGroupBoxStyle())
3035
}

0 commit comments

Comments
 (0)