mirror of
https://github.com/AgentSeal/codeburn.git
synced 2026-04-28 06:59:37 +00:00
feat(mac): add Connect Claude button to Plan pane
The Plan pane previously told users to "run claude login in your terminal, then retry" with no way to start the flow from the app. Added a primary Connect Claude button on both the no-credentials and failed states that launches Terminal.app with `claude login`, so the OAuth flow is one click away. TerminalLauncher.openClaudeLogin() uses a hardcoded literal, so no user input reaches AppleScript. Refactored the common path into runInTerminal(command:preValidated:) which re-validates any non- literal input against CodeburnCLI.isSafe as defense-in-depth. On machines without Terminal.app (iTerm/Ghostty/Warp), the button surfaces an inline instruction to run `claude login` manually instead of failing silently.
This commit is contained in:
parent
9483d66e65
commit
43a938ff9e
2 changed files with 84 additions and 27 deletions
|
|
@ -1039,6 +1039,7 @@ private struct PlanLoadingView: View {
|
|||
|
||||
private struct PlanNoCredentialsView: View {
|
||||
@Environment(AppStore.self) private var store
|
||||
@State private var showManualFallback = false
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 8) {
|
||||
|
|
@ -1048,16 +1049,32 @@ private struct PlanNoCredentialsView: View {
|
|||
Text("No Claude subscription connected")
|
||||
.font(.system(size: 12, weight: .semibold))
|
||||
.foregroundStyle(.primary)
|
||||
Text("Run `claude login` in your terminal, then retry.")
|
||||
.font(.system(size: 10.5))
|
||||
.foregroundStyle(.secondary)
|
||||
.multilineTextAlignment(.center)
|
||||
.frame(maxWidth: 260)
|
||||
Button("Retry") {
|
||||
Task { await store.refreshSubscription() }
|
||||
if showManualFallback {
|
||||
Text("Terminal.app isn't available. Open your terminal and run `claude login`, then click Retry.")
|
||||
.font(.system(size: 10.5))
|
||||
.foregroundStyle(.secondary)
|
||||
.multilineTextAlignment(.center)
|
||||
.frame(maxWidth: 280)
|
||||
} else {
|
||||
Text("Click Connect to sign in with Claude, then return here.")
|
||||
.font(.system(size: 10.5))
|
||||
.foregroundStyle(.secondary)
|
||||
.multilineTextAlignment(.center)
|
||||
.frame(maxWidth: 260)
|
||||
}
|
||||
HStack(spacing: 8) {
|
||||
Button("Connect Claude") {
|
||||
if !TerminalLauncher.openClaudeLogin() { showManualFallback = true }
|
||||
}
|
||||
.controlSize(.small)
|
||||
.buttonStyle(.borderedProminent)
|
||||
.tint(Theme.brandAccent)
|
||||
Button("Retry") {
|
||||
Task { await store.refreshSubscription() }
|
||||
}
|
||||
.controlSize(.small)
|
||||
.buttonStyle(.bordered)
|
||||
}
|
||||
.controlSize(.small)
|
||||
.buttonStyle(.bordered)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical, 14)
|
||||
|
|
@ -1067,6 +1084,7 @@ private struct PlanNoCredentialsView: View {
|
|||
private struct PlanFailedView: View {
|
||||
@Environment(AppStore.self) private var store
|
||||
let error: String?
|
||||
@State private var showManualFallback = false
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 8) {
|
||||
|
|
@ -1076,7 +1094,13 @@ private struct PlanFailedView: View {
|
|||
Text("Couldn't load plan data")
|
||||
.font(.system(size: 12, weight: .semibold))
|
||||
.foregroundStyle(.primary)
|
||||
if let error {
|
||||
if showManualFallback {
|
||||
Text("Terminal.app isn't available. Open your terminal and run `claude login`, then click Retry.")
|
||||
.font(.system(size: 10.5))
|
||||
.foregroundStyle(.secondary)
|
||||
.multilineTextAlignment(.center)
|
||||
.frame(maxWidth: 280)
|
||||
} else if let error {
|
||||
Text(error)
|
||||
.font(.system(size: 10))
|
||||
.foregroundStyle(.tertiary)
|
||||
|
|
@ -1084,11 +1108,19 @@ private struct PlanFailedView: View {
|
|||
.frame(maxWidth: 280)
|
||||
.lineLimit(3)
|
||||
}
|
||||
Button("Retry") {
|
||||
Task { await store.refreshSubscription() }
|
||||
HStack(spacing: 8) {
|
||||
Button("Reconnect Claude") {
|
||||
if !TerminalLauncher.openClaudeLogin() { showManualFallback = true }
|
||||
}
|
||||
.controlSize(.small)
|
||||
.buttonStyle(.borderedProminent)
|
||||
.tint(Theme.brandAccent)
|
||||
Button("Retry") {
|
||||
Task { await store.refreshSubscription() }
|
||||
}
|
||||
.controlSize(.small)
|
||||
.buttonStyle(.bordered)
|
||||
}
|
||||
.controlSize(.small)
|
||||
.buttonStyle(.bordered)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical, 14)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue