feat: advertise cli path for remote ssh

This commit is contained in:
Peter Steinberger
2025-12-20 16:43:08 +01:00
parent c7048973bb
commit f03d2d1b33
12 changed files with 123 additions and 10 deletions

View File

@@ -179,6 +179,10 @@ final class AppState {
didSet { self.ifNotPreview { UserDefaults.standard.set(self.remoteProjectRoot, forKey: remoteProjectRootKey) } }
}
var remoteCliPath: String {
didSet { self.ifNotPreview { UserDefaults.standard.set(self.remoteCliPath, forKey: remoteCliPathKey) } }
}
private var earBoostTask: Task<Void, Never>?
init(preview: Bool = false) {
@@ -235,6 +239,7 @@ final class AppState {
self.remoteTarget = UserDefaults.standard.string(forKey: remoteTargetKey) ?? ""
self.remoteIdentity = UserDefaults.standard.string(forKey: remoteIdentityKey) ?? ""
self.remoteProjectRoot = UserDefaults.standard.string(forKey: remoteProjectRootKey) ?? ""
self.remoteCliPath = UserDefaults.standard.string(forKey: remoteCliPathKey) ?? ""
self.canvasEnabled = UserDefaults.standard.object(forKey: canvasEnabledKey) as? Bool ?? true
self.peekabooBridgeEnabled = UserDefaults.standard
.object(forKey: peekabooBridgeEnabledKey) as? Bool ?? true
@@ -368,6 +373,7 @@ extension AppState {
state.remoteTarget = "user@example.com"
state.remoteIdentity = "~/.ssh/id_ed25519"
state.remoteProjectRoot = "~/Projects/clawdis"
state.remoteCliPath = ""
state.attachExistingGatewayOnly = false
return state
}

View File

@@ -21,6 +21,7 @@ let connectionModeKey = "clawdis.connectionMode"
let remoteTargetKey = "clawdis.remoteTarget"
let remoteIdentityKey = "clawdis.remoteIdentity"
let remoteProjectRootKey = "clawdis.remoteProjectRoot"
let remoteCliPathKey = "clawdis.remoteCliPath"
let canvasEnabledKey = "clawdis.canvasEnabled"
let cameraEnabledKey = "clawdis.cameraEnabled"
let peekabooBridgeEnabledKey = "clawdis.peekabooBridgeEnabled"

View File

@@ -17,6 +17,7 @@ final class GatewayDiscoveryModel {
var lanHost: String?
var tailnetDns: String?
var sshPort: Int
var cliPath: String?
var stableID: String
var debugID: String
var isLocal: Bool
@@ -66,6 +67,7 @@ final class GatewayDiscoveryModel {
var lanHost: String?
var tailnetDns: String?
var sshPort = 22
var cliPath: String?
if let value = txt["lanHost"] {
let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)
@@ -81,6 +83,10 @@ final class GatewayDiscoveryModel {
{
sshPort = parsed
}
if let value = txt["cliPath"] {
let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)
cliPath = trimmed.isEmpty ? nil : trimmed
}
let isLocal = Self.isLocalGateway(
lanHost: lanHost,
@@ -93,6 +99,7 @@ final class GatewayDiscoveryModel {
lanHost: lanHost,
tailnetDns: tailnetDns,
sshPort: sshPort,
cliPath: cliPath,
stableID: BridgeEndpointID.stableID(result.endpoint),
debugID: BridgeEndpointID.prettyDescription(result.endpoint),
isLocal: isLocal)

View File

@@ -181,6 +181,11 @@ struct GeneralSettings: View {
.textFieldStyle(.roundedBorder)
.frame(width: 280)
}
LabeledContent("CLI path") {
TextField("/Applications/Clawdis.app/.../clawdis", text: self.$state.remoteCliPath)
.textFieldStyle(.roundedBorder)
.frame(width: 280)
}
}
.padding(.top, 4)
} label: {
@@ -612,6 +617,7 @@ extension GeneralSettings {
target += ":\(gateway.sshPort)"
}
self.state.remoteTarget = target
self.state.remoteCliPath = gateway.cliPath ?? ""
}
}

View File

@@ -396,6 +396,14 @@ struct OnboardingView: View {
.textFieldStyle(.roundedBorder)
.frame(width: fieldWidth)
}
GridRow {
Text("CLI path")
.font(.callout.weight(.semibold))
.frame(width: labelWidth, alignment: .leading)
TextField("/Applications/Clawdis.app/.../clawdis", text: self.$state.remoteCliPath)
.textFieldStyle(.roundedBorder)
.frame(width: fieldWidth)
}
}
Text("Tip: keep Tailscale enabled so your gateway stays reachable.")
@@ -436,6 +444,7 @@ struct OnboardingView: View {
}
self.state.remoteTarget = target
}
self.state.remoteCliPath = gateway.cliPath ?? ""
self.state.connectionMode = .remote
MacNodeModeCoordinator.shared.setPreferredBridgeStableID(gateway.stableID)

View File

@@ -490,6 +490,7 @@ enum CommandResolver {
].joined(separator: ":")
let quotedArgs = ([subcommand] + extraArgs).map(self.shellQuote).joined(separator: " ")
let userPRJ = settings.projectRoot.trimmingCharacters(in: .whitespacesAndNewlines)
let userCLI = settings.cliPath.trimmingCharacters(in: .whitespacesAndNewlines)
let projectSection = if userPRJ.isEmpty {
"""
@@ -506,9 +507,31 @@ enum CommandResolver {
"""
}
let cliSection = if userCLI.isEmpty {
""
} else {
"""
CLI_HINT=\(self.shellQuote(userCLI))
if [ -n "$CLI_HINT" ]; then
if [ -x "$CLI_HINT" ]; then
CLI="$CLI_HINT"
"$CLI_HINT" \(quotedArgs);
exit $?;
elif [ -f "$CLI_HINT" ]; then
if command -v node >/dev/null 2>&1; then
CLI="node $CLI_HINT"
node "$CLI_HINT" \(quotedArgs);
exit $?;
fi
fi
fi
"""
}
let scriptBody = """
PATH=\(exportedPath);
CLI="";
\(cliSection)
\(projectSection)
if command -v clawdis >/dev/null 2>&1; then
CLI="$(command -v clawdis)"
@@ -543,6 +566,7 @@ enum CommandResolver {
let target: String
let identity: String
let projectRoot: String
let cliPath: String
}
static func connectionSettings(defaults: UserDefaults = .standard) -> RemoteSettings {
@@ -557,11 +581,13 @@ enum CommandResolver {
let target = defaults.string(forKey: remoteTargetKey) ?? ""
let identity = defaults.string(forKey: remoteIdentityKey) ?? ""
let projectRoot = defaults.string(forKey: remoteProjectRootKey) ?? ""
let cliPath = defaults.string(forKey: remoteCliPathKey) ?? ""
return RemoteSettings(
mode: mode,
target: self.sanitizedTarget(target),
identity: identity,
projectRoot: projectRoot)
projectRoot: projectRoot,
cliPath: cliPath)
}
static var attachExistingGatewayOnly: Bool {