Skip to content

Commit b68e6ea

Browse files
committed
feat: unify Free session and host management
1 parent de303fb commit b68e6ea

46 files changed

Lines changed: 3821 additions & 1025 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ node_modules/
22
dist/
33
dist-bin/
44
.tmp/
5+
.playwright-mcp/
56
.expo/
67

78
relay/node_modules/

README.md

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@ curl -fsSL https://free.saaskit.app/install | bash
1717
```
1818

1919
The binary installer downloads the current GitHub `latest` release asset for
20-
your platform, installs it to `~/.local/bin/free`, then runs `free auth login`
21-
by default.
20+
your platform, installs the unified `free` CLI to `~/.local/bin/free`, then runs
21+
`free login` by default.
2222

2323
Useful binary install flags:
2424

2525
- `--no-login` installs only the `free` CLI.
26-
- `--force-login` refreshes browser login and reinstalls the active host mode.
26+
- `--force-login` refreshes browser login and re-enables this machine.
2727
- `--relay-env local` signs in against the local relay.
2828
- `--relay-url <ws-url>` uses a custom relay.
2929

@@ -35,12 +35,12 @@ curl -fsSL https://raw.githubusercontent.com/saaskit-dev/Free/main/scripts/insta
3535

3636
The source installer clones Free and `acp-runtime`, builds the required local
3737
packages, packs Free, installs the resulting CLI globally with npm, then runs
38-
`free auth login` by default.
38+
`free login` by default.
3939

4040
Useful source install flags:
4141

4242
- `--no-login` installs only the `free` CLI.
43-
- `--force-login` refreshes browser login and reinstalls the active host mode.
43+
- `--force-login` refreshes browser login and re-enables this machine.
4444
- `--relay-url <ws-url>` uses a non-default relay.
4545
- `--ref <git-ref>` installs a specific Free branch, tag, or commit.
4646

@@ -55,17 +55,18 @@ For a local checkout:
5555
The installed package exposes `free`:
5656

5757
```sh
58-
free auth login
59-
free auth status
60-
free auth logout
61-
free bridge run
62-
free bridge config
58+
free login
59+
free logout
60+
free status
61+
free session list
62+
free session close <session-id>
6363
```
6464

6565
The normal user flow is:
6666

67-
1. Run `free auth login` on each machine that should host agents.
68-
2. Configure the editor ACP client to launch `free bridge run`.
67+
1. Install the unified `free` CLI.
68+
2. Run `free login` on each machine that should be available.
69+
3. Check availability with `free status`.
6970

7071
Hosts are registered in the relay control plane. Offline hosts can still appear
7172
in discovery using their last known metadata, but only currently connected hosts
@@ -202,19 +203,21 @@ Workbench.
202203
The default bridge, auth, and host environment is online:
203204

204205
```sh
205-
free bridge config
206-
free bridge run
207-
free auth login
206+
free login
207+
free status
208208
```
209209

210210
Use the local environment when testing against `make relay-dev` on port `8791`
211211
and Workbench Web on port `8790`:
212212

213213
```sh
214-
free bridge config --relay-env local
215-
free bridge run --relay-env local
216-
free auth login --relay-env local
214+
free login --relay-env local
215+
free status --relay-env local
217216
```
218217

218+
The online host and local test host use separate launchd services, so both can
219+
run at the same time. `free logout --relay-env local` stops only the local test
220+
host; plain `free logout` targets the default online host.
221+
219222
`--relay-url <ws-url>` remains available for custom relay deployments, but it
220223
should not be mixed with `--relay-env`.

apps/workbench/src/App.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { colors, typography } from "./ui/theme";
1414

1515
export default function App() {
1616
const [route, setRoute] = useState<RouteId>(readWorkbenchRouteFromLocation());
17-
const { preferences, setLanguage, setTheme } = useWorkbenchPreferences();
17+
const { preferences, setLanguage, setSidebar, setTheme } = useWorkbenchPreferences();
1818
const [fontsLoaded] = useFonts({
1919
BricolageGrotesqueBold: require("../assets/fonts/BricolageGrotesque-Bold.ttf"),
2020
IBMPlexMono: require("../assets/fonts/IBMPlexMono-Regular.ttf"),
@@ -95,6 +95,7 @@ export default function App() {
9595
route={route}
9696
setLanguage={setLanguage}
9797
setRoute={setRoute}
98+
setSidebar={setSidebar}
9899
setTheme={setTheme}
99100
/>
100101
</>

apps/workbench/src/api/relay.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,26 @@ export async function loadSessions() {
258258
return readJsonResult<{ sessions: SessionRecord[] }>("/api/sessions");
259259
}
260260

261+
export async function closeSession(sessionId: string) {
262+
const response = await fetch(`${defaultRelayUrl()}/api/sessions/${encodeURIComponent(sessionId)}`, {
263+
credentials: "include",
264+
headers: {
265+
accept: "application/json",
266+
},
267+
method: "DELETE",
268+
});
269+
const value = (await response.json().catch(() => null)) as { error?: unknown; reason?: unknown } | null;
270+
if (!response.ok) {
271+
throw new Error(
272+
value && typeof value === "object" && typeof value.error === "string"
273+
? value.error
274+
: value && typeof value === "object" && typeof value.reason === "string"
275+
? value.reason
276+
: `Request failed with ${response.status}.`,
277+
);
278+
}
279+
}
280+
261281
export async function checkSessionHealth() {
262282
return readJsonResult<SessionHealth>("/api/sessions/health");
263283
}

apps/workbench/src/features/access/AccessScreen.tsx

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { GithubIcon, Login03Icon } from "@hugeicons/core-free-icons";
2-
import { Pressable, Text, View } from "react-native";
2+
import { useState } from "react";
3+
import { ActivityIndicator, Pressable, Text, View } from "react-native";
34

45
import { createLoginUrl, currentWorkbenchUrl } from "../../api/relay";
56
import type { AccountSession, LanguageMode, LoadState } from "../../types";
@@ -13,6 +14,8 @@ type AccessScreenProps = {
1314
};
1415

1516
export function AccessScreen({ language, session }: AccessScreenProps) {
17+
const [openingLogin, setOpeningLogin] = useState(false);
18+
1619
if (session.status === "loading") {
1720
return (
1821
<StatePanel
@@ -34,12 +37,27 @@ export function AccessScreen({ language, session }: AccessScreenProps) {
3437
)}
3538
/>
3639
<Pressable
37-
onPress={() => navigateToLogin(createLoginUrl(currentWorkbenchUrl()))}
38-
style={[common.panel, { alignItems: "center", backgroundColor: colors.lime, flexDirection: "row", gap: 12, padding: 16 }]}
40+
accessibilityState={openingLogin ? { busy: true, disabled: true } : undefined}
41+
disabled={openingLogin}
42+
onPress={() => {
43+
setOpeningLogin(true);
44+
setTimeout(() => {
45+
navigateToLogin(createLoginUrl(currentWorkbenchUrl()));
46+
}, 120);
47+
}}
48+
style={[
49+
common.panel,
50+
{ alignItems: "center", backgroundColor: colors.lime, flexDirection: "row", gap: 12, padding: 16 },
51+
openingLogin ? { opacity: 0.65 } : null,
52+
]}
3953
>
40-
<Icon icon={GithubIcon} size={22} />
54+
{openingLogin ? (
55+
<ActivityIndicator color={colors.ink} size="small" />
56+
) : (
57+
<Icon icon={GithubIcon} size={22} />
58+
)}
4159
<Text style={{ color: colors.ink, fontFamily: typography.sansSemi, fontSize: 16 }}>
42-
{t(language, "使用 GitHub 登录", "Sign in with GitHub")}
60+
{openingLogin ? t(language, "正在打开登录", "Opening sign in") : t(language, "使用 GitHub 登录", "Sign in with GitHub")}
4361
</Text>
4462
<Icon icon={Login03Icon} size={20} />
4563
</Pressable>

0 commit comments

Comments
 (0)