@pencil-agent/nano-pencil 2.0.1 → 2.0.3
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 +267 -267
- package/dist/build-meta.json +3 -3
- package/dist/core/export-html/AGENT.md +11 -11
- package/dist/core/export-html/template.css +971 -971
- package/dist/core/export-html/template.html +54 -54
- package/dist/core/model/custom-providers.js +1 -1
- package/dist/core/model-registry.js +5 -5
- package/dist/extensions/builtin/AGENT.md +115 -115
- package/dist/extensions/builtin/browser/AGENT.md +17 -17
- package/dist/extensions/builtin/browser/agent-workspace/agent_helpers.py +12 -12
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/amazon/product-search.md +198 -198
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/archive-org/scraping.md +341 -341
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/arxiv/scraping.md +311 -311
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/arxiv-bulk/scraping.md +333 -333
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/atlas/overview.md +70 -70
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/booking-com/scraping.md +578 -578
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/capterra/scraping.md +440 -440
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/centilebrain/generate-estimates.md +110 -110
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/coingecko/scraping.md +325 -325
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/coinmarketcap/scraping.md +463 -463
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/coursera/scraping.md +360 -360
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/craigslist/scraping.md +390 -390
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/crossref/scraping.md +568 -568
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/dev-to/scraping.md +323 -323
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/duckduckgo/scraping.md +349 -349
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/ebay/scraping.md +435 -435
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/etsy/scraping.md +506 -506
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/eventbrite/scraping.md +363 -363
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/expedia/automation.md +168 -168
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/facebook/groups.md +236 -236
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/facebook/pages.md +295 -295
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/framer/editor.md +108 -108
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/fred/scraping.md +493 -493
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/g2/scraping.md +580 -580
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/genius/scraping.md +511 -511
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/github/repo-actions.md +65 -65
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/github/scraping.md +184 -184
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/glassdoor/scraping.md +543 -543
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/gmail/compose.md +122 -122
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/goodreads/scraping.md +461 -461
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/gutenberg/scraping.md +383 -383
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/hackernews/scraping.md +243 -243
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/howlongtobeat/scraping.md +473 -473
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/imdb/scraping.md +271 -271
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/itch-io/scraping.md +436 -436
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/job-boards/indeed-glassdoor.md +1021 -1021
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/letterboxd/scraping.md +349 -349
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/linkedin/invitation-manager.md +109 -109
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/loom/folder-enumeration.md +170 -170
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/macrotrends/scraping.md +537 -537
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/medium/article-hydration.md +120 -120
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/medium/scraping.md +414 -414
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/metacritic/scraping.md +477 -477
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/musicbrainz/scraping.md +478 -478
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/nasa/scraping.md +339 -339
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/news-aggregation/multi-source.md +205 -205
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/open-library/scraping.md +472 -472
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/openalex/scraping.md +470 -470
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/openstreetmap/scraping.md +490 -490
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/package-registries/npm-pypi.md +478 -478
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/polymarket/scraping.md +234 -234
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/producthunt/scraping.md +307 -307
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/pubmed/scraping.md +421 -421
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/quora/scraping.md +364 -364
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/rawg/scraping.md +352 -352
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/reddit/scraping.md +124 -124
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/rest-countries/scraping.md +233 -233
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/sec-edgar/scraping.md +361 -361
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/shopify-admin/README.md +36 -36
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/shopify-admin/embedded-apps.md +72 -72
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/shopify-admin/knowledge-base.md +109 -109
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/shopify-admin/polaris-inputs.md +137 -137
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/soundcloud/scraping.md +362 -362
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/spotify/scraping.md +339 -339
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/stackoverflow/scraping.md +435 -435
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/steam/scraping.md +575 -575
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/substack/scraping.md +338 -338
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/thetechgeeks/pricing.md +52 -52
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/tiktok/upload.md +107 -107
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/tradingview/scraping.md +309 -309
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/trello/boards-and-lists.md +88 -88
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/trustpilot/scraping.md +375 -375
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/walmart/scraping.md +444 -444
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/wayback-machine/scraping.md +306 -306
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/weather/scraping.md +398 -398
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/wellfound/scraping.md +596 -596
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/world-bank/scraping.md +356 -356
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/xiaohongshu/scraping.md +84 -84
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/youtube/scraping.md +418 -418
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/zillow/scraping.md +433 -433
- package/dist/extensions/builtin/browser/browser.md +73 -73
- package/dist/extensions/builtin/browser/install.md +142 -142
- package/dist/extensions/builtin/browser/interaction-skills/connection.md +48 -48
- package/dist/extensions/builtin/browser/interaction-skills/cookies.md +3 -3
- package/dist/extensions/builtin/browser/interaction-skills/cross-origin-iframes.md +3 -3
- package/dist/extensions/builtin/browser/interaction-skills/dialogs.md +64 -64
- package/dist/extensions/builtin/browser/interaction-skills/downloads.md +3 -3
- package/dist/extensions/builtin/browser/interaction-skills/drag-and-drop.md +3 -3
- package/dist/extensions/builtin/browser/interaction-skills/dropdowns.md +3 -3
- package/dist/extensions/builtin/browser/interaction-skills/iframes.md +3 -3
- package/dist/extensions/builtin/browser/interaction-skills/network-requests.md +3 -3
- package/dist/extensions/builtin/browser/interaction-skills/print-as-pdf.md +3 -3
- package/dist/extensions/builtin/browser/interaction-skills/profile-sync.md +90 -90
- package/dist/extensions/builtin/browser/interaction-skills/screenshots.md +17 -17
- package/dist/extensions/builtin/browser/interaction-skills/scrolling.md +3 -3
- package/dist/extensions/builtin/browser/interaction-skills/shadow-dom.md +3 -3
- package/dist/extensions/builtin/browser/interaction-skills/tabs.md +69 -69
- package/dist/extensions/builtin/browser/interaction-skills/uploads.md +1 -1
- package/dist/extensions/builtin/browser/interaction-skills/viewport.md +3 -3
- package/dist/extensions/builtin/browser/src/browser_harness/AGENT.md +15 -15
- package/dist/extensions/builtin/browser/src/browser_harness/__init__.py +8 -8
- package/dist/extensions/builtin/browser/src/browser_harness/_ipc.py +90 -90
- package/dist/extensions/builtin/browser/src/browser_harness/admin.py +722 -722
- package/dist/extensions/builtin/browser/src/browser_harness/daemon.py +328 -328
- package/dist/extensions/builtin/browser/src/browser_harness/helpers.py +396 -396
- package/dist/extensions/builtin/browser/src/browser_harness/run.py +103 -103
- package/dist/extensions/builtin/debug/index.js +9 -9
- package/dist/extensions/builtin/discipline/skills/brainstorming/SKILL.md +33 -33
- package/dist/extensions/builtin/discipline/skills/executing-plans/SKILL.md +25 -25
- package/dist/extensions/builtin/discipline/skills/finishing-development-branch/SKILL.md +25 -25
- package/dist/extensions/builtin/discipline/skills/receiving-code-review/SKILL.md +22 -22
- package/dist/extensions/builtin/discipline/skills/requesting-code-review/SKILL.md +31 -31
- package/dist/extensions/builtin/discipline/skills/systematic-debugging/SKILL.md +28 -28
- package/dist/extensions/builtin/discipline/skills/test-driven-development/SKILL.md +32 -32
- package/dist/extensions/builtin/discipline/skills/using-git-worktrees/SKILL.md +25 -25
- package/dist/extensions/builtin/discipline/skills/verification-before-completion/SKILL.md +27 -27
- package/dist/extensions/builtin/discipline/skills/writing-plans/SKILL.md +26 -26
- package/dist/extensions/builtin/goal/README.md +67 -67
- package/dist/extensions/builtin/goal/index.js +6 -6
- package/dist/extensions/builtin/grub/README.md +112 -112
- package/dist/extensions/builtin/link-world/agent-workspace/README.md +16 -16
- package/dist/extensions/builtin/link-world/internet-search/internet-search.md +65 -65
- package/dist/extensions/builtin/link-world/link-world-agent.md +82 -82
- package/dist/extensions/builtin/link-world/linkworld.md +313 -313
- package/dist/extensions/builtin/link-world/network-routing/network-routing.md +67 -67
- package/dist/extensions/builtin/loop/README.md +92 -92
- package/dist/extensions/builtin/mcp/figma-design.md +68 -68
- package/dist/extensions/builtin/mcp/mcp-management.md +85 -85
- package/dist/extensions/builtin/recap/AGENT.md +15 -15
- package/dist/extensions/builtin/sal/README.md +72 -72
- package/dist/extensions/builtin/security-audit/README.md +289 -289
- package/dist/extensions/builtin/team/AGENT.md +112 -112
- package/dist/extensions/builtin/team/TESTING.md +299 -299
- package/dist/extensions/builtin/token-save/README.md +56 -56
- package/dist/extensions/optional/AGENT.md +10 -10
- package/dist/modes/interactive/controllers/input-submit-controller.js +2 -2
- package/dist/modes/interactive/controllers/stream-render-controller.js +2 -2
- package/dist/modes/interactive/interactive-mode.js +19 -19
- package/dist/modes/interactive/theme/dark.json +85 -85
- package/dist/modes/interactive/theme/light.json +84 -84
- package/dist/modes/interactive/theme/theme-schema.json +335 -335
- package/dist/modes/interactive/theme/warm.json +81 -81
- package/dist/node_modules/@pencil-agent/ai/dist/cli.js +0 -0
- package/dist/node_modules/@pencil-agent/ai/dist/models.generated.js +1 -1
- package/docs/ACP/345/215/217/350/256/256/351/233/206/346/210/220/345/274/200/345/217/221/346/226/207/346/241/243.md +851 -0
- package/docs/SDK-TESTING.md +364 -0
- package/docs/codex-goal-command-impl.md +1055 -1055
- package/docs/codex-goal-vs-grub.md +500 -500
- package/docs/custom-provider.md +27 -27
- package/docs/extensions.md +27 -27
- package/docs/keybindings.md +27 -27
- package/docs/loop /351/207/215/346/236/204/345/256/214/346/210/220/346/200/273/347/273/223.md" +250 -250
- package/docs/loop /351/207/215/346/236/204/345/256/214/346/210/220/346/212/245/345/221/212.md" +122 -122
- package/docs/loop /351/207/215/346/236/204/346/226/271/346/241/210.md" +1222 -1222
- package/docs/loop /351/207/215/346/236/204/346/226/271/346/241/210/345/256/236/347/216/260/346/212/245/345/221/212.md" +158 -158
- package/docs/loop /351/207/215/346/236/204/346/226/271/346/241/210/345/257/271/346/257/224/345/210/206/346/236/220.md" +128 -128
- package/docs/loop /351/207/215/346/236/204/350/256/241/345/210/222.md" +320 -320
- package/docs/loop-usage-examples.md +214 -214
- package/docs/mem-core/346/212/200/346/234/257/346/226/207/346/241/243.md +593 -0
- package/docs/models.md +27 -27
- package/docs/packages.md +27 -27
- package/docs/pi-design-philosophy.md +457 -457
- package/docs/planmode.md +1987 -1987
- package/docs/prompt-templates.md +27 -27
- package/docs/providers.md +27 -27
- package/docs/sdk.md +27 -27
- package/docs/skills.md +27 -27
- package/docs/startup-performance-optimization.md +301 -0
- package/docs/themes.md +27 -27
- package/docs/tui.md +27 -27
- package/docs//350/256/244/347/237/245/345/234/260/345/233/276.md +47 -0
- package/package.json +190 -190
- package/docs/cc-agent-design.md +0 -1297
- package/docs/cc-tui-design.md +0 -1333
- package/docs/nanoPencil-/345/255/246/344/271/240/350/256/241/345/210/222.md +0 -170
- package/docs/scan-report.md +0 -3820
- package/docs//345/257/271/346/240/207Claude-Code.md +0 -1775
- package/docs//351/230/277/351/207/214/345/267/264/345/267/264/350/264/242/346/212/245/345/210/206/346/236/220/344/271/246.md +0 -261
|
@@ -1,90 +1,90 @@
|
|
|
1
|
-
"""Daemon IPC plumbing. AF_UNIX socket on POSIX, TCP loopback on Windows.
|
|
2
|
-
|
|
3
|
-
[WHO]: Provides socket path, process spawn, connect(), serve(), and endpoint cleanup helpers
|
|
4
|
-
[FROM]: Depends on asyncio, socket, subprocess, tempfile, pathlib for cross-platform daemon IPC
|
|
5
|
-
[TO]: Consumed by browser_harness.daemon, browser_harness.helpers, and browser_harness.admin
|
|
6
|
-
[HERE]: extensions/builtin/browser/src/browser_harness/_ipc.py within vendored Browser Harness package
|
|
7
|
-
"""
|
|
8
|
-
import asyncio, os, re, socket, subprocess, sys, tempfile
|
|
9
|
-
from pathlib import Path
|
|
10
|
-
|
|
11
|
-
IS_WINDOWS = sys.platform == "win32"
|
|
12
|
-
# BH_TMP_DIR set → caller-isolated dir, bare filenames (avoids AF_UNIX sun_path
|
|
13
|
-
# overrun: 104 macOS / 108 Linux). Unset → shared tmpdir, "bu-<NAME>" prefix
|
|
14
|
-
# disambiguates daemons. POSIX default is /tmp (gettempdir() returns long
|
|
15
|
-
# /var/folders/... on macOS); Windows uses TCP so any tempdir is fine.
|
|
16
|
-
BH_TMP_DIR = os.environ.get("BH_TMP_DIR")
|
|
17
|
-
_TMP = Path(BH_TMP_DIR or (tempfile.gettempdir() if IS_WINDOWS else "/tmp"))
|
|
18
|
-
_TMP.mkdir(parents=True, exist_ok=True)
|
|
19
|
-
_NAME_RE = re.compile(r"\A[A-Za-z0-9_-]{1,64}\Z")
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def _check(name): # path-traversal guard for BU_NAME
|
|
23
|
-
if not _NAME_RE.match(name or ""):
|
|
24
|
-
raise ValueError(f"invalid BU_NAME {name!r}: must match [A-Za-z0-9_-]{{1,64}}")
|
|
25
|
-
return name
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def _stem(name): # "bu" when BH_TMP_DIR isolates us, else "bu-<NAME>"
|
|
29
|
-
_check(name)
|
|
30
|
-
return "bu" if BH_TMP_DIR else f"bu-{name}"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def log_path(name): return _TMP / f"{_stem(name)}.log"
|
|
34
|
-
def pid_path(name): return _TMP / f"{_stem(name)}.pid"
|
|
35
|
-
def port_path(name): return _TMP / f"{_stem(name)}.port" # Windows-only: holds the daemon's TCP port
|
|
36
|
-
def _sock_path(name): return _TMP / f"{_stem(name)}.sock"
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
def sock_addr(name): # display-only, used in log lines
|
|
40
|
-
if not IS_WINDOWS: return str(_sock_path(name))
|
|
41
|
-
try: return f"127.0.0.1:{port_path(name).read_text().strip()}"
|
|
42
|
-
except FileNotFoundError: return f"tcp:{_stem(name)}"
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
def spawn_kwargs(): # subprocess.Popen flags so the daemon detaches from this terminal
|
|
46
|
-
if IS_WINDOWS:
|
|
47
|
-
# CREATE_NO_WINDOW: no console window for the daemon. CREATE_NEW_PROCESS_GROUP:
|
|
48
|
-
# daemon doesn't receive Ctrl-C/Ctrl-Break sent to the parent terminal, so
|
|
49
|
-
# closing that terminal doesn't kill it. DETACHED_PROCESS is intentionally
|
|
50
|
-
# omitted: per Win32 docs it overrides CREATE_NO_WINDOW, causing Windows to
|
|
51
|
-
# allocate a fresh console for the (still console-subsystem) python.exe.
|
|
52
|
-
return {"creationflags": subprocess.CREATE_NEW_PROCESS_GROUP | subprocess.CREATE_NO_WINDOW}
|
|
53
|
-
return {"start_new_session": True}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def connect(name, timeout=1.0):
|
|
57
|
-
"""Blocking client. Raises FileNotFoundError if no daemon, TimeoutError on connect timeout."""
|
|
58
|
-
if not IS_WINDOWS:
|
|
59
|
-
# uv-Python on Windows lacks socket.AF_UNIX, so this branch must be gated.
|
|
60
|
-
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
61
|
-
s.settimeout(timeout); s.connect(str(_sock_path(name))); return s
|
|
62
|
-
try: port = int(port_path(name).read_text().strip())
|
|
63
|
-
except (FileNotFoundError, ValueError): raise FileNotFoundError(str(port_path(name)))
|
|
64
|
-
s = socket.create_connection(("127.0.0.1", port), timeout=timeout)
|
|
65
|
-
s.settimeout(timeout); return s
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
async def serve(name, handler):
|
|
69
|
-
"""Run the server until cancelled. handler(reader, writer) sees the same interface either way."""
|
|
70
|
-
if not IS_WINDOWS:
|
|
71
|
-
path = str(_sock_path(name))
|
|
72
|
-
if os.path.exists(path): os.unlink(path)
|
|
73
|
-
server = await asyncio.start_unix_server(handler, path=path)
|
|
74
|
-
os.chmod(path, 0o600)
|
|
75
|
-
async with server: await asyncio.Event().wait()
|
|
76
|
-
return
|
|
77
|
-
server = await asyncio.start_server(handler, "127.0.0.1", 0)
|
|
78
|
-
pf = port_path(name)
|
|
79
|
-
pf.write_text(str(server.sockets[0].getsockname()[1])) # so clients can find us
|
|
80
|
-
try:
|
|
81
|
-
async with server: await asyncio.Event().wait()
|
|
82
|
-
finally:
|
|
83
|
-
try: pf.unlink()
|
|
84
|
-
except FileNotFoundError: pass
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
def cleanup_endpoint(name): # best-effort; silent if already gone
|
|
88
|
-
p = _sock_path(name) if not IS_WINDOWS else port_path(name)
|
|
89
|
-
try: p.unlink()
|
|
90
|
-
except FileNotFoundError: pass
|
|
1
|
+
"""Daemon IPC plumbing. AF_UNIX socket on POSIX, TCP loopback on Windows.
|
|
2
|
+
|
|
3
|
+
[WHO]: Provides socket path, process spawn, connect(), serve(), and endpoint cleanup helpers
|
|
4
|
+
[FROM]: Depends on asyncio, socket, subprocess, tempfile, pathlib for cross-platform daemon IPC
|
|
5
|
+
[TO]: Consumed by browser_harness.daemon, browser_harness.helpers, and browser_harness.admin
|
|
6
|
+
[HERE]: extensions/builtin/browser/src/browser_harness/_ipc.py within vendored Browser Harness package
|
|
7
|
+
"""
|
|
8
|
+
import asyncio, os, re, socket, subprocess, sys, tempfile
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
IS_WINDOWS = sys.platform == "win32"
|
|
12
|
+
# BH_TMP_DIR set → caller-isolated dir, bare filenames (avoids AF_UNIX sun_path
|
|
13
|
+
# overrun: 104 macOS / 108 Linux). Unset → shared tmpdir, "bu-<NAME>" prefix
|
|
14
|
+
# disambiguates daemons. POSIX default is /tmp (gettempdir() returns long
|
|
15
|
+
# /var/folders/... on macOS); Windows uses TCP so any tempdir is fine.
|
|
16
|
+
BH_TMP_DIR = os.environ.get("BH_TMP_DIR")
|
|
17
|
+
_TMP = Path(BH_TMP_DIR or (tempfile.gettempdir() if IS_WINDOWS else "/tmp"))
|
|
18
|
+
_TMP.mkdir(parents=True, exist_ok=True)
|
|
19
|
+
_NAME_RE = re.compile(r"\A[A-Za-z0-9_-]{1,64}\Z")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _check(name): # path-traversal guard for BU_NAME
|
|
23
|
+
if not _NAME_RE.match(name or ""):
|
|
24
|
+
raise ValueError(f"invalid BU_NAME {name!r}: must match [A-Za-z0-9_-]{{1,64}}")
|
|
25
|
+
return name
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _stem(name): # "bu" when BH_TMP_DIR isolates us, else "bu-<NAME>"
|
|
29
|
+
_check(name)
|
|
30
|
+
return "bu" if BH_TMP_DIR else f"bu-{name}"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def log_path(name): return _TMP / f"{_stem(name)}.log"
|
|
34
|
+
def pid_path(name): return _TMP / f"{_stem(name)}.pid"
|
|
35
|
+
def port_path(name): return _TMP / f"{_stem(name)}.port" # Windows-only: holds the daemon's TCP port
|
|
36
|
+
def _sock_path(name): return _TMP / f"{_stem(name)}.sock"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def sock_addr(name): # display-only, used in log lines
|
|
40
|
+
if not IS_WINDOWS: return str(_sock_path(name))
|
|
41
|
+
try: return f"127.0.0.1:{port_path(name).read_text().strip()}"
|
|
42
|
+
except FileNotFoundError: return f"tcp:{_stem(name)}"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def spawn_kwargs(): # subprocess.Popen flags so the daemon detaches from this terminal
|
|
46
|
+
if IS_WINDOWS:
|
|
47
|
+
# CREATE_NO_WINDOW: no console window for the daemon. CREATE_NEW_PROCESS_GROUP:
|
|
48
|
+
# daemon doesn't receive Ctrl-C/Ctrl-Break sent to the parent terminal, so
|
|
49
|
+
# closing that terminal doesn't kill it. DETACHED_PROCESS is intentionally
|
|
50
|
+
# omitted: per Win32 docs it overrides CREATE_NO_WINDOW, causing Windows to
|
|
51
|
+
# allocate a fresh console for the (still console-subsystem) python.exe.
|
|
52
|
+
return {"creationflags": subprocess.CREATE_NEW_PROCESS_GROUP | subprocess.CREATE_NO_WINDOW}
|
|
53
|
+
return {"start_new_session": True}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def connect(name, timeout=1.0):
|
|
57
|
+
"""Blocking client. Raises FileNotFoundError if no daemon, TimeoutError on connect timeout."""
|
|
58
|
+
if not IS_WINDOWS:
|
|
59
|
+
# uv-Python on Windows lacks socket.AF_UNIX, so this branch must be gated.
|
|
60
|
+
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
61
|
+
s.settimeout(timeout); s.connect(str(_sock_path(name))); return s
|
|
62
|
+
try: port = int(port_path(name).read_text().strip())
|
|
63
|
+
except (FileNotFoundError, ValueError): raise FileNotFoundError(str(port_path(name)))
|
|
64
|
+
s = socket.create_connection(("127.0.0.1", port), timeout=timeout)
|
|
65
|
+
s.settimeout(timeout); return s
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
async def serve(name, handler):
|
|
69
|
+
"""Run the server until cancelled. handler(reader, writer) sees the same interface either way."""
|
|
70
|
+
if not IS_WINDOWS:
|
|
71
|
+
path = str(_sock_path(name))
|
|
72
|
+
if os.path.exists(path): os.unlink(path)
|
|
73
|
+
server = await asyncio.start_unix_server(handler, path=path)
|
|
74
|
+
os.chmod(path, 0o600)
|
|
75
|
+
async with server: await asyncio.Event().wait()
|
|
76
|
+
return
|
|
77
|
+
server = await asyncio.start_server(handler, "127.0.0.1", 0)
|
|
78
|
+
pf = port_path(name)
|
|
79
|
+
pf.write_text(str(server.sockets[0].getsockname()[1])) # so clients can find us
|
|
80
|
+
try:
|
|
81
|
+
async with server: await asyncio.Event().wait()
|
|
82
|
+
finally:
|
|
83
|
+
try: pf.unlink()
|
|
84
|
+
except FileNotFoundError: pass
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def cleanup_endpoint(name): # best-effort; silent if already gone
|
|
88
|
+
p = _sock_path(name) if not IS_WINDOWS else port_path(name)
|
|
89
|
+
try: p.unlink()
|
|
90
|
+
except FileNotFoundError: pass
|