Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
90c7258
feat(protocol): a paired phone signs a browser in to the bridge's web…
deymosh Oct 16, 2026
725e639
feat(bridge): a web page on the direct link's port, signed in with th…
deymosh Oct 16, 2026
319f99e
fix(bridge): the web page's address is logged where phones dial, not …
deymosh Oct 16, 2026
0fc0195
feat(bridge): plain http on the page's port redirects to https, and a…
deymosh Oct 16, 2026
b8e31f7
feat(client): a phone signs a browser in to its bridge's web page
deymosh Oct 16, 2026
8779ccd
fix(bridge): a web sign-in's answer echoes the code as the phone sent it
deymosh Oct 16, 2026
eaa53c6
feat(android): sign a browser in to a machine's web page from the phone
deymosh Oct 16, 2026
868a84a
docs: the bridge's web page, its sign-in wire and the phone's page
deymosh Oct 16, 2026
3cd48b2
fix(android): the web sign-in says a bridge restart signs the browser…
deymosh Oct 16, 2026
3b49c8c
fix(bridge): the web module's doc states the session lifetime instead…
deymosh Oct 16, 2026
7e0993e
feat(bridge-core): the web page withdraws a code it stops showing
deymosh Oct 16, 2026
436dd34
fix(bridge): the web page's caps hold, and IPv6 starts count per /64
deymosh Oct 16, 2026
41a4990
fix(client): a resent web sign-in code that was refused stays signed in
deymosh Oct 16, 2026
0c2f2d0
fix(android): the web sign-in shows the bridge's late verdict, and ev…
deymosh Oct 16, 2026
64c871b
docs: the README lists the bridge's web page
deymosh Oct 16, 2026
ba8a9c0
feat(bridge): the web page gets tabs, and a Usage tab from the heartbeat
deymosh Oct 16, 2026
d7ca392
feat(bridge): the web page's APIs for browsers, devices and the safe …
deymosh Oct 16, 2026
86b57c5
perf(bridge): the web page never holds up the phones' links
deymosh Oct 16, 2026
ee18efb
feat(bridge): the web page's Browsers, Devices and Settings tabs
deymosh Oct 16, 2026
6d5fd54
fix(android): the web sign-in no longer says a bridge restart signs t…
deymosh Oct 16, 2026
57e39ba
fix(bridge): the web config page cannot leave a bridge that will not …
deymosh Oct 16, 2026
92afdb0
fix(bridge): the web page has no way in without a phone
deymosh Oct 17, 2026
1cd1cb5
test(android): the machine page's golden shows the Terminal and the W…
deymosh Oct 17, 2026
2925654
feat(protocol): list and end the browsers signed in to a machine's we…
deymosh Oct 17, 2026
fdfa8ab
feat(bridge): serve the web page's browser list to a paired phone
deymosh Oct 17, 2026
773ca89
feat(client): list and sign out a machine's web page browsers from th…
deymosh Oct 17, 2026
5d3a4bd
docs: the phone's web page browser list
deymosh Oct 17, 2026
a450c76
feat(android): the Browsers page lists the signed-in browsers and sig…
deymosh Oct 17, 2026
b74911a
docs: the phone's Browsers page, where browsers are signed in and out
deymosh Oct 17, 2026
8235493
fix(bridge): a browser whose sign-in lapsed is not listed as signed in
deymosh Oct 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ Pairing is a one-time QR scan; it survives restarts on both ends.
- NIP-42 `AUTH` relays, and Tor on both ends (see below)
- A direct link on your LAN or VPN: the phone talks to the bridge over its
own WebSocket, with the relays as the fallback
- A web page on the same port: the bridge's dashboard in a browser, signed in
from a paired phone instead of a password
([`docs/BRIDGE.md`](docs/BRIDGE.md#web-page))

## About this fork

Expand Down
10 changes: 9 additions & 1 deletion apps/android/app/src/main/java/com/codedeck/plus/ui/Shell.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import com.codedeck.plus.ui.screens.SessionsScreen
import com.codedeck.plus.platform.Login
import com.codedeck.plus.ui.screens.LogsScreen
import com.codedeck.plus.ui.screens.MachineSettingsScreen
import com.codedeck.plus.ui.screens.WebSignInScreen
import com.codedeck.plus.ui.screens.PairingScreen
import com.codedeck.plus.ui.screens.McpScreen
import com.codedeck.plus.ui.screens.AgentSettingsScreen
Expand All @@ -51,6 +52,8 @@ private sealed interface Screen {
data object Pairing : Screen
/** A machine's settings page, opened from its name on the sessions list. */
data class Machine(val machine: String) : Screen
/** Signing a browser in to a machine's web page, opened from the machine's page. */
data class WebSignIn(val machine: String) : Screen
/** One agent's page on a machine, opened from the machine's page. */
data class Agent(val machine: String, val agent: String) : Screen
/** One agent's plugins on a machine, opened from the agent's page. */
Expand All @@ -75,6 +78,7 @@ private fun Screen.parts(): List<String> = when (this) {
Screen.Logs -> listOf("logs")
Screen.Pairing -> listOf("pairing")
is Screen.Machine -> listOf("machine", machine)
is Screen.WebSignIn -> listOf("web-sign-in", machine)
is Screen.Agent -> listOf("agent", machine, agent)
is Screen.Plugins -> listOf("plugins", machine, agent)
is Screen.Mcp -> listOf("mcp", machine, agent)
Expand All @@ -89,7 +93,7 @@ private fun Screen.stateKey(): String = parts().joinToString("/")
* shallower one. */
private fun Screen.depth(): Int = when (this) {
Screen.Sessions -> 0
Screen.Logs, is Screen.Agent, is Screen.Terminal -> 2
Screen.Logs, is Screen.Agent, is Screen.Terminal, is Screen.WebSignIn -> 2
is Screen.Plugins, is Screen.Mcp, is Screen.PermissionRules, is Screen.Providers -> 3
else -> 1
}
Expand All @@ -106,6 +110,7 @@ private val ScreenSaver = listSaver<Screen, String>(
"logs" -> Screen.Logs
"pairing" -> Screen.Pairing
"machine" -> Screen.Machine(saved[1])
"web-sign-in" -> Screen.WebSignIn(saved[1])
"agent" -> Screen.Agent(saved[1], saved[2])
"plugins" -> Screen.Plugins(saved[1], saved[2])
"mcp" -> Screen.Mcp(saved[1], saved[2])
Expand Down Expand Up @@ -272,6 +277,7 @@ fun Shell(
when (val current = screen) {
Screen.Logs -> Screen.Settings
is Screen.Agent -> Screen.Machine(current.machine)
is Screen.WebSignIn -> Screen.Machine(current.machine)
is Screen.Plugins -> Screen.Agent(current.machine, current.agent)
is Screen.Mcp -> Screen.Agent(current.machine, current.agent)
is Screen.PermissionRules -> Screen.Agent(current.machine, current.agent)
Expand All @@ -298,6 +304,7 @@ fun Shell(
onBack = { go(Screen.Sessions) },
onOpenAgent = { agent -> go(Screen.Agent(current.machine, agent)) },
onOpenTerminal = { term -> go(Screen.Terminal(current.machine, term, session = null)) },
onOpenWebSignIn = { go(Screen.WebSignIn(current.machine)) },
)
is Screen.Terminal -> TerminalScreen(
core,
Expand All @@ -306,6 +313,7 @@ fun Shell(
current.session,
onBack = { leaveTerminal(current) },
)
is Screen.WebSignIn -> WebSignInScreen(core, current.machine, onBack = { go(Screen.Machine(current.machine)) })
is Screen.Agent -> AgentSettingsScreen(
core,
current.machine,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ internal fun shortKey(key: String): String = if (key.length <= 20) key else "${k
* plugins, MCP servers, saved permissions) or installs one it lacks — its
* terminals ([onOpenTerminal]: a new one, or one of [terminals]), then
* what is the machine's own: the relays it is reached over, its direct link,
* the bridge's credentials, and forgetting it. Pure — the caller supplies
* its web page and signing a browser in to it ([onOpenWebSignIn]), the
* bridge's credentials, and forgetting it. Pure — the caller supplies
* the machine and a dispatcher — so it renders the same in a snapshot.
*/
@Composable
Expand All @@ -133,6 +134,7 @@ fun MachineSettingsContent(
onOpenAgent: (agent: String) -> Unit,
terminals: List<UniffiTerminalInfo>,
onOpenTerminal: (term: String?) -> Unit,
onOpenWebSignIn: () -> Unit,
) {
var confirmRemove by remember(machine.pubkeyHex) { mutableStateOf(false) }

Expand All @@ -148,6 +150,7 @@ fun MachineSettingsContent(
) {
GroupBody { MachineDirectLink(machine, dispatch) }
}
MachineWebPageGroup(machine, onOpenWebSignIn)
if (machine.credentials.isNotEmpty()) {
Group(title = "Credentials") {
GroupBody { MachineCredentials(machine, credentialsStatus, dispatch, scope = CredentialScope.Bridge) }
Expand Down Expand Up @@ -329,6 +332,7 @@ fun MachineSettingsScreen(
onBack: () -> Unit,
onOpenAgent: (agent: String) -> Unit,
onOpenTerminal: (term: String?) -> Unit,
onOpenWebSignIn: () -> Unit,
) {
val connection by core.connection.collectAsState()
val ui by core.ui.collectAsState()
Expand All @@ -344,5 +348,6 @@ fun MachineSettingsScreen(
onOpenAgent = onOpenAgent,
terminals = ui?.terminals?.byMachine?.get(pubkey).orEmpty(),
onOpenTerminal = onOpenTerminal,
onOpenWebSignIn = onOpenWebSignIn,
)
}
Loading
Loading