ui(macos): polish onboarding wording
This commit is contained in:
@@ -20,7 +20,7 @@ struct MasterDiscoveryInlineList: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if self.discovery.masters.isEmpty {
|
if self.discovery.masters.isEmpty {
|
||||||
Text("No masters found yet.")
|
Text("No gateways found yet.")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
} else {
|
} else {
|
||||||
@@ -86,7 +86,7 @@ struct MasterDiscoveryInlineList: View {
|
|||||||
.fill(Color(NSColor.controlBackgroundColor)))
|
.fill(Color(NSColor.controlBackgroundColor)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.help("Click a discovered master to fill the SSH target.")
|
.help("Click a discovered gateway to fill the SSH target.")
|
||||||
}
|
}
|
||||||
|
|
||||||
private func suggestedSSHTarget(_ gateway: MasterDiscoveryModel.DiscoveredMaster) -> String? {
|
private func suggestedSSHTarget(_ gateway: MasterDiscoveryModel.DiscoveredMaster) -> String? {
|
||||||
|
|||||||
@@ -212,8 +212,8 @@ struct OnboardingView: View {
|
|||||||
Text("Welcome to Clawdis")
|
Text("Welcome to Clawdis")
|
||||||
.font(.largeTitle.weight(.semibold))
|
.font(.largeTitle.weight(.semibold))
|
||||||
Text(
|
Text(
|
||||||
"Your macOS menu bar companion for notifications, screenshots, and agent automation — " +
|
"Your macOS menu bar companion for notifications, screenshots, and agent automation. " +
|
||||||
"setup takes just a few minutes.")
|
"Setup takes a few minutes.")
|
||||||
.font(.body)
|
.font(.body)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
@@ -255,8 +255,8 @@ struct OnboardingView: View {
|
|||||||
Text("Where Clawdis runs")
|
Text("Where Clawdis runs")
|
||||||
.font(.largeTitle.weight(.semibold))
|
.font(.largeTitle.weight(.semibold))
|
||||||
Text(
|
Text(
|
||||||
"Clawdis has one primary Gateway (“master”) that runs continuously. " +
|
"Clawdis uses a single Gateway (“master”) that stays running. Run it on this Mac, " +
|
||||||
"Connect locally or over SSH/Tailscale so the agent can work on any Mac.")
|
"or connect to one on another Mac over SSH/Tailscale.")
|
||||||
.font(.body)
|
.font(.body)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
@@ -265,9 +265,9 @@ struct OnboardingView: View {
|
|||||||
.fixedSize(horizontal: false, vertical: true)
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
|
|
||||||
self.onboardingCard(spacing: 12, padding: 14) {
|
self.onboardingCard(spacing: 12, padding: 14) {
|
||||||
Picker("Clawdis runs", selection: self.$state.connectionMode) {
|
Picker("Gateway runs", selection: self.$state.connectionMode) {
|
||||||
Text("Local (this Mac)").tag(AppState.ConnectionMode.local)
|
Text("This Mac").tag(AppState.ConnectionMode.local)
|
||||||
Text("Remote over SSH").tag(AppState.ConnectionMode.remote)
|
Text("Remote (SSH)").tag(AppState.ConnectionMode.remote)
|
||||||
}
|
}
|
||||||
.pickerStyle(.segmented)
|
.pickerStyle(.segmented)
|
||||||
.frame(width: 360)
|
.frame(width: 360)
|
||||||
@@ -282,7 +282,7 @@ struct OnboardingView: View {
|
|||||||
Text("SSH target")
|
Text("SSH target")
|
||||||
.font(.callout.weight(.semibold))
|
.font(.callout.weight(.semibold))
|
||||||
.frame(width: labelWidth, alignment: .leading)
|
.frame(width: labelWidth, alignment: .leading)
|
||||||
TextField("user@host[:22]", text: self.$state.remoteTarget)
|
TextField("user@host[:port]", text: self.$state.remoteTarget)
|
||||||
.textFieldStyle(.roundedBorder)
|
.textFieldStyle(.roundedBorder)
|
||||||
.frame(width: fieldWidth)
|
.frame(width: fieldWidth)
|
||||||
}
|
}
|
||||||
@@ -312,7 +312,7 @@ struct OnboardingView: View {
|
|||||||
.padding(.top, 4)
|
.padding(.top, 4)
|
||||||
}
|
}
|
||||||
|
|
||||||
Text("Tip: enable Tailscale so your remote Clawdis stays reachable.")
|
Text("Tip: keep Tailscale enabled so your gateway stays reachable.")
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
@@ -345,7 +345,7 @@ struct OnboardingView: View {
|
|||||||
Circle()
|
Circle()
|
||||||
.fill(self.anthropicAuthConnected ? Color.green : Color.orange)
|
.fill(self.anthropicAuthConnected ? Color.green : Color.orange)
|
||||||
.frame(width: 10, height: 10)
|
.frame(width: 10, height: 10)
|
||||||
Text(self.anthropicAuthConnected ? "Anthropic OAuth connected" : "Not connected yet")
|
Text(self.anthropicAuthConnected ? "Claude connected (OAuth)" : "Not connected yet")
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
@@ -358,7 +358,7 @@ struct OnboardingView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
"This writes Pi-compatible credentials to `~/.pi/agent/oauth.json` (owner-only). " +
|
"This lets Pi use Claude immediately. Credentials are stored at `~/.pi/agent/oauth.json` (owner-only). " +
|
||||||
"You can redo this anytime.")
|
"You can redo this anytime.")
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
@@ -393,14 +393,14 @@ struct OnboardingView: View {
|
|||||||
if self.anthropicAuthBusy {
|
if self.anthropicAuthBusy {
|
||||||
ProgressView()
|
ProgressView()
|
||||||
} else {
|
} else {
|
||||||
Text("Open Claude login (OAuth)")
|
Text("Open Claude sign-in (OAuth)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.buttonStyle(.borderedProminent)
|
.buttonStyle(.borderedProminent)
|
||||||
.disabled(self.anthropicAuthBusy)
|
.disabled(self.anthropicAuthBusy)
|
||||||
|
|
||||||
Button("Skip for now") {
|
Button("Skip for now") {
|
||||||
self.anthropicAuthStatus = "Skipped. The agent may not respond until you authenticate."
|
self.anthropicAuthStatus = "Skipped. Pi may not respond until you connect Claude."
|
||||||
}
|
}
|
||||||
.buttonStyle(.bordered)
|
.buttonStyle(.bordered)
|
||||||
.disabled(self.anthropicAuthBusy)
|
.disabled(self.anthropicAuthBusy)
|
||||||
@@ -408,12 +408,12 @@ struct OnboardingView: View {
|
|||||||
|
|
||||||
if self.anthropicAuthPKCE != nil {
|
if self.anthropicAuthPKCE != nil {
|
||||||
VStack(alignment: .leading, spacing: 8) {
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
Text("Paste `code#state`")
|
Text("Paste the `code#state` value")
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
TextField("code#state", text: self.$anthropicAuthCode)
|
TextField("code#state", text: self.$anthropicAuthCode)
|
||||||
.textFieldStyle(.roundedBorder)
|
.textFieldStyle(.roundedBorder)
|
||||||
|
|
||||||
Button("Finish connection") {
|
Button("Connect") {
|
||||||
Task { await self.finishAnthropicOAuth() }
|
Task { await self.finishAnthropicOAuth() }
|
||||||
}
|
}
|
||||||
.buttonStyle(.bordered)
|
.buttonStyle(.bordered)
|
||||||
@@ -427,8 +427,8 @@ struct OnboardingView: View {
|
|||||||
Text("API key (advanced)")
|
Text("API key (advanced)")
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
Text(
|
Text(
|
||||||
"You can also use an Anthropic API key, but this is instructions-only for now " +
|
"You can also use an Anthropic API key, but this UI is instructions-only for now " +
|
||||||
"(GUI-launched processes don’t automatically inherit your shell env vars).")
|
"(GUI apps don’t automatically inherit your shell env vars).")
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
@@ -456,7 +456,7 @@ struct OnboardingView: View {
|
|||||||
self.anthropicAuthPKCE = pkce
|
self.anthropicAuthPKCE = pkce
|
||||||
let url = AnthropicOAuth.buildAuthorizeURL(pkce: pkce)
|
let url = AnthropicOAuth.buildAuthorizeURL(pkce: pkce)
|
||||||
NSWorkspace.shared.open(url)
|
NSWorkspace.shared.open(url)
|
||||||
self.anthropicAuthStatus = "Opened browser. After approving, paste the `code#state` here."
|
self.anthropicAuthStatus = "Browser opened. After approving, paste the `code#state` value here."
|
||||||
} catch {
|
} catch {
|
||||||
self.anthropicAuthStatus = "Failed to start OAuth: \(error.localizedDescription)"
|
self.anthropicAuthStatus = "Failed to start OAuth: \(error.localizedDescription)"
|
||||||
}
|
}
|
||||||
@@ -478,7 +478,7 @@ struct OnboardingView: View {
|
|||||||
let creds = try await AnthropicOAuth.exchangeCode(code: code, state: state, verifier: pkce.verifier)
|
let creds = try await AnthropicOAuth.exchangeCode(code: code, state: state, verifier: pkce.verifier)
|
||||||
try PiOAuthStore.saveAnthropicOAuth(creds)
|
try PiOAuthStore.saveAnthropicOAuth(creds)
|
||||||
self.refreshAnthropicOAuthStatus()
|
self.refreshAnthropicOAuthStatus()
|
||||||
self.anthropicAuthStatus = "Connected. Pi can now use Claude via Anthropic OAuth."
|
self.anthropicAuthStatus = "Connected. Pi can now use Claude."
|
||||||
} catch {
|
} catch {
|
||||||
self.anthropicAuthStatus = "OAuth failed: \(error.localizedDescription)"
|
self.anthropicAuthStatus = "OAuth failed: \(error.localizedDescription)"
|
||||||
}
|
}
|
||||||
@@ -494,7 +494,7 @@ struct OnboardingView: View {
|
|||||||
self.onboardingPage {
|
self.onboardingPage {
|
||||||
Text("Identity")
|
Text("Identity")
|
||||||
.font(.largeTitle.weight(.semibold))
|
.font(.largeTitle.weight(.semibold))
|
||||||
Text("Name your agent, pick a theme, and we’ll suggest an emoji.")
|
Text("Name your agent, pick a vibe, and choose an emoji.")
|
||||||
.font(.body)
|
.font(.body)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
@@ -568,8 +568,7 @@ struct OnboardingView: View {
|
|||||||
Text(
|
Text(
|
||||||
"This writes your identity to `~/.clawdis/clawdis.json` and into `AGENTS.md` " +
|
"This writes your identity to `~/.clawdis/clawdis.json` and into `AGENTS.md` " +
|
||||||
"inside the workspace. " +
|
"inside the workspace. " +
|
||||||
"Treat that workspace as the agent’s “memory” and consider making it a (private) git " +
|
"Treat that workspace as the agent’s “memory” and consider making it a private git repo.")
|
||||||
"repo.")
|
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
@@ -589,10 +588,8 @@ struct OnboardingView: View {
|
|||||||
Text("Install the gateway")
|
Text("Install the gateway")
|
||||||
.font(.largeTitle.weight(.semibold))
|
.font(.largeTitle.weight(.semibold))
|
||||||
Text(
|
Text(
|
||||||
"""
|
"The Gateway is the WebSocket service that keeps Clawdis connected. " +
|
||||||
Clawdis now runs the WebSocket gateway from the global "clawdis" package.
|
"We’ll install/update the `clawdis` npm package and verify Node is available.")
|
||||||
Install/update it here and we’ll check Node for you.
|
|
||||||
""")
|
|
||||||
.font(.body)
|
.font(.body)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
@@ -636,7 +633,7 @@ struct OnboardingView: View {
|
|||||||
if self.gatewayInstalling {
|
if self.gatewayInstalling {
|
||||||
ProgressView()
|
ProgressView()
|
||||||
} else {
|
} else {
|
||||||
Text("Install / Update gateway")
|
Text("Install or update gateway")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.buttonStyle(.borderedProminent)
|
.buttonStyle(.borderedProminent)
|
||||||
@@ -654,8 +651,8 @@ struct OnboardingView: View {
|
|||||||
.lineLimit(2)
|
.lineLimit(2)
|
||||||
} else {
|
} else {
|
||||||
Text(
|
Text(
|
||||||
"Uses \"npm install -g clawdis@<version>\" on your PATH. " +
|
"Runs `npm install -g clawdis@<version>` on your PATH. " +
|
||||||
"We keep the gateway on port 18789.")
|
"The gateway listens on port 18789.")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
.lineLimit(2)
|
.lineLimit(2)
|
||||||
@@ -681,7 +678,7 @@ struct OnboardingView: View {
|
|||||||
self.onboardingPage {
|
self.onboardingPage {
|
||||||
Text("Grant permissions")
|
Text("Grant permissions")
|
||||||
.font(.largeTitle.weight(.semibold))
|
.font(.largeTitle.weight(.semibold))
|
||||||
Text("Approve these once and the helper CLI reuses the same grants.")
|
Text("These macOS permissions let Clawdis automate apps and capture context on this Mac.")
|
||||||
.font(.body)
|
.font(.body)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
@@ -718,7 +715,7 @@ struct OnboardingView: View {
|
|||||||
self.onboardingPage {
|
self.onboardingPage {
|
||||||
Text("Install the helper CLI")
|
Text("Install the helper CLI")
|
||||||
.font(.largeTitle.weight(.semibold))
|
.font(.largeTitle.weight(.semibold))
|
||||||
Text("Link `clawdis-mac` so scripts and the agent can talk to this app.")
|
Text("Optional, but recommended: link `clawdis-mac` so scripts can talk to this app.")
|
||||||
.font(.body)
|
.font(.body)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
@@ -773,10 +770,8 @@ struct OnboardingView: View {
|
|||||||
Text("Agent workspace")
|
Text("Agent workspace")
|
||||||
.font(.largeTitle.weight(.semibold))
|
.font(.largeTitle.weight(.semibold))
|
||||||
Text(
|
Text(
|
||||||
"""
|
"Clawdis runs the agent from a dedicated workspace so it can load `AGENTS.md` " +
|
||||||
Clawdis runs the agent from a dedicated workspace so it can load AGENTS.md
|
"and write files there without mixing into your other projects.")
|
||||||
and write files without touching your other folders.
|
|
||||||
""")
|
|
||||||
.font(.body)
|
.font(.body)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
@@ -844,7 +839,7 @@ struct OnboardingView: View {
|
|||||||
} else {
|
} else {
|
||||||
Text(
|
Text(
|
||||||
"Tip: edit AGENTS.md in this folder to shape the assistant’s behavior. " +
|
"Tip: edit AGENTS.md in this folder to shape the assistant’s behavior. " +
|
||||||
"For backup, make the workspace a (private) git repo so Clawd’s “memory” is versioned.")
|
"For backup, make the workspace a private git repo so your agent’s “memory” is versioned.")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
.lineLimit(2)
|
.lineLimit(2)
|
||||||
@@ -856,13 +851,11 @@ struct OnboardingView: View {
|
|||||||
|
|
||||||
private func whatsappPage() -> some View {
|
private func whatsappPage() -> some View {
|
||||||
self.onboardingPage {
|
self.onboardingPage {
|
||||||
Text("Link WhatsApp or Telegram")
|
Text("Connect WhatsApp or Telegram")
|
||||||
.font(.largeTitle.weight(.semibold))
|
.font(.largeTitle.weight(.semibold))
|
||||||
Text(
|
Text(
|
||||||
"""
|
"Optional: WhatsApp uses a QR login for your personal account. Telegram uses a bot token. " +
|
||||||
WhatsApp uses a QR login for your personal account. Telegram uses a bot token.
|
"Configure them on the machine where the gateway runs.")
|
||||||
Either (or both) is fine; configure them where the gateway runs.
|
|
||||||
""")
|
|
||||||
.font(.body)
|
.font(.body)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
@@ -872,7 +865,7 @@ struct OnboardingView: View {
|
|||||||
self.onboardingCard {
|
self.onboardingCard {
|
||||||
self.featureRow(
|
self.featureRow(
|
||||||
title: "Open a terminal",
|
title: "Open a terminal",
|
||||||
subtitle: "Use the same host selected above. If remote, SSH in first.",
|
subtitle: "Use the machine where the gateway runs. If remote, SSH in first.",
|
||||||
systemImage: "terminal")
|
systemImage: "terminal")
|
||||||
|
|
||||||
Text("WhatsApp")
|
Text("WhatsApp")
|
||||||
@@ -881,7 +874,7 @@ struct OnboardingView: View {
|
|||||||
title: "Run `clawdis login --verbose`",
|
title: "Run `clawdis login --verbose`",
|
||||||
subtitle: """
|
subtitle: """
|
||||||
Scan the QR code with WhatsApp on your phone.
|
Scan the QR code with WhatsApp on your phone.
|
||||||
We only use your personal session; no cloud gateway involved.
|
This links your personal session; no cloud gateway involved.
|
||||||
""",
|
""",
|
||||||
systemImage: "qrcode.viewfinder")
|
systemImage: "qrcode.viewfinder")
|
||||||
self.featureRow(
|
self.featureRow(
|
||||||
@@ -900,7 +893,7 @@ struct OnboardingView: View {
|
|||||||
self.featureRow(
|
self.featureRow(
|
||||||
title: "Set `TELEGRAM_BOT_TOKEN`",
|
title: "Set `TELEGRAM_BOT_TOKEN`",
|
||||||
subtitle: """
|
subtitle: """
|
||||||
Create a bot with @BotFather and set the token as an env var
|
Create a bot with @BotFather and set the token as an env var,
|
||||||
(or `telegram.botToken` in `~/.clawdis/clawdis.json`).
|
(or `telegram.botToken` in `~/.clawdis/clawdis.json`).
|
||||||
""",
|
""",
|
||||||
systemImage: "key")
|
systemImage: "key")
|
||||||
@@ -917,13 +910,21 @@ struct OnboardingView: View {
|
|||||||
Text("All set")
|
Text("All set")
|
||||||
.font(.largeTitle.weight(.semibold))
|
.font(.largeTitle.weight(.semibold))
|
||||||
self.onboardingCard {
|
self.onboardingCard {
|
||||||
|
if self.state.connectionMode == .remote {
|
||||||
|
self.featureRow(
|
||||||
|
title: "Remote gateway checklist",
|
||||||
|
subtitle: """
|
||||||
|
On your gateway host: install/update the `clawdis` package and make sure Pi has credentials
|
||||||
|
(typically `~/.pi/agent/oauth.json`). Then connect again if needed.
|
||||||
|
""",
|
||||||
|
systemImage: "network")
|
||||||
|
Divider()
|
||||||
|
.padding(.vertical, 6)
|
||||||
|
}
|
||||||
self.featureRow(
|
self.featureRow(
|
||||||
title: "Run the dashboard",
|
title: "Open the menu bar panel",
|
||||||
subtitle: """
|
subtitle: "Click the Clawdis menu bar icon for quick chat and status.",
|
||||||
Use the CLI helper from your scripts, and reopen onboarding from Settings
|
systemImage: "bubble.left.and.bubble.right")
|
||||||
if you add a new user.
|
|
||||||
""",
|
|
||||||
systemImage: "checkmark.seal")
|
|
||||||
self.featureRow(
|
self.featureRow(
|
||||||
title: "Try Voice Wake",
|
title: "Try Voice Wake",
|
||||||
subtitle: "Enable Voice Wake in Settings for hands-free commands with a live transcript overlay.",
|
subtitle: "Enable Voice Wake in Settings for hands-free commands with a live transcript overlay.",
|
||||||
|
|||||||
Reference in New Issue
Block a user