sweetlink
v0.1.0
Published
Controlled-browser automation CLI for SweetLink sessions.
Downloads
324
Maintainers
Readme
SweetLink 🍭
SweetLink is the agent-ready way to "connect your agent to your web app. Like Playwright, but it works in your current tab. Close the loop." It drives a real browser session through the SweetLink daemon so you can authenticate, capture screenshots, run smoke tests, and gather DevTools telemetry without wiring up a headless automation stack.
Note: This README is the canonical copy. When updating docs for the public
steipete/sweetlinkrepository, edit here first and then sync the changes.
Features
- Session management – list active sessions, inspect console/network buffers, and reconnect after hot reloads.
- Controlled Chrome launch – spin up a DevTools-enabled browser, sync cookies from your main profile, and auto-approve the Twitter OAuth consent flow.
- Smoke tests – sweep configurable route presets (dashboard, reports, search, billing, settings) and flag authentication or runtime errors.
- Screenshots & selectors – capture JPEGs via Puppeteer/HTML renderers and discover DOM selectors for automation.
- DevTools telemetry – stream console/network logs to disk, dump diagnostics when a session fails to register, call Next.js DevTools (
/_next/mcp) for structured error summaries, and click the OAuth authorize button on demand.
Prerequisites
- Node.js 22+
pnpm(managed via Corepack)- TLS requirements:
brew install mkcert nss - SweetLink daemon (
apps/sweetlink/daemon) running locally or viapnpm sweetlink:daemon - Trust the local certificate once:
pnpm sweetlink trust-ca
Installation
pnpm install
pnpm run buildWorking from the Sweetistics monorepo? Follow the workspace-specific guide in
docs/cli/sweetlink.mdinstead of this README.
Usage
pnpm sweetlink --helpCommon workflows:
pnpm sweetlink open --controlled --path /dashboard– launch/reuse the controlled Chrome window.pnpm sweetlink open --url http://localhost:4100/dashboard– target a non-default host/port for one-off runs.pnpm sweetlink sessions– view active sessions (codename, heartbeat, socket state, buffered console errors).pnpm sweetlink smoke --routes main– sweep the configured dashboard/search/settings routes.pnpm sweetlink devtools authorize– force-click the OAuth consent button when Twitter prompts.
When a session fails to register, the CLI now emits a DevTools snapshot and Puppeteer scrape (overlay/body text) so build/runtime errors surface immediately.
Timeouts & readiness
Every sweetlink open waits until one of three things happens:
- The controlled tab re-registers with the daemon (happy path).
- The page reports it has finished loading but SweetLink still isn’t online, in which case we dump DevTools + Next.js diagnostics immediately.
- The command exceeds
--timeoutseconds (default45) and bails with the same diagnostics. Increase--timeout <seconds>if you intentionally run on a slow dev stack.
Because the readiness check follows the page rather than a fixed delay, healthy sessions attach quickly while failures are surfaced within the configured timeout window.
Next.js DevTools errors
If the target app exposes the Next.js MCP endpoint (the default for pnpm next dev), SweetLink automatically calls /_next/mcp → get_errors whenever a navigation looks suspicious. The CLI prints the same markdown summary you would see in the Next.js overlay—complete with source-mapped stack traces—then falls back to the existing overlay/Puppeteer scraping for non-Next projects. No extra configuration is required as long as next-devtools is registered in config/mcporter.json (it is by default in the Sweetistics repo).
TLS onboarding
SweetLink defaults to https://localhost:4455 for daemon traffic. Run pnpm sweetlink trust-ca once per machine to install the mkcert certificate authority, then open https://localhost:4455 in the browser profile you plan to automate and accept the prompt. The demo app surfaces a “Daemon TLS” banner—if it shows “not trusted,” click “Open Daemon Certificate,” accept the warning, then hit “Retry Check” before enabling SweetLink.
Heads-up: the daemon reuses the same certificate/key stored in ~/.sweetlink/certs, but browsers remember trust decisions per profile. The first time a profile (or a remote-debugging instance) hits https://localhost:4455, expect one warning screen—accept it once and the profile stays trusted.
Architecture
SweetLink consists of two cooperating pieces:
- CLI – a Node.js client that parses your commands (
open,smoke,sessions, etc.), readssweetlink.json, and establishes a control session with your browser. - Daemon – a long-lived service (
pnpm sweetlink:daemon) that launches or attaches to a DevTools-enabled Chrome instance, forwards console/network telemetry, and executes remote evaluations on behalf of the CLI.
The typical flow looks like this:
- You start the daemon once per workstation. It spins up (or reconnects to) Chromium with the remote debugging port exposed and registers a secure WebSocket endpoint.
- Running
pnpm sweetlink open --controlledprompts the CLI to locatesweetlink.json, resolve runtime defaults (hosts, smoke routes, OAuth automation scripts), and request a session token from the daemon using your admin key. - The daemon launches the controlled browser window (or reuses the existing one), hydrates it with cookies from your configured
cookieMappings, and signals the CLI when the target page is healthy (healthChecks.paths+ optionalserverschecks). - Commands like
sweetlink smokeorsweetlink devtools authorizestream instructions to the daemon. The daemon executes them via DevTools Protocol or Puppeteer, shipping back console output, screenshots, and failure diagnostics in real time. - When the CLI exits, the daemon keeps the browser alive so the next command can reuse the authenticated context; run
pnpm sweetlink sessionsto inspect or detach lingering sessions.
Because the CLI and daemon communicate over secure WebSockets, you can run the daemon locally or on a remote VM. Set daemonUrl in sweetlink.json (or SWEETLINK_DAEMON_URL) to tunnel to the remote instance, while keeping the same CLI workflows.
Configuration
Generic usage
SweetLink resolves defaults from (highest priority first):
- CLI flags (e.g.
--url,--app-url,--daemon-url,--port) sweetlink.json(orsweetlink.config.json) located in or above the current working directory (SweetLink walks up parent directories until it finds one)- Environment variables (
SWEETLINK_APP_URL,SWEETLINK_DAEMON_URL,SWEETLINK_PROD_URL) - Fallback
http://localhost:3000
Start by copying sweetlink.example.json from the repo root. It ships with a neutral baseline config:
{
"appUrl": "http://localhost:4100",
"prodUrl": "https://demo.acme.app",
"daemonUrl": "https://localhost:4455",
"port": 4100,
"healthChecks": {
"paths": ["/api/health"]
},
"cookieMappings": [
{
"hosts": ["example.dev", "*.example.dev", "localhost", "127.0.0.1"],
"origins": [
"https://app.example.dev",
"https://auth.example.dev",
"https://api.example.dev"
]
}
]
}Place the config file in your project root (or any parent directory). With the file in place, pnpm sweetlink open --controlled --foreground will automatically point at http://localhost:4100 unless an explicit --url/--app-url is provided. The CLI also exposes --port to temporarily rewrite the local host port without editing the JSON file. healthChecks.paths lets you point the readiness probe at specific endpoints (for example /api/health). cookieMappings declares extra origins to harvest cookies from (such as OAuth provider cookies when you reuse a signed-in Chrome profile). smokeRoutes.defaults overrides the built-in route sweep, and smokeRoutes.presets lets you register new comma-delimited shortcuts (the built-ins main, settings, billing-only, and pulse-only remain available). Hosts accept plain domains or wildcard-prefixed entries (*.example.dev), and origins must be fully-qualified URLs. Omit daemonUrl, prodUrl, healthChecks, smokeRoutes, or cookieMappings to keep SweetLink’s defaults for those targets.
- Update any
pnpm --filtercommands, tmux helpers, or scripts that referenced the old scope (for examplepnpm --filter sweetlink run build). - Prefer the new environment variables for admin keys:
SWEETLINK_LOCAL_ADMIN_API_KEY(dev shells) andSWEETLINK_ADMIN_API_KEY(prod shells). LegacySWEETISTICS_*vars continue to work, but plan to remove them once every integration is updated. - Copy the neutral
sweetlink.example.jsonfrom the repo root when onboarding SweetLink into another project so you start from generic hostnames/domains instead of the monorepo’s internal defaults.
Config keys at a glance
appLabel– Friendly display name used in CLI help, prompts, and error messages. Set it via config,--app-label, or theSWEETLINK_APP_LABELenv; defaults to “your application”.appUrl– Default URL SweetLink opens in dev mode. Pair it withportwhen your local server is not on 3000. CLI flags (--url,--app-url) orSWEETLINK_APP_URLoverride it.prodUrl– Base URL for--env prodruns (smoke tests, screenshots). Falls back to the same origin asappUrlwhen omitted or whenSWEETLINK_PROD_URLis unset.daemonUrl– Location of the SweetLink daemon. Defaults tohttps://localhost:4455; override when you run the daemon remotely.adminKey– Admin API key used when the CLI requests short-lived session tokens. Provide it via config/--admin-key,SWEETLINK_LOCAL_ADMIN_API_KEY(preferred for dev),SWEETLINK_ADMIN_API_KEY(prod), or the legacySWEETISTICS_*variables for backwards compatibility.port– Injected intoappUrlwhen no explicit host is provided. Handy for per-service configs (4100,5173, etc.).healthChecks.paths– Additional paths the CLI and watchdog probe before assuming the app is healthy. Include JSON APIs or custom/healthzendpoints to catch silent failures.cookieMappings– List of{ hosts, origins }entries that teach SweetLink which Chrome profiles to harvest cookies from. Map every hostname your app serves (including wildcards) to the origins you need (auth providers, REST APIs, CDNs). SweetLink combines these with the primary origin for the target URL; no extra domains are assumed automatically.smokeRoutes.defaults– Ordered array of routes visited bypnpm sweetlink smoke. Include additional views or dashboards specific to your app.smokeRoutes.presets– Named presets ({ "admin": ["admin/users", "admin/settings"] }) that becomepnpm sweetlink smoke --routes admin.servers– Optional list of commands that start/check your local server per environment. Useful when you want SweetLink to boot your app automatically before running automation.oauthScript– Absolute or relative path to an OAuth automation script (ESM module). When set, SweetLink loads the module at runtime and calls itsauthorize(context)export to approve third-party consent dialogs.
SweetLink reads the config once at start-up. When you edit sweetlink.json rerun the CLI command to pick up the new defaults. Multiple projects on the same machine can keep their own config files; SweetLink stops at the first file it finds while walking up the directory tree, so place project-specific configs as close to the repo root as possible.
See apps/sweetlink/docs/config.md for a full configuration reference (including environment overrides).
OAuth automation scripts
Out of the box SweetLink no longer ships opinionated OAuth heuristics. Instead you can point oauthScript at a small ESM module that exports an authorize(context) function. The helper receives a SweetLinkOauthAuthorizeContext with DevTools and Puppeteer helpers, so you can fully control how SweetLink approves third-party consent prompts. A ready-to-use implementation for Twitter/X lives at apps/sweetlink/examples/oauth/twitter-oauth-automation.ts; copy it into your project (or tweak it) and set oauthScript to that path to re-enable the previous behaviour. If the script is omitted, SweetLink will log that auto-authorization is disabled and leave the prompt untouched.
You can also specify the script through runtime inputs:
- CLI flag:
pnpm sweetlink open --controlled --oauth-script ./path/to/oauth-handler.ts - Environment variable:
SWEETLINK_OAUTH_SCRIPT=./path/to/oauth-handler.ts pnpm sweetlink open --controlled …
SweetLink resolves paths relative to the current working directory (or uses absolute paths unchanged). Configuration order still applies: CLI flag → config file oauthScript → SWEETLINK_OAUTH_SCRIPT env → disabled.
Each automation module must export a single async function:
import type { SweetLinkOauthAutomation } from 'sweetlink';
const automation: SweetLinkOauthAutomation = {
async authorize(context) {
// use context.fetchTabs / context.evaluateInDevToolsTab / context.connectPuppeteer
// to locate and click the consent button
return { handled: false, reason: 'button-not-found' };
},
};
export default automation;See the Twitter example for a complete script that works with X’s current consent UI (stacked DOM selectors, login fallback detection, Puppeteer retries).
Browser Runtime API
SweetLink’s browser client is now a first-class export. Instead of copying 1,500+ lines of glue from the Sweetistics repo, pull in the runtime directly:
import {
createSessionStorageAdapter,
createSweetLinkClient,
} from 'sweetlink/runtime/browser';
const storage = createSessionStorageAdapter();
export const sweetLinkClient = createSweetLinkClient({
storage,
status: {
onStatusSnapshot: (snapshot) => setSweetLinkStatus(snapshot),
},
autoReconnectHandshake: () => fetch('/api/admin/sweetlink/remote-handshake', { method: 'POST' }).then((res) => res.json()),
});The runtime mirrors everything we ship in production: websocket lifecycle, console buffering, screenshot hooks/renderers, selector discovery, and auto-reconnect with stored-session resume. Tests can import sweetLinkBrowserTestHelpers to reuse createHookRunner, stripDataUrlPrefix, and commandSelectorSummary without reaching into private modules.
👉 See apps/sweetlink/browser.md for a step-by-step integration guide (handshake endpoint, custom storage adapters, DOM events) plus a reference implementation you can copy into other apps.
Example App
Looking for a minimal integration? Launch the demo web app under apps/sweetlink/examples/basic-web:
cd examples/basic-web
pnpm devThe Vite dev server auto-reloads whenever you tweak the example UI. The site exposes a single page with an “Enable SweetLink” button. Clicking it calls the included /api/sweetlink/handshake route, registers with your locally running daemon, and keeps the socket alive so you can attach via pnpm sweetlink console demo. The example bundles a small browser client that handles the register, heartbeat, and runScript command flow so you can verify end-to-end behaviour without touching your production app. A status chip at the top of the page shows the active SweetLink codename so developers can confirm which CLI session is currently linked.
Once attached, experiment with commands such as:
pnpm sweetlink run-js demo --code "demo.updateKpi(87)"– change the KPI badge value.pnpm sweetlink run-js demo --code "demo.toggleBadge()"– flip the feature badge betweenbetaandstable.pnpm sweetlink screenshot demo --selector "#screenshot-card"– capture the pre-styled analytics card.
Tip: Every time you use a copy icon, the demo logs a “Copied … command” entry in the status panel. Tail them from the CLI via pnpm sweetlink devtools console --tail 50 to confirm clipboard-driven workflows are firing.
Scroll further down to the Automation prompt library for ready-to-paste prompts you can drop into Codex, Claude, or Cursor once a session is live.
Keep watching the status log (and DevTools tail) while you automate—the demo surfaces every clipboard copy, handshake, and CLI action there so you know exactly what ran.
Local Checks
pnpm lint
pnpm testLicense
SweetLink (CLI, daemon, and shared packages) is licensed under the MIT License. See apps/sweetlink/LICENSE for the full text.
TLS Onboarding
SweetLink’s daemon defaults to https://localhost:4455. Run pnpm sweetlink trust-ca once per machine to install the mkcert certificate authority, then visit https://localhost:4455 in the browser profile you plan to automate and accept the prompt. The example app (run pnpm dev inside examples/basic-web) now performs a preflight check via /api/sweetlink/status: it blocks the “Enable SweetLink” button until the daemon is reachable and the certificate is trusted, with quick actions to open the daemon URL or retry the check.
When automating, you can poll /api/sweetlink/status the same way—only proceed when reachable and tlsTrusted are both true.
