@reclaimprotocol/agent 0.1.24 → 0.1.25
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/README.md +3 -2
- package/lib/cdp/claims.d.ts +21 -0
- package/lib/cdp/claims.js +129 -0
- package/lib/cdp/client.d.ts +0 -10
- package/lib/cdp/container.d.ts +85 -0
- package/lib/cdp/container.js +481 -0
- package/lib/live/tunnel.d.ts +6 -3
- package/lib/live/tunnel.js +57 -17
- package/lib/mcp/instructions.js +2 -1
- package/lib/mcp/start.js +58 -3
- package/lib/mcp/tools/agent/attach.d.ts +22 -2
- package/lib/mcp/tools/agent/attach.js +197 -55
- package/lib/mcp/tools/agent/build.d.ts +5 -0
- package/lib/mcp/tools/agent/build.js +9 -2
- package/lib/mcp/tools/agent/index.d.ts +1 -2
- package/lib/mcp/tools/agent/index.js +1 -1
- package/lib/mcp/tools/agent/live-view.d.ts +3 -7
- package/lib/mcp/tools/agent/live-view.js +74 -81
- package/lib/mcp/tools/agent/session.d.ts +14 -0
- package/lib/mcp/tools/agent/session.js +14 -8
- package/package.json +2 -2
- package/skill/SKILL.common.md +115 -38
- package/skill/builder-mode.md +9 -7
- package/skill/interception.md +3 -2
- package/skill/old-mode.md +10 -8
- package/src/cdp/claims.ts +153 -0
- package/src/cdp/client.ts +0 -8
- package/src/cdp/container.ts +593 -0
- package/src/live/tunnel.ts +71 -20
- package/src/mcp/instructions.ts +2 -1
- package/src/mcp/start.ts +65 -2
- package/src/mcp/tools/agent/attach.ts +229 -60
- package/src/mcp/tools/agent/build.ts +10 -2
- package/src/mcp/tools/agent/index.ts +6 -2
- package/src/mcp/tools/agent/live-view.ts +77 -93
- package/src/mcp/tools/agent/session.ts +14 -8
- package/lib/cdp/browser-proxy.d.ts +0 -78
- package/lib/cdp/browser-proxy.js +0 -186
- package/lib/live/page.d.ts +0 -20
- package/lib/live/page.js +0 -134
- package/lib/live/viewer.d.ts +0 -28
- package/lib/live/viewer.js +0 -232
- package/lib/mcp/tools/agent/proxy.d.ts +0 -3
- package/lib/mcp/tools/agent/proxy.js +0 -199
- package/src/live/page.ts +0 -145
- package/src/live/viewer.ts +0 -293
|
@@ -16,12 +16,9 @@
|
|
|
16
16
|
import { badRequest, conflict } from '@hapi/boom';
|
|
17
17
|
import assert from 'node:assert';
|
|
18
18
|
import { startTunnel } from "../../../live/tunnel.js";
|
|
19
|
-
import { startLiveView } from "../../../live/viewer.js";
|
|
20
19
|
import { LOGGER } from "../../../logger.js";
|
|
21
20
|
import { defineTool } from "../../server.js";
|
|
22
|
-
|
|
23
|
-
* therefore have no hosted live view of their own. */
|
|
24
|
-
const SHAREABLE_MODES = new Set(['dedicated', 'attach', 'custom']);
|
|
21
|
+
import { SHAREABLE_MODES } from "./attach.js";
|
|
25
22
|
/** Tear down whatever share is running. Safe when none is. Exported so the
|
|
26
23
|
* session tools can call it from `dispose_browser` / `reset_session`. */
|
|
27
24
|
export async function stopLiveShare(ref) {
|
|
@@ -30,98 +27,87 @@ export async function stopLiveShare(ref) {
|
|
|
30
27
|
return false;
|
|
31
28
|
}
|
|
32
29
|
ref.current = undefined;
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
});
|
|
30
|
+
// Only the tunnel is ours to close. The browser and its viewer belong to the
|
|
31
|
+
// container, which keeps running until the browser is disposed.
|
|
36
32
|
await share.tunnel.stop().catch((err) => {
|
|
37
33
|
LOGGER.warn({ err }, 'live view: tunnel stop failed');
|
|
38
34
|
});
|
|
39
35
|
return true;
|
|
40
36
|
}
|
|
41
|
-
export function liveViewTools(attachRef,
|
|
37
|
+
export function liveViewTools(attachRef, shareRef) {
|
|
42
38
|
return [
|
|
43
39
|
defineTool({
|
|
44
40
|
name: 'share_browser_view',
|
|
45
|
-
description: 'Put the attached
|
|
46
|
-
+ 'quick tunnel) so someone elsewhere can watch it
|
|
47
|
-
+ '
|
|
48
|
-
+ '
|
|
49
|
-
+ '
|
|
50
|
-
+ '
|
|
51
|
-
+ 'ONLY for
|
|
52
|
-
+ '
|
|
53
|
-
+ '
|
|
54
|
-
+ '`
|
|
55
|
-
+ '
|
|
56
|
-
+ 'and
|
|
57
|
-
+ '
|
|
58
|
-
+ '
|
|
59
|
-
+ '
|
|
41
|
+
description: 'Put the attached browser\'s live view behind a PUBLIC HTTPS URL (a '
|
|
42
|
+
+ 'cloudflared quick tunnel) so someone elsewhere can watch it and '
|
|
43
|
+
+ 'act in it. Use it to hand a login or a 2FA step to the person '
|
|
44
|
+
+ 'who holds the credentials, or to let a colleague watch a '
|
|
45
|
+
+ 'capture. Returns the URL — relay it to the developer VERBATIM, '
|
|
46
|
+
+ 'on its own line. '
|
|
47
|
+
+ 'ONLY for `container` mode, whose browser runs its own viewer. '
|
|
48
|
+
+ 'A `builder` browser already has a hosted live view: use the '
|
|
49
|
+
+ '`liveViewUrl` attach_browser returned. A local browser '
|
|
50
|
+
+ '(`dedicated`, `attach`, `custom`) cannot be shared at all — no '
|
|
51
|
+
+ 'tunnel can fix that, because there is no viewer behind it. Tell '
|
|
52
|
+
+ 'them so, and offer to re-attach: `container` is free and needs '
|
|
53
|
+
+ 'Docker, `builder` needs no install but is chargeable. '
|
|
54
|
+
+ 'THE URL IS THE ONLY CREDENTIAL: the random hostname is the whole '
|
|
55
|
+
+ 'secret, with no token or login behind it, so anyone who learns '
|
|
56
|
+
+ 'it can see and drive this browser as the signed-in user. Share '
|
|
57
|
+
+ 'it only with the person who needs it, and call stop_browser_view '
|
|
58
|
+
+ 'the moment they are done. '
|
|
60
59
|
+ 'Needs cloudflared: an existing install on PATH is used as-is, '
|
|
61
60
|
+ 'otherwise it is downloaded once to ~/.reclaim/bin on first '
|
|
62
61
|
+ 'share (never at install time). Set RECLAIM_CLOUDFLARED_PATH to '
|
|
63
62
|
+ 'point at your own copy instead.',
|
|
64
|
-
inputSchema: {
|
|
65
|
-
|
|
66
|
-
properties: {
|
|
67
|
-
tabId: {
|
|
68
|
-
type: 'string',
|
|
69
|
-
description: 'Tab to stream, from list_tabs. Defaults to the tab the '
|
|
70
|
-
+ 'active capture is on, else the first tab.',
|
|
71
|
-
},
|
|
72
|
-
interactive: {
|
|
73
|
-
type: 'boolean',
|
|
74
|
-
description: 'Let the viewer click and type in the page. Default true. '
|
|
75
|
-
+ 'Set false to stream a read-only view.',
|
|
76
|
-
},
|
|
77
|
-
},
|
|
78
|
-
},
|
|
79
|
-
}, async ({ tabId, interactive }) => {
|
|
63
|
+
inputSchema: { type: 'object', properties: {} },
|
|
64
|
+
}, async () => {
|
|
80
65
|
const attached = attachRef.current;
|
|
81
66
|
assert(attached, badRequest('No browser attached. Call attach_browser first.'));
|
|
82
67
|
assert(SHAREABLE_MODES.has(attached.mode), badRequest(`share_browser_view does not apply to \`${attached.mode}\` mode. `
|
|
83
|
-
+
|
|
84
|
-
|
|
85
|
-
|
|
68
|
+
+ (attached.liveViewUrl
|
|
69
|
+
? 'That browser already has a hosted live view — use the '
|
|
70
|
+
+ `\`liveViewUrl\` from attach_browser: ${attached.liveViewUrl}`
|
|
71
|
+
: 'That browser has no live view to put behind a URL. To let '
|
|
72
|
+
+ 'someone else in, re-attach with mode "container" (the '
|
|
73
|
+
+ 'Reclaim runtime in Docker — free, needs Docker) or '
|
|
74
|
+
+ '"builder" (hosted, no install, chargeable)')
|
|
86
75
|
+ '.'));
|
|
87
76
|
assert(!shareRef.current, conflict('A live view is already running at '
|
|
88
77
|
+ `${shareRef.current?.url}. Call stop_browser_view first, or `
|
|
89
78
|
+ 'reuse that URL.'));
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
});
|
|
102
|
-
let tunnel;
|
|
103
|
-
try {
|
|
104
|
-
tunnel = await startTunnel(view.port);
|
|
105
|
-
}
|
|
106
|
-
catch (err) {
|
|
107
|
-
// Don't leave the loopback server behind if the tunnel failed.
|
|
108
|
-
await view.stop().catch(() => { });
|
|
109
|
-
throw err;
|
|
110
|
-
}
|
|
111
|
-
const url = `${tunnel.url}${view.path}`;
|
|
79
|
+
// The container publishes its viewer on loopback and its full CDP
|
|
80
|
+
// port beside it. Only the VIEWER is ever tunnelled: the CDP port is
|
|
81
|
+
// unauthenticated, total control of the browser, and putting it on a
|
|
82
|
+
// public URL would hand that to anyone with the link.
|
|
83
|
+
const viewerPort = attached.viewerPort;
|
|
84
|
+
assert(viewerPort, badRequest('This browser reports no live-view port. Re-attach with mode '
|
|
85
|
+
+ '"container".'));
|
|
86
|
+
const tunnel = await startTunnel(viewerPort);
|
|
87
|
+
// The path is the runtime's own viewer page; `magnify=1` is what makes
|
|
88
|
+
// it lay out for the device that opens it.
|
|
89
|
+
const url = `${tunnel.url}/liveview.html?magnify=1`;
|
|
112
90
|
const share = {
|
|
113
|
-
|
|
91
|
+
tunnel, url,
|
|
114
92
|
};
|
|
115
93
|
shareRef.current = share;
|
|
116
|
-
LOGGER.info({ mode: attached.mode
|
|
94
|
+
LOGGER.info({ mode: attached.mode }, 'live view shared');
|
|
117
95
|
return {
|
|
118
96
|
url,
|
|
119
|
-
interactive: wantsInput,
|
|
120
|
-
tabId: target,
|
|
121
97
|
...(share.tunnel.binary.source === 'downloaded'
|
|
122
98
|
? { cloudflaredInstalled: true }
|
|
123
99
|
: {}),
|
|
124
|
-
...(share.tunnel.ready
|
|
100
|
+
...(share.tunnel.ready
|
|
101
|
+
? {}
|
|
102
|
+
: {
|
|
103
|
+
propagating: true,
|
|
104
|
+
// cloudflared's own last words. Without them, "the URL does
|
|
105
|
+
// not connect" is unactionable: the tunnel, the local server
|
|
106
|
+
// and this machine's egress all look identical from outside.
|
|
107
|
+
...(share.tunnel.diagnostics
|
|
108
|
+
? { cloudflaredLog: share.tunnel.diagnostics }
|
|
109
|
+
: {}),
|
|
110
|
+
}),
|
|
125
111
|
_notes: [
|
|
126
112
|
...(share.tunnel.binary.source === 'downloaded'
|
|
127
113
|
? ['cloudflared was not installed, so it was downloaded once '
|
|
@@ -129,24 +115,31 @@ export function liveViewTools(attachRef, captures, shareRef) {
|
|
|
129
115
|
: []),
|
|
130
116
|
...(share.tunnel.ready
|
|
131
117
|
? []
|
|
132
|
-
: ['The tunnel
|
|
133
|
-
+ '
|
|
118
|
+
: ['The tunnel never confirmed a connection to Cloudflare, so '
|
|
119
|
+
+ 'the URL may not resolve yet. Give it ~10s and reload '
|
|
120
|
+
+ 'first. If it still fails, say so and offer to retry '
|
|
121
|
+
+ '(stop_browser_view then share_browser_view) — a quick '
|
|
122
|
+
+ 'tunnel does sometimes fail to register. '
|
|
123
|
+
+ '`cloudflaredLog` has its output if it keeps happening.']),
|
|
134
124
|
'Relay `url` to the developer verbatim, on its own line.',
|
|
135
|
-
'The URL is a credential: it needs no login, and anyone with '
|
|
136
|
-
+ '
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
125
|
+
'The URL is a credential: it needs no login, and anyone with it '
|
|
126
|
+
+ 'can see this browser and act in it as the signed-in user.',
|
|
127
|
+
'It shows the whole browser, so a sign-in that opens a separate '
|
|
128
|
+
+ 'window is visible too — unlike a single-tab stream.',
|
|
129
|
+
'Opening it on a phone lays the page out for that phone, with '
|
|
130
|
+
+ 'touch, pinch-zoom and the soft keyboard.',
|
|
131
|
+
'Call stop_browser_view when they are done. That closes the '
|
|
132
|
+
+ 'tunnel only — the browser keeps running.',
|
|
140
133
|
],
|
|
141
134
|
};
|
|
142
135
|
}),
|
|
143
136
|
defineTool({
|
|
144
137
|
name: 'stop_browser_view',
|
|
145
|
-
description: 'Close the public
|
|
146
|
-
+ '
|
|
147
|
-
+ '
|
|
148
|
-
+ '
|
|
149
|
-
+ 'keeps running.',
|
|
138
|
+
description: 'Close the public URL opened by share_browser_view: shuts the '
|
|
139
|
+
+ 'tunnel down, so the URL stops resolving for whoever still has '
|
|
140
|
+
+ 'it. Call it as soon as the person you shared with is finished. '
|
|
141
|
+
+ 'Nothing else changes — the browser stays attached, its own live '
|
|
142
|
+
+ 'view keeps serving locally, and any capture keeps running.',
|
|
150
143
|
inputSchema: { type: 'object', properties: {} },
|
|
151
144
|
}, async () => ({ stopped: await stopLiveShare(shareRef) })),
|
|
152
145
|
];
|
|
@@ -7,3 +7,17 @@ export declare function sessionTools(attachRef: {
|
|
|
7
7
|
current?: AttachState;
|
|
8
8
|
config: ConfigStore;
|
|
9
9
|
}, captures: CaptureStore, liveShare: LiveShareRef): RegisteredTool[];
|
|
10
|
+
/**
|
|
11
|
+
* Release everything a session holds: the public tunnel, the captures, and the
|
|
12
|
+
* browser itself.
|
|
13
|
+
*
|
|
14
|
+
* Exported because a session can end WITHOUT anyone calling `dispose_browser` —
|
|
15
|
+
* the developer closes their agent, the client kills the server, the transport
|
|
16
|
+
* drops. Nothing runs on its own then, so `start.ts` runs this. Without it a
|
|
17
|
+
* container keeps running and, worse, a public URL to a live browser outlives
|
|
18
|
+
* the session that created it.
|
|
19
|
+
*/
|
|
20
|
+
export declare function resetAll(attachRef: {
|
|
21
|
+
current?: AttachState;
|
|
22
|
+
config: ConfigStore;
|
|
23
|
+
}, captures: CaptureStore, liveShare: LiveShareRef): Promise<void>;
|
|
@@ -24,11 +24,7 @@ export function sessionTools(attachRef, captures, liveShare) {
|
|
|
24
24
|
// URL can recover it instead of opening a second tunnel.
|
|
25
25
|
...(liveShare.current
|
|
26
26
|
? {
|
|
27
|
-
sharedView: {
|
|
28
|
-
url: liveShare.current.url,
|
|
29
|
-
interactive: liveShare.current.interactive,
|
|
30
|
-
viewers: liveShare.current.view.viewers(),
|
|
31
|
-
},
|
|
27
|
+
sharedView: { url: liveShare.current.url },
|
|
32
28
|
}
|
|
33
29
|
: {}),
|
|
34
30
|
})),
|
|
@@ -55,9 +51,19 @@ export function sessionTools(attachRef, captures, liveShare) {
|
|
|
55
51
|
}),
|
|
56
52
|
];
|
|
57
53
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
54
|
+
/**
|
|
55
|
+
* Release everything a session holds: the public tunnel, the captures, and the
|
|
56
|
+
* browser itself.
|
|
57
|
+
*
|
|
58
|
+
* Exported because a session can end WITHOUT anyone calling `dispose_browser` —
|
|
59
|
+
* the developer closes their agent, the client kills the server, the transport
|
|
60
|
+
* drops. Nothing runs on its own then, so `start.ts` runs this. Without it a
|
|
61
|
+
* container keeps running and, worse, a public URL to a live browser outlives
|
|
62
|
+
* the session that created it.
|
|
63
|
+
*/
|
|
64
|
+
export async function resetAll(attachRef, captures, liveShare) {
|
|
65
|
+
// Before the captures: a public URL must never outlive the browser it points
|
|
66
|
+
// at.
|
|
61
67
|
await stopLiveShare(liveShare);
|
|
62
68
|
await resetCaptures(captures);
|
|
63
69
|
await disposeAttached(attachRef);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reclaimprotocol/agent",
|
|
3
3
|
"title": "Reclaim Agent",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.25",
|
|
5
5
|
"description": "Reclaim Protocol tools for AI coding agents. Reclaim lets your app verify data points about its users, such as a follower count, an account balance, or an order history. This package is the Reclaim MCP server: it gives your agent the tools to find, build, test, and fix data verification providers by capturing and replaying browser requests, drafting provider recipes, running zkTLS proofs, and publishing provider versions.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./lib/index.js",
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
84
84
|
"@noble/hashes": "^2.2.0",
|
|
85
85
|
"@reclaimprotocol/attestor-core": "^5.0.8",
|
|
86
|
-
"@reclaimprotocol/client": "0.1.
|
|
86
|
+
"@reclaimprotocol/client": "0.1.25",
|
|
87
87
|
"@reclaimprotocol/tls": "^0.1.3",
|
|
88
88
|
"chrome-remote-interface": "^0.33.0",
|
|
89
89
|
"domhandler": "^6.0.0",
|
package/skill/SKILL.common.md
CHANGED
|
@@ -22,24 +22,45 @@ already names it.
|
|
|
22
22
|
<!-- section: browser -->
|
|
23
23
|
## Choosing a browser
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
25
|
+
- Choosing one — ask who signs in, then offer the matching pair
|
|
26
|
+
- Sharing the container's live view with someone remote
|
|
27
|
+
- What happens when the session ends
|
|
28
|
+
- When the container will not start
|
|
29
|
+
|
|
30
|
+
**Ask one question first, before you list anything:**
|
|
31
|
+
|
|
32
|
+
> Will you be doing this yourself, or does someone else need to sign in — the
|
|
33
|
+
> person whose account it is, say?
|
|
34
|
+
|
|
35
|
+
That answer decides half the list, and a developer can answer it without knowing
|
|
36
|
+
what CDP is. Only then offer the matching pair, call `attach_browser` ONCE with
|
|
37
|
+
the `mode` they pick, and never pick for them or assume last session's choice
|
|
38
|
+
still applies.
|
|
39
|
+
|
|
40
|
+
**They're doing it themselves** — no link, no Docker, nothing to install:
|
|
41
|
+
|
|
42
|
+
| mode | what it is | needs |
|
|
43
|
+
| --- | --- | --- |
|
|
44
|
+
| `dedicated` | Chrome the agent launches with its own profile (`~/.reclaim/chrome-profile`), which persists, so a site stays signed in. The default choice. | nothing |
|
|
45
|
+
| `attach` | The Chrome they already have open, with its tabs, extensions and logins. | `port` (default 9222) |
|
|
46
|
+
|
|
47
|
+
Neither can be shared, and neither needs to be: the browser is on their screen.
|
|
48
|
+
|
|
49
|
+
**Someone else has to sign in, or the site only works on a phone** — these two
|
|
50
|
+
are the only modes that produce a link another person can open:
|
|
51
|
+
|
|
52
|
+
| mode | what it is | needs | costs |
|
|
33
53
|
| --- | --- | --- | --- |
|
|
34
|
-
| `
|
|
35
|
-
| `
|
|
36
|
-
| `builder` | Quota-accounted remote Popcorn browser. Only way to browse from a **specific country**. | Builder auth, `orgId` | hosted `liveViewUrl`, returned by `attach_browser` |
|
|
37
|
-
| `custom` | Any CDP websocket the developer supplies. | `cdpUrl` | `share_browser_view` on request |
|
|
54
|
+
| `container` | The Reclaim runtime in Docker, on their machine. `share_browser_view` turns its viewer into a public link. | Docker running | free — their Docker, their bandwidth. 432 MB the first time, about 2 GB of RAM, 7 to 17 seconds to start |
|
|
55
|
+
| `builder` | A browser in Reclaim's cloud. The link comes back immediately, with nothing to install. Also the only way to browse from a **specific country**. | Builder auth, `orgId` | **chargeable** — billed against the org's quota |
|
|
38
56
|
|
|
39
|
-
**Say
|
|
40
|
-
developer
|
|
41
|
-
|
|
42
|
-
|
|
57
|
+
**Say which one costs money.** They are otherwise interchangeable from the
|
|
58
|
+
developer's point of view, so price and "do you have Docker" are the whole
|
|
59
|
+
decision. Do not quietly pick the billed one.
|
|
60
|
+
|
|
61
|
+
`custom` (any `ws://`/`wss://` CDP endpoint they supply) exists for someone who
|
|
62
|
+
was handed an endpoint and knows why. Don't offer it unprompted; it cannot be
|
|
63
|
+
shared either.
|
|
43
64
|
|
|
44
65
|
**`attach` gotcha (Chrome 136+, May 2025):** the developer's Chrome must be
|
|
45
66
|
started with BOTH `--remote-debugging-port=9222` AND
|
|
@@ -72,31 +93,87 @@ default profile. If `attach` fails, say this verbatim and offer `dedicated`.
|
|
|
72
93
|
**`custom` specifics:** the `cdpUrl` is a credential — never repeat, log, or
|
|
73
94
|
persist it. If a `liveViewUrl` comes back, share it full and verbatim.
|
|
74
95
|
|
|
75
|
-
### Sharing
|
|
76
|
-
|
|
77
|
-
A `
|
|
78
|
-
|
|
79
|
-
HTTPS URL through a cloudflared quick tunnel —
|
|
80
|
-
to act is not at that machine: a login or 2FA only they can
|
|
81
|
-
colleague watching a capture.
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
96
|
+
### Sharing the container's live view (`share_browser_view`)
|
|
97
|
+
|
|
98
|
+
A `container` browser serves its own viewer on loopback, so the developer can
|
|
99
|
+
open it the moment `attach_browser` returns. `share_browser_view` puts that same
|
|
100
|
+
viewer behind a public HTTPS URL through a cloudflared quick tunnel — for when
|
|
101
|
+
the person who has to act is not at that machine: a login or 2FA only they can
|
|
102
|
+
complete, or a colleague watching a capture.
|
|
103
|
+
|
|
104
|
+
**Offer it whenever a login is coming and the developer is not the one holding
|
|
105
|
+
the credentials.** Nobody asks for a feature they have no way of knowing exists.
|
|
106
|
+
Ask plainly:
|
|
107
|
+
|
|
108
|
+
> Do you want a link to this browser? You can open it yourself, or send it to
|
|
109
|
+
> whoever holds the login — they can watch and click in it, including from a
|
|
110
|
+
> phone.
|
|
111
|
+
|
|
112
|
+
If they say yes, call `share_browser_view` and **relay the full URL verbatim, on
|
|
113
|
+
its own line.**
|
|
114
|
+
|
|
115
|
+
- **`container` is the only mode this applies to.** `builder` returns a hosted
|
|
116
|
+
`liveViewUrl` from `attach_browser` — relay that instead, full and verbatim.
|
|
117
|
+
`dedicated`, `attach`, and `custom` have no view to share at all; if someone
|
|
118
|
+
else needs to see the browser, say so and offer `container`.
|
|
119
|
+
- **The URL is the ONLY credential.** The hostname Cloudflare hands out is the
|
|
120
|
+
whole secret: there is no token and no login behind it, so whoever learns the
|
|
121
|
+
URL has the browser, as the signed-in user. Give it to one person, say plainly
|
|
122
|
+
what it grants, and stop the share when they are done rather than leaving it
|
|
123
|
+
open. Tell them the watcher sees everything typed into that browser, including
|
|
124
|
+
a password — so if the point is for the DEVELOPER to log in, they should do it
|
|
125
|
+
before you share, not after.
|
|
126
|
+
- **It shows the whole browser, not one tab.** A sign-in that opens a separate
|
|
127
|
+
window is visible too.
|
|
128
|
+
- **Opening it on a phone lays the page out for that phone**, with touch,
|
|
129
|
+
pinch-zoom and the soft keyboard. That is the reason to reach for `container`
|
|
130
|
+
when a site only works on mobile.
|
|
131
|
+
- **Call `stop_browser_view` as soon as they're done.** That closes the tunnel
|
|
132
|
+
only — the browser keeps running. `dispose_browser` and `reset_session` stop
|
|
133
|
+
the container itself.
|
|
134
|
+
- **If the URL will not load**, the tunnel may not have registered with
|
|
135
|
+
Cloudflare — the hostname exists before the route does. Have them reload after
|
|
136
|
+
10 seconds; if it still fails, `stop_browser_view` then `share_browser_view`
|
|
137
|
+
again, since a quick tunnel does occasionally fail to come up. A
|
|
138
|
+
`propagating: true` in the result is that case, and `cloudflaredLog` carries
|
|
139
|
+
the reason.
|
|
95
140
|
- **cloudflared is handled for you.** An existing install on PATH wins;
|
|
96
|
-
otherwise it's fetched once into `~/.reclaim/bin` on the first share, never
|
|
97
|
-
|
|
141
|
+
otherwise it's fetched once into `~/.reclaim/bin` on the first share, never at
|
|
142
|
+
install time. Say so when it happens — it's a 37 MB download onto their
|
|
98
143
|
machine. `RECLAIM_CLOUDFLARED_PATH` points at their own copy instead.
|
|
99
144
|
|
|
145
|
+
### How containers are shared, and when they close
|
|
146
|
+
|
|
147
|
+
- **Closing the agent releases the browser.** The server stops the container and
|
|
148
|
+
the tunnel when its connection drops, so a public URL never outlives the
|
|
149
|
+
session that made it. `dispose_browser` does the same on demand.
|
|
150
|
+
- **Re-attaching in one session reuses the same browser** — under a second,
|
|
151
|
+
rather than the twenty a fresh one costs — so whatever it is signed into is
|
|
152
|
+
still there.
|
|
153
|
+
- **A second session working at the same time gets its own container.** They
|
|
154
|
+
cannot share one: the live view streams the whole screen, so two sessions in
|
|
155
|
+
one container would see each other's browsing, and two browsers on one profile
|
|
156
|
+
corrupt the cookie jar they share. Each concurrent session therefore has its
|
|
157
|
+
own profile, and starts out signed in to nothing.
|
|
158
|
+
- **Four at once is the ceiling**, because each is about 2 GB of RAM. Past that,
|
|
159
|
+
`attach_browser` says so and points at `dedicated`.
|
|
160
|
+
- **A crash cannot strand one for long.** A killed session runs no cleanup, so
|
|
161
|
+
the next `container` attach removes any container whose owning process is gone.
|
|
162
|
+
One belonging to a session that is still alive is never touched.
|
|
163
|
+
|
|
164
|
+
### When the container will not start
|
|
165
|
+
|
|
166
|
+
- **Docker is not running.** The error says so and names the other browser
|
|
167
|
+
modes. Docker is not something you can install for them.
|
|
168
|
+
- **Apple Silicon.** The image is amd64 and cannot be built for arm64, so it
|
|
169
|
+
runs translated. OrbStack does this itself; Docker Desktop needs "Use Rosetta
|
|
170
|
+
for x86/amd64 emulation" turned on. Under QEMU instead of Rosetta, Chromium
|
|
171
|
+
crashes on launch.
|
|
172
|
+
- **First run looks stuck.** It is downloading 432 MB. Say that rather than
|
|
173
|
+
letting it look hung.
|
|
174
|
+
- A container left over from a crashed run is **reused if it is still serving**,
|
|
175
|
+
and replaced if it is not — a second attach does not need a different port.
|
|
176
|
+
|
|
100
177
|
**When a local attach errors or `waitForCdpReady` reports a dead port**, walk
|
|
101
178
|
these before retrying:
|
|
102
179
|
|
package/skill/builder-mode.md
CHANGED
|
@@ -18,15 +18,17 @@ device-pairing `code` + `attachUrl` — give the user the URL, then poll
|
|
|
18
18
|
`authenticate({ code })` until LINKED. Tokens cache ~2 days.
|
|
19
19
|
(→ `how_it_works({ topic: "auth" })`)
|
|
20
20
|
|
|
21
|
-
**
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
21
|
+
**Ask who is doing the signing in, then offer a browser.** "Yourself, or does
|
|
22
|
+
someone else need to log in?" decides half of `attach_browser`'s list. Doing it
|
|
23
|
+
themselves → `dedicated` or `attach`, both local, neither shareable. Someone
|
|
24
|
+
else, or a phone-only site → `container` (the Reclaim runtime in Docker, free,
|
|
25
|
+
shareable with `share_browser_view`) or `builder` (hosted, no install,
|
|
26
|
+
**chargeable**). Say which costs money, and say `container` exists at all:
|
|
27
|
+
nobody guesses it.
|
|
27
28
|
|
|
28
29
|
**The flow:** `attach_browser` (ask the developer which browser source — local
|
|
29
|
-
Chrome,
|
|
30
|
+
Chrome, the Reclaim runtime in Docker, a remote Builder browser, or a CDP URL
|
|
31
|
+
they supply) → `start_capture`
|
|
30
32
|
⇒ **`captureId`** (the handle every browser/inspect tool takes first) → drive
|
|
31
33
|
the page (`navigate`, `wait_for_page`, `eval_in_page`, `get_cookies`) → inspect
|
|
32
34
|
(`list_requests`, `get_request`, `find_requests_containing`) →
|
package/skill/interception.md
CHANGED
|
@@ -96,7 +96,7 @@ to detect and nothing to disable — and `builder` never delivers intercepted
|
|
|
96
96
|
traffic to the page at all (topic `user-script`). A verification on those that
|
|
97
97
|
won't load or won't sign in is almost always a CAPTCHA or bot challenge
|
|
98
98
|
instead: check `analyze_request_constraints` for a `botChallenge` concern, and
|
|
99
|
-
treat it as a site that needs a human through
|
|
99
|
+
treat it as a site that needs a human through a live view rather than a
|
|
100
100
|
provider to fix.
|
|
101
101
|
|
|
102
102
|
### Then bisect, cheapest change first
|
|
@@ -232,4 +232,5 @@ oversight.
|
|
|
232
232
|
`window.Reclaim` is inert on Builder's own client.
|
|
233
233
|
- `hash-validation` — declaring script-fired requests without breaking
|
|
234
234
|
`verifyProof({ providerId })`.
|
|
235
|
-
- `browser` —
|
|
235
|
+
- `browser` — the modes with a live view (`container`, `builder`), for handing
|
|
236
|
+
a stuck sign-in to a human.
|
package/skill/old-mode.md
CHANGED
|
@@ -12,16 +12,18 @@ topic at a time — call `how_it_works({})` for the topic index. Topics:
|
|
|
12
12
|
`user-script`, `interception`, `hash-validation`, `credentials`, `never-do`,
|
|
13
13
|
`troubleshooting`.
|
|
14
14
|
|
|
15
|
-
**
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
**Ask who is doing the signing in, then offer a browser.** "Yourself, or does
|
|
16
|
+
someone else need to log in?" decides half of `attach_browser`'s list. Doing it
|
|
17
|
+
themselves → `dedicated` or `attach`, both local, neither shareable. Someone
|
|
18
|
+
else, or a phone-only site → `container` (the Reclaim runtime in Docker, free,
|
|
19
|
+
shareable with `share_browser_view`) or `builder` (hosted, no install,
|
|
20
|
+
**chargeable**). Say which costs money, and say `container` exists at all:
|
|
21
|
+
nobody guesses it.
|
|
21
22
|
|
|
22
23
|
**The flow:** `reclaim_authenticate` once → `attach_browser` (ask the developer
|
|
23
|
-
which browser source — local Chrome,
|
|
24
|
-
browser) → `start_capture` ⇒ **`captureId`** →
|
|
24
|
+
which browser source — local Chrome, the Reclaim runtime in Docker, a remote
|
|
25
|
+
Builder browser, or a CDP URL they supply) → `start_capture` ⇒ **`captureId`** →
|
|
26
|
+
drive the page (`navigate`,
|
|
25
27
|
`wait_for_page`, `eval_in_page`) → inspect (`list_requests`, `get_request`,
|
|
26
28
|
`find_requests_containing`) → `propose_provider` ⇒ **`draftId`** →
|
|
27
29
|
`replay_request(captureId, draftId)` → `run_proof(captureId, draftId,
|