@pencil-agent/nano-pencil 2.0.1 → 2.0.2
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/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/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
package/dist/extensions/builtin/browser/agent-workspace/domain-skills/thetechgeeks/pricing.md
CHANGED
|
@@ -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
|