browser-workspace 0.4.0
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/PRIVACY.md +21 -0
- package/README.md +138 -0
- package/SKILL.md +301 -0
- package/agent-workspace/agent_helpers.py +718 -0
- package/bin/__pycache__/browser-workspacecpython-311.pyc +0 -0
- package/bin/browser-workspace +75 -0
- package/extension/manifest.json +26 -0
- package/extension/service-worker.mjs +126 -0
- package/extension/workspace-manager.mjs +680 -0
- package/extension/workspace-page.mjs +7 -0
- package/extension/workspace.html +8 -0
- package/interaction-skills/connection.md +52 -0
- package/interaction-skills/cookies.md +3 -0
- package/interaction-skills/cross-origin-iframes.md +3 -0
- package/interaction-skills/dialogs.md +64 -0
- package/interaction-skills/downloads.md +3 -0
- package/interaction-skills/drag-and-drop.md +3 -0
- package/interaction-skills/dropdowns.md +3 -0
- package/interaction-skills/iframes.md +3 -0
- package/interaction-skills/network-requests.md +3 -0
- package/interaction-skills/print-as-pdf.md +3 -0
- package/interaction-skills/profile-sync.md +90 -0
- package/interaction-skills/screenshots.md +17 -0
- package/interaction-skills/scrolling.md +22 -0
- package/interaction-skills/shadow-dom.md +3 -0
- package/interaction-skills/tabs.md +73 -0
- package/interaction-skills/uploads.md +1 -0
- package/interaction-skills/viewport.md +16 -0
- package/licenses/browser-harness-MIT.txt +21 -0
- package/managed_chrome.py +244 -0
- package/node/admin.mjs +44 -0
- package/package.json +36 -0
- package/platform_runner.py +187 -0
- package/platforms/chatgpt/README.md +60 -0
- package/platforms/chatgpt/actions/_bootstrap_start.py +58 -0
- package/platforms/chatgpt/actions/_lifecycle_common.py +119 -0
- package/platforms/chatgpt/actions/_mcp_app.py +80 -0
- package/platforms/chatgpt/actions/_new_thread.py +85 -0
- package/platforms/chatgpt/actions/_new_turn.py +33 -0
- package/platforms/chatgpt/actions/_project_setup.py +353 -0
- package/platforms/chatgpt/actions/_readiness.py +52 -0
- package/platforms/chatgpt/actions/_submit_thread.py +129 -0
- package/platforms/chatgpt/actions/_temporary_submit.py +571 -0
- package/platforms/chatgpt/actions/_thread_turn.py +362 -0
- package/platforms/chatgpt/manifest.yaml +34 -0
- package/platforms/chatgpt/tests/test_contract.py +176 -0
- package/platforms/chrome-web-store/README.md +49 -0
- package/platforms/chrome-web-store/actions/_chrome_web_store.py +425 -0
- package/platforms/chrome-web-store/flows/check-status.md +7 -0
- package/platforms/chrome-web-store/flows/open-item.md +10 -0
- package/platforms/chrome-web-store/flows/submit-review.md +10 -0
- package/platforms/chrome-web-store/flows/update-listing.md +18 -0
- package/platforms/chrome-web-store/flows/upload-package.md +11 -0
- package/platforms/chrome-web-store/flows/verify-published.md +9 -0
- package/platforms/chrome-web-store/manifest.yaml +33 -0
- package/platforms/chrome-web-store/tests/test_contract.py +83 -0
- package/platforms/microsoft-teams/README.md +32 -0
- package/platforms/microsoft-teams/actions/_meeting.py +210 -0
- package/platforms/microsoft-teams/flows/instant-meeting.md +21 -0
- package/platforms/microsoft-teams/manifest.yaml +25 -0
- package/platforms/microsoft-teams/tests/test_contract.py +24 -0
- package/platforms/tiktok/README.md +10 -0
- package/platforms/tiktok/actions/_post.py +180 -0
- package/platforms/tiktok/manifest.yaml +99 -0
- package/platforms/wechat-channels/README.md +10 -0
- package/platforms/wechat-channels/actions/_common.py +163 -0
- package/platforms/wechat-channels/actions/_manage.py +130 -0
- package/platforms/wechat-channels/actions/_post.py +232 -0
- package/platforms/wechat-channels/manifest.yaml +118 -0
- package/platforms/wechat-channels/tests/test_contract.py +36 -0
- package/platforms/xhs/README.md +10 -0
- package/platforms/xhs/actions/_manage.py +355 -0
- package/platforms/xhs/actions/_post.py +192 -0
- package/platforms/xhs/manifest.yaml +98 -0
- package/platforms/youtube/README.md +10 -0
- package/platforms/youtube/actions/_post.py +100 -0
- package/platforms/youtube/manifest.yaml +94 -0
- package/pyproject.toml +29 -0
- package/session_client.py +97 -0
- package/session_daemon.py +325 -0
- package/src/browser_harness/__init__.py +2 -0
- package/src/browser_harness/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/browser_harness/__pycache__/_ipc.cpython-311.pyc +0 -0
- package/src/browser_harness/__pycache__/admin.cpython-311.pyc +0 -0
- package/src/browser_harness/__pycache__/auth.cpython-311.pyc +0 -0
- package/src/browser_harness/__pycache__/daemon.cpython-311.pyc +0 -0
- package/src/browser_harness/__pycache__/helpers.cpython-311.pyc +0 -0
- package/src/browser_harness/__pycache__/paths.cpython-311.pyc +0 -0
- package/src/browser_harness/__pycache__/recorder.cpython-311.pyc +0 -0
- package/src/browser_harness/_ipc.py +201 -0
- package/src/browser_harness/admin.py +1588 -0
- package/src/browser_harness/auth.py +546 -0
- package/src/browser_harness/daemon.py +894 -0
- package/src/browser_harness/helpers.py +668 -0
- package/src/browser_harness/macos.py +131 -0
- package/src/browser_harness/mcp_cli.py +16 -0
- package/src/browser_harness/paths.py +49 -0
- package/src/browser_harness/recorder.py +343 -0
- package/src/browser_harness/run.py +413 -0
- package/src/browser_harness/telemetry.py +308 -0
- package/src/browser_harness/video-template.html +970 -0
- package/src/browser_harness/video.py +749 -0
- package/src/browser_harness/video_render.py +524 -0
- package/src/browser_workspace.egg-info/PKG-INFO +10 -0
- package/src/browser_workspace.egg-info/SOURCES.txt +28 -0
- package/src/browser_workspace.egg-info/dependency_links.txt +1 -0
- package/src/browser_workspace.egg-info/requires.txt +5 -0
- package/src/browser_workspace.egg-info/top_level.txt +1 -0
|
Binary file
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
import argparse, json, os, sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
ROOT=Path(__file__).resolve().parents[1]
|
|
6
|
+
VENV_PY=ROOT/'.venv'/'bin'/'python'
|
|
7
|
+
if VENV_PY.exists() and Path(sys.executable).resolve()!=VENV_PY.resolve() and os.environ.get('BROWSER_WORKSPACE_VENV_REEXEC')!='1':
|
|
8
|
+
env=dict(os.environ); env['BROWSER_WORKSPACE_VENV_REEXEC']='1'
|
|
9
|
+
os.execve(str(VENV_PY),[str(VENV_PY),str(Path(__file__).resolve()),*sys.argv[1:]],env)
|
|
10
|
+
sys.path.insert(0,str(ROOT))
|
|
11
|
+
from session_client import SOCKET_PATH, ensure_daemon
|
|
12
|
+
from platform_runner import run_platform_action
|
|
13
|
+
|
|
14
|
+
def emit(x): print(json.dumps(x,ensure_ascii=False))
|
|
15
|
+
|
|
16
|
+
def status():
|
|
17
|
+
daemon=ensure_daemon()
|
|
18
|
+
helper=(ROOT/'node'/'admin.mjs').resolve()
|
|
19
|
+
return {
|
|
20
|
+
'root': str(ROOT.resolve()),
|
|
21
|
+
'cli': str(Path(__file__).resolve()),
|
|
22
|
+
'admin_helper': str(helper),
|
|
23
|
+
'managed_chrome': daemon.get('managed_chrome') if daemon else None,
|
|
24
|
+
'daemon': {
|
|
25
|
+
'running': bool(daemon and daemon.get('ok')),
|
|
26
|
+
'pid': daemon.get('pid') if daemon else None,
|
|
27
|
+
'session_count': daemon.get('session_count') if daemon else None,
|
|
28
|
+
'socket': str(SOCKET_PATH),
|
|
29
|
+
},
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
def main():
|
|
33
|
+
p=argparse.ArgumentParser(prog='browser-workspace')
|
|
34
|
+
sp=p.add_subparsers(dest='group',required=True)
|
|
35
|
+
sp.add_parser('status')
|
|
36
|
+
session=sp.add_parser('session'); ssp=session.add_subparsers(dest='cmd',required=True)
|
|
37
|
+
start=ssp.add_parser('start'); start.add_argument('--workspace', help=argparse.SUPPRESS); start.add_argument('--url')
|
|
38
|
+
ssp.add_parser('list')
|
|
39
|
+
ex=ssp.add_parser('exec'); ex.add_argument('session_id'); ex.add_argument('--code-file')
|
|
40
|
+
stop=ssp.add_parser('stop'); stop.add_argument('session_id', nargs='?'); stop.add_argument('--all', action='store_true', dest='stop_all')
|
|
41
|
+
platform=sp.add_parser('platform'); psp=platform.add_subparsers(dest='pcmd',required=True)
|
|
42
|
+
profile=psp.add_parser('profile'); profile.add_argument('platform')
|
|
43
|
+
source=psp.add_parser('source'); source.add_argument('platform'); source.add_argument('action'); source.add_argument('--config')
|
|
44
|
+
run=psp.add_parser('run'); run.add_argument('platform'); run.add_argument('action'); run.add_argument('--config'); run.add_argument('--url'); run.add_argument('--session-id')
|
|
45
|
+
args=p.parse_args()
|
|
46
|
+
if args.group=='status': emit(status()); return
|
|
47
|
+
if args.group=='session' and args.cmd=='list':
|
|
48
|
+
from session_client import request
|
|
49
|
+
emit(request({'op':'list'})); return
|
|
50
|
+
if args.group=='session' and args.cmd=='start':
|
|
51
|
+
from session_client import request
|
|
52
|
+
emit(request({'op':'start','workspace':args.workspace,'url':args.url})); return
|
|
53
|
+
if args.group=='session' and args.cmd=='exec':
|
|
54
|
+
from session_client import request
|
|
55
|
+
if args.code_file: code=Path(args.code_file).read_text()
|
|
56
|
+
elif not sys.stdin.isatty(): code=sys.stdin.read()
|
|
57
|
+
else: p.error('session exec reads Python from stdin (use a heredoc) or --code-file')
|
|
58
|
+
r=request({'op':'exec','session_id':args.session_id,'code':code}); emit(r); raise SystemExit(0 if r.get('ok',False) else 1)
|
|
59
|
+
if args.group=='session' and args.cmd=='stop':
|
|
60
|
+
from session_client import request
|
|
61
|
+
if args.stop_all:
|
|
62
|
+
if args.session_id: p.error('session stop accepts either SESSION_ID or --all, not both')
|
|
63
|
+
emit(request({'op':'stop-all'})); return
|
|
64
|
+
if not args.session_id: p.error('session stop requires SESSION_ID or --all')
|
|
65
|
+
emit(request({'op':'stop','session_id':args.session_id})); return
|
|
66
|
+
if args.group=='platform' and args.pcmd=='source':
|
|
67
|
+
from platform_runner import action_path, prepare_action
|
|
68
|
+
path=action_path(args.platform,args.action); config=Path(args.config) if args.config else None
|
|
69
|
+
sys.stdout.write(prepare_action(path,config)); return
|
|
70
|
+
if args.group=='platform' and args.pcmd=='profile':
|
|
71
|
+
from platform_runner import platform_profile
|
|
72
|
+
emit(platform_profile(args.platform)); return
|
|
73
|
+
if args.group=='platform' and args.pcmd=='run':
|
|
74
|
+
r=run_platform_action(args.platform,args.action,config_path=args.config,url=args.url,session_id=args.session_id); emit(r); raise SystemExit(0 if (r.get('result') or {}).get('ok',False) else 1)
|
|
75
|
+
if __name__=='__main__': main()
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"manifest_version": 3,
|
|
3
|
+
"name": "Browser Workspace",
|
|
4
|
+
"version": "0.3.1",
|
|
5
|
+
"description": "Own named Chrome tab-group workspaces and lazy reusable tab pools for browser agents.",
|
|
6
|
+
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv7NU4eqCIkv793eZGTna5ntNKx42cj7wFWD0b+tEhGWHgXuYMDKXHuvtf1249vozaGGpDPFtH3d5uk0QNCl5yFA+kdL5RgTyg5qIpdFT4NET5Y8HSALxSjy27U4+Lf7XwYkQdSuClK7S9KxUI7OE9bloiDaSWbs9XjEBoeUGALXgqsIMgBVbyQ2RuMUo9mGpTreA+Tp2ATivU5ZnwmOuhJzniefbyAST/U6LY4VK/7jijnHLbfmTmCyi3xHWD2b8l9A7IsDBR1eRk6XY0YqZlC/H2wSmcUUQjhsgpiQK32iwwyNFspKgH4ReRPoO6RSuatUS8+J+/xevwCBF5eBq1QIDAQAB",
|
|
7
|
+
"permissions": [
|
|
8
|
+
"storage",
|
|
9
|
+
"tabs",
|
|
10
|
+
"tabGroups",
|
|
11
|
+
"alarms",
|
|
12
|
+
"webNavigation",
|
|
13
|
+
"contextMenus"
|
|
14
|
+
],
|
|
15
|
+
"host_permissions": [
|
|
16
|
+
"http://*/*",
|
|
17
|
+
"https://*/*"
|
|
18
|
+
],
|
|
19
|
+
"background": {
|
|
20
|
+
"service_worker": "service-worker.mjs",
|
|
21
|
+
"type": "module"
|
|
22
|
+
},
|
|
23
|
+
"action": {
|
|
24
|
+
"default_title": "Browser Workspace"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { WorkspaceManager } from "./workspace-manager.mjs";
|
|
2
|
+
|
|
3
|
+
const manager = new WorkspaceManager(chrome);
|
|
4
|
+
const RECLAIM_ALARM = "browser-workspace.reclaim-stale";
|
|
5
|
+
const closingWindowIds = new Set();
|
|
6
|
+
|
|
7
|
+
chrome.alarms.create(RECLAIM_ALARM, { periodInMinutes: 5 });
|
|
8
|
+
|
|
9
|
+
manager.restoreConfiguredWorkspaces().catch((error) => {
|
|
10
|
+
console.warn("Browser Workspace failed to restore configured workspaces", error);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const RELEASE_ALL_MENU_ID = "browser-workspace.release-all";
|
|
14
|
+
|
|
15
|
+
async function ensureContextMenus() {
|
|
16
|
+
try {
|
|
17
|
+
await chrome.contextMenus.remove(RELEASE_ALL_MENU_ID);
|
|
18
|
+
} catch {
|
|
19
|
+
// Menu does not exist yet.
|
|
20
|
+
}
|
|
21
|
+
chrome.contextMenus.create({
|
|
22
|
+
id: RELEASE_ALL_MENU_ID,
|
|
23
|
+
title: "Release all tabs",
|
|
24
|
+
contexts: ["action"],
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
chrome.runtime.onInstalled.addListener(() => {
|
|
29
|
+
ensureContextMenus().catch((error) => {
|
|
30
|
+
console.warn("Browser Workspace failed to create extension menu", error);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
chrome.runtime.onStartup.addListener(() => {
|
|
35
|
+
ensureContextMenus().catch((error) => {
|
|
36
|
+
console.warn("Browser Workspace failed to create extension menu", error);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
ensureContextMenus().catch((error) => {
|
|
41
|
+
console.warn("Browser Workspace failed to initialize extension menu", error);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
chrome.contextMenus.onClicked.addListener((info) => {
|
|
45
|
+
if (info.menuItemId !== RELEASE_ALL_MENU_ID) return;
|
|
46
|
+
manager.releaseAll().catch((error) => {
|
|
47
|
+
console.warn("Browser Workspace failed to release all tabs", error);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
globalThis.browserWorkspaceManagerRpc = async (request) => {
|
|
52
|
+
try {
|
|
53
|
+
return { ok: true, result: await manager.rpc(request) };
|
|
54
|
+
} catch (error) {
|
|
55
|
+
return {
|
|
56
|
+
ok: false,
|
|
57
|
+
error: {
|
|
58
|
+
message: error?.message || String(error),
|
|
59
|
+
name: error?.name || "Error",
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
chrome.runtime.onMessage.addListener((request, _sender, sendResponse) => {
|
|
66
|
+
globalThis.browserWorkspaceManagerRpc(request).then(sendResponse);
|
|
67
|
+
return true;
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
chrome.tabs.onCreated.addListener((tab) => {
|
|
71
|
+
manager.inheritWorkspaceForCreatedTab(tab).catch((error) => {
|
|
72
|
+
console.warn("Browser Workspace failed to inherit child tab group", error);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
setTimeout(() => {
|
|
76
|
+
manager.reconcileCreatedTab(tab.id).catch((error) => {
|
|
77
|
+
if (!/No tab with id|Invalid tab ID/i.test(String(error?.message || error))) {
|
|
78
|
+
console.warn("Browser Workspace failed delayed created-tab reconciliation", error);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}, 150);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
chrome.webNavigation.onCreatedNavigationTarget.addListener((details) => {
|
|
85
|
+
manager.inheritNavigationTarget(details).catch((error) => {
|
|
86
|
+
console.warn("Browser Workspace failed to inherit navigation target", error);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
chrome.tabs.onUpdated.addListener((_tabId, changeInfo, tab) => {
|
|
91
|
+
if (!Object.prototype.hasOwnProperty.call(changeInfo, "groupId")) return;
|
|
92
|
+
manager.inheritWorkspaceForCreatedTab(tab).catch((error) => {
|
|
93
|
+
console.warn("Browser Workspace failed to reconcile tab group ownership", error);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
chrome.tabs.onActivated.addListener((activeInfo) => {
|
|
98
|
+
manager.noteActivatedTab(activeInfo).catch((error) => {
|
|
99
|
+
console.warn("Browser Workspace failed to record active tab context", error);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
chrome.tabs.onRemoved.addListener((_tabId, removeInfo) => {
|
|
104
|
+
if (!removeInfo?.isWindowClosing) return;
|
|
105
|
+
closingWindowIds.add(removeInfo.windowId);
|
|
106
|
+
setTimeout(() => closingWindowIds.delete(removeInfo.windowId), 1000);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
chrome.tabGroups.onRemoved.addListener((group) => {
|
|
110
|
+
// Chrome also emits onRemoved while moving a group between windows, and
|
|
111
|
+
// while a whole window is closing. Defer one turn so a moved group can
|
|
112
|
+
// reappear, and preserve persisted workspaces across normal window closes.
|
|
113
|
+
setTimeout(() => {
|
|
114
|
+
if (closingWindowIds.has(group.windowId)) return;
|
|
115
|
+
manager.removeDeletedGroup(group).catch((error) => {
|
|
116
|
+
console.warn("Browser Workspace failed to remove deleted workspace", error);
|
|
117
|
+
});
|
|
118
|
+
}, 100);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
chrome.alarms.onAlarm.addListener((alarm) => {
|
|
122
|
+
if (alarm.name !== RECLAIM_ALARM) return;
|
|
123
|
+
manager.reclaimStaleTabs().catch((error) => {
|
|
124
|
+
console.warn("Browser Workspace failed to reclaim stale tabs", error);
|
|
125
|
+
});
|
|
126
|
+
});
|