@saadnvd1/agent-os 0.3.546 → 0.3.547

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/app/pair/page.tsx CHANGED
@@ -5,6 +5,7 @@ import { Loader2 } from "lucide-react";
5
5
  import { Button } from "@/components/ui/button";
6
6
  import { Input } from "@/components/ui/input";
7
7
  import { Logo } from "@/components/Logo";
8
+ import { appPairLink, isPhoneOrTablet } from "@/lib/security/pair-link";
8
9
  import { safeNextPath } from "@/lib/session-url";
9
10
 
10
11
  // Where to go once this device can use AgentOS: the page the gate turned
@@ -31,6 +32,7 @@ export default function PairPage() {
31
32
  const [busy, setBusy] = useState(false);
32
33
  const [error, setError] = useState<string | null>(null);
33
34
  const [signedIn, setSignedIn] = useState(false);
35
+ const [appLink, setAppLink] = useState<string | null>(null);
34
36
 
35
37
  useEffect(() => {
36
38
  // Client-only: the server has no user agent to guess from.
@@ -38,6 +40,11 @@ export default function PairPage() {
38
40
  const fromLink = () => {
39
41
  const code = decodeURIComponent(window.location.hash.slice(1));
40
42
  if (code) setCode(code);
43
+ setAppLink(
44
+ code && isPhoneOrTablet(navigator.userAgent, navigator.maxTouchPoints)
45
+ ? appPairLink(window.location.origin, code)
46
+ : null
47
+ );
41
48
  };
42
49
  fromLink();
43
50
  window.addEventListener("hashchange", fromLink);
@@ -124,6 +131,12 @@ export default function PairPage() {
124
131
  </Button>
125
132
  </form>
126
133
  )}
134
+
135
+ {appLink && (
136
+ <Button asChild variant="outline" className="h-11 w-full">
137
+ <a href={appLink}>Open in the AgentOS app</a>
138
+ </Button>
139
+ )}
127
140
  </div>
128
141
  </main>
129
142
  );
@@ -58,8 +58,8 @@ export function AddDevicePanel({ onDone }: { onDone: () => void }) {
58
58
  {shown ? (
59
59
  <>
60
60
  <p className="text-sm">
61
- Scan with the phone or tablet&apos;s camera. On a laptop, open the
62
- link and type the code.
61
+ On a phone or tablet, open the AgentOS app and tap Scan QR code. The
62
+ camera app works too. On a laptop, open the link and type the code.
63
63
  </p>
64
64
  {offer.links.length > 1 && (
65
65
  <div className="bg-background/60 mx-auto flex w-fit gap-1 rounded-lg p-1">
@@ -69,9 +69,7 @@ export function AddDevicePanel({ onDone }: { onDone: () => void }) {
69
69
  onClick={() => setKindIndex(i)}
70
70
  className={`h-11 rounded-md px-3 text-xs sm:h-8 ${i === kindIndex ? "bg-muted text-foreground" : "text-muted-foreground"}`}
71
71
  >
72
- {l.kind === "lan"
73
- ? "Phone on this Wi-Fi"
74
- : "Phone on Tailscale"}
72
+ {CHOICE[l.kind]}
75
73
  </button>
76
74
  ))}
77
75
  </div>
@@ -0,0 +1,20 @@
1
+ // Pure and import-free: the pair page bundles this for the browser, which
2
+ // pair-qr.ts (qrcode, reach) can't be.
3
+
4
+ // The code rides in the fragment, so it never reaches a server log.
5
+ export function pairPageLink(base: string, display: string): string {
6
+ return `${base.replace(/\/+$/, "")}/pair#${display}`;
7
+ }
8
+
9
+ // Hands the pairing page's address to the native app. It claims nothing.
10
+ export function appPairLink(base: string, display: string): string {
11
+ return `agentos://connect?url=${encodeURIComponent(pairPageLink(base, display))}`;
12
+ }
13
+
14
+ // iPadOS Safari sends a Mac user agent, so touch points tell it apart.
15
+ export function isPhoneOrTablet(ua: string, maxTouchPoints = 0): boolean {
16
+ return (
17
+ /iPhone|iPad|Android/.test(ua) ||
18
+ (/Macintosh/.test(ua) && maxTouchPoints > 1)
19
+ );
20
+ }
@@ -1,5 +1,6 @@
1
1
  import QRCode from "qrcode";
2
2
  import { formatCode, startPairing } from "./pairing";
3
+ import { pairPageLink } from "./pair-link";
3
4
  import { reachableAt, type Reach } from "./reach";
4
5
 
5
6
  export interface PairingLink {
@@ -26,7 +27,7 @@ export async function pairingOffer(): Promise<PairingOffer> {
26
27
  );
27
28
  const links = await Promise.all(
28
29
  firstOfKind.map(async (r) => {
29
- const link = `${r.url}/pair#${display}`;
30
+ const link = pairPageLink(r.url, display);
30
31
  const svg = await QRCode.toString(link, {
31
32
  type: "svg",
32
33
  margin: 1,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saadnvd1/agent-os",
3
- "version": "0.3.546",
3
+ "version": "0.3.547",
4
4
  "description": "Self-hosted web UI for managing Claude Code sessions",
5
5
  "bin": {
6
6
  "agent-os": "scripts/agent-os"