@pencil-agent/nano-pencil 2.0.0-beta.9 → 2.0.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 (207) hide show
  1. package/README.md +267 -267
  2. package/dist/build-meta.json +3 -3
  3. package/dist/core/export-html/AGENT.md +11 -11
  4. package/dist/core/export-html/template.css +971 -971
  5. package/dist/core/export-html/template.html +54 -54
  6. package/dist/core/extensions-host/index.d.ts +1 -1
  7. package/dist/core/extensions-host/types.d.ts +5 -8
  8. package/dist/extensions/builtin/AGENT.md +115 -115
  9. package/dist/extensions/builtin/browser/AGENT.md +17 -17
  10. package/dist/extensions/builtin/browser/agent-workspace/agent_helpers.py +12 -12
  11. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/amazon/product-search.md +198 -198
  12. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/archive-org/scraping.md +341 -341
  13. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/arxiv/scraping.md +311 -311
  14. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/arxiv-bulk/scraping.md +333 -333
  15. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/atlas/overview.md +70 -70
  16. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/booking-com/scraping.md +578 -578
  17. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/capterra/scraping.md +440 -440
  18. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/centilebrain/generate-estimates.md +110 -110
  19. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/coingecko/scraping.md +325 -325
  20. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/coinmarketcap/scraping.md +463 -463
  21. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/coursera/scraping.md +360 -360
  22. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/craigslist/scraping.md +390 -390
  23. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/crossref/scraping.md +568 -568
  24. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/dev-to/scraping.md +323 -323
  25. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/duckduckgo/scraping.md +349 -349
  26. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/ebay/scraping.md +435 -435
  27. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/etsy/scraping.md +506 -506
  28. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/eventbrite/scraping.md +363 -363
  29. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/expedia/automation.md +168 -168
  30. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/facebook/groups.md +236 -236
  31. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/facebook/pages.md +295 -295
  32. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/framer/editor.md +108 -108
  33. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/fred/scraping.md +493 -493
  34. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/g2/scraping.md +580 -580
  35. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/genius/scraping.md +511 -511
  36. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/github/repo-actions.md +65 -65
  37. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/github/scraping.md +184 -184
  38. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/glassdoor/scraping.md +543 -543
  39. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/gmail/compose.md +122 -122
  40. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/goodreads/scraping.md +461 -461
  41. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/gutenberg/scraping.md +383 -383
  42. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/hackernews/scraping.md +243 -243
  43. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/howlongtobeat/scraping.md +473 -473
  44. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/imdb/scraping.md +271 -271
  45. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/itch-io/scraping.md +436 -436
  46. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/job-boards/indeed-glassdoor.md +1021 -1021
  47. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/letterboxd/scraping.md +349 -349
  48. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/linkedin/invitation-manager.md +109 -109
  49. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/loom/folder-enumeration.md +170 -170
  50. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/macrotrends/scraping.md +537 -537
  51. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/medium/article-hydration.md +120 -120
  52. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/medium/scraping.md +414 -414
  53. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/metacritic/scraping.md +477 -477
  54. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/musicbrainz/scraping.md +478 -478
  55. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/nasa/scraping.md +339 -339
  56. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/news-aggregation/multi-source.md +205 -205
  57. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/open-library/scraping.md +472 -472
  58. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/openalex/scraping.md +470 -470
  59. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/openstreetmap/scraping.md +490 -490
  60. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/package-registries/npm-pypi.md +478 -478
  61. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/polymarket/scraping.md +234 -234
  62. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/producthunt/scraping.md +307 -307
  63. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/pubmed/scraping.md +421 -421
  64. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/quora/scraping.md +364 -364
  65. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/rawg/scraping.md +352 -352
  66. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/reddit/scraping.md +124 -124
  67. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/rest-countries/scraping.md +233 -233
  68. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/sec-edgar/scraping.md +361 -361
  69. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/shopify-admin/README.md +36 -36
  70. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/shopify-admin/embedded-apps.md +72 -72
  71. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/shopify-admin/knowledge-base.md +109 -109
  72. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/shopify-admin/polaris-inputs.md +137 -137
  73. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/soundcloud/scraping.md +362 -362
  74. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/spotify/scraping.md +339 -339
  75. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/stackoverflow/scraping.md +435 -435
  76. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/steam/scraping.md +575 -575
  77. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/substack/scraping.md +338 -338
  78. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/thetechgeeks/pricing.md +52 -52
  79. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/tiktok/upload.md +107 -107
  80. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/tradingview/scraping.md +309 -309
  81. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/trello/boards-and-lists.md +88 -88
  82. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/trustpilot/scraping.md +375 -375
  83. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/walmart/scraping.md +444 -444
  84. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/wayback-machine/scraping.md +306 -306
  85. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/weather/scraping.md +398 -398
  86. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/wellfound/scraping.md +596 -596
  87. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/world-bank/scraping.md +356 -356
  88. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/xiaohongshu/scraping.md +84 -84
  89. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/youtube/scraping.md +418 -418
  90. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/zillow/scraping.md +433 -433
  91. package/dist/extensions/builtin/browser/browser.md +73 -73
  92. package/dist/extensions/builtin/browser/install.md +142 -142
  93. package/dist/extensions/builtin/browser/interaction-skills/connection.md +48 -48
  94. package/dist/extensions/builtin/browser/interaction-skills/cookies.md +3 -3
  95. package/dist/extensions/builtin/browser/interaction-skills/cross-origin-iframes.md +3 -3
  96. package/dist/extensions/builtin/browser/interaction-skills/dialogs.md +64 -64
  97. package/dist/extensions/builtin/browser/interaction-skills/downloads.md +3 -3
  98. package/dist/extensions/builtin/browser/interaction-skills/drag-and-drop.md +3 -3
  99. package/dist/extensions/builtin/browser/interaction-skills/dropdowns.md +3 -3
  100. package/dist/extensions/builtin/browser/interaction-skills/iframes.md +3 -3
  101. package/dist/extensions/builtin/browser/interaction-skills/network-requests.md +3 -3
  102. package/dist/extensions/builtin/browser/interaction-skills/print-as-pdf.md +3 -3
  103. package/dist/extensions/builtin/browser/interaction-skills/profile-sync.md +90 -90
  104. package/dist/extensions/builtin/browser/interaction-skills/screenshots.md +17 -17
  105. package/dist/extensions/builtin/browser/interaction-skills/scrolling.md +3 -3
  106. package/dist/extensions/builtin/browser/interaction-skills/shadow-dom.md +3 -3
  107. package/dist/extensions/builtin/browser/interaction-skills/tabs.md +69 -69
  108. package/dist/extensions/builtin/browser/interaction-skills/uploads.md +1 -1
  109. package/dist/extensions/builtin/browser/interaction-skills/viewport.md +3 -3
  110. package/dist/extensions/builtin/browser/src/browser_harness/AGENT.md +15 -15
  111. package/dist/extensions/builtin/browser/src/browser_harness/__init__.py +8 -8
  112. package/dist/extensions/builtin/browser/src/browser_harness/_ipc.py +90 -90
  113. package/dist/extensions/builtin/browser/src/browser_harness/admin.py +722 -722
  114. package/dist/extensions/builtin/browser/src/browser_harness/daemon.py +328 -328
  115. package/dist/extensions/builtin/browser/src/browser_harness/helpers.py +396 -396
  116. package/dist/extensions/builtin/browser/src/browser_harness/run.py +103 -103
  117. package/dist/extensions/builtin/discipline/skills/brainstorming/SKILL.md +33 -33
  118. package/dist/extensions/builtin/discipline/skills/executing-plans/SKILL.md +25 -25
  119. package/dist/extensions/builtin/discipline/skills/finishing-development-branch/SKILL.md +25 -25
  120. package/dist/extensions/builtin/discipline/skills/receiving-code-review/SKILL.md +22 -22
  121. package/dist/extensions/builtin/discipline/skills/requesting-code-review/SKILL.md +31 -31
  122. package/dist/extensions/builtin/discipline/skills/systematic-debugging/SKILL.md +28 -28
  123. package/dist/extensions/builtin/discipline/skills/test-driven-development/SKILL.md +32 -32
  124. package/dist/extensions/builtin/discipline/skills/using-git-worktrees/SKILL.md +25 -25
  125. package/dist/extensions/builtin/discipline/skills/verification-before-completion/SKILL.md +27 -27
  126. package/dist/extensions/builtin/discipline/skills/writing-plans/SKILL.md +26 -26
  127. package/dist/extensions/builtin/goal/README.md +67 -67
  128. package/dist/extensions/builtin/goal/goal-controller.js +1 -1
  129. package/dist/extensions/builtin/goal/goal-prompts.js +4 -4
  130. package/dist/extensions/builtin/grub/README.md +112 -112
  131. package/dist/extensions/builtin/link-world/agent-workspace/README.md +16 -16
  132. package/dist/extensions/builtin/link-world/internet-search/internet-search.md +65 -65
  133. package/dist/extensions/builtin/link-world/link-world-agent.md +82 -82
  134. package/dist/extensions/builtin/link-world/linkworld.md +313 -313
  135. package/dist/extensions/builtin/link-world/network-routing/network-routing.md +67 -67
  136. package/dist/extensions/builtin/loop/README.md +92 -92
  137. package/dist/extensions/builtin/mcp/figma-design.md +68 -68
  138. package/dist/extensions/builtin/mcp/mcp-management.md +85 -85
  139. package/dist/extensions/builtin/recap/AGENT.md +15 -15
  140. package/dist/extensions/builtin/sal/README.md +72 -72
  141. package/dist/extensions/builtin/security-audit/README.md +289 -289
  142. package/dist/extensions/builtin/team/AGENT.md +112 -112
  143. package/dist/extensions/builtin/team/TESTING.md +299 -299
  144. package/dist/extensions/builtin/token-save/README.md +56 -56
  145. package/dist/extensions/optional/AGENT.md +10 -10
  146. package/dist/index.d.ts +5 -30
  147. package/dist/index.js +1 -1
  148. package/dist/models.d.ts +7 -0
  149. package/dist/models.js +1 -0
  150. package/dist/modes/interactive/theme/dark.json +85 -85
  151. package/dist/modes/interactive/theme/light.json +84 -84
  152. package/dist/modes/interactive/theme/theme-schema.json +335 -335
  153. package/dist/modes/interactive/theme/warm.json +81 -81
  154. package/dist/node_modules/@pencil-agent/ai/dist/cli.js +0 -0
  155. package/dist/packages/protocol/src/flags.d.ts +20 -0
  156. package/dist/packages/protocol/src/flags.js +0 -0
  157. package/dist/packages/protocol/src/hooks.d.ts +17 -0
  158. package/dist/packages/protocol/src/hooks.js +0 -0
  159. package/dist/packages/protocol/src/index.d.ts +4 -2
  160. package/dist/packages/protocol/src/index.js +1 -1
  161. package/dist/packages/protocol/src/lifecycle.d.ts +11 -21
  162. package/dist/public-config.d.ts +12 -0
  163. package/dist/public-config.js +1 -0
  164. package/dist/runtime.d.ts +9 -0
  165. package/dist/runtime.js +1 -0
  166. package/dist/session-compaction.d.ts +7 -0
  167. package/dist/session-compaction.js +1 -0
  168. package/dist/session.d.ts +7 -0
  169. package/dist/session.js +1 -0
  170. package/dist/skills.d.ts +7 -0
  171. package/dist/skills.js +1 -0
  172. package/dist/tools.d.ts +7 -0
  173. package/dist/tools.js +1 -0
  174. 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
  175. package/docs/SDK-TESTING.md +364 -0
  176. package/docs/codex-goal-command-impl.md +1055 -1055
  177. package/docs/codex-goal-vs-grub.md +500 -500
  178. package/docs/custom-provider.md +27 -27
  179. package/docs/extensions.md +27 -27
  180. package/docs/keybindings.md +27 -27
  181. package/docs/loop /351/207/215/346/236/204/345/256/214/346/210/220/346/200/273/347/273/223.md" +250 -250
  182. package/docs/loop /351/207/215/346/236/204/345/256/214/346/210/220/346/212/245/345/221/212.md" +122 -122
  183. package/docs/loop /351/207/215/346/236/204/346/226/271/346/241/210.md" +1222 -1222
  184. 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
  185. 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
  186. package/docs/loop /351/207/215/346/236/204/350/256/241/345/210/222.md" +320 -320
  187. package/docs/loop-usage-examples.md +214 -214
  188. package/docs/mem-core/346/212/200/346/234/257/346/226/207/346/241/243.md +593 -0
  189. package/docs/models.md +27 -27
  190. package/docs/packages.md +27 -27
  191. package/docs/pi-design-philosophy.md +457 -457
  192. package/docs/planmode.md +1987 -1987
  193. package/docs/prompt-templates.md +27 -27
  194. package/docs/providers.md +27 -27
  195. package/docs/sdk.md +27 -27
  196. package/docs/skills.md +27 -27
  197. package/docs/startup-performance-optimization.md +301 -0
  198. package/docs/themes.md +27 -27
  199. package/docs/tui.md +27 -27
  200. package/docs//350/256/244/347/237/245/345/234/260/345/233/276.md +47 -0
  201. package/package.json +190 -162
  202. package/docs/cc-agent-design.md +0 -1297
  203. package/docs/cc-tui-design.md +0 -1333
  204. package/docs/nanoPencil-/345/255/246/344/271/240/350/256/241/345/210/222.md +0 -170
  205. package/docs/scan-report.md +0 -3820
  206. package/docs//345/257/271/346/240/207Claude-Code.md +0 -1775
  207. 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,52 +1,52 @@
1
- # The Tech Geeks AU — Ubiquiti pricing
2
-
3
- `https://thetechgeeks.com` — Shopify Online Store 2.0, AU Ubiquiti reseller. Prices GST-inclusive ("All Prices Include Australian GST At 10%" in footer).
4
-
5
- ## Do this first
6
-
7
- **Hit the `.js` endpoint, not the DOM.** Shopify exposes canonical product JSON — no scraping, no screenshots.
8
-
9
- ```python
10
- import json
11
- d = json.loads(http_get(f"https://thetechgeeks.com/products/{handle}.js"))
12
- # {'title', 'price' (AUD cents — divide by 100), 'available', 'variants', 'compare_at_price', ...}
13
- ```
14
-
15
- Use this for title / SKU / price. One `http_get` replaces `goto + wait_for_load + screenshot + regex`.
16
-
17
- ## Do NOT trust `.js.available` for stock
18
-
19
- Tech Geeks marks many in-stock products `available: false` (backorder / order-from-supplier). Verified counterexample: UDM-Pro-Max `available: true`, U6-LR `available: false` but Add-to-cart live. To know real stock, cross-check the DOM:
20
-
21
- - `document.querySelector('.price--sold-out')` present → truly sold out
22
- - Body text contains "Sold out" (case-insensitive) near the product title → sold out
23
- - `document.querySelector('product-form__submit[disabled]')` → sold out
24
-
25
- Only if `.js.available = false` AND one of the above fires is the product actually unbuyable.
26
-
27
- ## Sold-out pages have junk prices
28
-
29
- Confirmed: UACC-Rack-12U-Wall listed **$3,080 AUD** (real AU street ~$420–$630). The sold-out listing carries stale / data-entry prices that nobody cleans up.
30
-
31
- **Sanity gate before reporting any Tech Geeks price:**
32
-
33
- 1. If `.js.available = false`, treat the price as unverified.
34
- 2. If the price deviates >2× from another AU vendor or the `store.ui.com` MSRP for the same SKU, assume Tech Geeks is wrong — not the other source.
35
- 3. Only in-stock prices should land in a final table.
36
-
37
- ## Finding the right product URL
38
-
39
- Slugs are long marketing titles, not SKUs. Don't guess. Two reliable shortcuts:
40
-
41
- - `https://thetechgeeks.com/search?q=<SKU>` → scrape first `a[href*="/products/"]` link
42
- - Google `site:thetechgeeks.com <SKU>` when the internal search misses
43
-
44
- ## Known gaps in their Ubiquiti catalogue (as of 2026-04)
45
-
46
- - **USP-PDU-Pro** — not stocked (no AU-plug Ubiquiti SKU exists anywhere in AU; region-wide gap, not a Tech Geeks issue)
47
- - **U-Cable-C6-CMP** (plenum Cat6) — only **U-Cable-C6-CMR** (riser) is carried
48
- - `available: false` is common even on items they'll still order in
49
-
50
- ## Don't use a browser for this
51
-
52
- Product pages are static HTML + one JSON endpoint. `http_get` over `asyncio`/`ThreadPoolExecutor` fetches all SKUs in <5s. CDP is wasted here unless you need to click through a cart / checkout flow.
1
+ # The Tech Geeks AU — Ubiquiti pricing
2
+
3
+ `https://thetechgeeks.com` — Shopify Online Store 2.0, AU Ubiquiti reseller. Prices GST-inclusive ("All Prices Include Australian GST At 10%" in footer).
4
+
5
+ ## Do this first
6
+
7
+ **Hit the `.js` endpoint, not the DOM.** Shopify exposes canonical product JSON — no scraping, no screenshots.
8
+
9
+ ```python
10
+ import json
11
+ d = json.loads(http_get(f"https://thetechgeeks.com/products/{handle}.js"))
12
+ # {'title', 'price' (AUD cents — divide by 100), 'available', 'variants', 'compare_at_price', ...}
13
+ ```
14
+
15
+ Use this for title / SKU / price. One `http_get` replaces `goto + wait_for_load + screenshot + regex`.
16
+
17
+ ## Do NOT trust `.js.available` for stock
18
+
19
+ Tech Geeks marks many in-stock products `available: false` (backorder / order-from-supplier). Verified counterexample: UDM-Pro-Max `available: true`, U6-LR `available: false` but Add-to-cart live. To know real stock, cross-check the DOM:
20
+
21
+ - `document.querySelector('.price--sold-out')` present → truly sold out
22
+ - Body text contains "Sold out" (case-insensitive) near the product title → sold out
23
+ - `document.querySelector('product-form__submit[disabled]')` → sold out
24
+
25
+ Only if `.js.available = false` AND one of the above fires is the product actually unbuyable.
26
+
27
+ ## Sold-out pages have junk prices
28
+
29
+ Confirmed: UACC-Rack-12U-Wall listed **$3,080 AUD** (real AU street ~$420–$630). The sold-out listing carries stale / data-entry prices that nobody cleans up.
30
+
31
+ **Sanity gate before reporting any Tech Geeks price:**
32
+
33
+ 1. If `.js.available = false`, treat the price as unverified.
34
+ 2. If the price deviates >2× from another AU vendor or the `store.ui.com` MSRP for the same SKU, assume Tech Geeks is wrong — not the other source.
35
+ 3. Only in-stock prices should land in a final table.
36
+
37
+ ## Finding the right product URL
38
+
39
+ Slugs are long marketing titles, not SKUs. Don't guess. Two reliable shortcuts:
40
+
41
+ - `https://thetechgeeks.com/search?q=<SKU>` → scrape first `a[href*="/products/"]` link
42
+ - Google `site:thetechgeeks.com <SKU>` when the internal search misses
43
+
44
+ ## Known gaps in their Ubiquiti catalogue (as of 2026-04)
45
+
46
+ - **USP-PDU-Pro** — not stocked (no AU-plug Ubiquiti SKU exists anywhere in AU; region-wide gap, not a Tech Geeks issue)
47
+ - **U-Cable-C6-CMP** (plenum Cat6) — only **U-Cable-C6-CMR** (riser) is carried
48
+ - `available: false` is common even on items they'll still order in
49
+
50
+ ## Don't use a browser for this
51
+
52
+ Product pages are static HTML + one JSON endpoint. `http_get` over `asyncio`/`ThreadPoolExecutor` fetches all SKUs in <5s. CDP is wasted here unless you need to click through a cart / checkout flow.
@@ -1,107 +1,107 @@
1
- # TikTok Studio — Upload Video
2
-
3
- URL: `https://www.tiktok.com/tiktokstudio/upload?from=upload&lang=en` (always append `&lang=en`)
4
-
5
- ## Prerequisites
6
-
7
- - Logged into TikTok in the Chrome profile browser-harness is attached to
8
- - Video file on local disk (mp4, <50MB)
9
-
10
- ## Stale draft banner
11
-
12
- TikTok shows "A video you were editing wasn't saved" if a previous upload was abandoned. Dismiss it:
13
-
14
- 1. Find the banner Discard button (y < 300 in the page)
15
- 2. CDP `click_at_xy(x, y)` on it
16
- 3. A confirmation modal appears — find the red Discard button (y > 300) and CDP `click_at_xy(x, y)`
17
- 4. Repeat if multiple stale drafts are stacked
18
-
19
- ## Upload flow
20
-
21
- ### 1. Attach file
22
-
23
- ```python
24
- upload_file('input[type="file"]', "/path/to/video.mp4")
25
- wait(12) # processing takes ~10s for 5-10MB
26
- ```
27
-
28
- ### 2. Caption
29
-
30
- TikTok pre-fills caption with the filename. Clear it first:
31
-
32
- ```python
33
- js("document.querySelector('div[contenteditable=\"true\"][role=\"combobox\"]').focus()")
34
- press_key("End")
35
- for _ in range(25): press_key("Backspace") # clear filename
36
- type_text("your caption here #hashtag1 #hashtag2")
37
- press_key("Escape") # dismiss hashtag suggestions
38
- click_at_xy(700, 50) # click away to deselect
39
- ```
40
-
41
- Verify: `js('document.querySelector(\'div[contenteditable="true"][role="combobox"]\').innerText')`
42
-
43
- ### 3. Schedule
44
-
45
- Click the Schedule radio label:
46
- ```python
47
- js("(()=>{var l=document.querySelectorAll('label');for(var i=0;i<l.length;i++){if(l[i].textContent.trim()==='Schedule'){l[i].click();break}}})()")
48
- ```
49
-
50
- **Time picker** — uses a scroll-wheel list, NOT a native select. Each `scroll(dy=32)` steps +1 unit, `dy=-32` steps -1 unit.
51
-
52
- ```python
53
- # 1. ScrollIntoView and open the time picker
54
- js("...scrollIntoView the time input...")
55
- click_at_xy(time_input_x, time_input_y)
56
-
57
- # 2. Read default time, calculate difference
58
- default_hour, default_min = 13, 5 # from input value
59
- target_hour, target_min = 20, 25
60
-
61
- # 3. Scroll hour column (left, x ≈ 349)
62
- for _ in range(target_hour - default_hour):
63
- scroll(349, dropdown_y, dy=32) # +1 hour per step
64
-
65
- # 4. Scroll minute column (right, x ≈ 437)
66
- for _ in range((target_min - default_min) // 5):
67
- scroll(437, dropdown_y, dy=32) # +5 min per step
68
-
69
- # 5. Close and verify
70
- press_key("Escape")
71
- ```
72
-
73
- **Date picker** — click the date input, then click the target day number span.
74
-
75
- ### 4. AI-generated content disclosure
76
-
77
- Under "Show more" section. Toggle is `[aria-checked]` inside the "AI-generated content" parent.
78
-
79
- ```python
80
- # Expand settings
81
- js("...click 'Show more' span...")
82
- # ScrollIntoView the toggle
83
- js("...scrollIntoView 'ai-generated content' span...")
84
- # Read state and click if false
85
- # A "Turn on" confirmation dialog may appear — click it
86
- ```
87
-
88
- ### 5. Submit
89
-
90
- Scroll the Schedule button into view, then CDP `click_at_xy(x, y)`. After success, page redirects to `/tiktokstudio/content`.
91
-
92
- ```python
93
- js("...scrollIntoView Schedule button (offsetWidth > 100)...")
94
- click_at_xy(button_x, button_y)
95
- wait(6)
96
- assert "content" in page_info()["url"]
97
- ```
98
-
99
- ## Gotchas
100
-
101
- - **JS `.click()` doesn't work on TikTok's time picker items** — must use CDP `click_at_xy(x, y)`
102
- - **Time picker uses virtual scroll** — `scroll(x, y, dy=32)` changes value, NOT regular DOM scroll
103
- - **Caption contenteditable appends on type** — always clear with End + Backspace first, never set innerHTML (breaks React state)
104
- - **beforeunload dialog** blocks navigation if upload is in progress — use `cdp("Page.handleJavaScriptDialog", accept=True)` to dismiss (see `interaction-skills/dialogs.md`)
105
- - **Schedule button text** is "Schedule" only after the Schedule radio is selected (otherwise "Post")
106
- - **"Show more" section** expands the page and pushes the time picker off-viewport — collapse it before adjusting time, expand after
107
- - **Unicode narrow no-break space** (char 8239) appears between time and AM/PM in scheduled post listings — use `.indexOf('12:30')` not exact string match
1
+ # TikTok Studio — Upload Video
2
+
3
+ URL: `https://www.tiktok.com/tiktokstudio/upload?from=upload&lang=en` (always append `&lang=en`)
4
+
5
+ ## Prerequisites
6
+
7
+ - Logged into TikTok in the Chrome profile browser-harness is attached to
8
+ - Video file on local disk (mp4, <50MB)
9
+
10
+ ## Stale draft banner
11
+
12
+ TikTok shows "A video you were editing wasn't saved" if a previous upload was abandoned. Dismiss it:
13
+
14
+ 1. Find the banner Discard button (y < 300 in the page)
15
+ 2. CDP `click_at_xy(x, y)` on it
16
+ 3. A confirmation modal appears — find the red Discard button (y > 300) and CDP `click_at_xy(x, y)`
17
+ 4. Repeat if multiple stale drafts are stacked
18
+
19
+ ## Upload flow
20
+
21
+ ### 1. Attach file
22
+
23
+ ```python
24
+ upload_file('input[type="file"]', "/path/to/video.mp4")
25
+ wait(12) # processing takes ~10s for 5-10MB
26
+ ```
27
+
28
+ ### 2. Caption
29
+
30
+ TikTok pre-fills caption with the filename. Clear it first:
31
+
32
+ ```python
33
+ js("document.querySelector('div[contenteditable=\"true\"][role=\"combobox\"]').focus()")
34
+ press_key("End")
35
+ for _ in range(25): press_key("Backspace") # clear filename
36
+ type_text("your caption here #hashtag1 #hashtag2")
37
+ press_key("Escape") # dismiss hashtag suggestions
38
+ click_at_xy(700, 50) # click away to deselect
39
+ ```
40
+
41
+ Verify: `js('document.querySelector(\'div[contenteditable="true"][role="combobox"]\').innerText')`
42
+
43
+ ### 3. Schedule
44
+
45
+ Click the Schedule radio label:
46
+ ```python
47
+ js("(()=>{var l=document.querySelectorAll('label');for(var i=0;i<l.length;i++){if(l[i].textContent.trim()==='Schedule'){l[i].click();break}}})()")
48
+ ```
49
+
50
+ **Time picker** — uses a scroll-wheel list, NOT a native select. Each `scroll(dy=32)` steps +1 unit, `dy=-32` steps -1 unit.
51
+
52
+ ```python
53
+ # 1. ScrollIntoView and open the time picker
54
+ js("...scrollIntoView the time input...")
55
+ click_at_xy(time_input_x, time_input_y)
56
+
57
+ # 2. Read default time, calculate difference
58
+ default_hour, default_min = 13, 5 # from input value
59
+ target_hour, target_min = 20, 25
60
+
61
+ # 3. Scroll hour column (left, x ≈ 349)
62
+ for _ in range(target_hour - default_hour):
63
+ scroll(349, dropdown_y, dy=32) # +1 hour per step
64
+
65
+ # 4. Scroll minute column (right, x ≈ 437)
66
+ for _ in range((target_min - default_min) // 5):
67
+ scroll(437, dropdown_y, dy=32) # +5 min per step
68
+
69
+ # 5. Close and verify
70
+ press_key("Escape")
71
+ ```
72
+
73
+ **Date picker** — click the date input, then click the target day number span.
74
+
75
+ ### 4. AI-generated content disclosure
76
+
77
+ Under "Show more" section. Toggle is `[aria-checked]` inside the "AI-generated content" parent.
78
+
79
+ ```python
80
+ # Expand settings
81
+ js("...click 'Show more' span...")
82
+ # ScrollIntoView the toggle
83
+ js("...scrollIntoView 'ai-generated content' span...")
84
+ # Read state and click if false
85
+ # A "Turn on" confirmation dialog may appear — click it
86
+ ```
87
+
88
+ ### 5. Submit
89
+
90
+ Scroll the Schedule button into view, then CDP `click_at_xy(x, y)`. After success, page redirects to `/tiktokstudio/content`.
91
+
92
+ ```python
93
+ js("...scrollIntoView Schedule button (offsetWidth > 100)...")
94
+ click_at_xy(button_x, button_y)
95
+ wait(6)
96
+ assert "content" in page_info()["url"]
97
+ ```
98
+
99
+ ## Gotchas
100
+
101
+ - **JS `.click()` doesn't work on TikTok's time picker items** — must use CDP `click_at_xy(x, y)`
102
+ - **Time picker uses virtual scroll** — `scroll(x, y, dy=32)` changes value, NOT regular DOM scroll
103
+ - **Caption contenteditable appends on type** — always clear with End + Backspace first, never set innerHTML (breaks React state)
104
+ - **beforeunload dialog** blocks navigation if upload is in progress — use `cdp("Page.handleJavaScriptDialog", accept=True)` to dismiss (see `interaction-skills/dialogs.md`)
105
+ - **Schedule button text** is "Schedule" only after the Schedule radio is selected (otherwise "Post")
106
+ - **"Show more" section** expands the page and pushes the time picker off-viewport — collapse it before adjusting time, expand after
107
+ - **Unicode narrow no-break space** (char 8239) appears between time and AM/PM in scheduled post listings — use `.indexOf('12:30')` not exact string match