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.
Files changed (108) hide show
  1. package/PRIVACY.md +21 -0
  2. package/README.md +138 -0
  3. package/SKILL.md +301 -0
  4. package/agent-workspace/agent_helpers.py +718 -0
  5. package/bin/__pycache__/browser-workspacecpython-311.pyc +0 -0
  6. package/bin/browser-workspace +75 -0
  7. package/extension/manifest.json +26 -0
  8. package/extension/service-worker.mjs +126 -0
  9. package/extension/workspace-manager.mjs +680 -0
  10. package/extension/workspace-page.mjs +7 -0
  11. package/extension/workspace.html +8 -0
  12. package/interaction-skills/connection.md +52 -0
  13. package/interaction-skills/cookies.md +3 -0
  14. package/interaction-skills/cross-origin-iframes.md +3 -0
  15. package/interaction-skills/dialogs.md +64 -0
  16. package/interaction-skills/downloads.md +3 -0
  17. package/interaction-skills/drag-and-drop.md +3 -0
  18. package/interaction-skills/dropdowns.md +3 -0
  19. package/interaction-skills/iframes.md +3 -0
  20. package/interaction-skills/network-requests.md +3 -0
  21. package/interaction-skills/print-as-pdf.md +3 -0
  22. package/interaction-skills/profile-sync.md +90 -0
  23. package/interaction-skills/screenshots.md +17 -0
  24. package/interaction-skills/scrolling.md +22 -0
  25. package/interaction-skills/shadow-dom.md +3 -0
  26. package/interaction-skills/tabs.md +73 -0
  27. package/interaction-skills/uploads.md +1 -0
  28. package/interaction-skills/viewport.md +16 -0
  29. package/licenses/browser-harness-MIT.txt +21 -0
  30. package/managed_chrome.py +244 -0
  31. package/node/admin.mjs +44 -0
  32. package/package.json +36 -0
  33. package/platform_runner.py +187 -0
  34. package/platforms/chatgpt/README.md +60 -0
  35. package/platforms/chatgpt/actions/_bootstrap_start.py +58 -0
  36. package/platforms/chatgpt/actions/_lifecycle_common.py +119 -0
  37. package/platforms/chatgpt/actions/_mcp_app.py +80 -0
  38. package/platforms/chatgpt/actions/_new_thread.py +85 -0
  39. package/platforms/chatgpt/actions/_new_turn.py +33 -0
  40. package/platforms/chatgpt/actions/_project_setup.py +353 -0
  41. package/platforms/chatgpt/actions/_readiness.py +52 -0
  42. package/platforms/chatgpt/actions/_submit_thread.py +129 -0
  43. package/platforms/chatgpt/actions/_temporary_submit.py +571 -0
  44. package/platforms/chatgpt/actions/_thread_turn.py +362 -0
  45. package/platforms/chatgpt/manifest.yaml +34 -0
  46. package/platforms/chatgpt/tests/test_contract.py +176 -0
  47. package/platforms/chrome-web-store/README.md +49 -0
  48. package/platforms/chrome-web-store/actions/_chrome_web_store.py +425 -0
  49. package/platforms/chrome-web-store/flows/check-status.md +7 -0
  50. package/platforms/chrome-web-store/flows/open-item.md +10 -0
  51. package/platforms/chrome-web-store/flows/submit-review.md +10 -0
  52. package/platforms/chrome-web-store/flows/update-listing.md +18 -0
  53. package/platforms/chrome-web-store/flows/upload-package.md +11 -0
  54. package/platforms/chrome-web-store/flows/verify-published.md +9 -0
  55. package/platforms/chrome-web-store/manifest.yaml +33 -0
  56. package/platforms/chrome-web-store/tests/test_contract.py +83 -0
  57. package/platforms/microsoft-teams/README.md +32 -0
  58. package/platforms/microsoft-teams/actions/_meeting.py +210 -0
  59. package/platforms/microsoft-teams/flows/instant-meeting.md +21 -0
  60. package/platforms/microsoft-teams/manifest.yaml +25 -0
  61. package/platforms/microsoft-teams/tests/test_contract.py +24 -0
  62. package/platforms/tiktok/README.md +10 -0
  63. package/platforms/tiktok/actions/_post.py +180 -0
  64. package/platforms/tiktok/manifest.yaml +99 -0
  65. package/platforms/wechat-channels/README.md +10 -0
  66. package/platforms/wechat-channels/actions/_common.py +163 -0
  67. package/platforms/wechat-channels/actions/_manage.py +130 -0
  68. package/platforms/wechat-channels/actions/_post.py +232 -0
  69. package/platforms/wechat-channels/manifest.yaml +118 -0
  70. package/platforms/wechat-channels/tests/test_contract.py +36 -0
  71. package/platforms/xhs/README.md +10 -0
  72. package/platforms/xhs/actions/_manage.py +355 -0
  73. package/platforms/xhs/actions/_post.py +192 -0
  74. package/platforms/xhs/manifest.yaml +98 -0
  75. package/platforms/youtube/README.md +10 -0
  76. package/platforms/youtube/actions/_post.py +100 -0
  77. package/platforms/youtube/manifest.yaml +94 -0
  78. package/pyproject.toml +29 -0
  79. package/session_client.py +97 -0
  80. package/session_daemon.py +325 -0
  81. package/src/browser_harness/__init__.py +2 -0
  82. package/src/browser_harness/__pycache__/__init__.cpython-311.pyc +0 -0
  83. package/src/browser_harness/__pycache__/_ipc.cpython-311.pyc +0 -0
  84. package/src/browser_harness/__pycache__/admin.cpython-311.pyc +0 -0
  85. package/src/browser_harness/__pycache__/auth.cpython-311.pyc +0 -0
  86. package/src/browser_harness/__pycache__/daemon.cpython-311.pyc +0 -0
  87. package/src/browser_harness/__pycache__/helpers.cpython-311.pyc +0 -0
  88. package/src/browser_harness/__pycache__/paths.cpython-311.pyc +0 -0
  89. package/src/browser_harness/__pycache__/recorder.cpython-311.pyc +0 -0
  90. package/src/browser_harness/_ipc.py +201 -0
  91. package/src/browser_harness/admin.py +1588 -0
  92. package/src/browser_harness/auth.py +546 -0
  93. package/src/browser_harness/daemon.py +894 -0
  94. package/src/browser_harness/helpers.py +668 -0
  95. package/src/browser_harness/macos.py +131 -0
  96. package/src/browser_harness/mcp_cli.py +16 -0
  97. package/src/browser_harness/paths.py +49 -0
  98. package/src/browser_harness/recorder.py +343 -0
  99. package/src/browser_harness/run.py +413 -0
  100. package/src/browser_harness/telemetry.py +308 -0
  101. package/src/browser_harness/video-template.html +970 -0
  102. package/src/browser_harness/video.py +749 -0
  103. package/src/browser_harness/video_render.py +524 -0
  104. package/src/browser_workspace.egg-info/PKG-INFO +10 -0
  105. package/src/browser_workspace.egg-info/SOURCES.txt +28 -0
  106. package/src/browser_workspace.egg-info/dependency_links.txt +1 -0
  107. package/src/browser_workspace.egg-info/requires.txt +5 -0
  108. package/src/browser_workspace.egg-info/top_level.txt +1 -0
@@ -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
+ });