npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@skill-router/runtime

v0.1.18

Published

SkillRouter runtime — command registry, local daemon (127.0.0.1:19825) and browser bridge for sr-* CLI adapters. Fork of the published @jackwener/opencli 1.8.6 npm artifact (Apache-2.0, see NOTICE); wire-compatible with additive SkillRouter handoff and co

Downloads

91

Readme

@skill-router/runtime (sr-runtime)

SkillRouter's local runtime: command registry, executor, plugin system, local daemon (127.0.0.1:19825) and browser bridge. Forked from the published @jackwener/opencli 1.8.6 npm artifact with selected 1.8.7/main framework changes ported (Apache-2.0, see NOTICE). It ships the bare runtime only, without the upstream site adapters; SkillRouter adapters are installed separately as plugins. Basic commands stay wire-compatible with the upstream protocol.

It is normally installed and upgraded together with @skill-router/cli by sr update; no separate steps are needed.

sr-runtime plugin install file:///abs/path/sr-arxiv   # install an adapter
sr-runtime sr-arxiv paper 1706.03762                  # run an adapter command directly
sr-runtime plugin migrate                             # adopt existing plugins from ~/.opencli/plugins
sr-runtime plugin verify [name]                       # check installed plugins against their content hashes
sr-runtime plugin lint /abs/path/sr-x                 # static pre-release checks for an adapter
sr-runtime sitedata sr-x --show                        # show the source and version of a site's calibration data
sr-runtime sitedata sr-x --refresh                     # refresh a site's calibration data now
sr-runtime verify sr-x                                 # run the whole-site fixtures (write commands stop at prepare)
sr-runtime doctor                                      # check daemon and Bridge connectivity

plugin verify exits non-zero for tampered, missing and unverified plugins.

Plugin startup indexes

Official bundle builds generate sr-startup.json from the final staged buyer tree. This is an explicit author contract: top-level code only registers deterministic commands and same-site auth helpers; metadata must not depend on environment variables or user caches. It must not depend on other plugins, register conditional/global lifecycle hooks, or perform work that must happen before another command runs. Set "startup": "eager" in the plugin manifest for packages that need ordinary startup. The build refuses an invalid index instead of shipping one. The generator uses an isolated cache directory and executes trusted author code; it is never run automatically during plugin installation. For a reviewed local plugin: node runtime/scripts/prepare-startup.mjs /path/to/plugin.

Startup fingerprints are limited to 256 directory entries and 2 MiB of package content (excluding generated index/lock and dependency directories); larger packages use eager startup to bound hashing overhead. Official authors mark those packages startup: eager. Security seals keep their full scope.

Valid indexed packages register command metadata without importing their JavaScript. The selected package loads once, before argument validation, hooks and execution policy, and its live metadata must match. Imports serialize; alias winners retain discovery order. Indexes generated by a different runtime version, and missing, stale or unsupported indexes use ordinary eager discovery. Before an unindexed plugin loads, preceding indexed commands are materialized and the rest stay eager, including packages with indexes. User CLI directories or existing global hooks also keep plugins eager. Packages sharing a site keep their full eager registration history. This preserves asynchronously registered legacy hooks; mixed installations may therefore retain the full eager startup cost.

The source fingerprint detects staleness and is not a trust root. It excludes its own index and the installer-generated package-lock.json; command metadata must not depend on that lock file. Strict integrity still seals all plugin packages before discovery imports, including the startup index itself. A package edited after discovery, an import failure, unexpected registration, or live metadata mismatch fails the command. Regenerate the index after editing a package; build it after filtering files for delivery. Old runtimes ignore the extra JSON file and continue eager loading. Updating only the runtime does not add indexes to existing installed plugins; updated plugin bundles are needed as well.

browser <session> open and analyze now use one DOM-stability wait during navigation: 500 ms of quiet, with a combined maximum of 3 seconds. The same browser response records the final redirected URL. analyze keeps its separate XHR observation step; back and explicit --state keep their own settling. DOM quiet is a heuristic, not proof that delayed application data has loaded. Transport failures and closed tabs during settling are reported as errors; the old second wait silently swallowed them. Navigation interruptions retain the existing retry behavior. The 3-second ceiling applies to a DOM observation, not transport latency or a retried observation. Use browser <session> wait selector ".ready" when a task needs a specific ready element.

Site calibration data (sitedata)

Every plugin ships a sitedata.json that is data only: URLs, selectors, labels, mappings, wait limits, fixtures and pace. After a command finishes, the runtime spawns at most one detached background process every 15 seconds to check for updates by ETag; the command itself never waits on the network. Selection order: a fresh cache under 24 hours old → a valid cache under 30 days old → the copy bundled with the plugin; a change in the bundled version discards the old cache. Remote documents must pass schema, regular-expression safety and plugin domain allowlist validation; a failure never replaces the last-known-good copy, and a document older than the bundled version is never accepted. SR_NO_SITEDATA_REFRESH=1 disables the background refresh; SR_SITEDATA_REFRESH_INTERVAL_MS adjusts the check interval (5 seconds to 24 hours).

Sites that declare pace keep persistent accounting in ~/.skillrouter/state/pace/<site>.json; when that state cannot be written, the command is refused rather than released. verify checks row counts, columns and pattern/notEmpty for read commands, and for write commands strips the submit arguments and checks only prepare_assert. plugin lint checks command/fixture alignment, literal sleeps, disabled automation surfaces and out-of-domain URLs.

When the same site serves different layouts, commands.<cmd>.strategies in sitedata.json carries extra selector sets (same keys as the command's own selectors). A read command tries the sticky winner, then its own selectors, then the active strategies — at most three — and only a layout-shaped failure (selector miss, empty result, timeout, command error) switches to the next one; a write command never switches. The winner for this machine lives in ~/.skillrouter/state/strategies/<site>.json; SR_STRATEGY=<id> pins one set for a run (s0 is the command's own), and verify reports the strategy it landed on.

Browser sessions

Browser commands reuse a persistent site session by default; precedence is --site-session > SR_SITE_SESSION > the command's declaration > the default. A read command first makes one non-waiting local lease attempt: when idle it reuses site:<site>, when busy it switches to an isolated tab and prints [sr] site:<site> busy → isolated tab (SR_QUIET=1 silences the notice). A read that loses its lease mid-command discards its result and reruns once in full on an isolated tab; if that fails too, it exits 65 with the original error. A write command returns SESSION_BUSY (exit 75) immediately when the session is busy and is never rerun automatically. After a process crash the lease is reclaimed by TTL within about 45 seconds. Read commands must therefore be idempotent.

After a persistent command succeeds, the runtime sends control-release: the final page and the site-to-tab mapping are kept, but debugger control is detached at once and the extension icon goes from LIVE to IDLE. Only an explicit tabs close really closes the tab.

The browser subcommand picks its window default by host: a person in an interactive terminal gets the foreground; when driven by an agent, MCP or a pipe it runs in the background and never steals focus. --window, --show and login-wall takeover are unaffected; pick and blocking record are always foreground.

Failure handoff and login walls

When an adapter command fails hard, the runtime writes a handoff bundle to ~/.skillrouter/handoff/<timestamp>-p<PID>-<UUID>.json and refreshes latest.json (mode 0600, kept seven days, never uploaded). When takeover is safe the bundle carries the adapter lease, the page target, a screenshot and candidate elements; inspect the scene with sr-runtime browser <session> --surface adapter state --tab <target>. Before taking over a write command, confirm whether the first attempt partially succeeded; never blindly replay it. Login and CAPTCHA go to a human; rate limits and risk-control decisions are never taken over.

On a login wall the runtime brings the tab to the foreground, shows a card asking a human to log in, and reruns the command once after the login lands. SR_REQUEST_HELP=off disables this.

sr report-fix --pick lets you pick the repair target on the live page at the breakpoint and uploads the chosen selector and its structural fingerprint to the SkillRouter repair service. Apart from the optional run signal below, it is the only command in this runtime that sends data out on its own.

Before browser <session> network --detail <key> writes to its cache or output, sensitive headers such as authorization, cookie, token, password, secret and session id, and credential-shaped values, are replaced with <redacted>; URL query parameters are redacted the same way.

Optional anonymous run signal

Off by default. After sr signal on, the runtime asynchronously sends a fixed five-field record {site, cmd, sitedata_version, ok, code} when a command completes; it carries no arguments, URLs, page content, output or credentials, and a failed send never affects the command. SR_SIGNAL=1|0 switches it explicitly. Details: https://skillrouter.org/us/privacy .

Local runs journal

Every real command run appends one line to ~/.skillrouter/journal/runs.jsonl (directory 0700, file 0600, one JSON object per line, ≤ 4096 bytes, rotated to runs.1.jsonl at 8 MiB). The line holds only ts, v: 1, site, cmd, access, browser, outcome, code, exit, ms, rows, shape, rule, strategy, pid — never arguments, output rows, URLs, page text or credentials — so "did it run, was the shape right" is answered without asking the agent. outcome is ok/empty/shape_mismatch when the sitedata fixture checked the shape, and auth/arg/env/timeout/selector_miss/crash on failure. SR_JOURNAL=off disables it; verify subprocesses never write, and a journal failure can never change a command's result or exit code.

Action overlay, recording and exploration audit

SR_SHOW_ACTIONS=1 or --show displays a synthetic cursor, target highlight and action labels (decoration only, not audit evidence).

sr-runtime browser work pick --prompt "click the publish button"   # returns ref + selector (300 s, Esc cancels)
sr-runtime browser work record --purpose "demo a search"           # writes trace.json + states/ after Finish
sr-runtime browser work record --values                            # record typed values (never passwords, OTP, card numbers, SSN)
sr-runtime browser work explore start                              # local black box: command names and redacted arguments only
sr-runtime browser work explore stop
sr-runtime browser audit <record-dir> --json                       # verdict: read_pure / write_actions / violations

audit exits 1 on a violations verdict. network, analyze and wait xhr read the network capture buffer and cannot leave complete network evidence, so an explore session that uses them is always judged violations.

Recordings are written with mode 0600 to ~/.skillrouter/records/<timestamp>-<host>/, kept seven days, capped at 500 steps or 5 MiB. URLs keep origin, path and parameter names; sensitive parameter values are redacted. trace.json is untrusted data supplied by the web page; states/*.txt are full-page text snapshots that may contain personal information from the page and should not be uploaded. Google/Microsoft sign-in, password managers, Alipay/Taobao login, PayPal, Stripe and any host containing bank refuse recording by default; SR_RECORD_BLOCK_HOST=host,*.suffix extends the blocklist, and SR_RECORD_ALLOW_HOST=a,b is an explicit risk acceptance that takes precedence over it. The bank substring rule has a clear ceiling: bank sites whose domain lacks the word and punycode domains not on the list are not recognised automatically. Recordings and exploration files stay on this machine, are never uploaded and must not be placed in the four-field feedback body; they leave the machine only when the owner explicitly submits a candidate later.

Fewer round trips

state --interactive keeps only interactive elements and the ancestors that hold them. state --diff prints, after the first diff snapshot of a mode on a document, only the rows that appeared, changed or were renumbered; its footer counts the rest (diff: new 3 | unchanged 220 | removed 1, where a removed row disappeared, changed, was renumbered, or had its ancestor text change). A document is the browser document: same-page anchors, in-app route changes and re-opening the URL you are already on keep it and its baselines; a real navigation starts a new one. The first diff snapshot of a mode on a document is the full baseline (diff: baseline); full and interactive diffs keep separate baselines, and plain state, record, verify or adapter snapshots never disturb them. --state takes an interactive diff snapshot, so it claims the interactive baseline for the following state --interactive --diff calls. Markdown tables, iframes and svg blocks take part in the diff as whole units; the hidden_interactive hint is repeated every time, compounds only for refs printed in that diff. [N] numbering is that of a full snapshot taken at the same moment, and a row whose [N] shifted is reprinted with its new number: read the reprinted rows before reusing a [N] from an earlier snapshot, and when a diff reports new or removed rows but prints no addressable row (identical-looking rows share one hash), take a plain state first. state --settle 1000 first waits up to one second for the DOM to stop changing.

open, click, type, fill, select, keys and scroll accept --state: after the action the runtime waits for the DOM to settle (up to 1 s) and prints the interactive diff state in the same call, so one process replaces the action-then-state pair. A row's diff hash covers what the row prints: its [N], tag, the listed attributes, scroll counts, the text the row prints and the first 200 characters of its subtree text; only the row's layout shape (inline text versus a text line under it) is not hashed. A text field prints its live value only when its markup has no non-empty value attribute, so fill … --state shows the filled field there and keeps printing the markup value on pre-filled fields; the fill receipt carries the value either way. State that is not printed — unlisted aria attributes, text past the printed prefix — is not part of the diff, and a plain state does not print it either; read it from the command's receipt, with browser extract for long text, or with browser eval for a single attribute. The baseline is kept on the page itself, so a page can tamper with it: --diff is always opt-in, a mode's first diff snapshot on a document that does not print diff: baseline means the page pre-seeded it, and a plain state is the deterministic view.

sr-runtime browser work state --interactive --diff
sr-runtime browser work click 12 --state
sr-runtime browser work state --settle 1000

Domain allowlist and plugin integrity

Every adapter command runs inside a domain allowlist: the plugin manifest domains ∪ the sitedata.json domains. Navigations and requests the runtime itself initiates outside the allowlist are refused (exit 68, handoff takeover_policy.mode = stop); navigations the page performs on its own are only recorded as landing URLs. A plugin that declares no domains gets a single warning; SR_STRICT_DOMAINS=1 turns that into a refusal and installs a page-side fetch/XHR observer (count only, never blocking). about:, data:, blob: and chrome-extension: pass; javascript: and chrome: do not. A manifest whose domains contain illegal entries (a scheme, path or port, loopback or private addresses, a public-suffix wildcard) is refused by plugin install/update; already-installed plugins are enforced on their valid subset. Matching is exact: a full hostname matches only that host, and *.example.com matches subdomains only.

plugin install/update records a content hash of the plugin directory in ~/.sr/plugins.lock.json (excluding node_modules, .git and *.log); plugin verify compares against it, the executor warns once per process about a mismatching plugin, and under SR_STRICT_INTEGRITY=1 only verified plugins run. A local-path install is a symlink hashed through its real directory; after editing its code, run plugin update <name> to re-record it. Threat model: the domain guard catches hijacked pages, poisoned sitedata and honest adapter mistakes; adapter code runs inside the runtime process, so this is not a sandbox against a malicious plugin author, which is what plugin lint and the integrity hash are for. The lock file lives in the user's home directory and only detects one-sided tampering.

Directories and environment variables

The runtime's own state lives under ~/.sr/ (plugins, plugins.lock.json, monorepos, the update-check cache); logins and configuration shared with upstream opencli stay under ~/.opencli/, so both can coexist. SR_DAEMON_TOKEN optionally enables a daemon handshake token (off by default).

Local workspaces

On macOS/Linux, sr-runtime workspace --help exposes local settings, task progress, files and encrypted credentials under ~/.skillrouter/. These commands bypass plugin discovery and startup hooks. They neither execute website actions nor upload local data. Domain, category and account are always explicit; task and run identify one batch. Read the returned revision before each update; conflicts require a fresh read and a decision, never an automatic retry of a website action.

sr-runtime workspace settings get --domain example.com --category invoices --account work
printf '%s' '{"parameters":{"month":"2026-09"}}' | sr-runtime workspace init --domain example.com --category invoices --account work --task monthly --run 2026-09 --revision 0 --stdin
sr-runtime workspace resume --domain example.com --category invoices --account work --task monthly --run 2026-09

Use item to record pending → running → succeeded with business evidence. Use attach --id invoice-1 --file /explicit/path/invoice.pdf --role output --revision N with the same run scope to copy a result, or --role input --reference to reference an input. finish --revision N --stdin takes {"evidence":"scope checked ..."}; all items must be succeeded/skipped and all registered files must verify. show reports historical status; resume checks current file integrity and never replays submitted, running or unknown actions.

credential put accepts {username,password} or {token} through stdin only; credential inspect returns metadata. Supply --domain, --account, --id, --origin, --kind, --revision and --stdin when saving. Do not put secrets in shell literals/history. Authorized local adapters can use readCredential from @skill-router/runtime/workspace with the exact origin; never log its return value. The vault uses AES-256-GCM and a private local key, optionally SR_WORKSPACE_KEY (64 hex characters). Back up the complete vault including key-id and its original key. Switching keys is rejected; rotation and Windows ACL support are not implemented. The vault does not protect against a process running as the same OS user.

Documents are limited to 1 MiB / 1024 immutable revisions, runs to 1000 items and 1000 artifacts, individual files to 512 MiB (JSON: 1 MiB). Keep secrets out of settings, notes and artifacts; field-name checks are not automatic secret detection.

Automatic login from the vault

When a browser command hits a login wall, the runtime may fill in and submit the login form once from the local vault before handing the tab over to a person. The guarantee it makes is deliberately narrow and is stated here in full.

Threat model

In scope — these are the shapes the network guard exists to stop:

  • the page HTML points the submit somewhere else: action, formaction, <base href>, a hidden button, an implicit Enter submit, a form= control outside its form, or method=get putting the value in a query string;
  • page script (careless or injected) sends the submitted content to another site with XHR, fetch or sendBeacon — a cross-site static GET (script, image, font, stylesheet, media, manifest, text-track) is still allowed by design, so same-site hostile JavaScript can abuse that channel; see Out of scope below;
  • the page navigates to another site after the submit and asks for the credential again.

Out of scope — not promised, and a person should not assume otherwise:

  • hostile JavaScript on the same site as the form. It can read the password box directly and never needs a submit path. No in-page check can stop that, which is why the guard moved to the network layer and this is written down instead of patched;
  • a phishing domain the user themselves saved the credential for: the credential's registered domain is the trust anchor;
  • WebSocket frames (the CDP Fetch domain does not intercept them);
  • forms inside a cross-site iframe (the probe only ever runs in the top-level document and never types into an iframe);
  • another process running as the same OS user;
  • the direct-CDP browser path (CDPPage, electron / --cdp): it has no guard at all, so automatic login there always ends as guard_unavailable.

Opt-in per credential

Automatic login is off for every credential unless it was saved with --auto-login:

printf '%s' '{"username":"…","password":"…"}' | sr-runtime workspace credential put \
  --domain example.com --account work --id wp --origin https://example.com \
  --kind password --revision 0 --auto-login --stdin

A credential without the flag (including every credential saved before the flag existed) is reported as auto_login_off without being decrypted, and the hint says to re-save it with --auto-login. Turning it off again is another put without the flag — a new revision. The flag lives beside the encrypted envelope, deliberately outside its authenticated data: it is a convenience switch, not a security boundary (anyone who can read the key can read the record either way).

Network guard

While the credential is being filled in and submitted, the runtime opens a tab-level request guard in the SkillRouter Bridge extension (CDP Fetch domain interception performed inside the extension). From start until stop:

  • requests to the credential's registered domain (and its declared domains, plus the wildcard of the credential's domain) are allowed;
  • cross-site static requests are allowed: GET/HEAD of script, stylesheet, image, font, media, manifest and text-track resources;
  • everything else cross-site is refused with BlockedByClient: XHR, fetch, sendBeacon (Ping), EventSource, the WebSocket handshake, every document navigation (top-level and iframe), and any non-GET/HEAD request.

The guard needs SkillRouter Bridge 0.1.8 or newer; an older extension answers the vault-guard action with "Unknown action" and the attempt ends as guard_unavailable before anything is decrypted — fail closed, no typing, no pause. The policy lives in the extension, so page HTML and page script cannot reach or change it, and Fetch.* is not reachable through the generic cdp command channel. Only the guard's own tab is affected, and it closes on stop, on CDP detach, or when its TTL (page timeout + 45s, capped at 3 minutes) expires.

A successful-looking login is only reported as logged_in after the guard has been stopped and reports that it stayed active for the whole window with nothing blocked. If the page looks logged in but the guard blocked a request, or was lost (DevTools attached, extension reloaded, guard expired), the attempt is rejected with the usual 10-minute pause: the extension's verdict is the one that counts, because a blocked request means the credential may have been on its way somewhere it does not belong.

Prerequisites

All of these must also hold:

  • the site is not access: red (those never touch a credential);
  • the live document is on a host the command is allowed to touch — the origin is read from the page itself (location.href), never from the navigation that was requested, so a redirect to another host, scheme or port is refused instead of trusted;
  • a kind=password credential is stored whose origin is exactly that live origin — scheme, host and port all included (https://example.com never matches https://example.com:8443 or http://example.com);
  • each sensitive keystroke carries the origin it was authorized for: the check happens after the field is focused and inside the same evaluate that prepares native input, so a page that moves during the click or between checks is never typed into, and the value is never submitted to a document that is not the one it belongs to;
  • exactly one credential matches, or SR_VAULT_ACCOUNT (and optionally SR_VAULT_ID) selects one (a named account that is not stored is reported as no credential rather than silently swapped for another);
  • the same domain has not failed a login within the last 10 minutes, and no other attempt for it is in flight (one attempt per domain at a time);
  • the page has a visible password box and a username box, with no one-time-code, CAPTCHA or other challenge control.

On success the command is re-run once on the same tab; if the wall is still there afterwards, the domain is paused for 10 minutes like any other rejection. A write command whose arguments include a true send / confirm / commit / submit / execute / publish / apply / pay is never replayed by itself: it fails with AUTH_RESTORED (exit code 75, agent handoff, verify_before_retry) so the user can confirm nothing was submitted and run it again. Any other outcome leaves behaviour exactly as before — the same AUTH_REQUIRED error and handoff classification — plus a one-line hint on how to save a credential. A write command may also be logged in from the vault at its pre-run login check, before anything has executed.

The pause is unconditional once the credential has been sent. Whatever the page does after the submit — stays on the login page, times out, keeps the password box, or lands on another origin — the domain is paused for 10 minutes. The same holds when the document changes origin after at least one character has been typed (during typing or just before the submit): the attempt ends as rejected, the domain is paused, and the hint says the value may have reached another site and the credential should be reviewed. That is deliberate: a conditional pause would let every following command submit the password again while the hint claimed it was paused.

The decrypted value lives only inside the login attempt. It reaches the page exclusively through the browser's native text input (insert-text / CDP), never through a page script: typeText(..., { sensitive: true }) refuses the DOM-setter fallback and the attempt stops with no_form instead of shipping the value. Any attempt that typed something and did not end in logged_in blanks both fields again before returning — but only on the document it filled: after a cross-origin navigation the old document is gone and this runtime runs no script at all in the new one. The value never enters the trace/observation stream, a log line, an error message, the returned object or the CLI output.

Two things this cannot promise, and which a person should know:

  • a site that re-fills the username itself after a failed login (WordPress does) puts it back on its own; clearing only removes what this runtime typed;
  • under SR_STRICT_ACCESS=1 no automatic login happens at all. The guard blocks synthetic input for read commands and vault typing is synthetic input too, so read commands are skipped with a hint that says exactly that. There is no exemption for vault typing: SR_STRICT_ACCESS=1 means "read commands get zero synthetic input", and this code does not relax that.

Nothing here has been validated against a real site yet — the spec's live acceptance run is still outstanding.

Audit log

Every attempt appends one line to ~/.skillrouter/state/vault-login/vault-login.log (mode 0600; rotated to vault-login.log.1, overwriting the previous .1, once the file passes 1 MiB):

{"ts":"…","domain":"…","origin":"https://…","account":"…","id":"…","revision":1,"outcome":"rejected","attempted":true,"guard":{"started":true,"active":true,"blocked":["127.0.0.1"],"detached":false,"expired":false}}

guard is null when no guard was opened (an auto_login_off, guard_unavailable or pre-guard outcome). The line never contains the username, the password, a URL path, a query string, a request body or a hint; origin is a bare origin and blocked is a list of hosts. Writing is best-effort — a read-only home changes no outcome.

# Ask the runtime to try the vault on the tab you are already looking at
sr-runtime browser work vault-login

The command prints the outcome as JSON (logged_in, no_credential, ambiguous, origin_mismatch, no_form, cooldown, disabled, rejected, guard_denied, auto_login_off, guard_unavailable) and exits 0 only for logged_in. Environment switches: SR_VAULT_LOGIN=0 turns automatic login off entirely; SR_VAULT_ACCOUNT / SR_VAULT_ID choose between several saved credentials for the same origin.