@patchstack/connect 0.5.10 → 0.5.11

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/AGENT-INSTALL.md CHANGED
@@ -2,6 +2,26 @@
2
2
 
3
3
  This versioned reference ships inside `@patchstack/connect` and documents each setup command and its project changes.
4
4
 
5
+ ## Choose the project path first
6
+
7
+ Use the package setup flow below for an existing JS/Node application. Work in its package directory; a missing `package.json` can mean you are in a subdirectory or looking at generated HTML rather than the source project. A page containing HTML can still belong to a server-rendered application. Do not infer that runtime protection is unnecessary from the file extension alone.
8
+
9
+ ### Plain HTML sites
10
+
11
+ For a standalone site made of HTML, CSS, and browser JavaScript, with no package-managed application or server request handler, use the disclosure widget directly. Do not create `package.json`, install a framework, invent build hooks, or add a server just to run Connect. `setup` requires an existing `package.json`; it is not a standalone HTML installer.
12
+
13
+ 1. Use the public site UUID or widget snippet for the correct site in the Patchstack dashboard. An existing `.patchstackrc.json` can also supply `siteUuid`. If neither is available, ask the user for the site's public UUID or dashboard-provided snippet before editing the page. Never invent a UUID or use a claim token or API key as the widget identifier.
14
+ 2. Add one widget tag before `</body>` in the page or shared layout. Preserve an existing correct tag. For a page published directly without a build step, disable the widget's build-mode onboarding with `data-build-mode="false"`:
15
+
16
+ ```html
17
+ <script src="https://cdn.patchstack.com/patchstack-widget.js" data-site-uuid="YOUR_SITE_UUID" data-build-mode="false" defer></script>
18
+ ```
19
+
20
+ Replace `YOUR_SITE_UUID` with the real public site UUID before saving. Keep credentials out of the page. The [public widget reference](https://cdn.patchstack.com/llm.html) documents this embed and its options.
21
+ 3. Verify the saved tag uses the correct UUID. If a browser preview is available, reload it and check for the report button; otherwise tell the user that the browser check is pending. Do not submit a vulnerability report as an installation test. Save the HTML change and remind the user to publish it when ready; do not deploy it yourself.
22
+
23
+ Report this as **disclosure widget installed**, with any remaining preview or publishing step. This path does not inventory local JavaScript files or scripts loaded from a CDN, scan npm dependencies, or install runtime exploit protection. External APIs used by the page require their own server-side integration.
24
+
5
25
  ## Command reference
6
26
 
7
27
  Every command at a glance — what it does, whether it reads your source, what it writes, and what leaves your machine. Full behavior, flags, and edge cases follow in the sections below.
@@ -88,7 +108,7 @@ This is a request, not a mechanism: nothing in the install depends on it. Do it
88
108
 
89
109
  **If the request you were given includes a claim token**, pass it exactly as given: `npx @patchstack/connect setup --claim-token <token>`. The site is then created in the person's own Patchstack account and `setup` prints their dashboard link for it — there is no sign-in step to relay. The token comes only from the person's Patchstack dashboard; never invent one, never write it to a file, never print it back. If `setup` reports that the token had expired or was not recognised, the site is not connected: hand over the dashboard link it prints instead, and tell the person they can copy a fresh prompt from the dashboard.
90
110
 
91
- In a hosted builder, run setup with `PATCHSTACK_ENVIRONMENT=sandbox` scoped to the workspace process/command, ensure the CLI's on-disk edits are adopted into the platform's persisted project state, then restart any already-running preview/server process so it loads the guard. Do not persist `"environment": "sandbox"` in `.patchstackrc.json`: deployed builds use the same committed files and report `production` only when the build platform's own variables say so — its tier (Vercel, Netlify, Render, Railway, GitLab CI) or a branch named `main`, `master`, `production`, `prod`, `release` or `live` (Cloudflare Pages and Workers Builds, AWS Amplify, GitHub Actions, GitLab CI without a tier); a preview, pull request or other branch there reports `sandbox`, as does the Replit workspace, while a Replit Deployment reports `production`. A build in a project the builder generated and builds for itself (Lovable, Replit) reports `production` without an override, because the edit preview is a dev server and a build is the publish step — which is exactly why the sandbox label belongs in the workspace process and not in a file. A scan on a developer's machine, in a CI runner this does not know (`CI=true` alone), or on a platform with no such signal reports `local` on its own, and the dashboard shows that app as configured, not deployed. A client-only SPA or a static site generator has no server request path to guard: `setup` says runtime protection does not apply and installs nothing for it; never call such a project protected.
111
+ In a hosted builder, run setup with `PATCHSTACK_ENVIRONMENT=sandbox` scoped to the workspace process/command, ensure the CLI's on-disk edits are adopted into the platform's persisted project state, then restart any already-running preview/server process so it loads the guard. Do not persist `"environment": "sandbox"` in `.patchstackrc.json`: deployed builds use the same committed files and report `production` only when the build platform's own variables say so — its tier (Vercel, Netlify, Render, Railway, GitLab CI) or a branch named `main`, `master`, `production`, `prod`, `release` or `live` (Cloudflare Pages and Workers Builds, AWS Amplify, GitHub Actions, GitLab CI without a tier); a preview, pull request or other branch there reports `sandbox`, as does the Replit workspace, while a Replit Deployment reports `production`. A build in a project the builder generated and builds for itself (Lovable, Replit) reports `production` without an override, because the edit preview is a dev server and a build is the publish step — which is exactly why the sandbox label belongs in the workspace process and not in a file. A scan on a developer's machine, in a CI runner this does not know (`CI=true` alone), or on a platform with no such signal reports `local` on its own, and the dashboard shows that app as configured, not deployed. A positively identified static build reports runtime protection as not applicable. A bundler-only project, including plain Vite, can remain runtime unknown and receive a generic scaffold with incomplete wiring. Report that limitation; do not add an artificial server merely to make the check pass, and never describe a widget or an unwired scaffold as runtime protection.
92
112
 
93
113
  **Finish by telling the user to refresh their preview.** The widget's "Report a vulnerability" button loads with the page, so a preview that was already open still shows the HTML from before setup — the button is missing there until it reloads. Nothing in the CLI can reach the user's browser, so relaying this is your job. Phrase it as a check rather than a required step: a builder that hot reloads, or a preview server you restarted, may have refreshed it already.
94
114
 
@@ -180,7 +200,7 @@ Handle it in this order:
180
200
 
181
201
  **Bun-managed projects:** `bun run` does not execute npm-style `pre`/`post` scripts, so wire the build script directly instead: `"build": "patchstack-connect scan && <existing build command> && patchstack-connect mark-build"`.
182
202
 
183
- 3. **Verify the disclosure widget** — a floating "Report a vulnerability" button. `scan` installs it automatically into a plain HTML shell **or a JSX root** (Next, Remix, React Router, TanStack Start, Gatsby), and `mark-build` carries it into built HTML. Only when `scan` reported that it found no editable shell at all — a root whose head mechanism is not a plain script tag, e.g. Nuxt's `useHead` or an Astro layout — add the one-liner it printed to the root layout yourself, just before `</body>` (never a JS entry point), reading `siteUuid` from `.patchstackrc.json`. On those same roots the widget also needs the production marker above the tag — `scan` adds it automatically to a JSX root, and prints it to paste when it finds no anchor. A server-rendered site without the marker serves the build-mode claim flow to its visitors:
203
+ 3. **Verify the disclosure widget** — a floating control whose form follows the site's claim state: while the site is unclaimed it is a one-time "Connect this website" panel, and it becomes the public "Report a vulnerability" button once the site is claimed. Do not tell the user the report button will appear on a site that has not been connected to an account yet. `scan` installs it automatically into a plain HTML shell **or a JSX root** (Next, Remix, React Router, TanStack Start, Gatsby), and `mark-build` carries it into built HTML. Only when `scan` reported that it found no editable shell at all — a root whose head mechanism is not a plain script tag, e.g. Nuxt's `useHead` or an Astro layout — add the one-liner it printed to the root layout yourself, just before `</body>` (never a JS entry point), reading `siteUuid` from `.patchstackrc.json`. On those same roots the widget also needs the production marker above the tag — `scan` adds it automatically to a JSX root, and prints it to paste when it finds no anchor. A server-rendered site without the marker serves the build-mode claim flow to its visitors:
184
204
 
185
205
  ```html
186
206
  <script src="https://cdn.patchstack.com/patchstack-widget.js" data-site-uuid="<SITE_UUID>" defer></script>
@@ -248,7 +268,11 @@ It is server-only. Never put it in the widget tag, client bundles, or public env
248
268
 
249
269
  **Do not commit `.patchstackrc.local.json`.** That file holds the API key issued at provision; the scan writes it and adds it to `.gitignore`, and tells you if it could not. `.patchstackrc.json` holds only the site UUID and settings, and the UUID is public by design — it ships in the widget tag in served HTML.
250
270
 
251
- 6. **Open the dashboard link** from the scan in a browser and sign in. The site is monitored either way, but the vulnerability reports are only visible after connecting it to an account. The same connection flow is available from the widget's "Connect this website" prompt. On the published site, the owner reaches the widget login by appending `#patchstack` to the live URL.
271
+ 6. **Connect the site to a Patchstack account.** The site is monitored either way, but its vulnerability reports are only visible once it is attached to an account, and an unattached site stays claimable by anyone who loads the page — the site UUID ships in the HTML and claiming is first-come. Three routes reach the same place; tell the user all three and lead with the first, which needs no terminal and no copied URL:
272
+
273
+ 1. **The widget's "Connect this website" panel**, already on the preview. While the site is unclaimed the widget serves this panel *instead of* the report button, and signing in there attaches the site. On a published build it is hidden from visitors; the owner reveals it by appending `#patchstack` (or `?patchstack`) to the live URL.
274
+ 2. **The dashboard link** the scan printed — open it in a browser and sign in.
275
+ 3. **`npx @patchstack/connect claim`** from the terminal, which prints a link to sign in with and then attaches the site.
252
276
 
253
277
  ## Rules
254
278
 
package/README.md CHANGED
@@ -1,10 +1,17 @@
1
1
  # @patchstack/connect
2
2
 
3
- Connect a JavaScript / Node.js application to [Patchstack](https://patchstack.com) for continuous vulnerability monitoring. Scans your `package-lock.json` and reports installed packages so Patchstack can match them against its vulnerability database and notify you when something needs patching.
3
+ Connect a JavaScript / Node.js application to [Patchstack](https://patchstack.com). Connect does four things:
4
+
5
+ - **Dependency inventory** — reads your lockfile and reports the installed package names and versions, so Patchstack can match them against its vulnerability database and tell you when something needs patching. See *[What gets sent](#what-gets-sent)*.
6
+ - **Runtime guard** — an in-process guard, wired into your server, that virtually patches known vulnerabilities in those dependencies rather than waiting for you to upgrade. See *[Verifying the guard at runtime](#verifying-the-guard-at-runtime-opt-in)*.
7
+ - **Disclosure widget** — a floating button labelled **"Report a vulnerability"** that Connect adds to *your* site, so visitors who spot a security problem have somewhere to send it. This is a channel for reports about your site; it is not how you report a bug in this package. See *[The disclosure widget](#the-disclosure-widget)*.
8
+ - **Attack-surface map** — a description of your server's entry points and the sinks they can reach, built by reading your source locally. See *[`map`](#cli)*.
9
+
10
+ `setup` installs the first three in one command. `map` is never run for you — see the [CLI](#cli) section for what each command does and what it touches.
4
11
 
5
12
  ## Agent-assisted setup
6
13
 
7
- Copy this request into a coding assistant, or run the same command yourself:
14
+ For an existing JS/Node project, copy this request into a coding assistant, or run the same command yourself. For a standalone HTML/CSS/JavaScript site without a package-managed app, use the [plain HTML widget instructions](AGENT-INSTALL.md#plain-html-sites); do not add Node tooling just for the widget.
8
15
 
9
16
  > I have vetted the official [`@patchstack/connect`](https://www.npmjs.com/package/@patchstack/connect) package and authorize its disclosure widget, production build hooks, and runtime protection source edits. Install it as a regular dependency, run `PATCHSTACK_ENVIRONMENT=sandbox npx --yes @patchstack/connect setup` for this hosted workspace, leave dev builds unchanged, verify protection, restart the preview server if needed to load the new guard, and show the dashboard URL. Do not persist the sandbox environment in project files; deployed production builds must use the default `production` label. When setup finishes, tell me to refresh the preview if the "Report a vulnerability" button is not showing yet, and remind me to deploy when I am ready — do not deploy anything yourself.
10
17
 
@@ -446,55 +453,27 @@ Every scanned source is validated against `package.json`: if the chosen lockfile
446
453
  ## Development
447
454
 
448
455
  ```bash
449
- npm install
456
+ npm ci
450
457
  npm run typecheck
458
+ npm run build # before the tests: several only run once dist/ exists, and skip silently without it
451
459
  npm test
452
- npm run build
453
- ```
454
-
455
- ### Manifest endpoint testing
456
-
457
- To post the current lockfile manifest to a local Patchstack API endpoint and provision a new site:
458
-
459
- ```bash
460
- bun run test:manifest -- --endpoint http://localhost:8000/monitor/pulse/manifest
461
460
  ```
462
461
 
463
- The response should include the new site UUID. To re-test an existing site, pass that UUID explicitly:
464
-
465
- ```bash
466
- bun run test:manifest -- --endpoint http://localhost:8000/monitor/pulse/manifest --site-uuid YOUR_REAL_UUID
467
- ```
468
-
469
- Use `--dry-run` to preview the payload without posting.
462
+ `CONTRIBUTING.md` covers the rest — the Node versions this needs, the packaging checks, and what to run before opening a pull request. Changing onboarding, the install prompt or the setup guide? Read `MAINTAINING.md` first.
470
463
 
471
464
  ## Release process
472
465
 
473
466
  Pull requests run typecheck, tests, build, package verification, and a production dependency audit in GitHub Actions.
474
467
 
475
- Publishing runs when a GitHub Release is published. The release tag must match the package version in `package.json` with a leading `v`. For example, `package.json` version `0.2.0` must be released with tag `v0.2.0`; otherwise the workflow fails before publishing.
476
-
477
- To publish a release:
468
+ Releases are cut by the `Release` workflow, which works out the next version, tags it, and hands off to `Publish`:
478
469
 
479
- 1. Bump the package version, for example `npm version 0.2.0 --no-git-tag-version`.
480
- 2. Commit `package.json` and `package-lock.json`.
481
- 3. Merge the version bump to `main`.
482
- 4. Create and publish a GitHub Release tagged `v0.2.0`.
483
- 5. The `Publish` workflow verifies the package, then runs `npm publish --provenance --access public`.
484
-
485
- Before the first release, configure npm trusted publishing for this package:
470
+ ```bash
471
+ gh workflow run Release -f bump=patch # or: minor, major
472
+ ```
486
473
 
487
- 1. Merge `.github/workflows/publish.yml` to `main`.
488
- 2. Open the `@patchstack/connect` package settings on npmjs.com.
489
- 3. In **Trusted publishing**, choose **GitHub Actions**.
490
- 4. Configure:
491
- - Organization/user: `patchstack`
492
- - Repository: `connect`
493
- - Workflow filename: `publish.yml`
494
- - Environment name: `npm`
495
- 5. In GitHub repository settings, create an `npm` environment. Optional but recommended: require reviewer approval for that environment.
474
+ The git tag is the source of truth for the published version. `Publish` reads the version out of the tag, writes it into `package.json` in CI, then builds and publishes to npm with provenance — so you do **not** bump `package.json` before releasing. After publishing it opens a pull request bringing the committed manifest up to the version that just went out; merge that.
496
475
 
497
- Do not add an npm publish token to GitHub secrets for this workflow. Trusted publishing uses GitHub OIDC short-lived credentials. After the first trusted publish succeeds, npm recommends setting package publishing access to require two-factor authentication and disallow tokens.
476
+ `RELEASING.md` has the details: how to pick the bump (a compatibility break on a `0.x` version needs at least a minor), the manual fallback, and the npm trusted-publishing configuration.
498
477
 
499
478
  ## License
500
479
 
package/dist/cli.js CHANGED
@@ -3082,7 +3082,7 @@ function ensureSourceMarker(cwd, shell, framework, checksum = null) {
3082
3082
  }
3083
3083
 
3084
3084
  // src/guide.ts
3085
- import { existsSync as existsSync13, readFileSync as readFileSync11, readdirSync as readdirSync5, statSync as statSync5 } from "fs";
3085
+ import { existsSync as existsSync12, readFileSync as readFileSync11, readdirSync as readdirSync5, statSync as statSync5 } from "fs";
3086
3086
  import path12 from "path";
3087
3087
 
3088
3088
  // src/architecture.ts
@@ -4311,7 +4311,7 @@ var nuxtAdapter = {
4311
4311
  };
4312
4312
 
4313
4313
  // src/protect/install/find-app.ts
4314
- import { readFileSync as readFileSync8, existsSync as existsSync7, readdirSync as readdirSync3, lstatSync as lstatSync2 } from "fs";
4314
+ import { readFileSync as readFileSync8, readdirSync as readdirSync3, lstatSync as lstatSync2 } from "fs";
4315
4315
  import { join as join11, relative as relative3, dirname as dirname4 } from "path";
4316
4316
  var SKIP_DIRS2 = /* @__PURE__ */ new Set([
4317
4317
  "node_modules",
@@ -4333,7 +4333,6 @@ var SKIP_DIRS2 = /* @__PURE__ */ new Set([
4333
4333
  ]);
4334
4334
  var MAX_DEPTH = 8;
4335
4335
  function findAppInstance(cwd, re) {
4336
- const root = existsSync7(join11(cwd, "src")) ? join11(cwd, "src") : cwd;
4337
4336
  const found = [];
4338
4337
  const walk2 = (dir, depth) => {
4339
4338
  if (depth > MAX_DEPTH) return;
@@ -4372,7 +4371,7 @@ function findAppInstance(cwd, re) {
4372
4371
  }
4373
4372
  }
4374
4373
  };
4375
- walk2(root, 0);
4374
+ walk2(cwd, 0);
4376
4375
  if (found.length === 0) return null;
4377
4376
  const declared = declaredEntries(cwd);
4378
4377
  const chosen = found.slice().sort((a, b) => rankEntry({ ...a, declared: declared.has(a.relPath) }, { ...b, declared: declared.has(b.relPath) }))[0];
@@ -4430,16 +4429,16 @@ function importSpecifier(fromRel, toRel, preserveExtension = false) {
4430
4429
  }
4431
4430
 
4432
4431
  // src/protect/install/register.ts
4433
- import { existsSync as existsSync10 } from "fs";
4432
+ import { existsSync as existsSync9 } from "fs";
4434
4433
  import { join as join14, resolve as resolve3 } from "path";
4435
4434
 
4436
4435
  // src/protect/install/generic.ts
4437
- import { readFileSync as readFileSync9, existsSync as existsSync9, readdirSync as readdirSync4, lstatSync as lstatSync3 } from "fs";
4436
+ import { readFileSync as readFileSync9, existsSync as existsSync8, readdirSync as readdirSync4, lstatSync as lstatSync3 } from "fs";
4438
4437
  import { join as join13, dirname as dirname6 } from "path";
4439
4438
 
4440
4439
  // src/protect/install/source-scope.ts
4441
4440
  import { execFileSync } from "child_process";
4442
- import { existsSync as existsSync8 } from "fs";
4441
+ import { existsSync as existsSync7 } from "fs";
4443
4442
  import { dirname as dirname5, join as join12, resolve as resolve2 } from "path";
4444
4443
 
4445
4444
  // src/protect/install/node-flags.ts
@@ -4731,7 +4730,7 @@ function moduleKindOf(filePath, packageIsModule) {
4731
4730
  function resolvesToGuardModule(specifier, query) {
4732
4731
  const resolved = resolve2(dirname5(query.fromFile), specifier);
4733
4732
  if (withoutModuleExtension(resolved) !== join12(query.guardDir, "guard")) return false;
4734
- const existing = GUARD_FILENAMES.filter((name) => existsSync8(join12(query.guardDir, name)));
4733
+ const existing = GUARD_FILENAMES.filter((name) => existsSync7(join12(query.guardDir, name)));
4735
4734
  if (existing.length === 0) return false;
4736
4735
  const extension = /(\.[A-Za-z0-9]+)$/.exec(specifier)?.[1] ?? "";
4737
4736
  if (!query.strictExtensions) return true;
@@ -4740,7 +4739,7 @@ function resolvesToGuardModule(specifier, query) {
4740
4739
  if (query.kind === "cjs") return existing.includes("guard.js");
4741
4740
  return true;
4742
4741
  }
4743
- if (existsSync8(resolved)) return true;
4742
+ if (existsSync7(resolved)) return true;
4744
4743
  return query.kind === "ts" && extension === ".js" && existing.includes("guard.ts");
4745
4744
  }
4746
4745
  function withoutModuleExtension(filePath) {
@@ -4756,10 +4755,10 @@ function packageIsEsm(cwd) {
4756
4755
  }
4757
4756
  }
4758
4757
  function genericDir(cwd) {
4759
- return existsSync9(join13(cwd, "src")) ? "src/patchstack" : "patchstack";
4758
+ return existsSync8(join13(cwd, "src")) ? "src/patchstack" : "patchstack";
4760
4759
  }
4761
4760
  function hasTypeScript(cwd) {
4762
- return existsSync9(join13(cwd, "tsconfig.json")) || candidateEntries(cwd).some((entry) => /\.tsx?$/.test(entry));
4761
+ return existsSync8(join13(cwd, "tsconfig.json")) || candidateEntries(cwd).some((entry) => /\.tsx?$/.test(entry));
4763
4762
  }
4764
4763
  function genericGuardTarget(cwd) {
4765
4764
  if (hasTypeScript(cwd)) {
@@ -4773,7 +4772,7 @@ function genericGuardTarget(cwd) {
4773
4772
  function genericScaffoldFiles(cwd) {
4774
4773
  const dir = genericDir(cwd);
4775
4774
  const candidates = ["guard.ts", "guard.js", "guard.cjs", "rules.json"];
4776
- return candidates.map((file) => `${dir}/${file}`).filter((relative8) => existsSync9(join13(cwd, relative8)));
4775
+ return candidates.map((file) => `${dir}/${file}`).filter((relative8) => existsSync8(join13(cwd, relative8)));
4777
4776
  }
4778
4777
  function scaffoldGeneric(cwd, opts, guardTemplate, guardFile) {
4779
4778
  const target = genericGuardTarget(cwd);
@@ -4788,7 +4787,7 @@ function scaffoldGeneric(cwd, opts, guardTemplate, guardFile) {
4788
4787
  const changed = [guardRel];
4789
4788
  if (!opts.demo) bakeSiteUuid(cwd, guardRel);
4790
4789
  const rulesDst = join13(dst, "rules.json");
4791
- if (opts.demo || !existsSync9(rulesDst)) {
4790
+ if (opts.demo || !existsSync8(rulesDst)) {
4792
4791
  copyProjectFileSync(cwd, join13(templates, opts.demo ? "demo-rules.json" : "rules.json"), rulesDst);
4793
4792
  changed.push(`${dir}/rules.json`);
4794
4793
  }
@@ -4810,10 +4809,10 @@ function candidateEntries(cwd) {
4810
4809
  "app.ts",
4811
4810
  "app.js"
4812
4811
  ];
4813
- const hits = names.filter((n) => existsSync9(join13(cwd, n)));
4812
+ const hits = names.filter((n) => existsSync8(join13(cwd, n)));
4814
4813
  try {
4815
4814
  const pkg = JSON.parse(read(join13(cwd, "package.json")));
4816
- if (typeof pkg.main === "string" && existsSync9(join13(cwd, pkg.main))) hits.push(pkg.main);
4815
+ if (typeof pkg.main === "string" && existsSync8(join13(cwd, pkg.main))) hits.push(pkg.main);
4817
4816
  } catch {
4818
4817
  }
4819
4818
  return [...new Set(hits)];
@@ -4924,7 +4923,7 @@ function escapeForRegExp(value) {
4924
4923
  function genericVerify(cwd) {
4925
4924
  const dir = genericDir(cwd);
4926
4925
  const target = genericGuardTarget(cwd);
4927
- const present2 = GUARD_FILENAMES.find((name) => existsSync9(join13(cwd, dir, name)));
4926
+ const present2 = GUARD_FILENAMES.find((name) => existsSync8(join13(cwd, dir, name)));
4928
4927
  const scaffolded = present2 !== void 0;
4929
4928
  const imported = scaffolded && guardIsImported(cwd, join13(cwd, dir, present2));
4930
4929
  return {
@@ -4962,7 +4961,7 @@ var NON_RUNTIME_DIRS = /* @__PURE__ */ new Set([
4962
4961
  function guardIsImported(cwd, guardPath) {
4963
4962
  const guardDir = dirname6(guardPath);
4964
4963
  const packageIsModule = packageIsEsm(cwd);
4965
- const root = existsSync9(join13(cwd, "src")) ? join13(cwd, "src") : cwd;
4964
+ const root = existsSync8(join13(cwd, "src")) ? join13(cwd, "src") : cwd;
4966
4965
  let found = false;
4967
4966
  const walk2 = (d, depth) => {
4968
4967
  if (found || depth > 8) return;
@@ -5166,10 +5165,10 @@ function serverIsGuarded(cwd, spec, server, guardDir) {
5166
5165
  return !spec.callAfter || routesBefore(source, server.appVar, state.callIndex).length === 0;
5167
5166
  }
5168
5167
  function verifyRegister(cwd, spec) {
5169
- const dir = existsSync10(join14(cwd, "src")) ? "src/patchstack" : "patchstack";
5168
+ const dir = existsSync9(join14(cwd, "src")) ? "src/patchstack" : "patchstack";
5170
5169
  const entry = findAppInstance(cwd, spec.appRe);
5171
5170
  const target = entry ? guardTarget(cwd, entry.relPath, spec) : null;
5172
- const scaffolded = target ? existsSync10(join14(cwd, dir, target.file)) : false;
5171
+ const scaffolded = target ? existsSync9(join14(cwd, dir, target.file)) : false;
5173
5172
  const entrySource = entry ? read(join14(cwd, entry.relPath)) : "";
5174
5173
  const state = entry ? wiringState(entrySource, spec, entry.appVar, entry.relPath, { cwd, guardDir: dir }) : { importAtTopLevel: false, callInAppScope: false, ordered: false, callIndex: -1, appIndex: -1 };
5175
5174
  const wired = state.importAtTopLevel && state.callInAppScope && state.ordered;
@@ -5260,7 +5259,7 @@ var expressAdapter = {
5260
5259
  };
5261
5260
 
5262
5261
  // src/protect/install/reporting.ts
5263
- import { existsSync as existsSync11 } from "fs";
5262
+ import { existsSync as existsSync10 } from "fs";
5264
5263
  import { join as join15 } from "path";
5265
5264
 
5266
5265
  // src/protect/reporting-state.js
@@ -5321,7 +5320,7 @@ function explainReportingState(state) {
5321
5320
  function siteIdentity(cwd, env) {
5322
5321
  const rc = join15(cwd, ".patchstackrc.json");
5323
5322
  let recorded;
5324
- if (existsSync11(rc)) {
5323
+ if (existsSync10(rc)) {
5325
5324
  try {
5326
5325
  recorded = JSON.parse(read(rc)).siteUuid;
5327
5326
  } catch {
@@ -5470,7 +5469,7 @@ function runVerify(cwd) {
5470
5469
  }
5471
5470
 
5472
5471
  // src/widget.ts
5473
- import { existsSync as existsSync12, readFileSync as readFileSync10 } from "fs";
5472
+ import { existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
5474
5473
  import path11 from "path";
5475
5474
  var WIDGET_SCRIPT_URL = "https://cdn.patchstack.com/patchstack-widget.js";
5476
5475
  var WIDGET_MARKER_ATTR = "data-patchstack-connect-widget";
@@ -5509,7 +5508,7 @@ ${indent}</body>`;
5509
5508
  }
5510
5509
  function findSourceShell(cwd) {
5511
5510
  for (const candidate of SOURCE_SHELL_CANDIDATES) {
5512
- if (existsSync12(path11.join(cwd, candidate))) {
5511
+ if (existsSync11(path11.join(cwd, candidate))) {
5513
5512
  return candidate;
5514
5513
  }
5515
5514
  }
@@ -5521,7 +5520,7 @@ function ensureSourceWidget(cwd, siteUuid, jsxShell = null) {
5521
5520
  return { shell: null, action: "no-shell" };
5522
5521
  }
5523
5522
  const file = path11.join(cwd, shell);
5524
- if (!existsSync12(file)) {
5523
+ if (!existsSync11(file)) {
5525
5524
  return { shell: null, action: "no-shell" };
5526
5525
  }
5527
5526
  const before = readFileSync10(file, "utf8");
@@ -5606,7 +5605,7 @@ function detectPackageManager(cwd) {
5606
5605
  return declared;
5607
5606
  }
5608
5607
  for (const { filename, pm } of PM_BY_LOCKFILE) {
5609
- if (existsSync13(path12.join(cwd, filename))) {
5608
+ if (existsSync12(path12.join(cwd, filename))) {
5610
5609
  return pm;
5611
5610
  }
5612
5611
  }
@@ -5703,7 +5702,7 @@ function resolveWidgetFileHint(cwd, framework) {
5703
5702
  ...GENERIC_WIDGET_FILES
5704
5703
  ];
5705
5704
  for (const candidate of candidates) {
5706
- if (existsSync13(path12.join(cwd, candidate))) {
5705
+ if (existsSync12(path12.join(cwd, candidate))) {
5707
5706
  return candidate;
5708
5707
  }
5709
5708
  }
@@ -5823,7 +5822,11 @@ function renderGuideChecklist(state, useColor) {
5823
5822
  lines.push("");
5824
5823
  if (!state.hasPackageJson) {
5825
5824
  lines.push(todo("No package.json found in this directory."));
5826
- lines.push(detail("Run the guide from the project root."));
5825
+ lines.push(detail("For a JS/Node app, run the guide from its package directory; check that package.json is readable and valid."));
5826
+ lines.push(detail("For a standalone HTML/CSS/browser-JavaScript site, use the disclosure widget directly."));
5827
+ lines.push(detail("Do not create a Node project, build hooks, or a server just to install the widget."));
5828
+ lines.push(detail("Use the correct site UUID or widget snippet from the Patchstack dashboard; never invent one."));
5829
+ lines.push(detail('See "Plain HTML sites" in AGENT-INSTALL.md. Widget-only setup provides no dependency scan or runtime protection.'));
5827
5830
  return lines.join("\n");
5828
5831
  }
5829
5832
  if (countRemainingSteps(state) > 0) {
@@ -5945,11 +5948,19 @@ function renderGuideChecklist(state, useColor) {
5945
5948
  }
5946
5949
  lines.push("");
5947
5950
  if (state.claimUrl !== null) {
5948
- lines.push(` ${paint(ANSI.cyan, "\u279C")} ${paint(ANSI.bold, "Dashboard link (open to view reports):")}`);
5949
- lines.push(` ${paint(ANSI.cyan, state.claimUrl)}`);
5950
- lines.push(detail("Open this link in a browser. The CLI never opens it."));
5951
- lines.push(detail("Or from this terminal \u2192 npx @patchstack/connect claim"));
5952
- lines.push(detail(" (prints a link to sign in with, then attaches the site to that account)"));
5951
+ lines.push(` ${paint(ANSI.cyan, "\u279C")} ${paint(ANSI.bold, "Connect this site to your Patchstack account:")}`);
5952
+ let route = 1;
5953
+ if (widgetTagInPlace(state)) {
5954
+ lines.push(` ${route++}. In the preview \u2014 while the site is unclaimed the widget shows a`);
5955
+ lines.push(' "Connect this website" panel. Signing in there attaches the site.');
5956
+ }
5957
+ lines.push(` ${route++}. In a browser \u2014 open the dashboard link (the CLI never opens it):`);
5958
+ lines.push(` ${paint(ANSI.cyan, state.claimUrl)}`);
5959
+ lines.push(` ${route}. From this terminal \u2192 npx @patchstack/connect claim`);
5960
+ lines.push(" (prints a link to sign in with, then attaches the site to that account)");
5961
+ lines.push(detail("Reports have no owner to reach until one of these completes. The site UUID ships"));
5962
+ lines.push(detail("in the page and claiming is first-come, so an unclaimed site stays claimable by"));
5963
+ lines.push(detail("anyone who loads it."));
5953
5964
  if (state.endpointOverride !== null) {
5954
5965
  lines.push(detail("(this URL inherits the endpoint override above)"));
5955
5966
  }
@@ -5959,9 +5970,11 @@ function renderGuideChecklist(state, useColor) {
5959
5970
  if (widgetTagInPlace(state)) {
5960
5971
  lines.push("");
5961
5972
  lines.push(` ${paint(ANSI.cyan, "\u279C")} ${paint(ANSI.bold, "Refresh the preview to see the widget:")}`);
5962
- lines.push(' The "Report a vulnerability" button loads with the page, so a preview that was');
5963
- lines.push(" already open still shows the HTML from before this change. Builders that hot");
5964
- lines.push(" reload refresh it themselves; if the button is missing, refresh the preview once.");
5973
+ lines.push(" The widget loads with the page, so a preview that was already open still shows");
5974
+ lines.push(" the HTML from before this change. Builders that hot reload refresh it themselves;");
5975
+ lines.push(" if nothing appears, refresh the preview once.");
5976
+ lines.push(' Unclaimed, the widget shows the "Connect this website" panel; the public');
5977
+ lines.push(' "Report a vulnerability" button takes its place once the site is claimed.');
5965
5978
  }
5966
5979
  if (state.siteUuid !== null) {
5967
5980
  lines.push("");
@@ -6295,7 +6308,7 @@ async function claim2(config, onPrompt, deps = {}) {
6295
6308
  import { relative as relative4 } from "path";
6296
6309
 
6297
6310
  // src/protect/install/runtime/entry.ts
6298
- import { existsSync as existsSync14, readFileSync as readFileSync13, realpathSync as realpathSync4, statSync as statSync6 } from "fs";
6311
+ import { existsSync as existsSync13, readFileSync as readFileSync13, realpathSync as realpathSync4, statSync as statSync6 } from "fs";
6299
6312
  import { join as join16, resolve as resolve4, sep as sep2 } from "path";
6300
6313
  var CONVENTIONAL = [
6301
6314
  "server.js",
@@ -6392,14 +6405,14 @@ function resolveEntry(cwd) {
6392
6405
  }
6393
6406
  return {
6394
6407
  kind: "unavailable",
6395
- reason: existsSync14(join16(cwd, "package.json")) ? 'no directly loadable entry was found \u2014 declare one as a "start" script of the form `node <file>`, or run the check against a built output' : "there is no package.json here"
6408
+ reason: existsSync13(join16(cwd, "package.json")) ? 'no directly loadable entry was found \u2014 declare one as a "start" script of the form `node <file>`, or run the check against a built output' : "there is no package.json here"
6396
6409
  };
6397
6410
  }
6398
6411
 
6399
6412
  // src/protect/install/runtime/probe.ts
6400
6413
  import { spawn } from "child_process";
6401
6414
  import { createHash as createHash3, randomBytes } from "crypto";
6402
- import { existsSync as existsSync15 } from "fs";
6415
+ import { existsSync as existsSync14 } from "fs";
6403
6416
  import { request as httpRequest } from "http";
6404
6417
  import { request as httpsRequest, Agent as HttpsAgent } from "https";
6405
6418
  import { fileURLToPath as fileURLToPath2 } from "url";
@@ -6427,7 +6440,7 @@ function preloadPath() {
6427
6440
  ];
6428
6441
  for (const candidate of candidates) {
6429
6442
  const path18 = fileURLToPath2(candidate);
6430
- if (existsSync15(path18)) return path18;
6443
+ if (existsSync14(path18)) return path18;
6431
6444
  }
6432
6445
  return null;
6433
6446
  }
@@ -9625,14 +9638,21 @@ async function runScan(args, options = {}) {
9625
9638
  const linkUuid = response.uuid ?? config.siteUuid;
9626
9639
  if (!connected && (provisioning || claimLines.length > 0) && linkUuid !== null && linkUuid !== void 0 && linkUuid.length > 0) {
9627
9640
  console.log("");
9628
- console.log("Open this dashboard link to view vulnerability reports:");
9629
- console.log(` ${buildClaimUrl(config.endpoint, linkUuid)}`);
9641
+ console.log("Connect this site to your Patchstack account \u2014 any one of these:");
9642
+ if (config.widget) {
9643
+ console.log(' 1. In the preview \u2014 the widget shows a "Connect this website" panel while the');
9644
+ console.log(" site is unclaimed. Signing in there attaches it.");
9645
+ console.log(" 2. Open this dashboard link in a browser:");
9646
+ console.log(` ${buildClaimUrl(config.endpoint, linkUuid)}`);
9647
+ console.log(" 3. From this terminal: npx @patchstack/connect claim");
9648
+ } else {
9649
+ console.log(" 1. Open this dashboard link in a browser:");
9650
+ console.log(` ${buildClaimUrl(config.endpoint, linkUuid)}`);
9651
+ console.log(" 2. From this terminal: npx @patchstack/connect claim");
9652
+ }
9630
9653
  if (config.endpoint !== DEFAULT_ENDPOINT) {
9631
9654
  console.log(" (this URL inherits the endpoint override above)");
9632
9655
  }
9633
- console.log("");
9634
- console.log("Or attach it to your account from this terminal:");
9635
- console.log(" npx @patchstack/connect claim");
9636
9656
  }
9637
9657
  if (options.showRemainingSetup !== false) {
9638
9658
  try {
@@ -9658,7 +9678,8 @@ function reportSourceWidget(siteUuid, framework) {
9658
9678
  const result = ensureSourceWidget(process.cwd(), siteUuid, jsxShell);
9659
9679
  switch (result.action) {
9660
9680
  case "added":
9661
- console.log(`Widget: added the "Report a vulnerability" tag to ${result.shell}. Reload your preview to see it.`);
9681
+ console.log(`Widget: added the disclosure widget tag to ${result.shell}. Reload your preview to see it.`);
9682
+ console.log(' Unclaimed, it shows a "Connect this website" panel; the "Report a vulnerability" button replaces it once the site is claimed.');
9662
9683
  break;
9663
9684
  case "updated":
9664
9685
  console.log(`Widget: updated the managed tag in ${result.shell} to site ${siteUuid}.`);
@@ -9890,6 +9911,7 @@ async function runSetup(args) {
9890
9911
  const before = await collectGuideState(process.cwd());
9891
9912
  if (!before.hasPackageJson) {
9892
9913
  console.error("Error: no package.json found. Run setup from the project root.");
9914
+ console.error("For a standalone HTML site, use the widget-only instructions in AGENT-INSTALL.md; do not create a Node project just to run setup.");
9893
9915
  return 1;
9894
9916
  }
9895
9917
  if (before.installed === null) {
@@ -9967,7 +9989,7 @@ function setupOutcome(state, protection, wired) {
9967
9989
  if (protection.install.status === "not-applicable" && protection.install.leftovers.length > 0) {
9968
9990
  warnings.push(`earlier guard scaffold does nothing here and can be deleted: ${protection.install.leftovers.join(", ")}`);
9969
9991
  }
9970
- if (state.claimUrl !== null) warnings.push("the site is not attached to an account until the dashboard link is opened or `npx @patchstack/connect claim` completes");
9992
+ if (state.claimUrl !== null) warnings.push('the site is not attached to an account until someone signs in through the widget\'s "Connect this website" panel, the dashboard link is opened, or `npx @patchstack/connect claim` completes');
9971
9993
  if (warnings.length > 0) lines.push(["Warnings", warnings.join("; ")]);
9972
9994
  return lines;
9973
9995
  }
@@ -9985,7 +10007,9 @@ async function runStatus(args) {
9985
10007
  console.log(`Environment: ${config.environment}`);
9986
10008
  if (config.siteUuid !== null) {
9987
10009
  console.log(`Dashboard URL: ${buildClaimUrl(config.endpoint, config.siteUuid)}`);
9988
- console.log(" Not attached to an account yet? Run `npx @patchstack/connect claim`.");
10010
+ console.log(` Not attached to an account yet? Sign in through the widget's "Connect this`);
10011
+ console.log(' website" panel on the preview, open the link above, or run');
10012
+ console.log(" `npx @patchstack/connect claim`.");
9989
10013
  switch (await fetchSiteStatus(config)) {
9990
10014
  case "active":
9991
10015
  console.log("Site status: active on Patchstack");