browser-workspace 0.4.5 → 0.4.9

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 (68) hide show
  1. package/bin/__pycache__/browser-workspacecpython-311.pyc +0 -0
  2. package/package.json +1 -1
  3. package/platform_runner.py +11 -2
  4. package/platforms/chatgpt/actions/_lifecycle_common.py +73 -20
  5. package/platforms/chatgpt/actions/_new_turn.py +1 -0
  6. package/platforms/chatgpt/actions/_readiness.py +29 -3
  7. package/platforms/chatgpt/actions/_submit_thread.py +20 -3
  8. package/platforms/chatgpt/actions/_thread_turn.py +31 -2
  9. package/platforms/chatgpt/tests/test_contract.py +107 -1
  10. package/platforms/iyf/AIRPLAY.md +25 -0
  11. package/platforms/iyf/actions/_find.py +18 -0
  12. package/platforms/iyf/actions/_open.py +10 -0
  13. package/platforms/iyf/manifest.yaml +11 -0
  14. package/platforms/iyf/safari-airplay +57 -0
  15. package/pyproject.toml +2 -2
  16. package/session_client.py +10 -0
  17. package/src/browser_workspace.egg-info/PKG-INFO +2 -2
  18. package/src/browser_workspace.egg-info/SOURCES.txt +53 -46
  19. package/src/browser_workspace.egg-info/requires.txt +1 -1
  20. package/src/browser_workspace_app/package.json +1 -1
  21. package/src/browser_workspace_app/pyproject.toml +2 -2
  22. package/src/browser_workspace_app/session_client.py +10 -0
  23. package/src/browser_workspace_app/platform_runner.py +0 -204
  24. package/src/browser_workspace_app/platforms/chatgpt/README.md +0 -60
  25. package/src/browser_workspace_app/platforms/chatgpt/actions/_bootstrap_start.py +0 -58
  26. package/src/browser_workspace_app/platforms/chatgpt/actions/_lifecycle_common.py +0 -119
  27. package/src/browser_workspace_app/platforms/chatgpt/actions/_mcp_app.py +0 -80
  28. package/src/browser_workspace_app/platforms/chatgpt/actions/_new_thread.py +0 -85
  29. package/src/browser_workspace_app/platforms/chatgpt/actions/_new_turn.py +0 -33
  30. package/src/browser_workspace_app/platforms/chatgpt/actions/_project_setup.py +0 -353
  31. package/src/browser_workspace_app/platforms/chatgpt/actions/_readiness.py +0 -52
  32. package/src/browser_workspace_app/platforms/chatgpt/actions/_submit_thread.py +0 -129
  33. package/src/browser_workspace_app/platforms/chatgpt/actions/_temporary_submit.py +0 -591
  34. package/src/browser_workspace_app/platforms/chatgpt/actions/_thread_turn.py +0 -362
  35. package/src/browser_workspace_app/platforms/chatgpt/manifest.yaml +0 -34
  36. package/src/browser_workspace_app/platforms/chatgpt/tests/test_contract.py +0 -183
  37. package/src/browser_workspace_app/platforms/chrome-web-store/README.md +0 -49
  38. package/src/browser_workspace_app/platforms/chrome-web-store/actions/_chrome_web_store.py +0 -425
  39. package/src/browser_workspace_app/platforms/chrome-web-store/flows/check-status.md +0 -7
  40. package/src/browser_workspace_app/platforms/chrome-web-store/flows/open-item.md +0 -10
  41. package/src/browser_workspace_app/platforms/chrome-web-store/flows/submit-review.md +0 -10
  42. package/src/browser_workspace_app/platforms/chrome-web-store/flows/update-listing.md +0 -18
  43. package/src/browser_workspace_app/platforms/chrome-web-store/flows/upload-package.md +0 -11
  44. package/src/browser_workspace_app/platforms/chrome-web-store/flows/verify-published.md +0 -9
  45. package/src/browser_workspace_app/platforms/chrome-web-store/manifest.yaml +0 -33
  46. package/src/browser_workspace_app/platforms/chrome-web-store/tests/test_contract.py +0 -83
  47. package/src/browser_workspace_app/platforms/microsoft-teams/README.md +0 -32
  48. package/src/browser_workspace_app/platforms/microsoft-teams/actions/_meeting.py +0 -210
  49. package/src/browser_workspace_app/platforms/microsoft-teams/flows/instant-meeting.md +0 -21
  50. package/src/browser_workspace_app/platforms/microsoft-teams/manifest.yaml +0 -25
  51. package/src/browser_workspace_app/platforms/microsoft-teams/tests/test_contract.py +0 -24
  52. package/src/browser_workspace_app/platforms/tiktok/README.md +0 -10
  53. package/src/browser_workspace_app/platforms/tiktok/actions/_post.py +0 -180
  54. package/src/browser_workspace_app/platforms/tiktok/manifest.yaml +0 -99
  55. package/src/browser_workspace_app/platforms/wechat-channels/README.md +0 -10
  56. package/src/browser_workspace_app/platforms/wechat-channels/actions/_common.py +0 -163
  57. package/src/browser_workspace_app/platforms/wechat-channels/actions/_manage.py +0 -130
  58. package/src/browser_workspace_app/platforms/wechat-channels/actions/_post.py +0 -231
  59. package/src/browser_workspace_app/platforms/wechat-channels/manifest.yaml +0 -124
  60. package/src/browser_workspace_app/platforms/wechat-channels/tests/test_contract.py +0 -36
  61. package/src/browser_workspace_app/platforms/xhs/README.md +0 -10
  62. package/src/browser_workspace_app/platforms/xhs/actions/_manage.py +0 -355
  63. package/src/browser_workspace_app/platforms/xhs/actions/_post.py +0 -281
  64. package/src/browser_workspace_app/platforms/xhs/manifest.yaml +0 -107
  65. package/src/browser_workspace_app/platforms/xhs/tests/test_contract.py +0 -34
  66. package/src/browser_workspace_app/platforms/youtube/README.md +0 -10
  67. package/src/browser_workspace_app/platforms/youtube/actions/_post.py +0 -103
  68. package/src/browser_workspace_app/platforms/youtube/manifest.yaml +0 -94
@@ -1,119 +0,0 @@
1
- import json
2
- import time
3
- from _readiness import wait_until_stable, submission_receipt, prompt_text_matches
4
-
5
-
6
- def composer_selector():
7
- return js("""(() => {
8
- const selectors=['#prompt-textarea','[contenteditable="true"][data-composer-markdown]','[contenteditable="true"][data-lexical-editor="true"]','textarea'];
9
- for (const selector of selectors) {
10
- const e=document.querySelector(selector);
11
- if (!e || e.disabled) continue;
12
- const r=e.getBoundingClientRect();
13
- if (r.width>0 && r.height>0) return selector;
14
- }
15
- return null;
16
- })()""")
17
-
18
-
19
- def composer_text(selector=None):
20
- selector=selector or composer_selector()
21
- if not selector: return ""
22
- return js(f"""(() => {{
23
- const e=document.querySelector({json.dumps(selector)});
24
- if (!e) return '';
25
- return (e.innerText || e.value || e.textContent || '').trim();
26
- }})()""") or ""
27
-
28
-
29
- def send_enabled():
30
- return bool(js("""(() => {
31
- const b=document.querySelector('button[data-testid="send-button"],button[aria-label="Send prompt"],button[aria-label="Send"],form button[type="submit"]');
32
- return !!b && !b.disabled && b.getAttribute('aria-disabled') !== 'true';
33
- })()"""))
34
-
35
-
36
- def is_generating():
37
- return bool(js("""(() => !![...document.querySelectorAll('button')].find(b => {
38
- const a=(b.getAttribute('aria-label')||'').toLowerCase();
39
- const t=(b.textContent||'').trim().toLowerCase();
40
- const id=(b.getAttribute('data-testid')||'').toLowerCase();
41
- return a.includes('stop answering') || a.includes('stop generating') || t === 'stop' || id.includes('stop');
42
- }))()"""))
43
-
44
-
45
- def user_turns():
46
- return js("""(() => {
47
- const legacy=[...document.querySelectorAll('[data-message-author-role="user"]')]
48
- .map(e=>({text:(e.innerText||'').trim(),id:e.getAttribute('data-message-id')||''})).filter(x=>x.text);
49
- if (legacy.length) return legacy;
50
- return [...document.querySelectorAll('h4')]
51
- .filter(e=>(e.innerText||'').trim()==='You said:')
52
- .map((e,index)=>({text:(e.parentElement?.innerText||'').replace(/^You said:\\s*/,'').trim(),id:`dom-user-${index}`}))
53
- .filter(x=>x.text);
54
- })()""") or []
55
-
56
-
57
- def wait_for_idle(timeout=300):
58
- return wait_until_stable(
59
- lambda: {"composer": composer_selector(), "generating": is_generating()},
60
- lambda state: bool(state["composer"]) and not state["generating"],
61
- timeout=timeout,
62
- interval=.25,
63
- stable_samples=3,
64
- phase="ChatGPT turn completion",
65
- )
66
-
67
-
68
- def fill_prompt(prompt):
69
- selector=wait_until_stable(
70
- lambda: composer_selector(),
71
- lambda value: bool(value),
72
- timeout=30,
73
- phase="ChatGPT composer readiness",
74
- )
75
- editable=bool(js(f"""(() => document.querySelector({json.dumps(selector)})?.getAttribute('contenteditable')==='true')()"""))
76
- if editable:
77
- chunks=[prompt[i:i+256] for i in range(0, len(prompt), 256)]
78
- ok=js(f"""(() => {{
79
- const e=document.querySelector({json.dumps(selector)});
80
- if(!e)return false;
81
- e.focus();
82
- const sel=window.getSelection(), range=document.createRange();
83
- range.selectNodeContents(e); sel.removeAllRanges(); sel.addRange(range);
84
- document.execCommand('delete',false,null); sel.removeAllRanges();
85
- for (const chunk of {json.dumps(chunks)}) {{
86
- const current=window.getSelection(), r=document.createRange();
87
- r.selectNodeContents(e); r.collapse(false); current.removeAllRanges(); current.addRange(r);
88
- if(!document.execCommand('insertText',false,chunk)) return false;
89
- }}
90
- e.dispatchEvent(new InputEvent('input',{{bubbles:true,inputType:'insertText',data:{json.dumps(prompt)}}}));
91
- return true;
92
- }})()""")
93
- if not ok: raise RuntimeError("ChatGPT composer rejected lifecycle continuation")
94
- else:
95
- fill_input(selector,prompt,clear_first=True)
96
- wait_until_stable(
97
- lambda: {"text":composer_text(selector),"send":send_enabled()},
98
- lambda state: prompt_text_matches(state["text"],prompt) and state["send"],
99
- timeout=20,
100
- phase="ChatGPT lifecycle continuation readiness",
101
- )
102
- return selector
103
-
104
-
105
- def submit(prompt):
106
- before=len(user_turns())
107
- selector=fill_prompt(prompt)
108
- clicked=js("""(() => {
109
- const b=document.querySelector('button[data-testid="send-button"],button[aria-label="Send prompt"],button[aria-label="Send"],form button[type="submit"]');
110
- if(!b||b.disabled||b.getAttribute('aria-disabled')==='true')return false;
111
- b.click(); return true;
112
- })()""")
113
- if not clicked: raise RuntimeError("ChatGPT send button changed before lifecycle submit")
114
- deadline=time.time()+30
115
- while time.time()<deadline:
116
- receipt=submission_receipt(user_turns(),before,prompt,composer_text(selector))
117
- if receipt: return receipt
118
- time.sleep(.2)
119
- raise RuntimeError("ChatGPT lifecycle submission was not observed")
@@ -1,80 +0,0 @@
1
- import json
2
- import time
3
-
4
- CFG = json.load(open("__CFG_PATH__", encoding="utf-8"))
5
- APP_NAME = str(CFG.get("app_name") or "").strip()
6
- OP = str(CFG.get("operation") or "status").strip()
7
-
8
- if not APP_NAME:
9
- raise ValueError("app_name is required")
10
- if OP not in {"status", "open", "refresh-tools"}:
11
- raise ValueError("operation must be status, open, or refresh-tools")
12
-
13
-
14
- def wait_until(pred, label, timeout=30):
15
- deadline = time.time() + timeout
16
- while time.time() < deadline:
17
- value = pred()
18
- if value:
19
- return value
20
- time.sleep(0.2)
21
- raise RuntimeError(f"{label} not observed")
22
-
23
-
24
- def app_button_exists():
25
- return js(f"""(() => {{
26
- const name={json.dumps(APP_NAME)};
27
- return Array.from(document.querySelectorAll('button,a')).some(el => {{
28
- const text=(el.innerText||'').trim();
29
- return text===name || text.startsWith(name+'\\n');
30
- }});
31
- }})()""")
32
-
33
-
34
- def open_app():
35
- ok = js(f"""(() => {{
36
- const name={json.dumps(APP_NAME)};
37
- const el=Array.from(document.querySelectorAll('button,a')).find(el => {{
38
- const text=(el.innerText||'').trim();
39
- return text===name || text.startsWith(name+'\\n');
40
- }});
41
- if(!el)return false; el.click(); return true;
42
- }})()""")
43
- if not ok:
44
- raise RuntimeError(f"installed ChatGPT plugin not found: {APP_NAME}")
45
- wait_until(lambda: js("document.body?.innerText?.includes('Refresh tools') || document.body?.innerText?.includes('Disconnect')"), "plugin detail controls")
46
-
47
-
48
- def detail_state():
49
- return js("""(() => {
50
- const body=(document.body?.innerText||'');
51
- const texts=Array.from(document.querySelectorAll('button,a')).map(el=>(el.innerText||el.getAttribute('aria-label')||'').trim()).filter(Boolean);
52
- return {
53
- connected:body.includes('Disconnect'),
54
- permissions:body.includes('Permissions'),
55
- refresh_tools:texts.includes('Refresh tools'),
56
- editable:texts.includes('Edit'),
57
- uninstallable:texts.includes('Uninstall'),
58
- url:location.href
59
- };
60
- })()""")
61
-
62
-
63
- goto_url("https://chatgpt.com/settings/plugins-settings")
64
- wait_for_load()
65
- wait_until(lambda: js("document.body?.innerText?.includes('Plugins')"), "ChatGPT Plugins settings")
66
- wait_until(app_button_exists, f"installed plugin {APP_NAME}")
67
- open_app()
68
-
69
- if OP == "refresh-tools":
70
- ok = js("""(() => {
71
- const b=Array.from(document.querySelectorAll('button')).find(x=>(x.innerText||'').trim()==='Refresh tools');
72
- if(!b||b.disabled)return false; b.click(); return true;
73
- })()""")
74
- if not ok:
75
- raise RuntimeError("Refresh tools control is unavailable")
76
- time.sleep(0.5)
77
-
78
- wait_until(lambda: js("document.body?.innerText?.includes('Refresh tools') || document.body?.innerText?.includes('Disconnect')"), "plugin detail hydration")
79
- state = detail_state()
80
- print(json.dumps({"app_name": APP_NAME, "operation": OP, **state}, ensure_ascii=False), flush=True)
@@ -1,85 +0,0 @@
1
- import json
2
- import time
3
- from urllib.parse import quote, urlsplit
4
- import _lifecycle_common as lifecycle_common
5
-
6
- lifecycle_common.js = js
7
- lifecycle_common.fill_input = fill_input
8
- wait_for_idle = lifecycle_common.wait_for_idle
9
- submit = lifecycle_common.submit
10
-
11
- CFG=json.load(open("__CFG_PATH__",encoding="utf-8"))
12
- project_id=str(CFG.get("project_id") or "").strip() or None
13
- message=str(CFG.get("message") or "").strip()
14
- source_thread_id=str(CFG.get("source_thread_id") or "").strip() or None
15
- temporary=bool(CFG.get("temporary", False))
16
-
17
- if not message:
18
- raise RuntimeError("message is required")
19
- if temporary and project_id:
20
- raise RuntimeError("temporary=true cannot be combined with project_id")
21
-
22
- if temporary:
23
- start_url="https://chatgpt.com/?temporary-chat=true"
24
- elif project_id:
25
- start_url=f"https://chatgpt.com/g/{quote(project_id,safe='')}/project"
26
- else:
27
- start_url="https://chatgpt.com/"
28
-
29
- goto_url(start_url)
30
- wait_for_load()
31
- wait_for_idle(timeout=60)
32
-
33
- if temporary:
34
- is_temporary=bool(js("""(() => {
35
- if (location.search.includes('temporary-chat=true')) return true;
36
- return [...document.querySelectorAll('button')].some(b =>
37
- (b.getAttribute('aria-label')||'').toLowerCase().includes('turn off temporary chat') ||
38
- (b.textContent||'').trim().toLowerCase()==='turn off temporary chat'
39
- ) || [...document.querySelectorAll('h1')].some(h => (h.textContent||'').trim().toLowerCase()==='temporary chat');
40
- })()"""))
41
- if not is_temporary:
42
- raise RuntimeError("ChatGPT Temporary Chat mode was not activated")
43
-
44
- receipt=submit(message)
45
-
46
- if temporary:
47
- print(json.dumps({
48
- "status":"submitted",
49
- "mode":"new-thread",
50
- "temporary":True,
51
- "project_id":None,
52
- "source_thread_id":source_thread_id,
53
- "thread_id":None,
54
- "thread_url":None,
55
- "verified_by":receipt["verified_by"],
56
- "user_message_id":receipt["turn"].get("id") or None,
57
- },ensure_ascii=False),flush=True)
58
- else:
59
- deadline=time.time()+60
60
- thread_id=None
61
- thread_url=None
62
- while time.time()<deadline:
63
- current=page_info().get("url","").split("?",1)[0]
64
- path=urlsplit(current).path.rstrip("/")
65
- if "/c/" in path and "local-chatgpt" not in current:
66
- candidate=path.rsplit("/",1)[-1]
67
- if candidate:
68
- thread_id=candidate
69
- thread_url=current
70
- break
71
- time.sleep(.2)
72
- if not thread_id:
73
- raise RuntimeError("ChatGPT did not assign a durable thread id")
74
-
75
- print(json.dumps({
76
- "status":"submitted",
77
- "mode":"new-thread",
78
- "temporary":False,
79
- "project_id":project_id,
80
- "source_thread_id":source_thread_id,
81
- "thread_id":thread_id,
82
- "thread_url":thread_url,
83
- "verified_by":receipt["verified_by"],
84
- "user_message_id":receipt["turn"].get("id") or None,
85
- },ensure_ascii=False),flush=True)
@@ -1,33 +0,0 @@
1
- import json
2
- from urllib.parse import quote, urlsplit
3
- import _lifecycle_common as lifecycle_common
4
-
5
- lifecycle_common.js = js
6
- lifecycle_common.fill_input = fill_input
7
- wait_for_idle = lifecycle_common.wait_for_idle
8
- submit = lifecycle_common.submit
9
-
10
- CFG=json.load(open("__CFG_PATH__",encoding="utf-8"))
11
- project_id=str(CFG.get("project_id") or "").strip() or None
12
- thread_id=str(CFG.get("thread_id") or "").strip()
13
- message=str(CFG.get("message") or "").strip()
14
- if not thread_id: raise RuntimeError("thread_id is required")
15
- if not message: raise RuntimeError("message is required")
16
- if project_id:
17
- thread_url=f"https://chatgpt.com/g/{quote(project_id,safe='')}/c/{quote(thread_id,safe='')}"
18
- else:
19
- thread_url=f"https://chatgpt.com/c/{quote(thread_id,safe='')}"
20
- goto_url(thread_url)
21
- wait_for_load()
22
- if urlsplit(page_info().get("url","")).path.rstrip("/") != urlsplit(thread_url).path.rstrip("/"):
23
- raise RuntimeError("ChatGPT thread redirect mismatch")
24
- wait_for_idle(timeout=int(CFG.get("wait_timeout",300)))
25
- receipt=submit(message)
26
- print(json.dumps({
27
- "status":"submitted",
28
- "mode":"new-turn",
29
- "project_id":project_id,
30
- "thread_id":thread_id,
31
- "verified_by":receipt["verified_by"],
32
- "user_message_id":receipt["turn"].get("id") or None,
33
- },ensure_ascii=False),flush=True)