@sprid/scout 0.1.2 → 0.1.4

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/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.4 - 2026-10-30
4
+
5
+ - Runs on Windows and Linux: finds Chrome under Program Files and LocalAppData (or the usual Linux paths), and stops a leftover research profile without `pkill`.
6
+ - `sprid-scout-quick` says it needs macOS instead of failing on a missing `osascript`.
7
+
8
+ ## 0.1.3 - 2026-10-25
9
+
10
+ - Update the Zod dependency to version 4.
11
+
3
12
  ## 0.1.2 - 2026-10-23
4
13
 
5
14
  - `job_results` refuses a run id that is not one scout issued. A crafted id could read any JSON file on the machine.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sprid/scout",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Local MCP server that researches a niche over your own Chrome: the accounts winning it, and the ads running in it.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "homepage": "https://sprid.studio/agents",
@@ -25,7 +25,7 @@
25
25
  "dependencies": {
26
26
  "@modelcontextprotocol/sdk": "^1.30.0",
27
27
  "playwright-core": "^1.58.0",
28
- "zod": "^3.25.76"
28
+ "zod": "^4.6.6"
29
29
  },
30
30
  "publishConfig": {
31
31
  "access": "public"
package/src/chrome.js CHANGED
@@ -225,6 +225,20 @@ function trackFeedRequests(page, pending) {
225
225
 
226
226
  async function killChrome(profileDir) {
227
227
  const pattern = `user-data-dir=${profileDir}`;
228
+ if (process.platform === "win32") {
229
+ // No pkill: match the profile on the command line through CIM. The pattern
230
+ // travels in the environment, so a path is never parsed as PowerShell.
231
+ await run("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command",
232
+ "Get-CimInstance Win32_Process -Filter \"Name='chrome.exe'\" | Where-Object { $_.CommandLine -and $_.CommandLine.Contains($env:SCOUT_PROFILE_PATTERN) } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }"],
233
+ { env: { ...process.env, SCOUT_PROFILE_PATTERN: pattern }, windowsHide: true }).catch(() => {});
234
+ await new Promise((resolve) => setTimeout(resolve, 1000));
235
+ for (const lock of ["lockfile", "SingletonLock"]) {
236
+ try {
237
+ fs.unlinkSync(path.join(profileDir, lock));
238
+ } catch {}
239
+ }
240
+ return;
241
+ }
228
242
  await run("pkill", ["-9", "-f", pattern]).catch(() => {});
229
243
  for (let attempt = 0; attempt < 20; attempt++) {
230
244
  const alive = await run("pgrep", ["-f", pattern]).then(
package/src/config.js CHANGED
@@ -24,12 +24,27 @@ function ensureDirs() {
24
24
  }
25
25
  }
26
26
 
27
- const CHROME_CANDIDATES = [
28
- "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
29
- `${os.homedir()}/Applications/Google Chrome.app/Contents/MacOS/Google Chrome`,
30
- "/Applications/Google Chrome Beta.app/Contents/MacOS/Google Chrome Beta",
31
- "/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary",
32
- ];
27
+ function chromeCandidates(platform = process.platform, env = process.env) {
28
+ if (platform === "win32") {
29
+ // Chrome installs per machine under Program Files, or per user under
30
+ // LocalAppData when the person had no admin rights.
31
+ const roots = [env.PROGRAMFILES, env["PROGRAMFILES(X86)"], env.LOCALAPPDATA].filter(Boolean);
32
+ return roots.flatMap((root) => [
33
+ path.win32.join(root, "Google", "Chrome", "Application", "chrome.exe"),
34
+ path.win32.join(root, "Google", "Chrome Beta", "Application", "chrome.exe"),
35
+ path.win32.join(root, "Google", "Chrome SxS", "Application", "chrome.exe"),
36
+ ]);
37
+ }
38
+ if (platform === "linux") {
39
+ return ["/usr/bin/google-chrome", "/usr/bin/google-chrome-stable", "/opt/google/chrome/chrome", "/usr/bin/google-chrome-beta"];
40
+ }
41
+ return [
42
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
43
+ `${os.homedir()}/Applications/Google Chrome.app/Contents/MacOS/Google Chrome`,
44
+ "/Applications/Google Chrome Beta.app/Contents/MacOS/Google Chrome Beta",
45
+ "/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary",
46
+ ];
47
+ }
33
48
 
34
49
  class ChromeMissingError extends Error {
35
50
  constructor() {
@@ -43,7 +58,7 @@ class ChromeMissingError extends Error {
43
58
  let resolvedChrome = null;
44
59
  function chromeBinary() {
45
60
  if (resolvedChrome) return resolvedChrome;
46
- const found = CHROME_CANDIDATES.find((candidate) => {
61
+ const found = chromeCandidates().find((candidate) => {
47
62
  try {
48
63
  fs.accessSync(candidate, fs.constants.X_OK);
49
64
  return true;
@@ -70,6 +85,7 @@ const BROWSER_UA =
70
85
 
71
86
  module.exports = {
72
87
  NAME,
88
+ chromeCandidates,
73
89
  ROOT,
74
90
  PROFILE_DIR,
75
91
  RUNS_DIR,
package/src/quick.js CHANGED
@@ -287,6 +287,10 @@ async function captureWall(urls, { at = 0, outDir, cols = 4 } = {}) {
287
287
  module.exports = { collect, captureFrames, captureWall, targetUrl, NOT_ENABLED };
288
288
 
289
289
  if (require.main === module) {
290
+ if (process.platform !== "darwin") {
291
+ console.error("sprid-scout-quick drives Chrome through Apple Events, so it runs on macOS only. Use the scout MCP tools instead: they work on every platform.");
292
+ process.exit(1);
293
+ }
290
294
  const [target, ...rest] = process.argv.slice(2);
291
295
  if (!target) {
292
296
  console.error("Usage: sprid-scout-quick <@handle | #hashtag | post-url> [--rounds 10] [--links-only] [--at 0,1,2,5,10]\n sprid-scout-quick wall <post-url> <post-url> ... [--at-second 0]");
package/src/register.js CHANGED
@@ -12,12 +12,20 @@ const { NAME } = require("./config");
12
12
  const SERVER = path.join(__dirname, "server.js");
13
13
  const ADD = ["mcp", "add", "--transport", "stdio", "--scope", "user", NAME, "--", process.execPath, SERVER];
14
14
 
15
+ // Windows installs `claude` through npm as a shim (`claude.cmd`) that only
16
+ // cmd.exe starts, and Node's own path lives under "Program Files".
17
+ function claude(args, options) {
18
+ if (process.platform !== "win32") return execFileSync("claude", args, options);
19
+ const line = ["claude", ...args].map((arg) => (/^[\w@+=:,./\\-]+$/.test(arg) ? arg : `"${arg}"`)).join(" ");
20
+ return execFileSync(line, [], { ...options, shell: true, windowsHide: true });
21
+ }
22
+
15
23
  try {
16
- execFileSync("claude", ["mcp", "remove", "--scope", "user", NAME], { stdio: "ignore" });
24
+ claude(["mcp", "remove", "--scope", "user", NAME], { stdio: "ignore" });
17
25
  } catch {}
18
26
 
19
27
  try {
20
- const output = execFileSync("claude", ADD, { encoding: "utf8" });
28
+ const output = claude(ADD, { encoding: "utf8" });
21
29
  console.log(`- Claude Code: ${output.trim() || "registered"}`);
22
30
  } catch {
23
31
  console.log("- Claude Code: could not run `claude mcp add`. Register it by hand with:");