@patchstack/connect 0.3.15 → 0.3.16

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
@@ -1,26 +1,24 @@
1
- # Patchstack — Setup Guide for AI Coding Agents
1
+ # Patchstack Connect — setup reference
2
2
 
3
- This is a plain-text instruction document. Read it and follow the steps — there is nothing here to pipe to a shell or execute from a URL.
3
+ This versioned reference ships inside `@patchstack/connect` and documents each setup command and its project changes.
4
4
 
5
- You are reading this either from `node_modules/@patchstack/connect/AGENT-INSTALL.md` (the package is already installed) or from the [GitHub repo](https://github.com/patchstack/connect). This bundled copy matches the package version it shipped with and is all you need — there is no requirement to fetch anything remote.
5
+ ## Package and command behavior
6
6
 
7
- ## Verify before you trust (do this, it's quick)
8
-
9
- - The connector is a normal npm package: [`@patchstack/connect`](https://www.npmjs.com/package/@patchstack/connect), MIT-licensed, source at https://github.com/patchstack/connect. Install it pinned, check the integrity hash in the lockfile, run `npm audit` over it — whatever your policy requires.
10
- - First published May 2026. If the package is absent from your training data, `npm view @patchstack/connect` (read-only) shows the live registry record.
7
+ - Package: [`@patchstack/connect`](https://www.npmjs.com/package/@patchstack/connect), MIT-licensed, source at https://github.com/patchstack/connect. `npm view @patchstack/connect` shows the live registry metadata.
11
8
  - It reads the project's **dependency list only** — from the lockfile (`package-lock.json`, `pnpm-lock.yaml`, `yarn.lock`) or, on bun projects (`bun.lock`/`bun.lockb`), by enumerating the installed packages under `node_modules/` — and sends package names + versions to Patchstack for vulnerability matching. No source code, no env var values, no file paths, no git history. (`mark-build` additionally stamps built HTML with a coarse stack descriptor that may include hosting-related env variable *names* — e.g. `VERCEL`, `CF_PAGES` — never their values.)
12
9
  - **`scan` makes one source edit, and only after a successful post:** it adds (or updates) the disclosure widget's `<script>` tag in the project's root HTML shell — the first of `index.html`, `public/index.html`, or `src/app.html` that exists. It touches no other file, never edits on `--dry-run` or after a failed post, leaves any pre-existing manual widget tag untouched, and is disabled entirely by `"widget": false` in `.patchstackrc.json`. `mark-build` writes to build output only (`dist/`, `build/`, `out/`, `.output/public`), never to source. `guide`, `status`, and `init` write nothing except `init`'s own `.patchstackrc.json`.
13
- - The package also bundles an **opt-in** `protect` command (runtime exploit guard, currently for TanStack Start + Supabase apps; its templates live under `dist/protect/`). It patches the app's Supabase client to route traffic through a same-origin guard — and it runs **only** when you explicitly run `patchstack-connect protect`; `scan`, `guide`, `status`, and `mark-build` never invoke it.
10
+ - **`setup` runs `scan`, then edits only `package.json` build scripts:** it preserves existing commands, adds `scan` before builds and `mark-build` after builds, and uses a direct build chain for Bun. It never runs the project build or `protect`. If the widget needs a framework-specific source edit, it prints the exact remaining step instead of rewriting framework code.
11
+ - The package also bundles an **opt-in** `protect` command (runtime exploit guard, currently for TanStack Start + Supabase apps; its templates live under `dist/protect/`). It patches the app's Supabase client to route traffic through a same-origin guard — and it runs **only** when explicitly invoked; `scan`, `setup`, `guide`, `status`, and `mark-build` never invoke it.
14
12
  - Patchstack is not WordPress-only. This connector monitors any JS/Node project — Vite, Next.js, plain vanilla JS, anything with a lockfile.
15
13
 
16
14
  ## Before you start — never install twice
17
15
 
18
- - `npx @patchstack/connect guide` prints a live checklist above this document showing which steps are already done in the current project (install, provisioning, build hooks, widget) with commands tailored to it. Its purpose is to prevent duplicate work — verify its claims however your policy requires, but only do the steps it marks ✖.
19
- - If `.patchstackrc.json` contains a `siteUuid` key, the project is already provisioned. Reuse that UUID; run `npx @patchstack/connect status` to re-print it and the claim URL. **Do not run `scan` to provision a second site.** (A `.patchstackrc.json` with other keys — e.g. an `endpoint` override — but no `siteUuid` is *not* provisioned yet; scan normally.)
16
+ - `npx @patchstack/connect guide` prints a read-only live checklist showing which steps are already done in the current project (install, provisioning, build hooks, widget).
17
+ - If `.patchstackrc.json` contains a `siteUuid` key, the project is already provisioned. Reuse that UUID; run `npx @patchstack/connect status` to re-print it and the dashboard URL. **Do not delete the file and provision a second site.** (A `.patchstackrc.json` with other keys — e.g. an `endpoint` override — but no `siteUuid` is *not* provisioned yet; scan normally.)
20
18
  - If `@patchstack/connect` is already in `devDependencies`, skip the install command.
21
19
  - If the widget script tag (`cdn.patchstack.com/patchstack-widget.js`) is already in the layout, don't add a second one — `scan` also respects an existing tag: it updates its own managed tag in place and leaves a manual one untouched.
22
20
 
23
- ## Install steps (do the ones the checklist marks ✖, in order; they are designed to run back-to-back — if one fails, stop and report rather than improvising)
21
+ ## Automated setup
24
22
 
25
23
  1. **Install** (skip if already present), matching the project's package manager:
26
24
 
@@ -31,15 +29,25 @@ You are reading this either from `node_modules/@patchstack/connect/AGENT-INSTALL
31
29
  bun add -d @patchstack/connect # bun.lock / bun-managed platforms
32
30
  ```
33
31
 
34
- 2. **First scan** — provisions a Patchstack site automatically, writes the UUID to `.patchstackrc.json`, and installs the disclosure widget's `<script>` tag into the root HTML shell (`index.html`, `public/index.html`, or `src/app.html`) when one exists. No signup, dashboard step, or UUID is needed up front:
32
+ 2. **Run bounded setup:**
33
+
34
+ ```
35
+ npx @patchstack/connect setup
36
+ ```
37
+
38
+ This provisions or reuses the site, manages the widget, wires the build scripts, prints a dashboard link, and finishes with the same status shown by `guide`. Re-running it reuses existing configuration, widget tags, and build commands rather than duplicating them.
39
+
40
+ ## Manual setup
41
+
42
+ 1. **First scan** — provisions a Patchstack site automatically, writes the UUID to `.patchstackrc.json`, and installs the disclosure widget's `<script>` tag into the root HTML shell (`index.html`, `public/index.html`, or `src/app.html`) when one exists. No signup, dashboard step, or UUID is needed up front:
35
43
 
36
44
  ```
37
45
  npx @patchstack/connect scan
38
46
  ```
39
47
 
40
- It prints a claim URL. Surface that URL to the user verbatim — never fetch it yourself; it must be opened in their browser. It also prints what it did about the widget — if it added the tag, reload the preview and confirm the "Report a vulnerability" button appears.
48
+ It prints a dashboard link but never opens it. Open that link in a browser to view reports. It also prints what it did about the widget — if it added the tag, reload the preview and confirm the "Report a vulnerability" button appears.
41
49
 
42
- 3. **Wire builds** in `package.json`:
50
+ 2. **Wire builds** in `package.json`:
43
51
 
44
52
  ```jsonc
45
53
  {
@@ -54,7 +62,7 @@ You are reading this either from `node_modules/@patchstack/connect/AGENT-INSTALL
54
62
 
55
63
  **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"`.
56
64
 
57
- 4. **Verify the disclosure widget** — a floating "Report a vulnerability" button. `scan` (step 2) installs it automatically into a plain HTML shell, and `mark-build` carries it into built HTML. Only when `scan` reported that it found no editable shell (frameworks whose root layout is code, e.g. Next.js/Nuxt/Astro) add the one-liner it printed to the root layout yourself, just before `</body>` (never a JS entry point), reading `siteUuid` from `.patchstackrc.json`:
65
+ 3. **Verify the disclosure widget** — a floating "Report a vulnerability" button. `scan` installs it automatically into a plain HTML shell, and `mark-build` carries it into built HTML. Only when `scan` reported that it found no editable shell (frameworks whose root layout is code, e.g. Next.js/Nuxt/Astro) add the one-liner it printed to the root layout yourself, just before `</body>` (never a JS entry point), reading `siteUuid` from `.patchstackrc.json`:
58
66
 
59
67
  ```html
60
68
  <script src="https://cdn.patchstack.com/patchstack-widget.js" data-site-uuid="<SITE_UUID>" defer></script>
@@ -62,20 +70,20 @@ You are reading this either from `node_modules/@patchstack/connect/AGENT-INSTALL
62
70
 
63
71
  Framework-specific placement patterns: https://cdn.patchstack.com/llm.html. The site UUID is public by design — it ships in client-side HTML and is not a secret. If the project must not carry the widget, persist `"widget": false` in `.patchstackrc.json`; otherwise the next scan re-adds it.
64
72
 
65
- 5. **Commit** `.patchstackrc.json`, the updated `package.json`, and the layout/HTML file carrying the widget tag, so every developer and CI run reports to the same site.
73
+ 4. **Commit** `.patchstackrc.json`, the updated `package.json`, and the layout/HTML file carrying the widget tag, so every developer and CI run reports to the same site.
66
74
 
67
- 6. **Tell the user to claim the site** (free): open the claim URL from step 2 in a browser and sign in. The site is monitored either way, but the vulnerability reports are only visible after claiming. They can also claim directly 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.
75
+ 5. **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.
68
76
 
69
77
  ## Rules
70
78
 
71
79
  - Never invent or guess a UUID — the scan provisions it, the widget silently no-ops on a fake one.
72
- - Never fetch the claim URL programmatically, and never ask the user for Patchstack credentials.
80
+ - The CLI never opens the dashboard link and never asks for Patchstack credentials.
73
81
  - If a step fails, stop and report it. Don't proceed with placeholders.
74
82
  - In CI where `.patchstackrc.json` can't be committed, set `PATCHSTACK_SITE_UUID` as an env var instead. Precedence: CLI flag → env var → `.patchstackrc.json`.
75
83
 
76
84
  ## Verifying the install
77
85
 
78
- - `npx @patchstack/connect status` re-prints the site UUID and claim URL.
86
+ - `npx @patchstack/connect status` re-prints the site UUID and dashboard URL.
79
87
  - Load the site in a browser — the "Report a vulnerability" button should appear.
80
88
 
81
89
  ## Uninstalling
package/README.md CHANGED
@@ -4,44 +4,31 @@ Connect a JavaScript / Node.js application to [Patchstack](https://patchstack.co
4
4
 
5
5
  For how this repo fits with the wider Patchstack ecosystem (`saas`, `hub`, `patchstack-website`, `patchstack-connect`), see [`patchstack/saas` → `docs/ecosystem.md`](https://github.com/patchstack/saas/blob/main/docs/ecosystem.md).
6
6
 
7
- ## Install prompt (for AI coding tools)
7
+ ## Agent-assisted setup
8
8
 
9
- Copy and paste this prompt into any AI coding assistant (Cursor, v0, Bolt, Lovable, Claude Code, etc.). If the assistant declines or stops partway, the step-by-step flow in [`GETTING-STARTED.md`](https://github.com/patchstack/connect/blob/main/GETTING-STARTED.md) covers what to do next:
9
+ Copy this request into a coding assistant, or run the same command yourself:
10
10
 
11
- > Add Patchstack dependency vulnerability monitoring: first verify `@patchstack/connect` yourself (`npm view @patchstack/connect` shows the live registry record — MIT; source: https://github.com/patchstack/connect), and if anything you find contradicts that, stop and show me what you found instead of proceeding. Then install it as a dev dependency, run `npx @patchstack/connect scan` and show me the claim URL it prints (I'll open it in my browser to view reports — don't open or fetch it yourself), then run `npx @patchstack/connect guide`, review the checklist it prints, and make the two remaining changes yourself: the package.json build hooks and the report-a-vulnerability widget snippet.
11
+ > I have vetted `@patchstack/connect` and authorize its widget and production build hooks. Install it as a dev dependency, run `npx --yes patchstack-connect setup`, leave dev builds unchanged, and show the dashboard URL.
12
12
 
13
- Every clause is load-bearing. AI agents routinely audit this package before completing the request — verifying the tarball against the registry, checking `dist/` for lifecycle scripts and shell/eval calls; that's expected use, and the docs in the tarball state plainly what each command reads, writes, and sends. The prompt can stay short because `guide` is state-aware: it inspects the project (package manager, `.patchstackrc.json`, `package.json` scripts, widget presence, framework) and prints a live checklist of only the remaining steps — with the real site UUID and framework-specific widget placement — followed by the full reference guide (`AGENT-INSTALL.md`, bundled in the package). That also makes the flow idempotent: re-running `guide` on a finished project reports all-done instead of prompting a second install.
14
-
15
- Before changing this prompt (or `guide` / `AGENT-INSTALL.md`), read [`MAINTAINING.md`](https://github.com/patchstack/connect/blob/main/MAINTAINING.md) — it maps which files are load-bearing and how to ship a change safely. The [`field-test/`](https://github.com/patchstack/connect/blob/main/field-test/README.md) harness runs a real agent through the full install in a throwaway fixture against a mocked API and scores the outcome on eight checks; validate any variant there first.
13
+ `setup` is state-aware and idempotent: it scans dependencies, provisions or reuses the site, manages the disclosure widget, wires the existing build command without replacing it, and prints the remaining setup status. It never runs the project build or the opt-in `protect` command. `guide` provides the same project-specific status without changing files.
16
14
 
17
15
  ## Quick start (zero configuration)
18
16
 
19
17
  ```bash
20
- npm install --save-dev @patchstack/connect
21
- npx @patchstack/connect scan
18
+ npm install --save-dev @patchstack/connect && npx @patchstack/connect setup
22
19
  ```
23
20
 
24
- > **Use your project's own package manager.** On bun-managed projects (Lovable, Bolt, most vibe-coding platforms) install with `bun add -d @patchstack/connect` instead — running `npm install` there plants a `package-lock.json` that the platform's native dependency flow never updates again, leaving a stale lockfile next to the live one. The connector detects and works around that (see *Stale lockfiles* below), but not creating the fossil is better.
21
+ > **Use your project's own package manager.** On Bun-managed projects (including many Lovable projects) install with `bun add -d @patchstack/connect` instead — running `npm install` there plants a `package-lock.json` that the platform's native dependency flow never updates again, leaving a stale lockfile next to the live one. The connector detects and works around that (see *Stale lockfiles* below), but not creating the fossil is better.
25
22
 
26
- That's it. The first `scan`:
23
+ That's it. `setup`:
27
24
 
28
25
  1. Reads your lockfile (see *Supported lockfiles*).
29
26
  2. POSTs the package list to Patchstack with **no** UUID.
30
27
  3. Patchstack provisions a fresh site and returns its UUID.
31
28
  4. The connector writes the UUID to `.patchstackrc.json` so the next `scan` targets the same site.
32
29
  5. The connector installs the disclosure widget's `<script>` tag into your root HTML shell (see *The disclosure widget* below) so the "Report a vulnerability" button shows up on the next preview reload.
33
- 6. The connector prints a claim URL — open it in a browser to attach the new site to your Patchstack account. You can re-display it any time with `npx @patchstack/connect status`.
34
-
35
- Then wire it into builds:
36
-
37
- ```jsonc
38
- // package.json
39
- {
40
- "scripts": {
41
- "prebuild": "patchstack-connect scan"
42
- }
43
- }
44
- ```
30
+ 6. Wires `scan` before builds and `mark-build` after builds, preserving existing commands and using direct build chaining for Bun.
31
+ 7. Prints a dashboard link — open it in a browser to attach the new site to your Patchstack account. You can re-display it any time with `npx @patchstack/connect status`.
45
32
 
46
33
  ## Quick start (existing site)
47
34
 
@@ -50,7 +37,7 @@ If you already created an "Application" site in the Patchstack dashboard, pre-se
50
37
  ```bash
51
38
  npm install --save-dev @patchstack/connect
52
39
  npx @patchstack/connect init <your-site-uuid>
53
- npx @patchstack/connect scan
40
+ npx @patchstack/connect setup
54
41
  ```
55
42
 
56
43
  ## CLI
@@ -62,6 +49,9 @@ patchstack-connect scan [options] Scan the lockfile and POST to
62
49
  successful post, adds/updates the disclosure
63
50
  widget tag in the root HTML shell (opt out
64
51
  with "widget": false in .patchstackrc.json)
52
+ patchstack-connect setup [options] Run scan, manage the widget, and idempotently
53
+ wire package.json build scripts. Never runs
54
+ the project build or protect
65
55
  patchstack-connect init <site-uuid> Optional: pre-seed .patchstackrc.json with
66
56
  an existing site UUID
67
57
  patchstack-connect status [options] Show current configuration
@@ -75,7 +65,7 @@ patchstack-connect protect Opt-in: install the always-on
75
65
  guard (currently TanStack Start + Supabase; it
76
66
  patches the app's Supabase client to route
77
67
  traffic through a same-origin guard). Never
78
- run by scan/guide/mark-build.
68
+ run by scan/setup/guide/mark-build.
79
69
  patchstack-connect help Print help
80
70
 
81
71
  Options (for scan and status):
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
4
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
5
5
 
6
6
  // src/parsers/index.ts
7
7
  import { access } from "fs/promises";
@@ -1324,11 +1324,11 @@ var INSTALL_COMMANDS = {
1324
1324
  bun: "bun add -d @patchstack/connect"
1325
1325
  };
1326
1326
  var PM_BY_LOCKFILE = [
1327
- { filename: "package-lock.json", pm: "npm" },
1328
1327
  { filename: "bun.lock", pm: "bun" },
1329
1328
  { filename: "bun.lockb", pm: "bun" },
1330
1329
  { filename: "pnpm-lock.yaml", pm: "pnpm" },
1331
- { filename: "yarn.lock", pm: "yarn" }
1330
+ { filename: "yarn.lock", pm: "yarn" },
1331
+ { filename: "package-lock.json", pm: "npm" }
1332
1332
  ];
1333
1333
  var WIDGET_FILE_CANDIDATES = {
1334
1334
  next: [
@@ -1383,6 +1383,10 @@ var WIDGET_SCAN_MAX_FILES = 4e3;
1383
1383
  var WIDGET_SCAN_MAX_DEPTH = 6;
1384
1384
  var WIDGET_SCAN_MAX_BYTES = 512 * 1024;
1385
1385
  function detectPackageManager(cwd) {
1386
+ const declared = readPackageJson(cwd)?.packageManager?.split("@")[0];
1387
+ if (declared === "npm" || declared === "pnpm" || declared === "yarn" || declared === "bun") {
1388
+ return declared;
1389
+ }
1386
1390
  for (const { filename, pm } of PM_BY_LOCKFILE) {
1387
1391
  if (existsSync3(path9.join(cwd, filename))) {
1388
1392
  return pm;
@@ -1587,8 +1591,8 @@ function renderGuideChecklist(state, useColor) {
1587
1591
  } else {
1588
1592
  lines.push(todo("Provision the site \u2014 run the first scan"));
1589
1593
  lines.push(detail("Run \u2192 npx @patchstack/connect scan"));
1590
- lines.push(detail("reads the lockfile, registers the project, writes .patchstackrc.json,"));
1591
- lines.push(detail("and prints a claim URL \u2014 show that URL to the user; never open it yourself."));
1594
+ lines.push(detail("Reads the lockfile, registers the project, writes .patchstackrc.json,"));
1595
+ lines.push(detail("and prints a dashboard link. The CLI prints the link but never opens it."));
1592
1596
  }
1593
1597
  if (state.prebuildWired && state.postbuildWired) {
1594
1598
  lines.push(done("Build hooks wired (scan before builds, mark-build after)"));
@@ -1626,14 +1630,14 @@ function renderGuideChecklist(state, useColor) {
1626
1630
  }
1627
1631
  lines.push("");
1628
1632
  if (state.claimUrl !== null) {
1629
- lines.push(` ${paint(ANSI.cyan, "\u279C")} ${paint(ANSI.bold, "Claim the site (free, opens the dashboard):")}`);
1633
+ lines.push(` ${paint(ANSI.cyan, "\u279C")} ${paint(ANSI.bold, "Dashboard link (open to view reports):")}`);
1630
1634
  lines.push(` ${paint(ANSI.cyan, state.claimUrl)}`);
1631
- lines.push(detail("Open in a browser. AI agents: show this URL to the user verbatim."));
1635
+ lines.push(detail("Open this link in a browser. The CLI never opens it."));
1632
1636
  if (state.endpointOverride !== null) {
1633
1637
  lines.push(detail("(this URL inherits the endpoint override above)"));
1634
1638
  }
1635
1639
  } else {
1636
- lines.push(detail("The claim URL appears after the first scan (re-print any time with `status`)."));
1640
+ lines.push(detail("The dashboard link appears after the first scan (re-print any time with `status`)."));
1637
1641
  }
1638
1642
  const remaining = countRemainingSteps(state);
1639
1643
  lines.push("");
@@ -1647,7 +1651,7 @@ function renderGuideChecklist(state, useColor) {
1647
1651
  )
1648
1652
  );
1649
1653
  if (state.claimUrl !== null) {
1650
- lines.push(detail("The only manual action left is claiming the site via the URL above (if not already claimed)."));
1654
+ lines.push(detail("The only manual action left is opening the dashboard link above (if not already connected)."));
1651
1655
  }
1652
1656
  } else {
1653
1657
  lines.push(
@@ -1802,6 +1806,78 @@ function runProtect(cwd) {
1802
1806
  log("done \u2014 guard wired and always-on (blocks by default). Set PATCHSTACK_MODE=dry-run for log-only.");
1803
1807
  }
1804
1808
 
1809
+ // src/setup.ts
1810
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
1811
+ import path10 from "path";
1812
+ var SCAN_COMMAND = "patchstack-connect scan";
1813
+ var MARK_BUILD_COMMAND = "patchstack-connect mark-build";
1814
+ function appendHook(existing, command) {
1815
+ if (existing === void 0 || existing.trim().length === 0) {
1816
+ return command;
1817
+ }
1818
+ if (existing.includes(command)) {
1819
+ return existing;
1820
+ }
1821
+ return `${existing} && ${command}`;
1822
+ }
1823
+ function wireBuildScripts(cwd, packageManager) {
1824
+ const target = path10.join(cwd, "package.json");
1825
+ const raw = readFileSync4(target, "utf8");
1826
+ const pkg = JSON.parse(raw);
1827
+ const scripts = pkg.scripts ?? {};
1828
+ const build = scripts.build;
1829
+ if (build === void 0 || build.trim().length === 0) {
1830
+ return {
1831
+ changed: false,
1832
+ strategy: "skipped",
1833
+ detail: "package.json has no build script; no build integration was added."
1834
+ };
1835
+ }
1836
+ if (packageManager === "bun") {
1837
+ let nextBuild = build;
1838
+ if (!nextBuild.includes(SCAN_COMMAND)) {
1839
+ nextBuild = `${SCAN_COMMAND} && ${nextBuild}`;
1840
+ }
1841
+ if (!nextBuild.includes(MARK_BUILD_COMMAND)) {
1842
+ nextBuild = `${nextBuild} && ${MARK_BUILD_COMMAND}`;
1843
+ }
1844
+ if (nextBuild === build) {
1845
+ return {
1846
+ changed: false,
1847
+ strategy: "build-chain",
1848
+ detail: "build script already runs scan and mark-build."
1849
+ };
1850
+ }
1851
+ scripts.build = nextBuild;
1852
+ } else {
1853
+ const prebuild = appendHook(scripts.prebuild, SCAN_COMMAND);
1854
+ const postbuild = appendHook(scripts.postbuild, MARK_BUILD_COMMAND);
1855
+ if (prebuild === scripts.prebuild && postbuild === scripts.postbuild) {
1856
+ return {
1857
+ changed: false,
1858
+ strategy: "lifecycle-hooks",
1859
+ detail: "prebuild and postbuild hooks are already wired."
1860
+ };
1861
+ }
1862
+ scripts.prebuild = prebuild;
1863
+ scripts.postbuild = postbuild;
1864
+ }
1865
+ pkg.scripts = scripts;
1866
+ const indentMatch = raw.match(/^[\t ]+(?=")/m)?.[0];
1867
+ const indent = indentMatch?.includes(" ") ? " " : indentMatch?.length ?? 2;
1868
+ const trailingNewline = raw.endsWith("\n") ? "\n" : "";
1869
+ writeFileSync3(target, `${JSON.stringify(pkg, null, indent)}${trailingNewline}`, "utf8");
1870
+ return packageManager === "bun" ? {
1871
+ changed: true,
1872
+ strategy: "build-chain",
1873
+ detail: "chained scan and mark-build around the existing build command."
1874
+ } : {
1875
+ changed: true,
1876
+ strategy: "lifecycle-hooks",
1877
+ detail: "added scan to prebuild and mark-build to postbuild."
1878
+ };
1879
+ }
1880
+
1805
1881
  // src/cli.ts
1806
1882
  var HELP = `@patchstack/connect \u2014 scan your lockfile and report packages to Patchstack.
1807
1883
 
@@ -1814,6 +1890,9 @@ Usage:
1814
1890
  HTML shell (index.html, public/index.html,
1815
1891
  or src/app.html) \u2014 opt out with
1816
1892
  "widget": false in .patchstackrc.json
1893
+ patchstack-connect setup [options] Finish the bounded project setup: run scan,
1894
+ manage the widget, and wire package.json build
1895
+ scripts. Never runs a build or protect
1817
1896
  patchstack-connect init <site-uuid> Optional: pre-seed .patchstackrc.json
1818
1897
  with an existing site UUID
1819
1898
  patchstack-connect status [options] Show current configuration
@@ -1829,7 +1908,7 @@ Usage:
1829
1908
  guide even when setup is complete
1830
1909
  patchstack-connect help Print this message
1831
1910
 
1832
- Options (for scan and status):
1911
+ Options (for scan, setup, and status):
1833
1912
  --site-uuid <uuid> Override the configured site UUID
1834
1913
  --endpoint <url> Override the API endpoint
1835
1914
  --dry-run (scan only) Show the payload without posting
@@ -1847,6 +1926,7 @@ Environment:
1847
1926
  Precedence: CLI flag > environment variable > .patchstackrc.json.
1848
1927
 
1849
1928
  Examples:
1929
+ npx @patchstack/connect setup
1850
1930
  npx @patchstack/connect scan
1851
1931
  npx @patchstack/connect scan --dry-run
1852
1932
  npx @patchstack/connect init 550e8400-e29b-41d4-a716-446655440000
@@ -1904,7 +1984,7 @@ async function runInit(args) {
1904
1984
  console.log("Next: run `npx @patchstack/connect scan` to send your first manifest.");
1905
1985
  return 0;
1906
1986
  }
1907
- async function runScan(args) {
1987
+ async function runScan(args, options = {}) {
1908
1988
  const dryRun = args.flags.get("dry-run") === true;
1909
1989
  const config = await resolveConfig({
1910
1990
  cwd: process.cwd(),
@@ -1971,24 +2051,26 @@ async function runScan(args) {
1971
2051
  }
1972
2052
  if (provisioning && response.uuid !== void 0 && response.uuid.length > 0) {
1973
2053
  console.log("");
1974
- console.log("Claim this site to view vulnerability reports in your Patchstack dashboard:");
2054
+ console.log("Open this dashboard link to view vulnerability reports:");
1975
2055
  console.log(` ${buildClaimUrl(config.endpoint, response.uuid)}`);
1976
2056
  if (config.endpoint !== DEFAULT_ENDPOINT) {
1977
2057
  console.log(" (this URL inherits the endpoint override above)");
1978
2058
  }
1979
2059
  }
1980
- try {
1981
- const state = await collectGuideState(process.cwd());
1982
- const remaining = countRemainingSteps(state);
1983
- if (remaining > 0) {
1984
- const hooksMissing = !(state.prebuildWired && state.postbuildWired);
1985
- console.log("");
1986
- console.log(
1987
- `Setup not complete \u2014 ${remaining} step(s) remaining${hooksMissing ? ", including the package.json build hooks (which scan can't wire)" : ""}.`
1988
- );
1989
- console.log("Run `npx @patchstack/connect guide` for the exact steps to finish for this project.");
2060
+ if (options.showRemainingSetup !== false) {
2061
+ try {
2062
+ const state = await collectGuideState(process.cwd());
2063
+ const remaining = countRemainingSteps(state);
2064
+ if (remaining > 0) {
2065
+ const hooksMissing = !(state.prebuildWired && state.postbuildWired);
2066
+ console.log("");
2067
+ console.log(
2068
+ `Setup not complete \u2014 ${remaining} step(s) remaining${hooksMissing ? ", including the package.json build hooks (which scan can't wire)" : ""}.`
2069
+ );
2070
+ console.log("Run `npx @patchstack/connect guide` for the exact steps to finish for this project.");
2071
+ }
2072
+ } catch {
1990
2073
  }
1991
- } catch {
1992
2074
  }
1993
2075
  return 0;
1994
2076
  }
@@ -2053,7 +2135,46 @@ async function runGuide(args) {
2053
2135
  console.log("\u2014\u2014\u2014\u2014 Full reference guide (ships as AGENT-INSTALL.md) \u2014\u2014\u2014\u2014");
2054
2136
  console.log("");
2055
2137
  const guidePath = new URL("../AGENT-INSTALL.md", import.meta.url);
2056
- console.log(readFileSync4(guidePath, "utf8"));
2138
+ console.log(readFileSync5(guidePath, "utf8"));
2139
+ return 0;
2140
+ }
2141
+ async function runSetup(args) {
2142
+ if (args.flags.get("dry-run") === true) {
2143
+ console.error("Error: setup does not support --dry-run. Use `scan --dry-run` to preview the manifest.");
2144
+ return 1;
2145
+ }
2146
+ const before = await collectGuideState(process.cwd());
2147
+ if (!before.hasPackageJson) {
2148
+ console.error("Error: no package.json found. Run setup from the project root.");
2149
+ return 1;
2150
+ }
2151
+ if (before.installed === null) {
2152
+ console.error(
2153
+ `Error: @patchstack/connect is not declared in package.json.
2154
+ Run: ${installCommand(before.packageManager)}`
2155
+ );
2156
+ return 1;
2157
+ }
2158
+ console.log("Patchstack setup \u2014 applying bounded project changes");
2159
+ console.log(" 1. Scan dependencies, provision/reuse the site, and manage the source widget");
2160
+ const scanCode = await runScan(args, { showRemainingSetup: false });
2161
+ if (scanCode !== 0) {
2162
+ return scanCode;
2163
+ }
2164
+ console.log("");
2165
+ console.log(" 2. Wire scan and mark-build into package.json");
2166
+ const wired = wireBuildScripts(process.cwd(), before.packageManager);
2167
+ console.log(`Build integration: ${wired.detail}`);
2168
+ console.log("");
2169
+ console.log(" 3. Verify setup status");
2170
+ const after = await collectGuideState(process.cwd());
2171
+ const useColor = process.stdout.isTTY === true && process.env.NO_COLOR === void 0;
2172
+ console.log(renderGuideChecklist(after, useColor));
2173
+ const remaining = countRemainingSteps(after);
2174
+ if (remaining > 0) {
2175
+ console.log("");
2176
+ console.log(`Setup applied its bounded changes; ${remaining} manual step(s) remain above.`);
2177
+ }
2057
2178
  return 0;
2058
2179
  }
2059
2180
  async function runStatus(args) {
@@ -2069,7 +2190,7 @@ async function runStatus(args) {
2069
2190
  console.log(`Timeout: ${config.timeoutMs}ms`);
2070
2191
  console.log(`Environment: ${config.environment}`);
2071
2192
  if (config.siteUuid !== null) {
2072
- console.log(`Claim URL: ${buildClaimUrl(config.endpoint, config.siteUuid)}`);
2193
+ console.log(`Dashboard URL: ${buildClaimUrl(config.endpoint, config.siteUuid)}`);
2073
2194
  }
2074
2195
  return 0;
2075
2196
  }
@@ -2129,7 +2250,7 @@ async function runMarkBuild(args) {
2129
2250
  let marked = 0;
2130
2251
  let widgetTouched = 0;
2131
2252
  for (const file of files) {
2132
- const before = readFileSync4(file, "utf8");
2253
+ const before = readFileSync5(file, "utf8");
2133
2254
  let after = injectMarker(before, snippet);
2134
2255
  if (widgetUuid !== null) {
2135
2256
  const ensured = ensureWidgetInHtml(after, widgetUuid);
@@ -2139,7 +2260,7 @@ async function runMarkBuild(args) {
2139
2260
  after = ensured.html;
2140
2261
  }
2141
2262
  if (after !== before) {
2142
- writeFileSync3(file, after);
2263
+ writeFileSync4(file, after);
2143
2264
  marked += 1;
2144
2265
  }
2145
2266
  }
@@ -2168,6 +2289,8 @@ async function main() {
2168
2289
  return runProtectCommand(args);
2169
2290
  case "guide":
2170
2291
  return runGuide(args);
2292
+ case "setup":
2293
+ return runSetup(args);
2171
2294
  default:
2172
2295
  console.error(`Unknown command: ${args.command}
2173
2296
  `);